From bed49120e7ff6833300d23e3ef78f1b27a36b3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Fri, 21 Aug 2026 18:28:43 +0400 Subject: [PATCH 01/77] docs: add user menu documentation --- docs/MENU.ru.txt | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 docs/MENU.ru.txt diff --git a/docs/MENU.ru.txt b/docs/MENU.ru.txt new file mode 100644 index 0000000..2c49d7b --- /dev/null +++ b/docs/MENU.ru.txt @@ -0,0 +1,29 @@ +ГЛАВНОЕ МЕНЮ + +1. Создать и вывести коллекцию + 1.1. Выбрать размер коллекции + 1.2. Выбрать способ заполнения + 1.2.1. Вручную + 1.2.1.1. Задать новый элемент + 1.2.1.2. Завершить создание коллекции + 1.2.2. Случайными значениями + 1.2.3. Из файла + 1.2.3.1. Указать абсолютный путь до файла + +2. Настроить и выполнить сортировку + 2.1. Выбрать тип сортировки + 2.1.1. Обычная сортировка + 2.1.1.1. Выбрать поле 1 + 2.1.1.2. Выбрать поле 2 + 2.1.1.3. Выбрать поле 3 + 2.1.2. Сортировка по чётности + 2.1.2.1. Выбрать числовое поле + 2.2. Выполнить сортировку и вывести результат + 2.3. Дописать в файл последний результат сортировки + +3. Подсчитать количество вхождений элемента + 3.1. Ввести элемент N + 3.2. Выполнить многопоточный подсчёт + 3.3. Вывести результат + +4. Выход From ba117ae21e57eee5fe86921a5e18f73f39bb4ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Sat, 22 Aug 2026 12:00:50 +0400 Subject: [PATCH 02/77] feat(collection): add MyList interface --- .../main/java/ru/project/collection/MyList.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 app/src/main/java/ru/project/collection/MyList.java diff --git a/app/src/main/java/ru/project/collection/MyList.java b/app/src/main/java/ru/project/collection/MyList.java new file mode 100644 index 0000000..e97132e --- /dev/null +++ b/app/src/main/java/ru/project/collection/MyList.java @@ -0,0 +1,16 @@ +package ru.project.collection; + +public interface MyList { + + void add(T element); + + T get(int index); + + T set(int index, T element); + + T remove(int index); + + int size(); + + boolean isEmpty(); +} From 52251c658cdf65173ed648240db54e864014e200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Sun, 23 Aug 2026 19:34:34 +0300 Subject: [PATCH 03/77] feat(sorting): add SortStrategy interface --- app/src/test/java/sorting/SortStrategy.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 app/src/test/java/sorting/SortStrategy.java diff --git a/app/src/test/java/sorting/SortStrategy.java b/app/src/test/java/sorting/SortStrategy.java new file mode 100644 index 0000000..f252317 --- /dev/null +++ b/app/src/test/java/sorting/SortStrategy.java @@ -0,0 +1,7 @@ +package sorting; + +import java.util.Comparator; + +public interface SortStrategy { + void sort(MyList list, Comparator comparator); +} \ No newline at end of file From 813b790214bcaa9ce9d47bafe9003ada16abc310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Sun, 23 Aug 2026 19:36:33 +0300 Subject: [PATCH 04/77] feat(sorting): implement merge sort strategy --- .../test/java/sorting/MergeSortStrategy.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 app/src/test/java/sorting/MergeSortStrategy.java diff --git a/app/src/test/java/sorting/MergeSortStrategy.java b/app/src/test/java/sorting/MergeSortStrategy.java new file mode 100644 index 0000000..86655f7 --- /dev/null +++ b/app/src/test/java/sorting/MergeSortStrategy.java @@ -0,0 +1,65 @@ +package sorting; + +import java.util.Comparator; + +public class MergeSortStrategy implements SortStrategy { + + @Override + public void sort(MyList list, Comparator comparator) { + if (list == null || list.size() <= 1) { + return; + } + mergeSort(list, 0, list.size() - 1, comparator); + } + + private void mergeSort(MyList list, int left, int right, Comparator comparator) { + if (left >= right) { + return; + } + int mid = (left + right) / 2; + mergeSort(list, left, mid, comparator); + mergeSort(list, mid + 1, right, comparator); + merge(list, left, mid, right, comparator); + } + + private void merge(MyList list, int left, int mid, int right, Comparator comparator) { + int leftSize = mid - left + 1; + int rightSize = right - mid; + + @SuppressWarnings("unchecked") + T[] leftArray = (T[]) new Object[leftSize]; + @SuppressWarnings("unchecked") + T[] rightArray = (T[]) new Object[rightSize]; + + for (int i = 0; i < leftSize; i++) { + leftArray[i] = list.get(left + i); + } + for (int j = 0; j < rightSize; j++) { + rightArray[j] = list.get(mid + 1 + j); + } + + int i = 0, j = 0, k = left; + + while (i < leftSize && j < rightSize) { + if (comparator.compare(leftArray[i], rightArray[j]) <= 0) { + list.set(k, leftArray[i]); + i++; + } else { + list.set(k, rightArray[j]); + j++; + } + k++; + } + + while (i < leftSize) { + list.set(k, leftArray[i]); + i++; + k++; + } + while (j < rightSize) { + list.set(k, rightArray[j]); + j++; + k++; + } + } +} \ No newline at end of file From edc1e6f84014ea8a961e197db3b6be78c75da3a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Sun, 23 Aug 2026 19:37:29 +0300 Subject: [PATCH 05/77] feat(sorting): add comparator for student group --- app/src/test/java/sorting/StudentGroupComparator.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/src/test/java/sorting/StudentGroupComparator.java diff --git a/app/src/test/java/sorting/StudentGroupComparator.java b/app/src/test/java/sorting/StudentGroupComparator.java new file mode 100644 index 0000000..3d40be4 --- /dev/null +++ b/app/src/test/java/sorting/StudentGroupComparator.java @@ -0,0 +1,11 @@ +package sorting; + +import java.util.Comparator; + +public class StudentGroupComparator implements Comparator { + + @Override + public int compare(Student s1, Student s2) { + return Integer.compare(s1.getGroupNumber(), s2.getGroupNumber()); + } +} \ No newline at end of file From f77babf81f6d1720850ce1b5014b726b79976b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Sun, 23 Aug 2026 19:39:12 +0300 Subject: [PATCH 06/77] feat(sorting): add comparator for average grade --- .../java/sorting/StudentAvarageGradeComparator.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/src/test/java/sorting/StudentAvarageGradeComparator.java diff --git a/app/src/test/java/sorting/StudentAvarageGradeComparator.java b/app/src/test/java/sorting/StudentAvarageGradeComparator.java new file mode 100644 index 0000000..46204c2 --- /dev/null +++ b/app/src/test/java/sorting/StudentAvarageGradeComparator.java @@ -0,0 +1,11 @@ +package sorting; + +import java.util.Comparator; + +public class StudentAverageGradeComparator implements Comparator { + + @Override + public int compare(Student s1, Student s2) { + return Double.compare(s1.getAverageGrade(), s2.getAverageGrade()); + } +} \ No newline at end of file From f05c79c3cbcfbd0844b917c80c0f81930a3bdda5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Sun, 23 Aug 2026 19:40:57 +0300 Subject: [PATCH 07/77] feat(sorting): add comparator for record book number --- .../java/sorting/StudentRecordBookComparator.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/src/test/java/sorting/StudentRecordBookComparator.java diff --git a/app/src/test/java/sorting/StudentRecordBookComparator.java b/app/src/test/java/sorting/StudentRecordBookComparator.java new file mode 100644 index 0000000..22b73e4 --- /dev/null +++ b/app/src/test/java/sorting/StudentRecordBookComparator.java @@ -0,0 +1,11 @@ +package sorting; + +import java.util.Comparator; + +public class StudentRecordBookComparator implements Comparator { + + @Override + public int compare(Student s1, Student s2) { + return Integer.compare(s1.getRecordBookNumber(), s2.getRecordBookNumber()); + } +} \ No newline at end of file From f7443a9d6c2e994409c2d4f30c7b6bd7d64954d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Mon, 24 Aug 2026 18:30:17 +0300 Subject: [PATCH 08/77] fix: correct groupNumber type in StudentGroupComparator --- app/src/test/java/sorting/StudentAvarageGradeComparator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/sorting/StudentAvarageGradeComparator.java b/app/src/test/java/sorting/StudentAvarageGradeComparator.java index 46204c2..1946fd4 100644 --- a/app/src/test/java/sorting/StudentAvarageGradeComparator.java +++ b/app/src/test/java/sorting/StudentAvarageGradeComparator.java @@ -2,10 +2,10 @@ package sorting; import java.util.Comparator; -public class StudentAverageGradeComparator implements Comparator { +public class StudentAvarageGradeComparator implements Comparator { @Override public int compare(Student s1, Student s2) { - return Double.compare(s1.getAverageGrade(), s2.getAverageGrade()); + return Double.compare(s1.getAvarageGrade(), s2.getAvarageGrade()); } } \ No newline at end of file From dfd6ab9ce658eb76e5989ebda956ae1dcf1cb9cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Mon, 24 Aug 2026 18:54:28 +0300 Subject: [PATCH 09/77] refactor: change groupNumber type from int to String in StudentGroupComparator --- app/src/test/java/sorting/StudentGroupComparator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/sorting/StudentGroupComparator.java b/app/src/test/java/sorting/StudentGroupComparator.java index 3d40be4..b1efd93 100644 --- a/app/src/test/java/sorting/StudentGroupComparator.java +++ b/app/src/test/java/sorting/StudentGroupComparator.java @@ -5,7 +5,7 @@ import java.util.Comparator; public class StudentGroupComparator implements Comparator { @Override - public int compare(Student s1, Student s2) { + public String compare(Student s1, Student s2) { return Integer.compare(s1.getGroupNumber(), s2.getGroupNumber()); } -} \ No newline at end of file +} From 13c4ff995ae1eaa08481d6d8307d4216d2f7f79a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Tue, 25 Aug 2026 16:20:23 +0400 Subject: [PATCH 10/77] docs(menu): redesign application menu structure --- docs/MENU.ru.txt | 92 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/docs/MENU.ru.txt b/docs/MENU.ru.txt index 2c49d7b..ad703cd 100644 --- a/docs/MENU.ru.txt +++ b/docs/MENU.ru.txt @@ -1,29 +1,73 @@ ГЛАВНОЕ МЕНЮ -1. Создать и вывести коллекцию - 1.1. Выбрать размер коллекции - 1.2. Выбрать способ заполнения - 1.2.1. Вручную - 1.2.1.1. Задать новый элемент - 1.2.1.2. Завершить создание коллекции - 1.2.2. Случайными значениями - 1.2.3. Из файла - 1.2.3.1. Указать абсолютный путь до файла +Показать текущую коллекцию -2. Настроить и выполнить сортировку - 2.1. Выбрать тип сортировки - 2.1.1. Обычная сортировка - 2.1.1.1. Выбрать поле 1 - 2.1.1.2. Выбрать поле 2 - 2.1.1.3. Выбрать поле 3 - 2.1.2. Сортировка по чётности - 2.1.2.1. Выбрать числовое поле - 2.2. Выполнить сортировку и вывести результат - 2.3. Дописать в файл последний результат сортировки +Меню создания новой коллекции + Вернуться + Задать размер + Настроить способ заполнения + Пользовательские данные + Задать поле 'Название A' + Задать поле 'Название B' + Задать поле 'Название C' + Задать индекс изменяемого объекта + Изменить данные объекта + Предпросмотр + Случайные данные + Настройка диапазона генерации + Поле 'Название A' + Задать минимальное значение + Задать максимальное значение + Поле 'Название B' + Задать минимальное значение + Задать максимальное значение + Поле 'Название C' + Задать минимальное значение + Задать максимальное значение + Данные из файла + Задать путь до файла + Абсолютный + Относительный + Выбрать способ заполнения + Пользовательские данные * + Случайные данные + Данные из файла + Создать новую коллекцию -3. Подсчитать количество вхождений элемента - 3.1. Ввести элемент N - 3.2. Выполнить многопоточный подсчёт - 3.3. Вывести результат +Меню сохранения текущей коллекции в файл + Вывести текущий абсолютный путь до файла + Задать путь до файла + Абсолютный + Относительный + Дописать коллекцию в текущий файл -4. Выход +Меню сортировки текущей коллекции + Вернуться + Настроить способ сортировки + Обычная сортировка + Сортировать по полю 'Название A' + Сортировать по полю 'Название B' + Сортировать по полю 'Название C' +Настроить способ сортировки + Сортировка по чётности + Выбрать числовое поле 'Название A' + Выбрать числовое поле 'Название B' +Выбрать способ сортировки + Обычная сортировка * + Сортировка по чётности +Сортировать текущую коллекцию + +Меню поиска элемента в текущей коллекции + Вернуться + Задать значение поля 'Название A' + Задать значение поля 'Название B' + Задать значение поля 'Название C' + Найти первое вхождение в текущей коллекции + +Меню подсчёта количества вхождений элемента + Задать значение поля 'Название A' + Задать значение поля 'Название B' + Задать значение поля 'Название C' + Выполнить многопоточный подсчёт + +Завершить выполнение программы From a139fa10c6765498d0dd147a47e8b4e16055af2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Tue, 25 Aug 2026 17:14:32 +0400 Subject: [PATCH 11/77] feat(ui): add main menu and navigation skeleton --- app/pom.xml | 20 +++++++++ app/src/main/java/ru/project/App.java | 33 ++++++++++++--- .../java/ru/project/ui/CollectionView.java | 8 ++++ .../ru/project/ui/CountOccurrencesMenu.java | 8 ++++ .../ru/project/ui/CreateCollectionMenu.java | 8 ++++ .../main/java/ru/project/ui/EmptyMenu.java | 23 +++++++++++ app/src/main/java/ru/project/ui/MainMenu.java | 34 +++++++++++++++ .../main/java/ru/project/ui/Navigator.java | 41 +++++++++++++++++++ .../ru/project/ui/SaveCollectionMenu.java | 8 ++++ .../java/ru/project/ui/SearchElementMenu.java | 8 ++++ .../ru/project/ui/SortCollectionMenu.java | 8 ++++ 11 files changed, 193 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/ru/project/ui/CollectionView.java create mode 100644 app/src/main/java/ru/project/ui/CountOccurrencesMenu.java create mode 100644 app/src/main/java/ru/project/ui/CreateCollectionMenu.java create mode 100644 app/src/main/java/ru/project/ui/EmptyMenu.java create mode 100644 app/src/main/java/ru/project/ui/MainMenu.java create mode 100644 app/src/main/java/ru/project/ui/Navigator.java create mode 100644 app/src/main/java/ru/project/ui/SaveCollectionMenu.java create mode 100644 app/src/main/java/ru/project/ui/SearchElementMenu.java create mode 100644 app/src/main/java/ru/project/ui/SortCollectionMenu.java diff --git a/app/pom.xml b/app/pom.xml index 509e563..5afca24 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -41,6 +41,13 @@ junit-jupiter-params test + + + com.googlecode.lanterna + lanterna + 3.1.5 + + @@ -91,5 +98,18 @@ + + + + org.codehaus.mojo + exec-maven-plugin + 3.6.3 + + + ru.project.App + + + + diff --git a/app/src/main/java/ru/project/App.java b/app/src/main/java/ru/project/App.java index 8fd4c39..bf57afc 100644 --- a/app/src/main/java/ru/project/App.java +++ b/app/src/main/java/ru/project/App.java @@ -1,10 +1,31 @@ package ru.project; -/** - * Hello world! - */ -public class App { - public static void main(String[] args) { - System.out.println("Hello World!"); +import com.googlecode.lanterna.gui2.MultiWindowTextGUI; +import com.googlecode.lanterna.screen.Screen; +import com.googlecode.lanterna.screen.TerminalScreen; +import com.googlecode.lanterna.terminal.DefaultTerminalFactory; +import ru.project.ui.MainMenu; +import ru.project.ui.Navigator; + +public final class App { + + private App() {} + + public static void main(String[] args) throws Exception { + DefaultTerminalFactory factory = new DefaultTerminalFactory(); + + Screen screen = new TerminalScreen(factory.createTerminal()); + + screen.startScreen(); + + try { + MultiWindowTextGUI gui = new MultiWindowTextGUI(screen); + + Navigator navigator = new Navigator(gui); + + gui.addWindowAndWait(new MainMenu(navigator)); + } finally { + screen.stopScreen(); } + } } diff --git a/app/src/main/java/ru/project/ui/CollectionView.java b/app/src/main/java/ru/project/ui/CollectionView.java new file mode 100644 index 0000000..82b9540 --- /dev/null +++ b/app/src/main/java/ru/project/ui/CollectionView.java @@ -0,0 +1,8 @@ +package ru.project.ui; + +public class CollectionView extends EmptyMenu { + + public CollectionView() { + super("Текущая коллекция"); + } +} diff --git a/app/src/main/java/ru/project/ui/CountOccurrencesMenu.java b/app/src/main/java/ru/project/ui/CountOccurrencesMenu.java new file mode 100644 index 0000000..db8e0c8 --- /dev/null +++ b/app/src/main/java/ru/project/ui/CountOccurrencesMenu.java @@ -0,0 +1,8 @@ +package ru.project.ui; + +public class CountOccurrencesMenu extends EmptyMenu { + + public CountOccurrencesMenu() { + super("Подсчёт количества вхождений"); + } +} diff --git a/app/src/main/java/ru/project/ui/CreateCollectionMenu.java b/app/src/main/java/ru/project/ui/CreateCollectionMenu.java new file mode 100644 index 0000000..6ea56fc --- /dev/null +++ b/app/src/main/java/ru/project/ui/CreateCollectionMenu.java @@ -0,0 +1,8 @@ +package ru.project.ui; + +public class CreateCollectionMenu extends EmptyMenu { + + public CreateCollectionMenu() { + super("Создание новой коллекции"); + } +} diff --git a/app/src/main/java/ru/project/ui/EmptyMenu.java b/app/src/main/java/ru/project/ui/EmptyMenu.java new file mode 100644 index 0000000..0385780 --- /dev/null +++ b/app/src/main/java/ru/project/ui/EmptyMenu.java @@ -0,0 +1,23 @@ +package ru.project.ui; + +import com.googlecode.lanterna.gui2.BasicWindow; +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Direction; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; + +public abstract class EmptyMenu extends BasicWindow { + + protected EmptyMenu(String title) { + super(title); + + Panel panel = new Panel(new LinearLayout(Direction.VERTICAL)); + + panel.addComponent(new Label("Раздел находится в разработке")); + + panel.addComponent(new Button("Назад", this::close)); + + setComponent(panel); + } +} diff --git a/app/src/main/java/ru/project/ui/MainMenu.java b/app/src/main/java/ru/project/ui/MainMenu.java new file mode 100644 index 0000000..361ce13 --- /dev/null +++ b/app/src/main/java/ru/project/ui/MainMenu.java @@ -0,0 +1,34 @@ +package ru.project.ui; + +import com.googlecode.lanterna.gui2.BasicWindow; +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Direction; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; + +public class MainMenu extends BasicWindow { + + public MainMenu(Navigator navigator) { + super("ГЛАВНОЕ МЕНЮ"); + + Panel panel = new Panel(new LinearLayout(Direction.VERTICAL)); + + panel.addComponent(new Button("Показать текущую коллекцию", navigator::showCollectionView)); + + panel.addComponent(new Button("Создать новую коллекцию", navigator::showCreateCollection)); + + panel.addComponent( + new Button("Сохранить текущую коллекцию в файл", navigator::showSaveCollection)); + + panel.addComponent(new Button("Сортировать текущую коллекцию", navigator::showSortCollection)); + + panel.addComponent(new Button("Найти элемент", navigator::showSearchElement)); + + panel.addComponent( + new Button("Подсчитать количество вхождений", navigator::showCountOccurrences)); + + panel.addComponent(new Button("Завершить выполнение программы", this::close)); + + setComponent(panel); + } +} diff --git a/app/src/main/java/ru/project/ui/Navigator.java b/app/src/main/java/ru/project/ui/Navigator.java new file mode 100644 index 0000000..13489c1 --- /dev/null +++ b/app/src/main/java/ru/project/ui/Navigator.java @@ -0,0 +1,41 @@ +package ru.project.ui; + +import com.googlecode.lanterna.gui2.BasicWindow; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; + +public class Navigator { + + private final WindowBasedTextGUI gui; + + public Navigator(WindowBasedTextGUI gui) { + this.gui = gui; + } + + public void show(BasicWindow window) { + gui.addWindowAndWait(window); + } + + public void showCollectionView() { + show(new CollectionView()); + } + + public void showCreateCollection() { + show(new CreateCollectionMenu()); + } + + public void showSaveCollection() { + show(new SaveCollectionMenu()); + } + + public void showSortCollection() { + show(new SortCollectionMenu()); + } + + public void showSearchElement() { + show(new SearchElementMenu()); + } + + public void showCountOccurrences() { + show(new CountOccurrencesMenu()); + } +} diff --git a/app/src/main/java/ru/project/ui/SaveCollectionMenu.java b/app/src/main/java/ru/project/ui/SaveCollectionMenu.java new file mode 100644 index 0000000..cc0f36a --- /dev/null +++ b/app/src/main/java/ru/project/ui/SaveCollectionMenu.java @@ -0,0 +1,8 @@ +package ru.project.ui; + +public class SaveCollectionMenu extends EmptyMenu { + + public SaveCollectionMenu() { + super("Сохранение текущей коллекции"); + } +} diff --git a/app/src/main/java/ru/project/ui/SearchElementMenu.java b/app/src/main/java/ru/project/ui/SearchElementMenu.java new file mode 100644 index 0000000..7e92c4f --- /dev/null +++ b/app/src/main/java/ru/project/ui/SearchElementMenu.java @@ -0,0 +1,8 @@ +package ru.project.ui; + +public class SearchElementMenu extends EmptyMenu { + + public SearchElementMenu() { + super("Поиск элемента"); + } +} diff --git a/app/src/main/java/ru/project/ui/SortCollectionMenu.java b/app/src/main/java/ru/project/ui/SortCollectionMenu.java new file mode 100644 index 0000000..68e6a7a --- /dev/null +++ b/app/src/main/java/ru/project/ui/SortCollectionMenu.java @@ -0,0 +1,8 @@ +package ru.project.ui; + +public class SortCollectionMenu extends EmptyMenu { + + public SortCollectionMenu() { + super("Сортировка текущей коллекции"); + } +} From 4d20a59072715cd62d4406b75cdec0f3a05ca2d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Tue, 25 Aug 2026 23:14:35 +0400 Subject: [PATCH 12/77] refactor: move sorting classes from test to main source set --- .../java => main/java/ru/project}/sorting/MergeSortStrategy.java | 0 .../{test/java => main/java/ru/project}/sorting/SortStrategy.java | 0 .../java/ru/project}/sorting/StudentAvarageGradeComparator.java | 0 .../java/ru/project}/sorting/StudentGroupComparator.java | 0 .../java/ru/project}/sorting/StudentRecordBookComparator.java | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename app/src/{test/java => main/java/ru/project}/sorting/MergeSortStrategy.java (100%) rename app/src/{test/java => main/java/ru/project}/sorting/SortStrategy.java (100%) rename app/src/{test/java => main/java/ru/project}/sorting/StudentAvarageGradeComparator.java (100%) rename app/src/{test/java => main/java/ru/project}/sorting/StudentGroupComparator.java (100%) rename app/src/{test/java => main/java/ru/project}/sorting/StudentRecordBookComparator.java (100%) diff --git a/app/src/test/java/sorting/MergeSortStrategy.java b/app/src/main/java/ru/project/sorting/MergeSortStrategy.java similarity index 100% rename from app/src/test/java/sorting/MergeSortStrategy.java rename to app/src/main/java/ru/project/sorting/MergeSortStrategy.java diff --git a/app/src/test/java/sorting/SortStrategy.java b/app/src/main/java/ru/project/sorting/SortStrategy.java similarity index 100% rename from app/src/test/java/sorting/SortStrategy.java rename to app/src/main/java/ru/project/sorting/SortStrategy.java diff --git a/app/src/test/java/sorting/StudentAvarageGradeComparator.java b/app/src/main/java/ru/project/sorting/StudentAvarageGradeComparator.java similarity index 100% rename from app/src/test/java/sorting/StudentAvarageGradeComparator.java rename to app/src/main/java/ru/project/sorting/StudentAvarageGradeComparator.java diff --git a/app/src/test/java/sorting/StudentGroupComparator.java b/app/src/main/java/ru/project/sorting/StudentGroupComparator.java similarity index 100% rename from app/src/test/java/sorting/StudentGroupComparator.java rename to app/src/main/java/ru/project/sorting/StudentGroupComparator.java diff --git a/app/src/test/java/sorting/StudentRecordBookComparator.java b/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java similarity index 100% rename from app/src/test/java/sorting/StudentRecordBookComparator.java rename to app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java From c2efae1248b5153af38bcb7f08e2a97e017cd1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=B0=D0=B4=D0=B6=D0=B8=D0=BA=D1=83=D1=80=D0=B1?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=A2=D0=B5=D0=BC=D0=B8=D1=80=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD?= Date: Mon, 24 Aug 2026 00:27:47 +0300 Subject: [PATCH 13/77] feat: add element to linked list --- app/src/main/java/ru/project/collection/MyLinkedList.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/src/main/java/ru/project/collection/MyLinkedList.java diff --git a/app/src/main/java/ru/project/collection/MyLinkedList.java b/app/src/main/java/ru/project/collection/MyLinkedList.java new file mode 100644 index 0000000..e69de29 From ffb229b1a72960987913d2d20c4c1ef664a1ac4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=B0=D0=B4=D0=B6=D0=B8=D0=BA=D1=83=D1=80=D0=B1?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=A2=D0=B5=D0=BC=D0=B8=D1=80=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD?= Date: Mon, 24 Aug 2026 00:40:02 +0300 Subject: [PATCH 14/77] feat: add implement get by index for linked --- .../ru/project/collection/MyLinkedList.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/app/src/main/java/ru/project/collection/MyLinkedList.java b/app/src/main/java/ru/project/collection/MyLinkedList.java index e69de29..42ea519 100644 --- a/app/src/main/java/ru/project/collection/MyLinkedList.java +++ b/app/src/main/java/ru/project/collection/MyLinkedList.java @@ -0,0 +1,74 @@ +public class MyLinkedList implements MyList +{ + private Node head; + private int size; + + public void add(T value) { + if (head == null) { + this.head = new Node<>(value); + } else { + Node temp = head; + while (temp.getNext() != null) { + temp = temp.getNext(); + } + temp.setNext(new Node<>(value)); + } + size++; + } + public T get(int index) + { + int currentIndex = 0; + Node temp = head; + while(temp != null) + { + if (currentIndex == index) + { + return (T) temp.getValue(); + + } + else { + temp = temp.getNext(); + currentIndex++; + } + + } + throw new IllegalArgumentException(); + } + public String toString() { + Object[] res = new Object[size]; + int index = 0; + Node temp = head; + while (temp != null) { + res[index] = temp.getValue(); + index++; + temp = temp.getNext(); + } + return Arrays.toString(res); + } + public static class Node { + private T value; + private Node next; + + public Node(T value) { + this.value = value; + } + + public void setValue(T value) { + this.value = value; + } + + public void setNext(Node next) { + this.next = next; + } + + public T getValue() { + return value; + } + + public Node getNext() { + return next; + } + } + + +} \ No newline at end of file From ef1c54f10ed4a9ab2d3ea997c11702577194ac06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=B0=D0=B4=D0=B6=D0=B8=D0=BA=D1=83=D1=80=D0=B1?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=A2=D0=B5=D0=BC=D0=B8=D1=80=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD?= Date: Mon, 24 Aug 2026 00:43:12 +0300 Subject: [PATCH 15/77] feat: add an index deletion method --- .../ru/project/collection/MyLinkedList.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/src/main/java/ru/project/collection/MyLinkedList.java b/app/src/main/java/ru/project/collection/MyLinkedList.java index 42ea519..1b1dc47 100644 --- a/app/src/main/java/ru/project/collection/MyLinkedList.java +++ b/app/src/main/java/ru/project/collection/MyLinkedList.java @@ -34,6 +34,31 @@ public class MyLinkedList implements MyList } throw new IllegalArgumentException(); } + public void remove(int index) + { + if (index == 0) + { + head = head.getNext(); + size--; + return; + + } + int currentIndex = 0; + Node temp = head; + while (temp != null) + { + if (currentIndex == index-1) + { + temp.setNext(temp.getNext().getNext()); + size--; + return; + } + else { + temp = temp.getNext(); + currentIndex++; + } + } + } public String toString() { Object[] res = new Object[size]; int index = 0; From a7861dd58a891881d00455c7495164be0131ae67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=B0=D0=B4=D0=B6=D0=B8=D0=BA=D1=83=D1=80=D0=B1?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=A2=D0=B5=D0=BC=D0=B8=D1=80=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD?= Date: Mon, 24 Aug 2026 00:48:14 +0300 Subject: [PATCH 16/77] feat: The set method for setting the value of a node by index. --- .../main/java/ru/project/collection/MyLinkedList.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/java/ru/project/collection/MyLinkedList.java b/app/src/main/java/ru/project/collection/MyLinkedList.java index 1b1dc47..6ba300f 100644 --- a/app/src/main/java/ru/project/collection/MyLinkedList.java +++ b/app/src/main/java/ru/project/collection/MyLinkedList.java @@ -59,6 +59,17 @@ public class MyLinkedList implements MyList } } } + public T set(int index, T value) { + if (index < 0 || index >= size) { + throw new IllegalArgumentException(); + } + + Node temp = head; + for (int i = 0; i < index; i++) { + temp = temp.getNext(); + } + + public String toString() { Object[] res = new Object[size]; int index = 0; From 9e8fc5effb6453fa440db1a3d5d6f2eb1e74f747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=B0=D0=B4=D0=B6=D0=B8=D0=BA=D1=83=D1=80=D0=B1?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=A2=D0=B5=D0=BC=D0=B8=D1=80=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD?= Date: Mon, 24 Aug 2026 00:49:43 +0300 Subject: [PATCH 17/77] feat: add size() method to MyLinkedList --- app/src/main/java/ru/project/collection/MyLinkedList.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/ru/project/collection/MyLinkedList.java b/app/src/main/java/ru/project/collection/MyLinkedList.java index 6ba300f..4d54ab8 100644 --- a/app/src/main/java/ru/project/collection/MyLinkedList.java +++ b/app/src/main/java/ru/project/collection/MyLinkedList.java @@ -69,7 +69,14 @@ public class MyLinkedList implements MyList temp = temp.getNext(); } + T oldValue = (T) temp.getValue(); + temp.setValue(value); + return oldValue; + } + public int size() { + return size; + } public String toString() { Object[] res = new Object[size]; int index = 0; From e7db5fee4db500653c32571283ce046e4e3cf5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=B0=D0=B4=D0=B6=D0=B8=D0=BA=D1=83=D1=80=D0=B1?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=A2=D0=B5=D0=BC=D0=B8=D1=80=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD?= Date: Mon, 24 Aug 2026 00:52:09 +0300 Subject: [PATCH 18/77] feat: add an isEmpty() method that allows you to check whether the colle --- app/src/main/java/ru/project/collection/MyLinkedList.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/ru/project/collection/MyLinkedList.java b/app/src/main/java/ru/project/collection/MyLinkedList.java index 4d54ab8..7164857 100644 --- a/app/src/main/java/ru/project/collection/MyLinkedList.java +++ b/app/src/main/java/ru/project/collection/MyLinkedList.java @@ -77,6 +77,10 @@ public class MyLinkedList implements MyList public int size() { return size; } + public boolean isEmpty() + { + return size == 0; + } public String toString() { Object[] res = new Object[size]; int index = 0; From f855a760922fda49f019e5f00b18ee54c1c70306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=B0=D0=B4=D0=B6=D0=B8=D0=BA=D1=83=D1=80=D0=B1?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=A2=D0=B5=D0=BC=D0=B8=D1=80=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD?= Date: Tue, 25 Aug 2026 13:25:03 +0300 Subject: [PATCH 19/77] fix: correct minor syntax issues --- .../ru/project/collection/MyLinkedList.java | 28 ++++++++++--------- .../java/ru/project/collection/MyList.java | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/ru/project/collection/MyLinkedList.java b/app/src/main/java/ru/project/collection/MyLinkedList.java index 7164857..23b2b75 100644 --- a/app/src/main/java/ru/project/collection/MyLinkedList.java +++ b/app/src/main/java/ru/project/collection/MyLinkedList.java @@ -1,13 +1,13 @@ +import java.util.Arrays; public class MyLinkedList implements MyList { - private Node head; + private Node head; private int size; - public void add(T value) { if (head == null) { this.head = new Node<>(value); } else { - Node temp = head; + Node temp = head; while (temp.getNext() != null) { temp = temp.getNext(); } @@ -18,12 +18,12 @@ public class MyLinkedList implements MyList public T get(int index) { int currentIndex = 0; - Node temp = head; + Node temp = head; while(temp != null) { if (currentIndex == index) { - return (T) temp.getValue(); + return temp.getValue(); } else { @@ -36,6 +36,9 @@ public class MyLinkedList implements MyList } public void remove(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(); + } if (index == 0) { head = head.getNext(); @@ -44,7 +47,7 @@ public class MyLinkedList implements MyList } int currentIndex = 0; - Node temp = head; + Node temp = head; while (temp != null) { if (currentIndex == index-1) @@ -64,16 +67,15 @@ public class MyLinkedList implements MyList throw new IllegalArgumentException(); } - Node temp = head; + Node temp = head; for (int i = 0; i < index; i++) { temp = temp.getNext(); } - T oldValue = (T) temp.getValue(); + T oldValue = temp.getValue(); temp.setValue(value); return oldValue; } - public int size() { return size; } @@ -81,10 +83,11 @@ public class MyLinkedList implements MyList { return size == 0; } + @Override public String toString() { Object[] res = new Object[size]; int index = 0; - Node temp = head; + Node temp = head; while (temp != null) { res[index] = temp.getValue(); index++; @@ -92,7 +95,8 @@ public class MyLinkedList implements MyList } return Arrays.toString(res); } - public static class Node { + + private static class Node { private T value; private Node next; @@ -116,6 +120,4 @@ public class MyLinkedList implements MyList return next; } } - - } \ No newline at end of file diff --git a/app/src/main/java/ru/project/collection/MyList.java b/app/src/main/java/ru/project/collection/MyList.java index e97132e..bd47567 100644 --- a/app/src/main/java/ru/project/collection/MyList.java +++ b/app/src/main/java/ru/project/collection/MyList.java @@ -8,7 +8,7 @@ public interface MyList { T set(int index, T element); - T remove(int index); + void remove(int index); int size(); From 2775c5fc430a0438588038ee30a2358282b5811c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Sat, 22 Aug 2026 21:18:44 +0400 Subject: [PATCH 20/77] docs: add guide for dependencies between git branches --- GIT_BRANCH_DEPENDENCIES.ru.md | 122 ++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 GIT_BRANCH_DEPENDENCIES.ru.md diff --git a/GIT_BRANCH_DEPENDENCIES.ru.md b/GIT_BRANCH_DEPENDENCIES.ru.md new file mode 100644 index 0000000..f427e43 --- /dev/null +++ b/GIT_BRANCH_DEPENDENCIES.ru.md @@ -0,0 +1,122 @@ +# Работа с зависимостями между Git-ветками + +## Сценарий + +Предположим, мы находимся в ветке: + +```bash +feature/student +``` + +В этой ветке реализуется функциональность, связанная со `Student`. + +При этом у нас появляется новая, независимая по смыслу фича: + +```bash +feature/sorting +``` + +Для реализации сортировки нам необходим класс или интерфейс `Student`, который уже был создан в `feature/student`. + +## Как создать новую ветку + +Если нам необходимо продолжить разработку `feature/sorting`, используя текущее состояние `feature/student`, создаём новую ветку **от `feature/student`**. + +Сначала переключаемся на `feature/student`: + +```bash +git checkout feature/student +``` + +Затем создаём новую ветку: + +```bash +git branch feature/sorting +``` + +После этого история будет выглядеть примерно так: + +```text + feature/sorting + / +---------------● + \ + feature/student +``` + +Обе ветки на момент создания указывают на один и тот же коммит. + +Теперь в `feature/sorting` доступен весь код, который был добавлен в `feature/student`, в том числе `Student`. + +Чтобы перейти в новую ветку: + +```bash +git checkout feature/sorting +``` + +## Почему важно создавать ветку именно таким образом + +В данном случае `feature/sorting` зависит от кода, находящегося в `feature/student`. + +При этом сами фичи логически разные: + +- `feature/student` — разработка `Student`; +- `feature/sorting` — реализация сортировки; +- `Student` нужен в `feature/sorting` как зависимость для реализации сортировки. + +Поэтому не стоит продолжать разработку сортировки непосредственно в `feature/student`. Лучше создать отдельную ветку от того состояния, где необходимый код уже существует. + +--- + +## Если ветки уже разошлись + +Иногда невозможно создать `feature/sorting` непосредственно от нужного места в истории `feature/student`. + +Например, `feature/sorting` уже была создана раньше: + +```text +A---B---C---D feature/student + \ + E---F---G feature/sorting +``` + +В таком случае можно получить изменения из `feature/student` посредством слияния (merge). + +Находясь в `feature/sorting`, выполняем: + +```bash +git checkout feature/sorting +git merge feature/student +``` + +История станет примерно такой: + +```text +A---B---C---D feature/student + \ \ + E---F---G---M feature/sorting +``` + +Коммит `M` — результат слияния двух веток. + +После этого в `feature/sorting` будут доступны изменения из `feature/student`, в том числе необходимый `Student`. + +## Главное правило + +**Если новая фича зависит от кода из другой ветки, но при этом является самостоятельной по смыслу — создаём для неё отдельную ветку, используя нужную ветку как основу.** + +Если новую ветку можно создать непосредственно от нужного состояния исходной ветки: + +```bash +git checkout feature/student +git branch feature/sorting +``` + +Если ветки уже разошлись и создать её от нужного состояния невозможно: + +```bash +git checkout feature/sorting +git merge feature/student +``` + +Таким образом, мы сохраняем логическое разделение фич и одновременно можем использовать необходимый код из другой ветки. From 99255f392f0d9e7d7020eca6e5bbd2bc88e973d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Sun, 23 Aug 2026 15:17:57 +0300 Subject: [PATCH 21/77] feat(student): add Student class --- .../main/java/ru/project/student/Student.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 app/src/main/java/ru/project/student/Student.java diff --git a/app/src/main/java/ru/project/student/Student.java b/app/src/main/java/ru/project/student/Student.java new file mode 100644 index 0000000..121aea5 --- /dev/null +++ b/app/src/main/java/ru/project/student/Student.java @@ -0,0 +1,38 @@ +package ru.project.student; + +public class Student { + private final String groupNumber; + private final double averageGrade; + private final String recordBookNumber; + + Student(String groupNumber, double averageGrade, String recordBookNumber) { + if (groupNumber == null || groupNumber.isBlank()) { + throw new IllegalArgumentException("Group number cannot be empty"); + } + if (averageGrade < 0.0 || averageGrade > 5.0) { + throw new IllegalArgumentException("Average grade must be between 0.0 and 5.0"); + } + if (recordBookNumber == null || recordBookNumber.isBlank()) { + throw new IllegalArgumentException("Record book number cannot be empty"); + } + this.groupNumber = groupNumber; + this.averageGrade = averageGrade; + this.recordBookNumber = recordBookNumber; + } + + public String getGroupNumber() { + return groupNumber; + } + + public double getAverageGrade() { + return averageGrade; + } + + public String getRecordBookNumber() { + return recordBookNumber; + } +@Override + public String toString(){ + return "Student ="+groupNumber+ +} +} From cdd33b6cf80e8917e46828dcc5bac172b60d7f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Sun, 23 Aug 2026 19:41:21 +0300 Subject: [PATCH 22/77] feat(student): update Student class --- .../main/java/ru/project/student/Student.java | 39 ++++++++++++++++--- .../ru/project/student/StudentBuilder.java | 4 ++ .../java/ru/project/student/StudentText.java | 4 ++ 3 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/ru/project/student/StudentBuilder.java create mode 100644 app/src/test/java/ru/project/student/StudentText.java diff --git a/app/src/main/java/ru/project/student/Student.java b/app/src/main/java/ru/project/student/Student.java index 121aea5..2eaa6ed 100644 --- a/app/src/main/java/ru/project/student/Student.java +++ b/app/src/main/java/ru/project/student/Student.java @@ -1,5 +1,7 @@ package ru.project.student; +import java.util.Objects; + public class Student { private final String groupNumber; private final double averageGrade; @@ -31,8 +33,35 @@ public class Student { public String getRecordBookNumber() { return recordBookNumber; } -@Override - public String toString(){ - return "Student ="+groupNumber+ -} -} + + @Override + public String toString() { + return "Student{" + + "groupNumber='" + groupNumber + '\'' + + ", averageGrade=" + averageGrade + + ", recordBookNumber='" + recordBookNumber + '\'' + + '}'; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + + } + Student student = (Student) obj; + + return Double.compare(averageGrade, student.averageGrade) == 0 + && Objects.equals(groupNumber, student.groupNumber) + && Objects.equals(recordBookNumber, student.recordBookNumber); + } + + @Override + public int hashCode() { + return Objects.hash(groupNumber, averageGrade, recordBookNumber); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/student/StudentBuilder.java b/app/src/main/java/ru/project/student/StudentBuilder.java new file mode 100644 index 0000000..a370f1f --- /dev/null +++ b/app/src/main/java/ru/project/student/StudentBuilder.java @@ -0,0 +1,4 @@ +package ru.project.student; + +public class StudentBuilder { +} diff --git a/app/src/test/java/ru/project/student/StudentText.java b/app/src/test/java/ru/project/student/StudentText.java new file mode 100644 index 0000000..cad3bef --- /dev/null +++ b/app/src/test/java/ru/project/student/StudentText.java @@ -0,0 +1,4 @@ +package ru.project.student; + +public class StudentText { +} From 4e53eb03f9c02cfff695c99fdc9e7ba163ab79d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Sun, 23 Aug 2026 19:46:00 +0300 Subject: [PATCH 23/77] feat(student): add StudentBuilder --- .../ru/project/student/StudentBuilder.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/ru/project/student/StudentBuilder.java b/app/src/main/java/ru/project/student/StudentBuilder.java index a370f1f..3e1ec41 100644 --- a/app/src/main/java/ru/project/student/StudentBuilder.java +++ b/app/src/main/java/ru/project/student/StudentBuilder.java @@ -1,4 +1,26 @@ package ru.project.student; public class StudentBuilder { -} + private String groupNumber; + private double averageGrade; + private String recordBookNumber; + + public StudentBuilder setGroupNumber(String groupNumber) { + this.groupNumber = groupNumber; + return this; + } + + public StudentBuilder setAverageGrade(double averageGrade) { + this.averageGrade = averageGrade; + return this; + } + + public StudentBuilder setRecordBookNumber(String recordBookNumber) { + this.recordBookNumber = recordBookNumber; + return this; + } + + public Student build() { + return new Student(groupNumber, averageGrade, recordBookNumber); + } +} \ No newline at end of file From 5e71d2788537137313c162b9cd300b664e39d6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Mon, 24 Aug 2026 18:36:58 +0300 Subject: [PATCH 24/77] fix(student): add hashCode check to equals --- app/src/main/java/ru/project/student/Student.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/ru/project/student/Student.java b/app/src/main/java/ru/project/student/Student.java index 2eaa6ed..3e4332f 100644 --- a/app/src/main/java/ru/project/student/Student.java +++ b/app/src/main/java/ru/project/student/Student.java @@ -51,8 +51,12 @@ public class Student { if (obj == null || getClass() != obj.getClass()) { return false; - } + + if (this.hashCode() != obj.hashCode()) { + return false; + } + Student student = (Student) obj; return Double.compare(averageGrade, student.averageGrade) == 0 From caf2c95dbb295b656f9dae9ae014bb37ee0f319a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Mon, 24 Aug 2026 21:14:09 +0300 Subject: [PATCH 25/77] test(student): add JUnit tests --- .../java/ru/project/student/StudentTest.java | 168 ++++++++++++++++++ .../java/ru/project/student/StudentText.java | 4 - 2 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 app/src/test/java/ru/project/student/StudentTest.java delete mode 100644 app/src/test/java/ru/project/student/StudentText.java diff --git a/app/src/test/java/ru/project/student/StudentTest.java b/app/src/test/java/ru/project/student/StudentTest.java new file mode 100644 index 0000000..e9a7434 --- /dev/null +++ b/app/src/test/java/ru/project/student/StudentTest.java @@ -0,0 +1,168 @@ +package ru.project.student; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + + +class StudentTest { + @Test + void shouldCreateValidStudent() { + Student student = new StudentBuilder() + .setGroupNumber("101") + .setAverageGrade(4.5) + .setRecordBookNumber("12345") + .build(); + + assertEquals("101", student.getGroupNumber()); + assertEquals(4.5, student.getAverageGrade()); + assertEquals("12345", student.getRecordBookNumber()); + } + + @Test + void shouldThrowExceptionWhenGroupNumberIsEmpty() { + assertThrows(IllegalArgumentException.class, () -> { + new StudentBuilder() + .setGroupNumber("") + .setAverageGrade(4.5) + .setRecordBookNumber("12345") + .build(); + }); + } + + @Test + void shouldThrowExceptionWhenAverageGradeIsGreaterThanFive() { + assertThrows(IllegalArgumentException.class, () -> { + new StudentBuilder() + .setGroupNumber("101") + .setAverageGrade(6.0) + .setRecordBookNumber("12345") + .build(); + }); + } + + @Test + void shouldThrowExceptionWhenAverageGradeIsLessThanZero() { + assertThrows(IllegalArgumentException.class, () -> { + new StudentBuilder() + .setGroupNumber("101") + .setAverageGrade(-1.0) + .setRecordBookNumber("12345") + .build(); + }); + } + + @Test + void shouldCreateStudentWhenAverageGradeIsZero() { + Student student = new StudentBuilder() + .setGroupNumber("101") + .setAverageGrade(0.0) + .setRecordBookNumber("12345") + .build(); + + assertEquals(0.0, student.getAverageGrade()); + } + + @Test + void shouldCreateStudentWhenAverageGradeIsFive() { + Student student = new StudentBuilder() + .setGroupNumber("101") + .setAverageGrade(5.0) + .setRecordBookNumber("12345") + .build(); + + assertEquals(5.0, student.getAverageGrade()); + } + + @Test + void shouldThrowExceptionWhenRecordBookNumberIsEmpty() { + assertThrows(IllegalArgumentException.class, () -> { + new StudentBuilder() + .setGroupNumber("101") + .setAverageGrade(4.5) + .setRecordBookNumber("") + .build(); + }); + } + + @Test + void shouldThrowExceptionWhenGroupNumberIsNull() { + assertThrows(IllegalArgumentException.class, () -> { + new StudentBuilder() + .setGroupNumber(null) + .setAverageGrade(4.5) + .setRecordBookNumber("12345") + .build(); + }); + } + + @Test + void shouldThrowExceptionWhenGroupNumberIsBlank() { + assertThrows(IllegalArgumentException.class, () -> { + new StudentBuilder() + .setGroupNumber(" ") + .setAverageGrade(4.5) + .setRecordBookNumber("12345") + .build(); + }); + } + + @Test + void shouldThrowExceptionWhenRecordBookNumberIsNull() { + assertThrows(IllegalArgumentException.class, () -> { + new StudentBuilder() + .setGroupNumber("101") + .setAverageGrade(4.5) + .setRecordBookNumber(null) + .build(); + }); + } + + @Test + void shouldThrowExceptionWhenRecordBookNumberIsBlank() { + assertThrows(IllegalArgumentException.class, () -> { + new StudentBuilder() + .setGroupNumber("101") + .setAverageGrade(4.5) + .setRecordBookNumber(" ") + .build(); + }); + } + + @Test + void shouldReturnTrueWhenStudentsAreEqual() { + Student student1 = new StudentBuilder() + .setGroupNumber("101") + .setAverageGrade(4.5) + .setRecordBookNumber("12345") + .build(); + + Student student2 = new StudentBuilder() + .setGroupNumber("101") + .setAverageGrade(4.5) + .setRecordBookNumber("12345") + .build(); + + assertEquals(student1, student2); + assertEquals(student1.hashCode(), student2.hashCode()); + } + + @Test + void shouldReturnFalseWhenStudentsAreDifferent() { + Student student1 = new StudentBuilder() + .setGroupNumber("101") + .setAverageGrade(4.5) + .setRecordBookNumber("12345") + .build(); + + Student student2 = new StudentBuilder() + .setGroupNumber("102") + .setAverageGrade(4.5) + .setRecordBookNumber("12345") + .build(); + + assertNotEquals(student1, student2); + } +} diff --git a/app/src/test/java/ru/project/student/StudentText.java b/app/src/test/java/ru/project/student/StudentText.java deleted file mode 100644 index cad3bef..0000000 --- a/app/src/test/java/ru/project/student/StudentText.java +++ /dev/null @@ -1,4 +0,0 @@ -package ru.project.student; - -public class StudentText { -} From 2a3700b6c4abf0ce9d02d1d5f7b1084a2fddb572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Wed, 26 Aug 2026 12:36:38 +0400 Subject: [PATCH 26/77] refactor: separate collection state from UI navigation --- app/src/main/java/ru/project/App.java | 11 ++++- app/src/main/java/ru/project/AppState.java | 18 +++++++ .../java/ru/project/CollectionController.java | 49 +++++++++++++++++++ .../main/java/ru/project/DataCollection.java | 47 ++++++++++++++++++ .../java/ru/project/ui/CollectionView.java | 4 +- .../ru/project/ui/CountOccurrencesMenu.java | 4 +- .../ru/project/ui/CreateCollectionMenu.java | 4 +- .../main/java/ru/project/ui/Navigator.java | 17 ++++--- .../ru/project/ui/SaveCollectionMenu.java | 4 +- .../java/ru/project/ui/SearchElementMenu.java | 4 +- .../ru/project/ui/SortCollectionMenu.java | 4 +- 11 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/ru/project/AppState.java create mode 100644 app/src/main/java/ru/project/CollectionController.java create mode 100644 app/src/main/java/ru/project/DataCollection.java diff --git a/app/src/main/java/ru/project/App.java b/app/src/main/java/ru/project/App.java index bf57afc..646773c 100644 --- a/app/src/main/java/ru/project/App.java +++ b/app/src/main/java/ru/project/App.java @@ -21,9 +21,18 @@ public final class App { try { MultiWindowTextGUI gui = new MultiWindowTextGUI(screen); - Navigator navigator = new Navigator(gui); + // Application state + AppState state = new AppState(); + // Controllers + CollectionController collectionController = new CollectionController(state); + + // Navigation + Navigator navigator = new Navigator(gui, collectionController); + + // Application entry point gui.addWindowAndWait(new MainMenu(navigator)); + } finally { screen.stopScreen(); } diff --git a/app/src/main/java/ru/project/AppState.java b/app/src/main/java/ru/project/AppState.java new file mode 100644 index 0000000..144f857 --- /dev/null +++ b/app/src/main/java/ru/project/AppState.java @@ -0,0 +1,18 @@ +package ru.project; + +public class AppState { + + private DataCollection collection; + + public DataCollection getCollection() { + return collection; + } + + public void setCollection(DataCollection collection) { + this.collection = collection; + } + + public boolean hasCollection() { + return collection != null; + } +} diff --git a/app/src/main/java/ru/project/CollectionController.java b/app/src/main/java/ru/project/CollectionController.java new file mode 100644 index 0000000..8cf3deb --- /dev/null +++ b/app/src/main/java/ru/project/CollectionController.java @@ -0,0 +1,49 @@ +package ru.project; + +public class CollectionController { + + private final AppState state; + + public CollectionController(AppState state) { + this.state = state; + } + + public void createCollection() { + DataCollection collection = new DataCollection(); + + collection.add("Charlie"); + collection.add("Alice"); + collection.add("Bob"); + collection.add("Alice"); + + state.setCollection(collection); + } + + public DataCollection getCollection() { + return state.getCollection(); + } + + public boolean hasCollection() { + return state.hasCollection(); + } + + public void sortCollection() { + if (!state.hasCollection()) { + return; + } + + state.getCollection().sort(); + } + + public boolean contains(String element) { + return state.hasCollection() && state.getCollection().contains(element); + } + + public int countOccurrences(String element) { + if (!state.hasCollection()) { + return 0; + } + + return state.getCollection().count(element); + } +} diff --git a/app/src/main/java/ru/project/DataCollection.java b/app/src/main/java/ru/project/DataCollection.java new file mode 100644 index 0000000..bd5553a --- /dev/null +++ b/app/src/main/java/ru/project/DataCollection.java @@ -0,0 +1,47 @@ +package ru.project; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class DataCollection { + + private final List elements = new ArrayList<>(); + + public void add(String element) { + elements.add(element); + } + + public void sort() { + Collections.sort(elements); + } + + public boolean contains(String element) { + return elements.contains(element); + } + + public int count(String element) { + int count = 0; + + for (String current : elements) { + if (current.equals(element)) { + count++; + } + } + + return count; + } + + public List getElements() { + return Collections.unmodifiableList(elements); + } + + @Override + public String toString() { + if (elements.isEmpty()) { + return "Коллекция пуста"; + } + + return String.join(", ", elements); + } +} diff --git a/app/src/main/java/ru/project/ui/CollectionView.java b/app/src/main/java/ru/project/ui/CollectionView.java index 82b9540..ddad8cc 100644 --- a/app/src/main/java/ru/project/ui/CollectionView.java +++ b/app/src/main/java/ru/project/ui/CollectionView.java @@ -1,8 +1,10 @@ package ru.project.ui; +import ru.project.CollectionController; + public class CollectionView extends EmptyMenu { - public CollectionView() { + public CollectionView(CollectionController controller) { super("Текущая коллекция"); } } diff --git a/app/src/main/java/ru/project/ui/CountOccurrencesMenu.java b/app/src/main/java/ru/project/ui/CountOccurrencesMenu.java index db8e0c8..5ef70e5 100644 --- a/app/src/main/java/ru/project/ui/CountOccurrencesMenu.java +++ b/app/src/main/java/ru/project/ui/CountOccurrencesMenu.java @@ -1,8 +1,10 @@ package ru.project.ui; +import ru.project.CollectionController; + public class CountOccurrencesMenu extends EmptyMenu { - public CountOccurrencesMenu() { + public CountOccurrencesMenu(CollectionController controller) { super("Подсчёт количества вхождений"); } } diff --git a/app/src/main/java/ru/project/ui/CreateCollectionMenu.java b/app/src/main/java/ru/project/ui/CreateCollectionMenu.java index 6ea56fc..69cfd2d 100644 --- a/app/src/main/java/ru/project/ui/CreateCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/CreateCollectionMenu.java @@ -1,8 +1,10 @@ package ru.project.ui; +import ru.project.CollectionController; + public class CreateCollectionMenu extends EmptyMenu { - public CreateCollectionMenu() { + public CreateCollectionMenu(CollectionController controller) { super("Создание новой коллекции"); } } diff --git a/app/src/main/java/ru/project/ui/Navigator.java b/app/src/main/java/ru/project/ui/Navigator.java index 13489c1..873a1a4 100644 --- a/app/src/main/java/ru/project/ui/Navigator.java +++ b/app/src/main/java/ru/project/ui/Navigator.java @@ -2,13 +2,16 @@ package ru.project.ui; import com.googlecode.lanterna.gui2.BasicWindow; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.CollectionController; public class Navigator { private final WindowBasedTextGUI gui; + private final CollectionController collectionController; - public Navigator(WindowBasedTextGUI gui) { + public Navigator(WindowBasedTextGUI gui, CollectionController collectionController) { this.gui = gui; + this.collectionController = collectionController; } public void show(BasicWindow window) { @@ -16,26 +19,26 @@ public class Navigator { } public void showCollectionView() { - show(new CollectionView()); + show(new CollectionView(collectionController)); } public void showCreateCollection() { - show(new CreateCollectionMenu()); + show(new CreateCollectionMenu(collectionController)); } public void showSaveCollection() { - show(new SaveCollectionMenu()); + show(new SaveCollectionMenu(collectionController)); } public void showSortCollection() { - show(new SortCollectionMenu()); + show(new SortCollectionMenu(collectionController)); } public void showSearchElement() { - show(new SearchElementMenu()); + show(new SearchElementMenu(collectionController)); } public void showCountOccurrences() { - show(new CountOccurrencesMenu()); + show(new CountOccurrencesMenu(collectionController)); } } diff --git a/app/src/main/java/ru/project/ui/SaveCollectionMenu.java b/app/src/main/java/ru/project/ui/SaveCollectionMenu.java index cc0f36a..9f7d2e4 100644 --- a/app/src/main/java/ru/project/ui/SaveCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/SaveCollectionMenu.java @@ -1,8 +1,10 @@ package ru.project.ui; +import ru.project.CollectionController; + public class SaveCollectionMenu extends EmptyMenu { - public SaveCollectionMenu() { + public SaveCollectionMenu(CollectionController controller) { super("Сохранение текущей коллекции"); } } diff --git a/app/src/main/java/ru/project/ui/SearchElementMenu.java b/app/src/main/java/ru/project/ui/SearchElementMenu.java index 7e92c4f..b185219 100644 --- a/app/src/main/java/ru/project/ui/SearchElementMenu.java +++ b/app/src/main/java/ru/project/ui/SearchElementMenu.java @@ -1,8 +1,10 @@ package ru.project.ui; +import ru.project.CollectionController; + public class SearchElementMenu extends EmptyMenu { - public SearchElementMenu() { + public SearchElementMenu(CollectionController controller) { super("Поиск элемента"); } } diff --git a/app/src/main/java/ru/project/ui/SortCollectionMenu.java b/app/src/main/java/ru/project/ui/SortCollectionMenu.java index 68e6a7a..99bc1a8 100644 --- a/app/src/main/java/ru/project/ui/SortCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/SortCollectionMenu.java @@ -1,8 +1,10 @@ package ru.project.ui; +import ru.project.CollectionController; + public class SortCollectionMenu extends EmptyMenu { - public SortCollectionMenu() { + public SortCollectionMenu(CollectionController controller) { super("Сортировка текущей коллекции"); } } From 5ae40ff089c53df213022ccdbd33bee39e01a3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Wed, 26 Aug 2026 16:09:38 +0400 Subject: [PATCH 27/77] docs: clarify menu hierarchy with explicit nesting --- docs/MENU.ru.txt | 185 +++++++++++++++++++++++++++++++---------------- 1 file changed, 121 insertions(+), 64 deletions(-) diff --git a/docs/MENU.ru.txt b/docs/MENU.ru.txt index ad703cd..0be1048 100644 --- a/docs/MENU.ru.txt +++ b/docs/MENU.ru.txt @@ -1,73 +1,130 @@ -ГЛАВНОЕ МЕНЮ +Главное меню +{ + Показать текущую коллекцию -Показать текущую коллекцию + Меню создания новой коллекции + { + Вернуться -Меню создания новой коллекции - Вернуться - Задать размер - Настроить способ заполнения - Пользовательские данные - Задать поле 'Название A' - Задать поле 'Название B' - Задать поле 'Название C' - Задать индекс изменяемого объекта - Изменить данные объекта - Предпросмотр - Случайные данные - Настройка диапазона генерации - Поле 'Название A' - Задать минимальное значение - Задать максимальное значение - Поле 'Название B' - Задать минимальное значение - Задать максимальное значение - Поле 'Название C' - Задать минимальное значение - Задать максимальное значение - Данные из файла - Задать путь до файла + Задать размер + + Настроить способ заполнения + { + Пользовательские данные + { + Задать поле 'Название A' + Задать поле 'Название B' + Задать поле 'Название C' + Задать индекс изменяемого объекта + Изменить данные объекта + Предпросмотр + } + + Случайные данные + { + Настройка диапазона генерации + { + Поле 'Название A' + { + Задать минимальное значение + Задать максимальное значение + } + + Поле 'Название B' + { + Задать минимальное значение + Задать максимальное значение + } + + Поле 'Название C' + { + Задать минимальное значение + Задать максимальное значение + } + } + } + + Данные из файла + { + Задать путь до файла + { + Абсолютный + Относительный + } + } + } + + Выбрать способ заполнения + { + Пользовательские данные * + Случайные данные + Данные из файла + } + + Создать новую коллекцию + } + + Меню сохранения текущей коллекции в файл + { + Вывести текущий абсолютный путь до файла + + Задать путь до файла + { Абсолютный Относительный - Выбрать способ заполнения - Пользовательские данные * - Случайные данные - Данные из файла - Создать новую коллекцию + } -Меню сохранения текущей коллекции в файл - Вывести текущий абсолютный путь до файла - Задать путь до файла - Абсолютный - Относительный - Дописать коллекцию в текущий файл + Дописать коллекцию в текущий файл + } -Меню сортировки текущей коллекции - Вернуться - Настроить способ сортировки - Обычная сортировка - Сортировать по полю 'Название A' - Сортировать по полю 'Название B' - Сортировать по полю 'Название C' -Настроить способ сортировки - Сортировка по чётности - Выбрать числовое поле 'Название A' - Выбрать числовое поле 'Название B' -Выбрать способ сортировки - Обычная сортировка * - Сортировка по чётности -Сортировать текущую коллекцию + Меню сортировки текущей коллекции + { + Вернуться -Меню поиска элемента в текущей коллекции - Вернуться - Задать значение поля 'Название A' - Задать значение поля 'Название B' - Задать значение поля 'Название C' - Найти первое вхождение в текущей коллекции + Настроить способ сортировки + { + Обычная сортировка + { + Сортировать по полю 'Название A' + Сортировать по полю 'Название B' + Сортировать по полю 'Название C' + } + } -Меню подсчёта количества вхождений элемента - Задать значение поля 'Название A' - Задать значение поля 'Название B' - Задать значение поля 'Название C' - Выполнить многопоточный подсчёт + Настроить способ сортировки + { + Сортировка по чётности + { + Выбрать числовое поле 'Название A' + Выбрать числовое поле 'Название B' + } + } -Завершить выполнение программы + Выбрать способ сортировки + { + Обычная сортировка * + Сортировка по чётности + } + + Сортировать текущую коллекцию + } + + Меню поиска элемента в текущей коллекции + { + Вернуться + Задать значение поля 'Название A' + Задать значение поля 'Название B' + Задать значение поля 'Название C' + Найти первое вхождение в текущей коллекции + } + + Меню подсчёта количества вхождений элемента + { + Задать значение поля 'Название A' + Задать значение поля 'Название B' + Задать значение поля 'Название C' + Выполнить многопоточный подсчёт + } + + Завершить выполнение программы +} From 2f9e681b54b6f0794c333813277ab83f5d4d6efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Wed, 26 Aug 2026 15:57:17 +0300 Subject: [PATCH 28/77] fix: add missing imports for MyList, Student and Comparator in sorting package --- app/src/main/java/ru/project/sorting/MergeSortStrategy.java | 4 +++- app/src/main/java/ru/project/sorting/SortStrategy.java | 3 ++- .../ru/project/sorting/StudentAvarageGradeComparator.java | 3 ++- .../main/java/ru/project/sorting/StudentGroupComparator.java | 3 ++- .../java/ru/project/sorting/StudentRecordBookComparator.java | 3 ++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/ru/project/sorting/MergeSortStrategy.java b/app/src/main/java/ru/project/sorting/MergeSortStrategy.java index 86655f7..ab22562 100644 --- a/app/src/main/java/ru/project/sorting/MergeSortStrategy.java +++ b/app/src/main/java/ru/project/sorting/MergeSortStrategy.java @@ -1,4 +1,6 @@ -package sorting; +package ru.project.sorting; + +import ru.project.list.MyList; import java.util.Comparator; diff --git a/app/src/main/java/ru/project/sorting/SortStrategy.java b/app/src/main/java/ru/project/sorting/SortStrategy.java index f252317..7dc9983 100644 --- a/app/src/main/java/ru/project/sorting/SortStrategy.java +++ b/app/src/main/java/ru/project/sorting/SortStrategy.java @@ -1,5 +1,6 @@ -package sorting; +package ru.project.sorting; +import ru.project.list.MyList; import java.util.Comparator; public interface SortStrategy { diff --git a/app/src/main/java/ru/project/sorting/StudentAvarageGradeComparator.java b/app/src/main/java/ru/project/sorting/StudentAvarageGradeComparator.java index 1946fd4..a4ca818 100644 --- a/app/src/main/java/ru/project/sorting/StudentAvarageGradeComparator.java +++ b/app/src/main/java/ru/project/sorting/StudentAvarageGradeComparator.java @@ -1,5 +1,6 @@ -package sorting; +package ru.project.sorting; +import ru.project.model.Student; import java.util.Comparator; public class StudentAvarageGradeComparator implements Comparator { diff --git a/app/src/main/java/ru/project/sorting/StudentGroupComparator.java b/app/src/main/java/ru/project/sorting/StudentGroupComparator.java index b1efd93..a43c0e1 100644 --- a/app/src/main/java/ru/project/sorting/StudentGroupComparator.java +++ b/app/src/main/java/ru/project/sorting/StudentGroupComparator.java @@ -1,5 +1,6 @@ -package sorting; +package ru.project.sorting; +import ru.project.model.Student; import java.util.Comparator; public class StudentGroupComparator implements Comparator { diff --git a/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java b/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java index 22b73e4..fd72fb4 100644 --- a/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java +++ b/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java @@ -1,5 +1,6 @@ -package sorting; +package ru.project.sorting; +import ru.project.model.Student; import java.util.Comparator; public class StudentRecordBookComparator implements Comparator { From 2616603af4256cf4047cf711d88504bec6b264df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Wed, 26 Aug 2026 16:00:37 +0300 Subject: [PATCH 29/77] fix: rename StudentAvarageGradeComparator to StudentAverageGradeComparator --- ...radeComparator.java => StudentAverageGradeComparator.java} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename app/src/main/java/ru/project/sorting/{StudentAvarageGradeComparator.java => StudentAverageGradeComparator.java} (59%) diff --git a/app/src/main/java/ru/project/sorting/StudentAvarageGradeComparator.java b/app/src/main/java/ru/project/sorting/StudentAverageGradeComparator.java similarity index 59% rename from app/src/main/java/ru/project/sorting/StudentAvarageGradeComparator.java rename to app/src/main/java/ru/project/sorting/StudentAverageGradeComparator.java index a4ca818..488aff9 100644 --- a/app/src/main/java/ru/project/sorting/StudentAvarageGradeComparator.java +++ b/app/src/main/java/ru/project/sorting/StudentAverageGradeComparator.java @@ -3,10 +3,10 @@ package ru.project.sorting; import ru.project.model.Student; import java.util.Comparator; -public class StudentAvarageGradeComparator implements Comparator { +public class StudentAverageGradeComparator implements Comparator { @Override public int compare(Student s1, Student s2) { - return Double.compare(s1.getAvarageGrade(), s2.getAvarageGrade()); + return Double.compare(s1.getAverageGrade(), s2.getAverageGrade()); } } \ No newline at end of file From 3a77ce7d889a6b205cb8bfac8ffd6d7e864399dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Wed, 26 Aug 2026 18:58:39 +0400 Subject: [PATCH 30/77] style: format Java code with google-java-format --- .../ru/project/collection/MyLinkedList.java | 217 +++++++++--------- .../main/java/ru/project/student/Student.java | 119 +++++----- .../ru/project/student/StudentBuilder.java | 38 +-- 3 files changed, 188 insertions(+), 186 deletions(-) diff --git a/app/src/main/java/ru/project/collection/MyLinkedList.java b/app/src/main/java/ru/project/collection/MyLinkedList.java index 23b2b75..23bbad0 100644 --- a/app/src/main/java/ru/project/collection/MyLinkedList.java +++ b/app/src/main/java/ru/project/collection/MyLinkedList.java @@ -1,123 +1,120 @@ +package ru.project.collection; + import java.util.Arrays; -public class MyLinkedList implements MyList -{ - private Node head; - private int size; - public void add(T value) { - if (head == null) { - this.head = new Node<>(value); - } else { - Node temp = head; - while (temp.getNext() != null) { - temp = temp.getNext(); - } - temp.setNext(new Node<>(value)); - } - size++; - } - public T get(int index) - { - int currentIndex = 0; - Node temp = head; - while(temp != null) - { - if (currentIndex == index) - { - return temp.getValue(); - } - else { - temp = temp.getNext(); - currentIndex++; - } +public class MyLinkedList implements MyList { + private Node head; + private int size; - } - throw new IllegalArgumentException(); + public void add(T value) { + if (head == null) { + this.head = new Node<>(value); + } else { + Node temp = head; + while (temp.getNext() != null) { + temp = temp.getNext(); + } + temp.setNext(new Node<>(value)); } - public void remove(int index) - { - if (index < 0 || index >= size) { - throw new IndexOutOfBoundsException(); - } - if (index == 0) - { - head = head.getNext(); - size--; - return; + size++; + } - } - int currentIndex = 0; - Node temp = head; - while (temp != null) - { - if (currentIndex == index-1) - { - temp.setNext(temp.getNext().getNext()); - size--; - return; - } - else { - temp = temp.getNext(); - currentIndex++; - } - } - } - public T set(int index, T value) { - if (index < 0 || index >= size) { - throw new IllegalArgumentException(); - } + public T get(int index) { + int currentIndex = 0; + Node temp = head; + while (temp != null) { + if (currentIndex == index) { + return temp.getValue(); - Node temp = head; - for (int i = 0; i < index; i++) { - temp = temp.getNext(); - } + } else { + temp = temp.getNext(); + currentIndex++; + } + } + throw new IllegalArgumentException(); + } - T oldValue = temp.getValue(); - temp.setValue(value); - return oldValue; + public void remove(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(); } - public int size() { - return size; + if (index == 0) { + head = head.getNext(); + size--; + return; } - public boolean isEmpty() - { - return size == 0; + int currentIndex = 0; + Node temp = head; + while (temp != null) { + if (currentIndex == index - 1) { + temp.setNext(temp.getNext().getNext()); + size--; + return; + } else { + temp = temp.getNext(); + currentIndex++; + } } - @Override - public String toString() { - Object[] res = new Object[size]; - int index = 0; - Node temp = head; - while (temp != null) { - res[index] = temp.getValue(); - index++; - temp = temp.getNext(); - } - return Arrays.toString(res); + } + + public T set(int index, T value) { + if (index < 0 || index >= size) { + throw new IllegalArgumentException(); } - private static class Node { - private T value; - private Node next; - - public Node(T value) { - this.value = value; - } - - public void setValue(T value) { - this.value = value; - } - - public void setNext(Node next) { - this.next = next; - } - - public T getValue() { - return value; - } - - public Node getNext() { - return next; - } + Node temp = head; + for (int i = 0; i < index; i++) { + temp = temp.getNext(); } -} \ No newline at end of file + + T oldValue = temp.getValue(); + temp.setValue(value); + return oldValue; + } + + public int size() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + @Override + public String toString() { + Object[] res = new Object[size]; + int index = 0; + Node temp = head; + while (temp != null) { + res[index] = temp.getValue(); + index++; + temp = temp.getNext(); + } + return Arrays.toString(res); + } + + private static class Node { + private T value; + private Node next; + + public Node(T value) { + this.value = value; + } + + public void setValue(T value) { + this.value = value; + } + + public void setNext(Node next) { + this.next = next; + } + + public T getValue() { + return value; + } + + public Node getNext() { + return next; + } + } +} diff --git a/app/src/main/java/ru/project/student/Student.java b/app/src/main/java/ru/project/student/Student.java index 3e4332f..f433387 100644 --- a/app/src/main/java/ru/project/student/Student.java +++ b/app/src/main/java/ru/project/student/Student.java @@ -3,69 +3,74 @@ package ru.project.student; import java.util.Objects; public class Student { - private final String groupNumber; - private final double averageGrade; - private final String recordBookNumber; + private final String groupNumber; + private final double averageGrade; + private final String recordBookNumber; - Student(String groupNumber, double averageGrade, String recordBookNumber) { - if (groupNumber == null || groupNumber.isBlank()) { - throw new IllegalArgumentException("Group number cannot be empty"); - } - if (averageGrade < 0.0 || averageGrade > 5.0) { - throw new IllegalArgumentException("Average grade must be between 0.0 and 5.0"); - } - if (recordBookNumber == null || recordBookNumber.isBlank()) { - throw new IllegalArgumentException("Record book number cannot be empty"); - } - this.groupNumber = groupNumber; - this.averageGrade = averageGrade; - this.recordBookNumber = recordBookNumber; + Student(String groupNumber, double averageGrade, String recordBookNumber) { + if (groupNumber == null || groupNumber.isBlank()) { + throw new IllegalArgumentException("Group number cannot be empty"); + } + if (averageGrade < 0.0 || averageGrade > 5.0) { + throw new IllegalArgumentException("Average grade must be between 0.0 and 5.0"); + } + if (recordBookNumber == null || recordBookNumber.isBlank()) { + throw new IllegalArgumentException("Record book number cannot be empty"); + } + this.groupNumber = groupNumber; + this.averageGrade = averageGrade; + this.recordBookNumber = recordBookNumber; + } + + public String getGroupNumber() { + return groupNumber; + } + + public double getAverageGrade() { + return averageGrade; + } + + public String getRecordBookNumber() { + return recordBookNumber; + } + + @Override + public String toString() { + return "Student{" + + "groupNumber='" + + groupNumber + + '\'' + + ", averageGrade=" + + averageGrade + + ", recordBookNumber='" + + recordBookNumber + + '\'' + + '}'; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; } - public String getGroupNumber() { - return groupNumber; + if (obj == null || getClass() != obj.getClass()) { + return false; } - public double getAverageGrade() { - return averageGrade; + if (this.hashCode() != obj.hashCode()) { + return false; } - public String getRecordBookNumber() { - return recordBookNumber; - } + Student student = (Student) obj; - @Override - public String toString() { - return "Student{" + - "groupNumber='" + groupNumber + '\'' + - ", averageGrade=" + averageGrade + - ", recordBookNumber='" + recordBookNumber + '\'' + - '}'; - } + return Double.compare(averageGrade, student.averageGrade) == 0 + && Objects.equals(groupNumber, student.groupNumber) + && Objects.equals(recordBookNumber, student.recordBookNumber); + } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj == null || getClass() != obj.getClass()) { - return false; - } - - if (this.hashCode() != obj.hashCode()) { - return false; - } - - Student student = (Student) obj; - - return Double.compare(averageGrade, student.averageGrade) == 0 - && Objects.equals(groupNumber, student.groupNumber) - && Objects.equals(recordBookNumber, student.recordBookNumber); - } - - @Override - public int hashCode() { - return Objects.hash(groupNumber, averageGrade, recordBookNumber); - } -} \ No newline at end of file + @Override + public int hashCode() { + return Objects.hash(groupNumber, averageGrade, recordBookNumber); + } +} diff --git a/app/src/main/java/ru/project/student/StudentBuilder.java b/app/src/main/java/ru/project/student/StudentBuilder.java index 3e1ec41..af67320 100644 --- a/app/src/main/java/ru/project/student/StudentBuilder.java +++ b/app/src/main/java/ru/project/student/StudentBuilder.java @@ -1,26 +1,26 @@ package ru.project.student; public class StudentBuilder { - private String groupNumber; - private double averageGrade; - private String recordBookNumber; + private String groupNumber; + private double averageGrade; + private String recordBookNumber; - public StudentBuilder setGroupNumber(String groupNumber) { - this.groupNumber = groupNumber; - return this; - } + public StudentBuilder setGroupNumber(String groupNumber) { + this.groupNumber = groupNumber; + return this; + } - public StudentBuilder setAverageGrade(double averageGrade) { - this.averageGrade = averageGrade; - return this; - } + public StudentBuilder setAverageGrade(double averageGrade) { + this.averageGrade = averageGrade; + return this; + } - public StudentBuilder setRecordBookNumber(String recordBookNumber) { - this.recordBookNumber = recordBookNumber; - return this; - } + public StudentBuilder setRecordBookNumber(String recordBookNumber) { + this.recordBookNumber = recordBookNumber; + return this; + } - public Student build() { - return new Student(groupNumber, averageGrade, recordBookNumber); - } -} \ No newline at end of file + public Student build() { + return new Student(groupNumber, averageGrade, recordBookNumber); + } +} From 1ee2fc3d062068171b6c7372f7375acd7462b428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Wed, 26 Aug 2026 19:01:39 +0400 Subject: [PATCH 31/77] refactor: restructure application using MVP architecture --- app/src/main/java/ru/project/App.java | 41 ++++++----- app/src/main/java/ru/project/AppState.java | 18 ----- .../java/ru/project/CollectionController.java | 49 ------------- .../main/java/ru/project/DataCollection.java | 47 ------------- .../main/java/ru/project/model/AppState.java | 32 +++++++++ .../presenter/CollectionPresenter.java | 31 ++++++++ .../ru/project/presenter/MainPresenter.java | 70 +++++++++++++++++++ .../java/ru/project/ui/CollectionView.java | 10 --- .../ru/project/ui/CountOccurrencesMenu.java | 10 --- .../ru/project/ui/CreateCollectionMenu.java | 10 --- .../main/java/ru/project/ui/EmptyMenu.java | 23 ------ app/src/main/java/ru/project/ui/MainMenu.java | 34 --------- .../main/java/ru/project/ui/Navigator.java | 44 ------------ .../ru/project/ui/SaveCollectionMenu.java | 10 --- .../java/ru/project/ui/SearchElementMenu.java | 10 --- .../ru/project/ui/SortCollectionMenu.java | 10 --- .../java/ru/project/view/CollectionView.java | 47 +++++++++++++ .../main/java/ru/project/view/MainView.java | 62 ++++++++++++++++ 18 files changed, 266 insertions(+), 292 deletions(-) delete mode 100644 app/src/main/java/ru/project/AppState.java delete mode 100644 app/src/main/java/ru/project/CollectionController.java delete mode 100644 app/src/main/java/ru/project/DataCollection.java create mode 100644 app/src/main/java/ru/project/model/AppState.java create mode 100644 app/src/main/java/ru/project/presenter/CollectionPresenter.java create mode 100644 app/src/main/java/ru/project/presenter/MainPresenter.java delete mode 100644 app/src/main/java/ru/project/ui/CollectionView.java delete mode 100644 app/src/main/java/ru/project/ui/CountOccurrencesMenu.java delete mode 100644 app/src/main/java/ru/project/ui/CreateCollectionMenu.java delete mode 100644 app/src/main/java/ru/project/ui/EmptyMenu.java delete mode 100644 app/src/main/java/ru/project/ui/MainMenu.java delete mode 100644 app/src/main/java/ru/project/ui/Navigator.java delete mode 100644 app/src/main/java/ru/project/ui/SaveCollectionMenu.java delete mode 100644 app/src/main/java/ru/project/ui/SearchElementMenu.java delete mode 100644 app/src/main/java/ru/project/ui/SortCollectionMenu.java create mode 100644 app/src/main/java/ru/project/view/CollectionView.java create mode 100644 app/src/main/java/ru/project/view/MainView.java diff --git a/app/src/main/java/ru/project/App.java b/app/src/main/java/ru/project/App.java index 646773c..f7c48d9 100644 --- a/app/src/main/java/ru/project/App.java +++ b/app/src/main/java/ru/project/App.java @@ -2,39 +2,46 @@ package ru.project; import com.googlecode.lanterna.gui2.MultiWindowTextGUI; import com.googlecode.lanterna.screen.Screen; -import com.googlecode.lanterna.screen.TerminalScreen; import com.googlecode.lanterna.terminal.DefaultTerminalFactory; -import ru.project.ui.MainMenu; -import ru.project.ui.Navigator; +import ru.project.model.AppState; +import ru.project.presenter.MainPresenter; +import ru.project.view.MainView; public final class App { private App() {} - public static void main(String[] args) throws Exception { - DefaultTerminalFactory factory = new DefaultTerminalFactory(); - - Screen screen = new TerminalScreen(factory.createTerminal()); - - screen.startScreen(); + public static void main(String[] args) { + Screen screen = null; try { + DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory(); + + screen = terminalFactory.createScreen(); + screen.startScreen(); + MultiWindowTextGUI gui = new MultiWindowTextGUI(screen); - // Application state AppState state = new AppState(); + MainView view = new MainView(); - // Controllers - CollectionController collectionController = new CollectionController(state); + MainPresenter presenter = new MainPresenter(state, view, gui); - // Navigation - Navigator navigator = new Navigator(gui, collectionController); + presenter.start(); - // Application entry point - gui.addWindowAndWait(new MainMenu(navigator)); + } catch (Exception e) { + System.err.println("Произошла ошибка при выполнении программы:"); + e.printStackTrace(); } finally { - screen.stopScreen(); + if (screen != null) { + try { + screen.stopScreen(); + } catch (Exception e) { + System.err.println("Не удалось корректно завершить работу терминала:"); + e.printStackTrace(); + } + } } } } diff --git a/app/src/main/java/ru/project/AppState.java b/app/src/main/java/ru/project/AppState.java deleted file mode 100644 index 144f857..0000000 --- a/app/src/main/java/ru/project/AppState.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.project; - -public class AppState { - - private DataCollection collection; - - public DataCollection getCollection() { - return collection; - } - - public void setCollection(DataCollection collection) { - this.collection = collection; - } - - public boolean hasCollection() { - return collection != null; - } -} diff --git a/app/src/main/java/ru/project/CollectionController.java b/app/src/main/java/ru/project/CollectionController.java deleted file mode 100644 index 8cf3deb..0000000 --- a/app/src/main/java/ru/project/CollectionController.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.project; - -public class CollectionController { - - private final AppState state; - - public CollectionController(AppState state) { - this.state = state; - } - - public void createCollection() { - DataCollection collection = new DataCollection(); - - collection.add("Charlie"); - collection.add("Alice"); - collection.add("Bob"); - collection.add("Alice"); - - state.setCollection(collection); - } - - public DataCollection getCollection() { - return state.getCollection(); - } - - public boolean hasCollection() { - return state.hasCollection(); - } - - public void sortCollection() { - if (!state.hasCollection()) { - return; - } - - state.getCollection().sort(); - } - - public boolean contains(String element) { - return state.hasCollection() && state.getCollection().contains(element); - } - - public int countOccurrences(String element) { - if (!state.hasCollection()) { - return 0; - } - - return state.getCollection().count(element); - } -} diff --git a/app/src/main/java/ru/project/DataCollection.java b/app/src/main/java/ru/project/DataCollection.java deleted file mode 100644 index bd5553a..0000000 --- a/app/src/main/java/ru/project/DataCollection.java +++ /dev/null @@ -1,47 +0,0 @@ -package ru.project; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class DataCollection { - - private final List elements = new ArrayList<>(); - - public void add(String element) { - elements.add(element); - } - - public void sort() { - Collections.sort(elements); - } - - public boolean contains(String element) { - return elements.contains(element); - } - - public int count(String element) { - int count = 0; - - for (String current : elements) { - if (current.equals(element)) { - count++; - } - } - - return count; - } - - public List getElements() { - return Collections.unmodifiableList(elements); - } - - @Override - public String toString() { - if (elements.isEmpty()) { - return "Коллекция пуста"; - } - - return String.join(", ", elements); - } -} diff --git a/app/src/main/java/ru/project/model/AppState.java b/app/src/main/java/ru/project/model/AppState.java new file mode 100644 index 0000000..9b2a61e --- /dev/null +++ b/app/src/main/java/ru/project/model/AppState.java @@ -0,0 +1,32 @@ +package ru.project.model; + +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.student.Student; + +public class AppState { + + private MyList collection; + private String currentFilePath; + + public AppState() { + this.collection = new MyLinkedList<>(); + this.currentFilePath = null; + } + + public MyList getCollection() { + return collection; + } + + public void setCollection(MyList collection) { + this.collection = collection; + } + + public String getCurrentFilePath() { + return currentFilePath; + } + + public void setCurrentFilePath(String currentFilePath) { + this.currentFilePath = currentFilePath; + } +} diff --git a/app/src/main/java/ru/project/presenter/CollectionPresenter.java b/app/src/main/java/ru/project/presenter/CollectionPresenter.java new file mode 100644 index 0000000..f9c6606 --- /dev/null +++ b/app/src/main/java/ru/project/presenter/CollectionPresenter.java @@ -0,0 +1,31 @@ +package ru.project.presenter; + +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.collection.MyList; +import ru.project.model.AppState; +import ru.project.student.Student; +import ru.project.view.CollectionView; + +public class CollectionPresenter { + + private final AppState state; + private final CollectionView view; + private final WindowBasedTextGUI gui; + + public CollectionPresenter(AppState state, CollectionView view, WindowBasedTextGUI gui) { + this.state = state; + this.view = view; + this.gui = gui; + } + + public void start() { + MyList collection = state.getCollection(); + + if (collection == null || collection.isEmpty()) { + view.showEmptyMessage(gui); + return; + } + + view.showCollection(gui, collection); + } +} diff --git a/app/src/main/java/ru/project/presenter/MainPresenter.java b/app/src/main/java/ru/project/presenter/MainPresenter.java new file mode 100644 index 0000000..91b195b --- /dev/null +++ b/app/src/main/java/ru/project/presenter/MainPresenter.java @@ -0,0 +1,70 @@ +package ru.project.presenter; + +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import com.googlecode.lanterna.gui2.dialogs.MessageDialog; +import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton; +import ru.project.model.AppState; +import ru.project.view.CollectionView; +import ru.project.view.MainView; + +public class MainPresenter { + + private final AppState state; + private final MainView view; + private final WindowBasedTextGUI gui; + private final CollectionPresenter collectionPresenter; + + public MainPresenter(AppState state, MainView view, WindowBasedTextGUI gui) { + this.state = state; + this.view = view; + this.gui = gui; + this.collectionPresenter = new CollectionPresenter(state, new CollectionView(), gui); + } + + public void start() { + bindActions(); + view.show(gui); + } + + private void bindActions() { + view.setShowCollectionAction(this::showCollection); + view.setCreateCollectionAction(this::createCollection); + view.setSaveCollectionAction(this::saveCollection); + view.setSortCollectionAction(this::sortCollection); + view.setSearchCollectionAction(this::searchCollection); + view.setCountOccurrencesAction(this::countOccurrences); + view.setExitAction(this::exit); + } + + private void showCollection() { + collectionPresenter.start(); + } + + private void createCollection() { + showMessage("Меню создания коллекции."); + } + + private void saveCollection() { + showMessage("Меню сохранения коллекции."); + } + + private void sortCollection() { + showMessage("Меню сортировки коллекции."); + } + + private void searchCollection() { + showMessage("Меню поиска элемента."); + } + + private void countOccurrences() { + showMessage("Меню подсчёта количества вхождений."); + } + + private void exit() { + view.close(); + } + + private void showMessage(String message) { + MessageDialog.showMessageDialog(gui, "Информация", message, MessageDialogButton.OK); + } +} diff --git a/app/src/main/java/ru/project/ui/CollectionView.java b/app/src/main/java/ru/project/ui/CollectionView.java deleted file mode 100644 index ddad8cc..0000000 --- a/app/src/main/java/ru/project/ui/CollectionView.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.project.ui; - -import ru.project.CollectionController; - -public class CollectionView extends EmptyMenu { - - public CollectionView(CollectionController controller) { - super("Текущая коллекция"); - } -} diff --git a/app/src/main/java/ru/project/ui/CountOccurrencesMenu.java b/app/src/main/java/ru/project/ui/CountOccurrencesMenu.java deleted file mode 100644 index 5ef70e5..0000000 --- a/app/src/main/java/ru/project/ui/CountOccurrencesMenu.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.project.ui; - -import ru.project.CollectionController; - -public class CountOccurrencesMenu extends EmptyMenu { - - public CountOccurrencesMenu(CollectionController controller) { - super("Подсчёт количества вхождений"); - } -} diff --git a/app/src/main/java/ru/project/ui/CreateCollectionMenu.java b/app/src/main/java/ru/project/ui/CreateCollectionMenu.java deleted file mode 100644 index 69cfd2d..0000000 --- a/app/src/main/java/ru/project/ui/CreateCollectionMenu.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.project.ui; - -import ru.project.CollectionController; - -public class CreateCollectionMenu extends EmptyMenu { - - public CreateCollectionMenu(CollectionController controller) { - super("Создание новой коллекции"); - } -} diff --git a/app/src/main/java/ru/project/ui/EmptyMenu.java b/app/src/main/java/ru/project/ui/EmptyMenu.java deleted file mode 100644 index 0385780..0000000 --- a/app/src/main/java/ru/project/ui/EmptyMenu.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.project.ui; - -import com.googlecode.lanterna.gui2.BasicWindow; -import com.googlecode.lanterna.gui2.Button; -import com.googlecode.lanterna.gui2.Direction; -import com.googlecode.lanterna.gui2.Label; -import com.googlecode.lanterna.gui2.LinearLayout; -import com.googlecode.lanterna.gui2.Panel; - -public abstract class EmptyMenu extends BasicWindow { - - protected EmptyMenu(String title) { - super(title); - - Panel panel = new Panel(new LinearLayout(Direction.VERTICAL)); - - panel.addComponent(new Label("Раздел находится в разработке")); - - panel.addComponent(new Button("Назад", this::close)); - - setComponent(panel); - } -} diff --git a/app/src/main/java/ru/project/ui/MainMenu.java b/app/src/main/java/ru/project/ui/MainMenu.java deleted file mode 100644 index 361ce13..0000000 --- a/app/src/main/java/ru/project/ui/MainMenu.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.project.ui; - -import com.googlecode.lanterna.gui2.BasicWindow; -import com.googlecode.lanterna.gui2.Button; -import com.googlecode.lanterna.gui2.Direction; -import com.googlecode.lanterna.gui2.LinearLayout; -import com.googlecode.lanterna.gui2.Panel; - -public class MainMenu extends BasicWindow { - - public MainMenu(Navigator navigator) { - super("ГЛАВНОЕ МЕНЮ"); - - Panel panel = new Panel(new LinearLayout(Direction.VERTICAL)); - - panel.addComponent(new Button("Показать текущую коллекцию", navigator::showCollectionView)); - - panel.addComponent(new Button("Создать новую коллекцию", navigator::showCreateCollection)); - - panel.addComponent( - new Button("Сохранить текущую коллекцию в файл", navigator::showSaveCollection)); - - panel.addComponent(new Button("Сортировать текущую коллекцию", navigator::showSortCollection)); - - panel.addComponent(new Button("Найти элемент", navigator::showSearchElement)); - - panel.addComponent( - new Button("Подсчитать количество вхождений", navigator::showCountOccurrences)); - - panel.addComponent(new Button("Завершить выполнение программы", this::close)); - - setComponent(panel); - } -} diff --git a/app/src/main/java/ru/project/ui/Navigator.java b/app/src/main/java/ru/project/ui/Navigator.java deleted file mode 100644 index 873a1a4..0000000 --- a/app/src/main/java/ru/project/ui/Navigator.java +++ /dev/null @@ -1,44 +0,0 @@ -package ru.project.ui; - -import com.googlecode.lanterna.gui2.BasicWindow; -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -import ru.project.CollectionController; - -public class Navigator { - - private final WindowBasedTextGUI gui; - private final CollectionController collectionController; - - public Navigator(WindowBasedTextGUI gui, CollectionController collectionController) { - this.gui = gui; - this.collectionController = collectionController; - } - - public void show(BasicWindow window) { - gui.addWindowAndWait(window); - } - - public void showCollectionView() { - show(new CollectionView(collectionController)); - } - - public void showCreateCollection() { - show(new CreateCollectionMenu(collectionController)); - } - - public void showSaveCollection() { - show(new SaveCollectionMenu(collectionController)); - } - - public void showSortCollection() { - show(new SortCollectionMenu(collectionController)); - } - - public void showSearchElement() { - show(new SearchElementMenu(collectionController)); - } - - public void showCountOccurrences() { - show(new CountOccurrencesMenu(collectionController)); - } -} diff --git a/app/src/main/java/ru/project/ui/SaveCollectionMenu.java b/app/src/main/java/ru/project/ui/SaveCollectionMenu.java deleted file mode 100644 index 9f7d2e4..0000000 --- a/app/src/main/java/ru/project/ui/SaveCollectionMenu.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.project.ui; - -import ru.project.CollectionController; - -public class SaveCollectionMenu extends EmptyMenu { - - public SaveCollectionMenu(CollectionController controller) { - super("Сохранение текущей коллекции"); - } -} diff --git a/app/src/main/java/ru/project/ui/SearchElementMenu.java b/app/src/main/java/ru/project/ui/SearchElementMenu.java deleted file mode 100644 index b185219..0000000 --- a/app/src/main/java/ru/project/ui/SearchElementMenu.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.project.ui; - -import ru.project.CollectionController; - -public class SearchElementMenu extends EmptyMenu { - - public SearchElementMenu(CollectionController controller) { - super("Поиск элемента"); - } -} diff --git a/app/src/main/java/ru/project/ui/SortCollectionMenu.java b/app/src/main/java/ru/project/ui/SortCollectionMenu.java deleted file mode 100644 index 99bc1a8..0000000 --- a/app/src/main/java/ru/project/ui/SortCollectionMenu.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.project.ui; - -import ru.project.CollectionController; - -public class SortCollectionMenu extends EmptyMenu { - - public SortCollectionMenu(CollectionController controller) { - super("Сортировка текущей коллекции"); - } -} diff --git a/app/src/main/java/ru/project/view/CollectionView.java b/app/src/main/java/ru/project/view/CollectionView.java new file mode 100644 index 0000000..27d139e --- /dev/null +++ b/app/src/main/java/ru/project/view/CollectionView.java @@ -0,0 +1,47 @@ +package ru.project.view; + +import com.googlecode.lanterna.gui2.BasicWindow; +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.collection.MyList; +import ru.project.student.Student; + +public class CollectionView { + + public void showCollection(WindowBasedTextGUI gui, MyList collection) { + BasicWindow window = new BasicWindow("Текущая коллекция"); + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + for (int i = 0; i < collection.size(); i++) { + Student student = collection.get(i); + + panel.addComponent(new Label((i + 1) + ". " + student)); + } + + panel.addComponent(new Button("Вернуться", window::close)); + + window.setComponent(panel); + + gui.addWindowAndWait(window); + } + + public void showEmptyMessage(WindowBasedTextGUI gui) { + BasicWindow window = new BasicWindow("Текущая коллекция"); + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + panel.addComponent(new Label("Текущая коллекция пуста.")); + + panel.addComponent(new Button("Вернуться", window::close)); + + window.setComponent(panel); + + gui.addWindowAndWait(window); + } +} diff --git a/app/src/main/java/ru/project/view/MainView.java b/app/src/main/java/ru/project/view/MainView.java new file mode 100644 index 0000000..632f2db --- /dev/null +++ b/app/src/main/java/ru/project/view/MainView.java @@ -0,0 +1,62 @@ +package ru.project.view; + +import com.googlecode.lanterna.gui2.ActionListBox; +import com.googlecode.lanterna.gui2.BasicWindow; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; + +public class MainView { + + private final BasicWindow window; + private final ActionListBox menu; + + public MainView() { + window = new BasicWindow("Главное меню"); + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + menu = new ActionListBox(); + + panel.addComponent(menu); + + window.setComponent(panel); + } + + public void setShowCollectionAction(Runnable action) { + menu.addItem("Показать текущую коллекцию", action); + } + + public void setCreateCollectionAction(Runnable action) { + menu.addItem("Меню создания новой коллекции", action); + } + + public void setSaveCollectionAction(Runnable action) { + menu.addItem("Меню сохранения текущей коллекции в файл", action); + } + + public void setSortCollectionAction(Runnable action) { + menu.addItem("Меню сортировки текущей коллекции", action); + } + + public void setSearchCollectionAction(Runnable action) { + menu.addItem("Меню поиска элемента в текущей коллекции", action); + } + + public void setCountOccurrencesAction(Runnable action) { + menu.addItem("Меню подсчёта количества вхождений элемента", action); + } + + public void setExitAction(Runnable action) { + menu.addItem("Завершить выполнение программы", action); + } + + public void show(WindowBasedTextGUI gui) { + gui.addWindowAndWait(window); + } + + public void close() { + window.close(); + } +} From d08546e05c89fb3335fc5d47c56c6ddd2444a067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Wed, 26 Aug 2026 16:40:03 +0300 Subject: [PATCH 32/77] refactor: change recordBookNumber type to int and add copy methods --- .../main/java/ru/project/student/Student.java | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/ru/project/student/Student.java b/app/src/main/java/ru/project/student/Student.java index 3e4332f..f8c529a 100644 --- a/app/src/main/java/ru/project/student/Student.java +++ b/app/src/main/java/ru/project/student/Student.java @@ -5,17 +5,17 @@ import java.util.Objects; public class Student { private final String groupNumber; private final double averageGrade; - private final String recordBookNumber; + private final int recordBookNumber; - Student(String groupNumber, double averageGrade, String recordBookNumber) { + public Student(String groupNumber, double averageGrade, int recordBookNumber) { if (groupNumber == null || groupNumber.isBlank()) { throw new IllegalArgumentException("Group number cannot be empty"); } if (averageGrade < 0.0 || averageGrade > 5.0) { throw new IllegalArgumentException("Average grade must be between 0.0 and 5.0"); } - if (recordBookNumber == null || recordBookNumber.isBlank()) { - throw new IllegalArgumentException("Record book number cannot be empty"); + if (recordBookNumber <= 0) { + throw new IllegalArgumentException("Record book number must be positive"); } this.groupNumber = groupNumber; this.averageGrade = averageGrade; @@ -30,42 +30,43 @@ public class Student { return averageGrade; } - public String getRecordBookNumber() { + public int getRecordBookNumber() { return recordBookNumber; } + public Student withGroupNumber(String groupNumber) { + return new Student(groupNumber, this.averageGrade, this.recordBookNumber); + } + + public Student withAverageGrade(double averageGrade) { + return new Student(this.groupNumber, averageGrade, this.recordBookNumber); + } + + public Student withRecordBookNumber(int recordBookNumber) { + return new Student(this.groupNumber, this.averageGrade, recordBookNumber); + } + @Override public String toString() { return "Student{" + "groupNumber='" + groupNumber + '\'' + ", averageGrade=" + averageGrade + - ", recordBookNumber='" + recordBookNumber + '\'' + + ", recordBookNumber=" + recordBookNumber + '}'; } @Override public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj == null || getClass() != obj.getClass()) { - return false; - } - - if (this.hashCode() != obj.hashCode()) { - return false; - } - + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; Student student = (Student) obj; - return Double.compare(averageGrade, student.averageGrade) == 0 - && Objects.equals(groupNumber, student.groupNumber) - && Objects.equals(recordBookNumber, student.recordBookNumber); + && recordBookNumber == student.recordBookNumber + && Objects.equals(groupNumber, student.groupNumber); } @Override public int hashCode() { return Objects.hash(groupNumber, averageGrade, recordBookNumber); } -} \ No newline at end of file +} From 456ea9005ab86cccae7a7e29e90b8fabca3590c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Wed, 26 Aug 2026 17:49:39 +0300 Subject: [PATCH 33/77] feat: add validation for averageGrade and recordBookNumber --- app/src/main/java/ru/project/student/Student.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ru/project/student/Student.java b/app/src/main/java/ru/project/student/Student.java index f8c529a..288fddd 100644 --- a/app/src/main/java/ru/project/student/Student.java +++ b/app/src/main/java/ru/project/student/Student.java @@ -7,9 +7,11 @@ public class Student { private final double averageGrade; private final int recordBookNumber; + // Конструктор с проверками public Student(String groupNumber, double averageGrade, int recordBookNumber) { - if (groupNumber == null || groupNumber.isBlank()) { - throw new IllegalArgumentException("Group number cannot be empty"); + // Проверка groupNumber на формат + if (groupNumber == null || !groupNumber.matches("^[A-Z][0-9]{2}$")) { + throw new IllegalArgumentException("Group number must match format: one uppercase letter + two digits (e.g. A12)"); } if (averageGrade < 0.0 || averageGrade > 5.0) { throw new IllegalArgumentException("Average grade must be between 0.0 and 5.0"); @@ -22,6 +24,7 @@ public class Student { this.recordBookNumber = recordBookNumber; } + // Геттеры — исправлен тип groupNumber (теперь String) public String getGroupNumber() { return groupNumber; } @@ -34,6 +37,7 @@ public class Student { return recordBookNumber; } + // Методы копирования (with-методы) public Student withGroupNumber(String groupNumber) { return new Student(groupNumber, this.averageGrade, this.recordBookNumber); } From 26843401cee9e090c8bfe5ba1598824a931c659f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Wed, 26 Aug 2026 17:57:14 +0300 Subject: [PATCH 34/77] feat: update StudentBuilder and dependent classes for new Student model --- app/src/main/java/ru/project/student/StudentBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/ru/project/student/StudentBuilder.java b/app/src/main/java/ru/project/student/StudentBuilder.java index 3e1ec41..6d7d5fc 100644 --- a/app/src/main/java/ru/project/student/StudentBuilder.java +++ b/app/src/main/java/ru/project/student/StudentBuilder.java @@ -3,7 +3,7 @@ package ru.project.student; public class StudentBuilder { private String groupNumber; private double averageGrade; - private String recordBookNumber; + private int recordBookNumber; public StudentBuilder setGroupNumber(String groupNumber) { this.groupNumber = groupNumber; @@ -15,7 +15,7 @@ public class StudentBuilder { return this; } - public StudentBuilder setRecordBookNumber(String recordBookNumber) { + public StudentBuilder setRecordBookNumber(int recordBookNumber) { this.recordBookNumber = recordBookNumber; return this; } @@ -23,4 +23,4 @@ public class StudentBuilder { public Student build() { return new Student(groupNumber, averageGrade, recordBookNumber); } -} \ No newline at end of file +} From 37dde57c2f70b6222ef200d0c8cd9da7462b73f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Wed, 26 Aug 2026 20:33:15 +0300 Subject: [PATCH 35/77] feat: update Student --- .../main/java/ru/project/student/Student.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/main/java/ru/project/student/Student.java b/app/src/main/java/ru/project/student/Student.java index 3e4332f..156059d 100644 --- a/app/src/main/java/ru/project/student/Student.java +++ b/app/src/main/java/ru/project/student/Student.java @@ -68,4 +68,21 @@ public class Student { public int hashCode() { return Objects.hash(groupNumber, averageGrade, recordBookNumber); } + + // ------------------------ Статические геттеры для границ и формата ------------------------ + public static double getMinAverageGrade() { + return 0.0; + } + + public static double getMaxAverageGrade() { + return 5.0; + } + + public static int getMinRecordBookNumber() { + return 1; + } + + public static String getGroupNumberPattern() { + return "^[A-Z][0-9]{2}$"; + } } \ No newline at end of file From d615a6e3d6b2a160f44013d1a55e3ca6ec2f2681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Wed, 26 Aug 2026 23:36:04 +0400 Subject: [PATCH 36/77] feat(view): add reusable base and action list views --- .../java/ru/project/view/ActionListView.java | 21 +++++++++ .../main/java/ru/project/view/BaseView.java | 23 +++++++++ .../main/java/ru/project/view/PanelView.java | 47 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 app/src/main/java/ru/project/view/ActionListView.java create mode 100644 app/src/main/java/ru/project/view/BaseView.java create mode 100644 app/src/main/java/ru/project/view/PanelView.java diff --git a/app/src/main/java/ru/project/view/ActionListView.java b/app/src/main/java/ru/project/view/ActionListView.java new file mode 100644 index 0000000..fa2bb07 --- /dev/null +++ b/app/src/main/java/ru/project/view/ActionListView.java @@ -0,0 +1,21 @@ +package ru.project.view; + +import com.googlecode.lanterna.gui2.ActionListBox; + +public class ActionListView extends BaseView { + + protected final ActionListBox actionList; + + public ActionListView(String title) { + this(title, new ActionListBox()); + } + + private ActionListView(String title, ActionListBox actionList) { + super(title, actionList); + this.actionList = actionList; + } + + public void addItem(String text, Runnable action) { + actionList.addItem(text, action); + } +} diff --git a/app/src/main/java/ru/project/view/BaseView.java b/app/src/main/java/ru/project/view/BaseView.java new file mode 100644 index 0000000..1bbf8c8 --- /dev/null +++ b/app/src/main/java/ru/project/view/BaseView.java @@ -0,0 +1,23 @@ +package ru.project.view; + +import com.googlecode.lanterna.gui2.BasicWindow; +import com.googlecode.lanterna.gui2.Component; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; + +public abstract class BaseView { + + protected final BasicWindow window; + + protected BaseView(String title, Component mainComponent) { + window = new BasicWindow(title); + window.setComponent(mainComponent); + } + + public void show(WindowBasedTextGUI gui) { + gui.addWindowAndWait(window); + } + + public void close() { + window.close(); + } +} diff --git a/app/src/main/java/ru/project/view/PanelView.java b/app/src/main/java/ru/project/view/PanelView.java new file mode 100644 index 0000000..ab36e50 --- /dev/null +++ b/app/src/main/java/ru/project/view/PanelView.java @@ -0,0 +1,47 @@ +package ru.project.view; + +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.TextBox; + +public class PanelView extends BaseView { + + protected final Panel panel; + + public PanelView(String title) { + this(new Panel(), title); + } + + private PanelView(Panel panel, String title) { + super(title, panel); + + this.panel = panel; + panel.setLayoutManager(new LinearLayout()); + } + + public Button addButton(String text, Runnable action) { + Button button = new Button(text, action); + + panel.addComponent(button); + + return button; + } + + public Label addLabel(String text) { + Label label = new Label(text); + + panel.addComponent(label); + + return label; + } + + public TextBox addTextBox() { + TextBox textBox = new TextBox(); + + panel.addComponent(textBox); + + return textBox; + } +} From 9dc79936d29447eaad3271c9995291a3217ce210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Wed, 26 Aug 2026 23:36:42 +0400 Subject: [PATCH 37/77] feat(presenter): add base presenter abstraction --- .../ru/project/presenter/BasePresenter.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 app/src/main/java/ru/project/presenter/BasePresenter.java diff --git a/app/src/main/java/ru/project/presenter/BasePresenter.java b/app/src/main/java/ru/project/presenter/BasePresenter.java new file mode 100644 index 0000000..5683048 --- /dev/null +++ b/app/src/main/java/ru/project/presenter/BasePresenter.java @@ -0,0 +1,17 @@ +package ru.project.presenter; + +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.model.AppState; + +public abstract class BasePresenter { + + protected final AppState state; + protected final WindowBasedTextGUI gui; + + protected BasePresenter(AppState state, WindowBasedTextGUI gui) { + this.state = state; + this.gui = gui; + } + + public abstract void start(); +} From 9faa6cff1457ec2e140c21682debc4ee8b7c7eb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Thu, 27 Aug 2026 00:09:05 +0400 Subject: [PATCH 38/77] refactor(view): add default action to base view --- app/src/main/java/ru/project/view/BaseView.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/ru/project/view/BaseView.java b/app/src/main/java/ru/project/view/BaseView.java index 1bbf8c8..dd90a17 100644 --- a/app/src/main/java/ru/project/view/BaseView.java +++ b/app/src/main/java/ru/project/view/BaseView.java @@ -20,4 +20,8 @@ public abstract class BaseView { public void close() { window.close(); } + + protected void defaultAction() { + close(); + } } From a7a295398027ad05ce25e90c106478e3d3301a97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Thu, 27 Aug 2026 00:09:40 +0400 Subject: [PATCH 39/77] refactor(view): reuse base views and decouple menu actions --- .../java/ru/project/view/CollectionView.java | 40 ++++---------- .../main/java/ru/project/view/MainView.java | 55 +++++++++---------- 2 files changed, 36 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/ru/project/view/CollectionView.java b/app/src/main/java/ru/project/view/CollectionView.java index 27d139e..1704fe4 100644 --- a/app/src/main/java/ru/project/view/CollectionView.java +++ b/app/src/main/java/ru/project/view/CollectionView.java @@ -1,47 +1,27 @@ package ru.project.view; -import com.googlecode.lanterna.gui2.BasicWindow; -import com.googlecode.lanterna.gui2.Button; -import com.googlecode.lanterna.gui2.Label; -import com.googlecode.lanterna.gui2.LinearLayout; -import com.googlecode.lanterna.gui2.Panel; -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import ru.project.collection.MyList; import ru.project.student.Student; -public class CollectionView { +public class CollectionView extends PanelView { - public void showCollection(WindowBasedTextGUI gui, MyList collection) { - BasicWindow window = new BasicWindow("Текущая коллекция"); - - Panel panel = new Panel(); - panel.setLayoutManager(new LinearLayout()); + public CollectionView() { + super("Текущая коллекция"); + } + public void showCollection(MyList collection) { for (int i = 0; i < collection.size(); i++) { Student student = collection.get(i); - panel.addComponent(new Label((i + 1) + ". " + student)); + addLabel((i + 1) + ". " + student); } - panel.addComponent(new Button("Вернуться", window::close)); - - window.setComponent(panel); - - gui.addWindowAndWait(window); + addButton("Выйти", this::close); } - public void showEmptyMessage(WindowBasedTextGUI gui) { - BasicWindow window = new BasicWindow("Текущая коллекция"); + public void showEmptyMessage() { + addLabel("Текущая коллекция пуста."); - Panel panel = new Panel(); - panel.setLayoutManager(new LinearLayout()); - - panel.addComponent(new Label("Текущая коллекция пуста.")); - - panel.addComponent(new Button("Вернуться", window::close)); - - window.setComponent(panel); - - gui.addWindowAndWait(window); + addButton("Выйти", this::close); } } diff --git a/app/src/main/java/ru/project/view/MainView.java b/app/src/main/java/ru/project/view/MainView.java index 632f2db..55cbdb4 100644 --- a/app/src/main/java/ru/project/view/MainView.java +++ b/app/src/main/java/ru/project/view/MainView.java @@ -1,62 +1,59 @@ package ru.project.view; -import com.googlecode.lanterna.gui2.ActionListBox; -import com.googlecode.lanterna.gui2.BasicWindow; -import com.googlecode.lanterna.gui2.LinearLayout; -import com.googlecode.lanterna.gui2.Panel; -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -public class MainView { +public class MainView extends ActionListView { - private final BasicWindow window; - private final ActionListBox menu; + private Runnable showCollectionAction = this::defaultAction; + private Runnable createCollectionAction = this::defaultAction; + private Runnable saveCollectionAction = this::defaultAction; + private Runnable sortCollectionAction = this::defaultAction; + private Runnable searchCollectionAction = this::defaultAction; + private Runnable countOccurrencesAction = this::defaultAction; + private Runnable exitAction = this::defaultAction; public MainView() { - window = new BasicWindow("Главное меню"); + super("Главное меню"); - Panel panel = new Panel(); - panel.setLayoutManager(new LinearLayout()); + addItem("Показать текущую коллекцию", () -> showCollectionAction.run()); - menu = new ActionListBox(); + addItem("Меню создания новой коллекции", () -> createCollectionAction.run()); - panel.addComponent(menu); + addItem("Меню сохранения текущей коллекции в файл", () -> saveCollectionAction.run()); - window.setComponent(panel); + addItem("Меню сортировки текущей коллекции", () -> sortCollectionAction.run()); + + addItem("Меню поиска элемента в текущей коллекции", () -> searchCollectionAction.run()); + + addItem("Меню подсчёта количества вхождений элемента", () -> countOccurrencesAction.run()); + + addItem("Завершить выполнение программы", () -> exitAction.run()); } public void setShowCollectionAction(Runnable action) { - menu.addItem("Показать текущую коллекцию", action); + showCollectionAction = action; } public void setCreateCollectionAction(Runnable action) { - menu.addItem("Меню создания новой коллекции", action); + createCollectionAction = action; } public void setSaveCollectionAction(Runnable action) { - menu.addItem("Меню сохранения текущей коллекции в файл", action); + saveCollectionAction = action; } public void setSortCollectionAction(Runnable action) { - menu.addItem("Меню сортировки текущей коллекции", action); + sortCollectionAction = action; } public void setSearchCollectionAction(Runnable action) { - menu.addItem("Меню поиска элемента в текущей коллекции", action); + searchCollectionAction = action; } public void setCountOccurrencesAction(Runnable action) { - menu.addItem("Меню подсчёта количества вхождений элемента", action); + countOccurrencesAction = action; } public void setExitAction(Runnable action) { - menu.addItem("Завершить выполнение программы", action); - } - - public void show(WindowBasedTextGUI gui) { - gui.addWindowAndWait(window); - } - - public void close() { - window.close(); + exitAction = action; } } From 140128cde095ff83c7af0654772d71b946d31dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Thu, 27 Aug 2026 00:10:05 +0400 Subject: [PATCH 40/77] refactor(presenter): extend base presenter and defer menu actions --- .../presenter/CollectionPresenter.java | 15 ++++----- .../ru/project/presenter/MainPresenter.java | 32 +++++++------------ 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/ru/project/presenter/CollectionPresenter.java b/app/src/main/java/ru/project/presenter/CollectionPresenter.java index f9c6606..db1d09b 100644 --- a/app/src/main/java/ru/project/presenter/CollectionPresenter.java +++ b/app/src/main/java/ru/project/presenter/CollectionPresenter.java @@ -6,26 +6,25 @@ import ru.project.model.AppState; import ru.project.student.Student; import ru.project.view.CollectionView; -public class CollectionPresenter { +public class CollectionPresenter extends BasePresenter { - private final AppState state; private final CollectionView view; - private final WindowBasedTextGUI gui; public CollectionPresenter(AppState state, CollectionView view, WindowBasedTextGUI gui) { - this.state = state; + super(state, gui); this.view = view; - this.gui = gui; } + @Override public void start() { MyList collection = state.getCollection(); if (collection == null || collection.isEmpty()) { - view.showEmptyMessage(gui); - return; + view.showEmptyMessage(); + } else { + view.showCollection(collection); } - view.showCollection(gui, collection); + view.show(gui); } } diff --git a/app/src/main/java/ru/project/presenter/MainPresenter.java b/app/src/main/java/ru/project/presenter/MainPresenter.java index 91b195b..824f030 100644 --- a/app/src/main/java/ru/project/presenter/MainPresenter.java +++ b/app/src/main/java/ru/project/presenter/MainPresenter.java @@ -1,28 +1,22 @@ package ru.project.presenter; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -import com.googlecode.lanterna.gui2.dialogs.MessageDialog; -import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton; import ru.project.model.AppState; -import ru.project.view.CollectionView; import ru.project.view.MainView; -public class MainPresenter { +public class MainPresenter extends BasePresenter { - private final AppState state; private final MainView view; - private final WindowBasedTextGUI gui; - private final CollectionPresenter collectionPresenter; public MainPresenter(AppState state, MainView view, WindowBasedTextGUI gui) { - this.state = state; + super(state, gui); this.view = view; - this.gui = gui; - this.collectionPresenter = new CollectionPresenter(state, new CollectionView(), gui); + + bindActions(); } + @Override public void start() { - bindActions(); view.show(gui); } @@ -37,34 +31,30 @@ public class MainPresenter { } private void showCollection() { - collectionPresenter.start(); + // TODO } private void createCollection() { - showMessage("Меню создания коллекции."); + // TODO } private void saveCollection() { - showMessage("Меню сохранения коллекции."); + // TODO } private void sortCollection() { - showMessage("Меню сортировки коллекции."); + // TODO } private void searchCollection() { - showMessage("Меню поиска элемента."); + // TODO } private void countOccurrences() { - showMessage("Меню подсчёта количества вхождений."); + // TODO } private void exit() { view.close(); } - - private void showMessage(String message) { - MessageDialog.showMessageDialog(gui, "Информация", message, MessageDialogButton.OK); - } } From 51e7859520f725b10f4af9fb3b3782ff18f3fd96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Thu, 27 Aug 2026 00:10:30 +0400 Subject: [PATCH 41/77] feat(fill-method): add fill method selection presenter --- .../java/ru/project/model/FillMethod.java | 7 +++ .../presenter/FillMethodPresenter.java | 44 +++++++++++++++++++ .../java/ru/project/view/FillMethodView.java | 32 ++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 app/src/main/java/ru/project/model/FillMethod.java create mode 100644 app/src/main/java/ru/project/presenter/FillMethodPresenter.java create mode 100644 app/src/main/java/ru/project/view/FillMethodView.java diff --git a/app/src/main/java/ru/project/model/FillMethod.java b/app/src/main/java/ru/project/model/FillMethod.java new file mode 100644 index 0000000..df682af --- /dev/null +++ b/app/src/main/java/ru/project/model/FillMethod.java @@ -0,0 +1,7 @@ +package ru.project.model; + +public enum FillMethod { + CUSTOM, + RANDOM, + FILE +} diff --git a/app/src/main/java/ru/project/presenter/FillMethodPresenter.java b/app/src/main/java/ru/project/presenter/FillMethodPresenter.java new file mode 100644 index 0000000..321326b --- /dev/null +++ b/app/src/main/java/ru/project/presenter/FillMethodPresenter.java @@ -0,0 +1,44 @@ +package ru.project.presenter; + +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.Consumer; +import ru.project.model.AppState; +import ru.project.model.FillMethod; +import ru.project.view.FillMethodView; + +public class FillMethodPresenter extends BasePresenter { + + private final FillMethodView view; + private final Consumer onMethodSelected; + + public FillMethodPresenter( + AppState state, + FillMethodView view, + WindowBasedTextGUI gui, + Consumer onMethodSelected) { + + super(state, gui); + + this.view = view; + this.onMethodSelected = onMethodSelected; + } + + @Override + public void start() { + bindActions(); + view.show(gui); + } + + private void bindActions() { + view.setCustomAction(() -> selectMethod(FillMethod.CUSTOM)); + + view.setRandomAction(() -> selectMethod(FillMethod.RANDOM)); + + view.setFileAction(() -> selectMethod(FillMethod.FILE)); + } + + private void selectMethod(FillMethod method) { + onMethodSelected.accept(method); + view.close(); + } +} diff --git a/app/src/main/java/ru/project/view/FillMethodView.java b/app/src/main/java/ru/project/view/FillMethodView.java new file mode 100644 index 0000000..027d7fe --- /dev/null +++ b/app/src/main/java/ru/project/view/FillMethodView.java @@ -0,0 +1,32 @@ +package ru.project.view; + +public class FillMethodView extends ActionListView { + + private Runnable customAction = this::defaultAction; + private Runnable randomAction = this::defaultAction; + private Runnable fileAction = this::defaultAction; + + public FillMethodView() { + super("Настроить способ заполнения"); + + addItem("Пользовательские данные", () -> customAction.run()); + + addItem("Случайные данные", () -> randomAction.run()); + + addItem("Данные из файла", () -> fileAction.run()); + + addItem("Выйти", this::close); + } + + public void setCustomAction(Runnable action) { + customAction = action; + } + + public void setRandomAction(Runnable action) { + randomAction = action; + } + + public void setFileAction(Runnable action) { + fileAction = action; + } +} From ea868cc6874a0573cc824854a0cc207b0869bc52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Thu, 27 Aug 2026 00:12:39 +0400 Subject: [PATCH 42/77] feat(collection): add collection creation flow --- .../presenter/CreateCollectionPresenter.java | 91 +++++++++++++++++ .../ru/project/view/CreateCollectionView.java | 98 +++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 app/src/main/java/ru/project/presenter/CreateCollectionPresenter.java create mode 100644 app/src/main/java/ru/project/view/CreateCollectionView.java diff --git a/app/src/main/java/ru/project/presenter/CreateCollectionPresenter.java b/app/src/main/java/ru/project/presenter/CreateCollectionPresenter.java new file mode 100644 index 0000000..326466a --- /dev/null +++ b/app/src/main/java/ru/project/presenter/CreateCollectionPresenter.java @@ -0,0 +1,91 @@ +package ru.project.presenter; + +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.model.AppState; +import ru.project.model.FillMethod; +import ru.project.view.CreateCollectionView; +import ru.project.view.FillMethodView; + +public class CreateCollectionPresenter extends BasePresenter { + + private final CreateCollectionView view; + + private int size; + private FillMethod fillMethod; + + public CreateCollectionPresenter( + AppState state, CreateCollectionView view, WindowBasedTextGUI gui) { + + super(state, gui); + + this.view = view; + this.size = 0; + this.fillMethod = FillMethod.CUSTOM; + } + + @Override + public void start() { + bindActions(); + view.show(gui); + } + + private void bindActions() { + view.setSetSizeAction(this::setSize); + view.setConfigureFillMethodAction(this::configureFillMethod); + view.setCreateCollectionAction(this::createCollection); + } + + private void setSize() { + view.showSetSizeDialog(gui, this::handleSizeEntered); + } + + private void handleSizeEntered(int sizeValue) { + if (sizeValue <= 0) { + view.showMessage(gui, "Размер должен быть больше 0."); + return; + } + + size = sizeValue; + view.updateSize(size); + } + + private void configureFillMethod() { + FillMethodView fillMethodView = new FillMethodView(); + + FillMethodPresenter presenter = + new FillMethodPresenter(state, fillMethodView, gui, this::handleFillMethodSelected); + + presenter.start(); + } + + private void handleFillMethodSelected(FillMethod selectedMethod) { + fillMethod = selectedMethod; + + view.updateFillMethod(fillMethod); + } + + private void createCollection() { + if (size <= 0) { + view.showMessage(gui, "Сначала задайте размер коллекции."); + return; + } + + switch (fillMethod) { + case CUSTOM: + view.showMessage(gui, "Выбран способ заполнения: пользовательские данные."); + break; + + case RANDOM: + view.showMessage(gui, "Выбран способ заполнения: случайные данные."); + break; + + case FILE: + view.showMessage(gui, "Выбран способ заполнения: данные из файла."); + break; + } + + // TODO: + // state.setCollection(...); + // view.close(); + } +} diff --git a/app/src/main/java/ru/project/view/CreateCollectionView.java b/app/src/main/java/ru/project/view/CreateCollectionView.java new file mode 100644 index 0000000..55825a1 --- /dev/null +++ b/app/src/main/java/ru/project/view/CreateCollectionView.java @@ -0,0 +1,98 @@ +package ru.project.view; + +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.TextBox; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.IntConsumer; +import ru.project.model.FillMethod; + +public class CreateCollectionView extends PanelView { + + private final Label sizeLabel; + private final Label fillMethodLabel; + + private Runnable setSizeAction = this::defaultAction; + private Runnable configureFillMethodAction = this::defaultAction; + private Runnable createCollectionAction = this::defaultAction; + + public CreateCollectionView() { + super("Создание новой коллекции"); + + sizeLabel = addLabel("Размер: не задан"); + + fillMethodLabel = addLabel("Способ заполнения: Пользовательские данные"); + + addButton("Задать размер", () -> setSizeAction.run()); + + addButton("Настроить способ заполнения", () -> configureFillMethodAction.run()); + + addButton("Создать новую коллекцию", () -> createCollectionAction.run()); + + addButton("Выйти", this::close); + } + + public void setSetSizeAction(Runnable action) { + setSizeAction = action; + } + + public void setConfigureFillMethodAction(Runnable action) { + configureFillMethodAction = action; + } + + public void setCreateCollectionAction(Runnable action) { + createCollectionAction = action; + } + + public void updateSize(int size) { + sizeLabel.setText("Размер: " + size); + } + + public void updateFillMethod(FillMethod fillMethod) { + fillMethodLabel.setText("Способ заполнения: " + getFillMethodName(fillMethod)); + } + + public void showMessage(WindowBasedTextGUI gui, String message) { + PanelView messageView = new PanelView("Информация"); + + messageView.addLabel(message); + + messageView.addButton("OK", messageView::close); + + messageView.show(gui); + } + + public void showSetSizeDialog(WindowBasedTextGUI gui, IntConsumer onSizeEntered) { + PanelView dialog = new PanelView("Задать размер"); + + dialog.addLabel("Введите размер:"); + + TextBox textBox = dialog.addTextBox(); + + dialog.addButton("Подтвердить", () -> handleSizeInput(gui, dialog, textBox, onSizeEntered)); + + dialog.addButton("Вернуться", dialog::close); + + dialog.show(gui); + } + + private void handleSizeInput( + WindowBasedTextGUI gui, PanelView dialog, TextBox textBox, IntConsumer onSizeEntered) { + try { + int size = Integer.parseInt(textBox.getText().trim()); + + dialog.close(); + onSizeEntered.accept(size); + + } catch (NumberFormatException e) { + showMessage(gui, "Введите целое число."); + } + } + + private String getFillMethodName(FillMethod fillMethod) { + return switch (fillMethod) { + case CUSTOM -> "Пользовательские данные"; + case RANDOM -> "Случайные данные"; + case FILE -> "Данные из файла"; + }; + } +} From abef518d7416f83ba413b5088836bb0c071cf056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Thu, 27 Aug 2026 00:23:02 +0400 Subject: [PATCH 43/77] feat(presenter): connect collection creation and viewing flows --- app/src/main/java/ru/project/App.java | 1 + .../java/ru/project/presenter/MainPresenter.java | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ru/project/App.java b/app/src/main/java/ru/project/App.java index f7c48d9..5c16866 100644 --- a/app/src/main/java/ru/project/App.java +++ b/app/src/main/java/ru/project/App.java @@ -23,6 +23,7 @@ public final class App { MultiWindowTextGUI gui = new MultiWindowTextGUI(screen); AppState state = new AppState(); + MainView view = new MainView(); MainPresenter presenter = new MainPresenter(state, view, gui); diff --git a/app/src/main/java/ru/project/presenter/MainPresenter.java b/app/src/main/java/ru/project/presenter/MainPresenter.java index 824f030..e1c90be 100644 --- a/app/src/main/java/ru/project/presenter/MainPresenter.java +++ b/app/src/main/java/ru/project/presenter/MainPresenter.java @@ -2,16 +2,27 @@ package ru.project.presenter; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import ru.project.model.AppState; +import ru.project.view.CollectionView; +import ru.project.view.CreateCollectionView; import ru.project.view.MainView; public class MainPresenter extends BasePresenter { private final MainView view; + private final CollectionPresenter collectionPresenter; + private final CreateCollectionPresenter createCollectionPresenter; + public MainPresenter(AppState state, MainView view, WindowBasedTextGUI gui) { super(state, gui); + this.view = view; + this.collectionPresenter = new CollectionPresenter(state, new CollectionView(), gui); + + this.createCollectionPresenter = + new CreateCollectionPresenter(state, new CreateCollectionView(), gui); + bindActions(); } @@ -31,11 +42,11 @@ public class MainPresenter extends BasePresenter { } private void showCollection() { - // TODO + collectionPresenter.start(); } private void createCollection() { - // TODO + createCollectionPresenter.start(); } private void saveCollection() { From e1d76cf69cbc0d94b1584b74e083bfc0b8c09268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Thu, 27 Aug 2026 18:03:01 +0400 Subject: [PATCH 44/77] refactor: migrate UI from views and presenters to windows and menus --- app/src/main/java/ru/project/App.java | 9 +- .../ru/project/presenter/BasePresenter.java | 17 --- .../presenter/CollectionPresenter.java | 30 ----- .../presenter/CreateCollectionPresenter.java | 91 -------------- .../presenter/FillMethodPresenter.java | 44 ------- .../ru/project/presenter/MainPresenter.java | 71 ----------- .../java/ru/project/ui/base/BaseWindow.java | 19 +++ .../project/ui/menu/CreateCollectionMenu.java | 119 ++++++++++++++++++ .../ui/menu/FillMethodSelectionWindow.java | 37 ++++++ .../java/ru/project/ui/menu/MainMenu.java | 74 +++++++++++ .../ui/window/CollectionSizeInputWindow.java | 54 ++++++++ .../ui/window/CurrentCollectionWindow.java | 41 ++++++ .../ru/project/ui/window/MessageWindow.java | 28 +++++ .../java/ru/project/view/ActionListView.java | 21 ---- .../main/java/ru/project/view/BaseView.java | 27 ---- .../java/ru/project/view/CollectionView.java | 27 ---- .../ru/project/view/CreateCollectionView.java | 98 --------------- .../java/ru/project/view/FillMethodView.java | 32 ----- .../main/java/ru/project/view/MainView.java | 59 --------- .../main/java/ru/project/view/PanelView.java | 47 ------- 20 files changed, 375 insertions(+), 570 deletions(-) delete mode 100644 app/src/main/java/ru/project/presenter/BasePresenter.java delete mode 100644 app/src/main/java/ru/project/presenter/CollectionPresenter.java delete mode 100644 app/src/main/java/ru/project/presenter/CreateCollectionPresenter.java delete mode 100644 app/src/main/java/ru/project/presenter/FillMethodPresenter.java delete mode 100644 app/src/main/java/ru/project/presenter/MainPresenter.java create mode 100644 app/src/main/java/ru/project/ui/base/BaseWindow.java create mode 100644 app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java create mode 100644 app/src/main/java/ru/project/ui/menu/FillMethodSelectionWindow.java create mode 100644 app/src/main/java/ru/project/ui/menu/MainMenu.java create mode 100644 app/src/main/java/ru/project/ui/window/CollectionSizeInputWindow.java create mode 100644 app/src/main/java/ru/project/ui/window/CurrentCollectionWindow.java create mode 100644 app/src/main/java/ru/project/ui/window/MessageWindow.java delete mode 100644 app/src/main/java/ru/project/view/ActionListView.java delete mode 100644 app/src/main/java/ru/project/view/BaseView.java delete mode 100644 app/src/main/java/ru/project/view/CollectionView.java delete mode 100644 app/src/main/java/ru/project/view/CreateCollectionView.java delete mode 100644 app/src/main/java/ru/project/view/FillMethodView.java delete mode 100644 app/src/main/java/ru/project/view/MainView.java delete mode 100644 app/src/main/java/ru/project/view/PanelView.java diff --git a/app/src/main/java/ru/project/App.java b/app/src/main/java/ru/project/App.java index 5c16866..1e1d933 100644 --- a/app/src/main/java/ru/project/App.java +++ b/app/src/main/java/ru/project/App.java @@ -4,8 +4,7 @@ import com.googlecode.lanterna.gui2.MultiWindowTextGUI; import com.googlecode.lanterna.screen.Screen; import com.googlecode.lanterna.terminal.DefaultTerminalFactory; import ru.project.model.AppState; -import ru.project.presenter.MainPresenter; -import ru.project.view.MainView; +import ru.project.ui.menu.MainMenu; public final class App { @@ -24,11 +23,9 @@ public final class App { AppState state = new AppState(); - MainView view = new MainView(); + MainMenu mainMenu = new MainMenu(gui, state); - MainPresenter presenter = new MainPresenter(state, view, gui); - - presenter.start(); + mainMenu.show(); } catch (Exception e) { System.err.println("Произошла ошибка при выполнении программы:"); diff --git a/app/src/main/java/ru/project/presenter/BasePresenter.java b/app/src/main/java/ru/project/presenter/BasePresenter.java deleted file mode 100644 index 5683048..0000000 --- a/app/src/main/java/ru/project/presenter/BasePresenter.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.project.presenter; - -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -import ru.project.model.AppState; - -public abstract class BasePresenter { - - protected final AppState state; - protected final WindowBasedTextGUI gui; - - protected BasePresenter(AppState state, WindowBasedTextGUI gui) { - this.state = state; - this.gui = gui; - } - - public abstract void start(); -} diff --git a/app/src/main/java/ru/project/presenter/CollectionPresenter.java b/app/src/main/java/ru/project/presenter/CollectionPresenter.java deleted file mode 100644 index db1d09b..0000000 --- a/app/src/main/java/ru/project/presenter/CollectionPresenter.java +++ /dev/null @@ -1,30 +0,0 @@ -package ru.project.presenter; - -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -import ru.project.collection.MyList; -import ru.project.model.AppState; -import ru.project.student.Student; -import ru.project.view.CollectionView; - -public class CollectionPresenter extends BasePresenter { - - private final CollectionView view; - - public CollectionPresenter(AppState state, CollectionView view, WindowBasedTextGUI gui) { - super(state, gui); - this.view = view; - } - - @Override - public void start() { - MyList collection = state.getCollection(); - - if (collection == null || collection.isEmpty()) { - view.showEmptyMessage(); - } else { - view.showCollection(collection); - } - - view.show(gui); - } -} diff --git a/app/src/main/java/ru/project/presenter/CreateCollectionPresenter.java b/app/src/main/java/ru/project/presenter/CreateCollectionPresenter.java deleted file mode 100644 index 326466a..0000000 --- a/app/src/main/java/ru/project/presenter/CreateCollectionPresenter.java +++ /dev/null @@ -1,91 +0,0 @@ -package ru.project.presenter; - -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -import ru.project.model.AppState; -import ru.project.model.FillMethod; -import ru.project.view.CreateCollectionView; -import ru.project.view.FillMethodView; - -public class CreateCollectionPresenter extends BasePresenter { - - private final CreateCollectionView view; - - private int size; - private FillMethod fillMethod; - - public CreateCollectionPresenter( - AppState state, CreateCollectionView view, WindowBasedTextGUI gui) { - - super(state, gui); - - this.view = view; - this.size = 0; - this.fillMethod = FillMethod.CUSTOM; - } - - @Override - public void start() { - bindActions(); - view.show(gui); - } - - private void bindActions() { - view.setSetSizeAction(this::setSize); - view.setConfigureFillMethodAction(this::configureFillMethod); - view.setCreateCollectionAction(this::createCollection); - } - - private void setSize() { - view.showSetSizeDialog(gui, this::handleSizeEntered); - } - - private void handleSizeEntered(int sizeValue) { - if (sizeValue <= 0) { - view.showMessage(gui, "Размер должен быть больше 0."); - return; - } - - size = sizeValue; - view.updateSize(size); - } - - private void configureFillMethod() { - FillMethodView fillMethodView = new FillMethodView(); - - FillMethodPresenter presenter = - new FillMethodPresenter(state, fillMethodView, gui, this::handleFillMethodSelected); - - presenter.start(); - } - - private void handleFillMethodSelected(FillMethod selectedMethod) { - fillMethod = selectedMethod; - - view.updateFillMethod(fillMethod); - } - - private void createCollection() { - if (size <= 0) { - view.showMessage(gui, "Сначала задайте размер коллекции."); - return; - } - - switch (fillMethod) { - case CUSTOM: - view.showMessage(gui, "Выбран способ заполнения: пользовательские данные."); - break; - - case RANDOM: - view.showMessage(gui, "Выбран способ заполнения: случайные данные."); - break; - - case FILE: - view.showMessage(gui, "Выбран способ заполнения: данные из файла."); - break; - } - - // TODO: - // state.setCollection(...); - // view.close(); - } -} diff --git a/app/src/main/java/ru/project/presenter/FillMethodPresenter.java b/app/src/main/java/ru/project/presenter/FillMethodPresenter.java deleted file mode 100644 index 321326b..0000000 --- a/app/src/main/java/ru/project/presenter/FillMethodPresenter.java +++ /dev/null @@ -1,44 +0,0 @@ -package ru.project.presenter; - -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -import java.util.function.Consumer; -import ru.project.model.AppState; -import ru.project.model.FillMethod; -import ru.project.view.FillMethodView; - -public class FillMethodPresenter extends BasePresenter { - - private final FillMethodView view; - private final Consumer onMethodSelected; - - public FillMethodPresenter( - AppState state, - FillMethodView view, - WindowBasedTextGUI gui, - Consumer onMethodSelected) { - - super(state, gui); - - this.view = view; - this.onMethodSelected = onMethodSelected; - } - - @Override - public void start() { - bindActions(); - view.show(gui); - } - - private void bindActions() { - view.setCustomAction(() -> selectMethod(FillMethod.CUSTOM)); - - view.setRandomAction(() -> selectMethod(FillMethod.RANDOM)); - - view.setFileAction(() -> selectMethod(FillMethod.FILE)); - } - - private void selectMethod(FillMethod method) { - onMethodSelected.accept(method); - view.close(); - } -} diff --git a/app/src/main/java/ru/project/presenter/MainPresenter.java b/app/src/main/java/ru/project/presenter/MainPresenter.java deleted file mode 100644 index e1c90be..0000000 --- a/app/src/main/java/ru/project/presenter/MainPresenter.java +++ /dev/null @@ -1,71 +0,0 @@ -package ru.project.presenter; - -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -import ru.project.model.AppState; -import ru.project.view.CollectionView; -import ru.project.view.CreateCollectionView; -import ru.project.view.MainView; - -public class MainPresenter extends BasePresenter { - - private final MainView view; - - private final CollectionPresenter collectionPresenter; - private final CreateCollectionPresenter createCollectionPresenter; - - public MainPresenter(AppState state, MainView view, WindowBasedTextGUI gui) { - super(state, gui); - - this.view = view; - - this.collectionPresenter = new CollectionPresenter(state, new CollectionView(), gui); - - this.createCollectionPresenter = - new CreateCollectionPresenter(state, new CreateCollectionView(), gui); - - bindActions(); - } - - @Override - public void start() { - view.show(gui); - } - - private void bindActions() { - view.setShowCollectionAction(this::showCollection); - view.setCreateCollectionAction(this::createCollection); - view.setSaveCollectionAction(this::saveCollection); - view.setSortCollectionAction(this::sortCollection); - view.setSearchCollectionAction(this::searchCollection); - view.setCountOccurrencesAction(this::countOccurrences); - view.setExitAction(this::exit); - } - - private void showCollection() { - collectionPresenter.start(); - } - - private void createCollection() { - createCollectionPresenter.start(); - } - - private void saveCollection() { - // TODO - } - - private void sortCollection() { - // TODO - } - - private void searchCollection() { - // TODO - } - - private void countOccurrences() { - // TODO - } - - private void exit() { - view.close(); - } -} diff --git a/app/src/main/java/ru/project/ui/base/BaseWindow.java b/app/src/main/java/ru/project/ui/base/BaseWindow.java new file mode 100644 index 0000000..90fbb7c --- /dev/null +++ b/app/src/main/java/ru/project/ui/base/BaseWindow.java @@ -0,0 +1,19 @@ +package ru.project.ui.base; + +import com.googlecode.lanterna.gui2.BasicWindow; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; + +public abstract class BaseWindow extends BasicWindow { + + protected final WindowBasedTextGUI gui; + + protected BaseWindow(String title, WindowBasedTextGUI gui) { + super(title); + + this.gui = gui; + } + + public void show() { + gui.addWindowAndWait(this); + } +} diff --git a/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java b/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java new file mode 100644 index 0000000..2f7facd --- /dev/null +++ b/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java @@ -0,0 +1,119 @@ +package ru.project.ui.menu; + +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.model.AppState; +import ru.project.model.FillMethod; +import ru.project.ui.base.BaseWindow; +import ru.project.ui.window.CollectionSizeInputWindow; +import ru.project.ui.window.MessageWindow; + +public class CreateCollectionMenu extends BaseWindow { + + private int collectionSize; + private FillMethod selectedFillMethod; + + private final Label sizeLabel; + private final Label fillMethodLabel; + + public CreateCollectionMenu(WindowBasedTextGUI gui, AppState state) { + super("Создание новой коллекции", gui); + + collectionSize = 0; + selectedFillMethod = FillMethod.CUSTOM; + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + sizeLabel = new Label("Размер: не задан"); + + fillMethodLabel = new Label("Способ заполнения: " + getFillMethodName(selectedFillMethod)); + + panel.addComponent(sizeLabel); + panel.addComponent(fillMethodLabel); + + panel.addComponent(new Button("Задать размер", this::openCollectionSizeInputWindow)); + + panel.addComponent(new Button("Настроить способ заполнения", this::openFillMethodMenu)); + + panel.addComponent(new Button("Создать новую коллекцию", this::createCollection)); + + panel.addComponent(new Button("Выйти", this::close)); + + setComponent(panel); + } + + private void openCollectionSizeInputWindow() { + CollectionSizeInputWindow window = + new CollectionSizeInputWindow( + gui, + size -> { + collectionSize = size; + sizeLabel.setText("Размер: " + collectionSize); + }); + + window.show(); + } + + private void openFillMethodMenu() { + FillMethodSelectionWindow menu = + new FillMethodSelectionWindow( + gui, + fillMethod -> { + selectedFillMethod = fillMethod; + fillMethodLabel.setText( + "Способ заполнения: " + getFillMethodName(selectedFillMethod)); + }); + + menu.show(); + } + + private void createCollection() { + if (collectionSize <= 0) { + MessageWindow.create(gui, "Сначала задайте размер коллекции."); + return; + } + + switch (selectedFillMethod) { + case CUSTOM: + createCollectionFromCustomData(); + break; + + case RANDOM: + createCollectionFromRandomData(); + break; + + case FILE: + createCollectionFromFileData(); + break; + } + } + + private void createCollectionFromCustomData() { + // TODO + MessageWindow.create(gui, "Не реализовано."); + } + + private void createCollectionFromRandomData() { + // TODO + MessageWindow.create(gui, "Не реализовано."); + } + + private void createCollectionFromFileData() { + // TODO + MessageWindow.create(gui, "Не реализовано."); + } + + private String getFillMethodName(FillMethod fillMethod) { + return switch (fillMethod) { + case CUSTOM -> "Пользовательские данные"; + + case RANDOM -> "Случайные данные"; + + case FILE -> "Данные из файла"; + }; + } +} diff --git a/app/src/main/java/ru/project/ui/menu/FillMethodSelectionWindow.java b/app/src/main/java/ru/project/ui/menu/FillMethodSelectionWindow.java new file mode 100644 index 0000000..245fd06 --- /dev/null +++ b/app/src/main/java/ru/project/ui/menu/FillMethodSelectionWindow.java @@ -0,0 +1,37 @@ +package ru.project.ui.menu; + +import com.googlecode.lanterna.gui2.ActionListBox; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.Consumer; +import ru.project.model.FillMethod; +import ru.project.ui.base.BaseWindow; + +public class FillMethodSelectionWindow extends BaseWindow { + + private final Consumer onFillMethodSelected; + + public FillMethodSelectionWindow( + WindowBasedTextGUI gui, Consumer onFillMethodSelected) { + + super("Выбор способа заполнения", gui); + + this.onFillMethodSelected = onFillMethodSelected; + + ActionListBox menu = new ActionListBox(); + + menu.addItem("Пользовательские данные", () -> selectFillMethod(FillMethod.CUSTOM)); + + menu.addItem("Случайные данные", () -> selectFillMethod(FillMethod.RANDOM)); + + menu.addItem("Данные из файла", () -> selectFillMethod(FillMethod.FILE)); + + menu.addItem("Выйти", this::close); + + setComponent(menu); + } + + private void selectFillMethod(FillMethod fillMethod) { + onFillMethodSelected.accept(fillMethod); + close(); + } +} diff --git a/app/src/main/java/ru/project/ui/menu/MainMenu.java b/app/src/main/java/ru/project/ui/menu/MainMenu.java new file mode 100644 index 0000000..3b3fbcb --- /dev/null +++ b/app/src/main/java/ru/project/ui/menu/MainMenu.java @@ -0,0 +1,74 @@ +package ru.project.ui.menu; + +import com.googlecode.lanterna.gui2.ActionListBox; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.model.AppState; +import ru.project.ui.base.BaseWindow; +import ru.project.ui.window.CurrentCollectionWindow; +import ru.project.ui.window.MessageWindow; + +public class MainMenu extends BaseWindow { + + private final ActionListBox menu; + private final AppState state; + + public MainMenu(WindowBasedTextGUI gui, AppState state) { + super("Главное меню", gui); + + this.state = state; + + menu = new ActionListBox(); + + menu.addItem("Показать текущую коллекцию", this::showCurrentCollection); + + menu.addItem("Меню создания новой коллекции", this::createCollection); + + menu.addItem("Меню сохранения текущей коллекции в файл", this::saveCollection); + + menu.addItem("Меню сортировки текущей коллекции", this::sortCollection); + + menu.addItem("Меню поиска элемента в текущей коллекции", this::searchCollection); + + menu.addItem("Меню подсчёта количества вхождений элемента", this::countOccurrences); + + menu.addItem("Завершить выполнение программы", this::exit); + + setComponent(menu); + } + + private void showCurrentCollection() { + CurrentCollectionWindow window = new CurrentCollectionWindow(gui, state); + + window.show(); + } + + private void createCollection() { + CreateCollectionMenu window = new CreateCollectionMenu(gui, state); + + window.show(); + } + + private void saveCollection() { + // TODO + MessageWindow.create(gui, "Не реализовано."); + } + + private void sortCollection() { + // TODO + MessageWindow.create(gui, "Не реализовано."); + } + + private void searchCollection() { + // TODO + MessageWindow.create(gui, "Не реализовано."); + } + + private void countOccurrences() { + // TODO + MessageWindow.create(gui, "Не реализовано."); + } + + private void exit() { + close(); + } +} diff --git a/app/src/main/java/ru/project/ui/window/CollectionSizeInputWindow.java b/app/src/main/java/ru/project/ui/window/CollectionSizeInputWindow.java new file mode 100644 index 0000000..25b7775 --- /dev/null +++ b/app/src/main/java/ru/project/ui/window/CollectionSizeInputWindow.java @@ -0,0 +1,54 @@ +package ru.project.ui.window; + +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.TextBox; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.IntConsumer; +import ru.project.ui.base.BaseWindow; + +public class CollectionSizeInputWindow extends BaseWindow { + + private final TextBox sizeTextBox; + private final IntConsumer onSizeEntered; + + public CollectionSizeInputWindow(WindowBasedTextGUI gui, IntConsumer onSizeEntered) { + + super("Задать размер", gui); + + this.onSizeEntered = onSizeEntered; + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + panel.addComponent(new Label("Введите размер:")); + + sizeTextBox = new TextBox(); + panel.addComponent(sizeTextBox); + + panel.addComponent(new Button("Подтвердить", this::handleSizeEntered)); + + panel.addComponent(new Button("Выйти", this::close)); + + setComponent(panel); + } + + private void handleSizeEntered() { + try { + int size = Integer.parseInt(sizeTextBox.getText().trim()); + + if (size <= 0) { + MessageWindow.create(gui, "Размер должен быть больше 0."); + return; + } + + close(); + onSizeEntered.accept(size); + + } catch (NumberFormatException e) { + MessageWindow.create(gui, "Введите целое число."); + } + } +} diff --git a/app/src/main/java/ru/project/ui/window/CurrentCollectionWindow.java b/app/src/main/java/ru/project/ui/window/CurrentCollectionWindow.java new file mode 100644 index 0000000..0fd7e29 --- /dev/null +++ b/app/src/main/java/ru/project/ui/window/CurrentCollectionWindow.java @@ -0,0 +1,41 @@ +package ru.project.ui.window; + +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.collection.MyList; +import ru.project.model.AppState; +import ru.project.student.Student; +import ru.project.ui.base.BaseWindow; + +public class CurrentCollectionWindow extends BaseWindow { + + public CurrentCollectionWindow(WindowBasedTextGUI gui, AppState state) { + super("Текущая коллекция", gui); + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + MyList collection = state.getCollection(); + + if (collection == null || collection.isEmpty()) { + panel.addComponent(new Label("Текущая коллекция пуста.")); + } else { + addStudents(panel, collection); + } + + panel.addComponent(new Button("Выйти", this::close)); + + setComponent(panel); + } + + private void addStudents(Panel panel, MyList collection) { + for (int i = 0; i < collection.size(); i++) { + Student student = collection.get(i); + + panel.addComponent(new Label((i + 1) + ". " + student)); + } + } +} diff --git a/app/src/main/java/ru/project/ui/window/MessageWindow.java b/app/src/main/java/ru/project/ui/window/MessageWindow.java new file mode 100644 index 0000000..ee56ea8 --- /dev/null +++ b/app/src/main/java/ru/project/ui/window/MessageWindow.java @@ -0,0 +1,28 @@ +package ru.project.ui.window; + +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.ui.base.BaseWindow; + +public class MessageWindow extends BaseWindow { + + public MessageWindow(WindowBasedTextGUI gui, String message) { + super("Информация", gui); + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + panel.addComponent(new Label(message)); + + panel.addComponent(new Button("OK", this::close)); + + setComponent(panel); + } + + public static void create(WindowBasedTextGUI gui, String message) { + new MessageWindow(gui, message).show(); + } +} diff --git a/app/src/main/java/ru/project/view/ActionListView.java b/app/src/main/java/ru/project/view/ActionListView.java deleted file mode 100644 index fa2bb07..0000000 --- a/app/src/main/java/ru/project/view/ActionListView.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.project.view; - -import com.googlecode.lanterna.gui2.ActionListBox; - -public class ActionListView extends BaseView { - - protected final ActionListBox actionList; - - public ActionListView(String title) { - this(title, new ActionListBox()); - } - - private ActionListView(String title, ActionListBox actionList) { - super(title, actionList); - this.actionList = actionList; - } - - public void addItem(String text, Runnable action) { - actionList.addItem(text, action); - } -} diff --git a/app/src/main/java/ru/project/view/BaseView.java b/app/src/main/java/ru/project/view/BaseView.java deleted file mode 100644 index dd90a17..0000000 --- a/app/src/main/java/ru/project/view/BaseView.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.project.view; - -import com.googlecode.lanterna.gui2.BasicWindow; -import com.googlecode.lanterna.gui2.Component; -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; - -public abstract class BaseView { - - protected final BasicWindow window; - - protected BaseView(String title, Component mainComponent) { - window = new BasicWindow(title); - window.setComponent(mainComponent); - } - - public void show(WindowBasedTextGUI gui) { - gui.addWindowAndWait(window); - } - - public void close() { - window.close(); - } - - protected void defaultAction() { - close(); - } -} diff --git a/app/src/main/java/ru/project/view/CollectionView.java b/app/src/main/java/ru/project/view/CollectionView.java deleted file mode 100644 index 1704fe4..0000000 --- a/app/src/main/java/ru/project/view/CollectionView.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.project.view; - -import ru.project.collection.MyList; -import ru.project.student.Student; - -public class CollectionView extends PanelView { - - public CollectionView() { - super("Текущая коллекция"); - } - - public void showCollection(MyList collection) { - for (int i = 0; i < collection.size(); i++) { - Student student = collection.get(i); - - addLabel((i + 1) + ". " + student); - } - - addButton("Выйти", this::close); - } - - public void showEmptyMessage() { - addLabel("Текущая коллекция пуста."); - - addButton("Выйти", this::close); - } -} diff --git a/app/src/main/java/ru/project/view/CreateCollectionView.java b/app/src/main/java/ru/project/view/CreateCollectionView.java deleted file mode 100644 index 55825a1..0000000 --- a/app/src/main/java/ru/project/view/CreateCollectionView.java +++ /dev/null @@ -1,98 +0,0 @@ -package ru.project.view; - -import com.googlecode.lanterna.gui2.Label; -import com.googlecode.lanterna.gui2.TextBox; -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -import java.util.function.IntConsumer; -import ru.project.model.FillMethod; - -public class CreateCollectionView extends PanelView { - - private final Label sizeLabel; - private final Label fillMethodLabel; - - private Runnable setSizeAction = this::defaultAction; - private Runnable configureFillMethodAction = this::defaultAction; - private Runnable createCollectionAction = this::defaultAction; - - public CreateCollectionView() { - super("Создание новой коллекции"); - - sizeLabel = addLabel("Размер: не задан"); - - fillMethodLabel = addLabel("Способ заполнения: Пользовательские данные"); - - addButton("Задать размер", () -> setSizeAction.run()); - - addButton("Настроить способ заполнения", () -> configureFillMethodAction.run()); - - addButton("Создать новую коллекцию", () -> createCollectionAction.run()); - - addButton("Выйти", this::close); - } - - public void setSetSizeAction(Runnable action) { - setSizeAction = action; - } - - public void setConfigureFillMethodAction(Runnable action) { - configureFillMethodAction = action; - } - - public void setCreateCollectionAction(Runnable action) { - createCollectionAction = action; - } - - public void updateSize(int size) { - sizeLabel.setText("Размер: " + size); - } - - public void updateFillMethod(FillMethod fillMethod) { - fillMethodLabel.setText("Способ заполнения: " + getFillMethodName(fillMethod)); - } - - public void showMessage(WindowBasedTextGUI gui, String message) { - PanelView messageView = new PanelView("Информация"); - - messageView.addLabel(message); - - messageView.addButton("OK", messageView::close); - - messageView.show(gui); - } - - public void showSetSizeDialog(WindowBasedTextGUI gui, IntConsumer onSizeEntered) { - PanelView dialog = new PanelView("Задать размер"); - - dialog.addLabel("Введите размер:"); - - TextBox textBox = dialog.addTextBox(); - - dialog.addButton("Подтвердить", () -> handleSizeInput(gui, dialog, textBox, onSizeEntered)); - - dialog.addButton("Вернуться", dialog::close); - - dialog.show(gui); - } - - private void handleSizeInput( - WindowBasedTextGUI gui, PanelView dialog, TextBox textBox, IntConsumer onSizeEntered) { - try { - int size = Integer.parseInt(textBox.getText().trim()); - - dialog.close(); - onSizeEntered.accept(size); - - } catch (NumberFormatException e) { - showMessage(gui, "Введите целое число."); - } - } - - private String getFillMethodName(FillMethod fillMethod) { - return switch (fillMethod) { - case CUSTOM -> "Пользовательские данные"; - case RANDOM -> "Случайные данные"; - case FILE -> "Данные из файла"; - }; - } -} diff --git a/app/src/main/java/ru/project/view/FillMethodView.java b/app/src/main/java/ru/project/view/FillMethodView.java deleted file mode 100644 index 027d7fe..0000000 --- a/app/src/main/java/ru/project/view/FillMethodView.java +++ /dev/null @@ -1,32 +0,0 @@ -package ru.project.view; - -public class FillMethodView extends ActionListView { - - private Runnable customAction = this::defaultAction; - private Runnable randomAction = this::defaultAction; - private Runnable fileAction = this::defaultAction; - - public FillMethodView() { - super("Настроить способ заполнения"); - - addItem("Пользовательские данные", () -> customAction.run()); - - addItem("Случайные данные", () -> randomAction.run()); - - addItem("Данные из файла", () -> fileAction.run()); - - addItem("Выйти", this::close); - } - - public void setCustomAction(Runnable action) { - customAction = action; - } - - public void setRandomAction(Runnable action) { - randomAction = action; - } - - public void setFileAction(Runnable action) { - fileAction = action; - } -} diff --git a/app/src/main/java/ru/project/view/MainView.java b/app/src/main/java/ru/project/view/MainView.java deleted file mode 100644 index 55cbdb4..0000000 --- a/app/src/main/java/ru/project/view/MainView.java +++ /dev/null @@ -1,59 +0,0 @@ -package ru.project.view; - - -public class MainView extends ActionListView { - - private Runnable showCollectionAction = this::defaultAction; - private Runnable createCollectionAction = this::defaultAction; - private Runnable saveCollectionAction = this::defaultAction; - private Runnable sortCollectionAction = this::defaultAction; - private Runnable searchCollectionAction = this::defaultAction; - private Runnable countOccurrencesAction = this::defaultAction; - private Runnable exitAction = this::defaultAction; - - public MainView() { - super("Главное меню"); - - addItem("Показать текущую коллекцию", () -> showCollectionAction.run()); - - addItem("Меню создания новой коллекции", () -> createCollectionAction.run()); - - addItem("Меню сохранения текущей коллекции в файл", () -> saveCollectionAction.run()); - - addItem("Меню сортировки текущей коллекции", () -> sortCollectionAction.run()); - - addItem("Меню поиска элемента в текущей коллекции", () -> searchCollectionAction.run()); - - addItem("Меню подсчёта количества вхождений элемента", () -> countOccurrencesAction.run()); - - addItem("Завершить выполнение программы", () -> exitAction.run()); - } - - public void setShowCollectionAction(Runnable action) { - showCollectionAction = action; - } - - public void setCreateCollectionAction(Runnable action) { - createCollectionAction = action; - } - - public void setSaveCollectionAction(Runnable action) { - saveCollectionAction = action; - } - - public void setSortCollectionAction(Runnable action) { - sortCollectionAction = action; - } - - public void setSearchCollectionAction(Runnable action) { - searchCollectionAction = action; - } - - public void setCountOccurrencesAction(Runnable action) { - countOccurrencesAction = action; - } - - public void setExitAction(Runnable action) { - exitAction = action; - } -} diff --git a/app/src/main/java/ru/project/view/PanelView.java b/app/src/main/java/ru/project/view/PanelView.java deleted file mode 100644 index ab36e50..0000000 --- a/app/src/main/java/ru/project/view/PanelView.java +++ /dev/null @@ -1,47 +0,0 @@ -package ru.project.view; - -import com.googlecode.lanterna.gui2.Button; -import com.googlecode.lanterna.gui2.Label; -import com.googlecode.lanterna.gui2.LinearLayout; -import com.googlecode.lanterna.gui2.Panel; -import com.googlecode.lanterna.gui2.TextBox; - -public class PanelView extends BaseView { - - protected final Panel panel; - - public PanelView(String title) { - this(new Panel(), title); - } - - private PanelView(Panel panel, String title) { - super(title, panel); - - this.panel = panel; - panel.setLayoutManager(new LinearLayout()); - } - - public Button addButton(String text, Runnable action) { - Button button = new Button(text, action); - - panel.addComponent(button); - - return button; - } - - public Label addLabel(String text) { - Label label = new Label(text); - - panel.addComponent(label); - - return label; - } - - public TextBox addTextBox() { - TextBox textBox = new TextBox(); - - panel.addComponent(textBox); - - return textBox; - } -} From 74effff84e62a94347e272dea393cfbc4f0502cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Thu, 27 Aug 2026 18:30:31 +0300 Subject: [PATCH 45/77] feat: implement BubbleSortStrategy --- .../project/sorting/BubbleSortStrategy.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 app/src/main/java/ru/project/sorting/BubbleSortStrategy.java diff --git a/app/src/main/java/ru/project/sorting/BubbleSortStrategy.java b/app/src/main/java/ru/project/sorting/BubbleSortStrategy.java new file mode 100644 index 0000000..38172db --- /dev/null +++ b/app/src/main/java/ru/project/sorting/BubbleSortStrategy.java @@ -0,0 +1,24 @@ +package ru.project.sorting; + +import ru.project.list.MyList; +import java.util.Comparator; + +public class BubbleSortStrategy implements SortStrategy { + + @Override + public void sort(MyList list, Comparator comparator) { + if (list == null || list.size() <= 1) { + return; + } + int n = list.size(); + for (int i = 0; i < n - 1; i++) { + for (int j = 0; j < n - i - 1; j++) { + if (comparator.compare(list.get(j), list.get(j + 1)) > 0) { + T temp = list.get(j); + list.set(j, list.get(j + 1)); + list.set(j + 1, temp); + } + } + } + } +} \ No newline at end of file From 7a856903108701d64c3c2629577c88a87781f5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Thu, 27 Aug 2026 18:44:46 +0300 Subject: [PATCH 46/77] feat: add constants for validation, use them in constructor; implement with-methods --- .../main/java/ru/project/student/Student.java | 67 ++++++++++--------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/ru/project/student/Student.java b/app/src/main/java/ru/project/student/Student.java index 9dc74e2..be6fa91 100644 --- a/app/src/main/java/ru/project/student/Student.java +++ b/app/src/main/java/ru/project/student/Student.java @@ -3,28 +3,34 @@ package ru.project.student; import java.util.Objects; public class Student { + + public static final double MIN_AVERAGE_GRADE = 0.0; + public static final double MAX_AVERAGE_GRADE = 5.0; + public static final int MIN_RECORD_BOOK_NUMBER = 1; + public static final String GROUP_NUMBER_PATTERN = "^[A-Z][0-9]{2}$"; + private final String groupNumber; private final double averageGrade; private final int recordBookNumber; - // Конструктор с проверками public Student(String groupNumber, double averageGrade, int recordBookNumber) { - // Проверка groupNumber на формат - if (groupNumber == null || !groupNumber.matches("^[A-Z][0-9]{2}$")) { - throw new IllegalArgumentException("Group number must match format: one uppercase letter + two digits (e.g. A12)"); + if (groupNumber == null || !groupNumber.matches(GROUP_NUMBER_PATTERN)) { + throw new IllegalArgumentException( + "Group number must match pattern: " + GROUP_NUMBER_PATTERN); } - if (averageGrade < 0.0 || averageGrade > 5.0) { - throw new IllegalArgumentException("Average grade must be between 0.0 and 5.0"); + if (averageGrade < MIN_AVERAGE_GRADE || averageGrade > MAX_AVERAGE_GRADE) { + throw new IllegalArgumentException( + "Average grade must be between " + MIN_AVERAGE_GRADE + " and " + MAX_AVERAGE_GRADE); } - if (recordBookNumber <= 0) { - throw new IllegalArgumentException("Record book number must be positive"); + if (recordBookNumber < MIN_RECORD_BOOK_NUMBER) { + throw new IllegalArgumentException( + "Record book number must be at least " + MIN_RECORD_BOOK_NUMBER); } this.groupNumber = groupNumber; this.averageGrade = averageGrade; this.recordBookNumber = recordBookNumber; } - // Геттеры — исправлен тип groupNumber (теперь String) public String getGroupNumber() { return groupNumber; } @@ -37,7 +43,6 @@ public class Student { return recordBookNumber; } - // Методы копирования (with-методы) public Student withGroupNumber(String groupNumber) { return new Student(groupNumber, this.averageGrade, this.recordBookNumber); } @@ -50,7 +55,20 @@ public class Student { return new Student(this.groupNumber, this.averageGrade, recordBookNumber); } - @Override + } + + public static double getMaxAverageGrade() { + return MAX_AVERAGE_GRADE; + } + + public static int getMinRecordBookNumber() { + return MIN_RECORD_BOOK_NUMBER; + } + + public static String getGroupNumberPattern() { + return GROUP_NUMBER_PATTERN; + } + public String toString() { return "Student{" + "groupNumber='" + groupNumber + '\'' + @@ -61,8 +79,12 @@ public class Student { @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } Student student = (Student) obj; return Double.compare(averageGrade, student.averageGrade) == 0 && recordBookNumber == student.recordBookNumber @@ -73,21 +95,4 @@ public class Student { public int hashCode() { return Objects.hash(groupNumber, averageGrade, recordBookNumber); } - - // ------------------------ Статические геттеры для границ и формата ------------------------ - public static double getMinAverageGrade() { - return 0.0; - } - - public static double getMaxAverageGrade() { - return 5.0; - } - - public static int getMinRecordBookNumber() { - return 1; - } - - public static String getGroupNumberPattern() { - return "^[A-Z][0-9]{2}$"; - } -} \ No newline at end of file +} From a7a253d7264bf677f320386761bac38931fd91c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Thu, 27 Aug 2026 00:43:53 +0300 Subject: [PATCH 47/77] fix(sorting): update MyList package imports --- app/src/main/java/ru/project/sorting/MergeSortStrategy.java | 2 +- app/src/main/java/ru/project/sorting/SortStrategy.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ru/project/sorting/MergeSortStrategy.java b/app/src/main/java/ru/project/sorting/MergeSortStrategy.java index ab22562..598f754 100644 --- a/app/src/main/java/ru/project/sorting/MergeSortStrategy.java +++ b/app/src/main/java/ru/project/sorting/MergeSortStrategy.java @@ -1,6 +1,6 @@ package ru.project.sorting; -import ru.project.list.MyList; +import ru.project.collection.MyList; import java.util.Comparator; diff --git a/app/src/main/java/ru/project/sorting/SortStrategy.java b/app/src/main/java/ru/project/sorting/SortStrategy.java index 7dc9983..059d908 100644 --- a/app/src/main/java/ru/project/sorting/SortStrategy.java +++ b/app/src/main/java/ru/project/sorting/SortStrategy.java @@ -1,6 +1,7 @@ package ru.project.sorting; -import ru.project.list.MyList; +import ru.project.collection.MyList; + import java.util.Comparator; public interface SortStrategy { From 4794f03f3cdfefa98209be816fc333cf86a8406d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Thu, 27 Aug 2026 01:14:23 +0300 Subject: [PATCH 48/77] feat(sorting): add even-only merge sort strategy --- .../sorting/EvenOnlyMergeSortStrategy.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java diff --git a/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java b/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java new file mode 100644 index 0000000..a784d7a --- /dev/null +++ b/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java @@ -0,0 +1,128 @@ +package ru.project.sorting; + +import ru.project.collection.MyList; + +import java.util.Comparator; +import java.util.function.ToIntFunction; + +public class EvenOnlyMergeSortStrategy implements SortStrategy { + private final ToIntFunction valueExtractor; + + public EvenOnlyMergeSortStrategy(ToIntFunction valueExtractor) { + this.valueExtractor = valueExtractor; + } + + @Override + public void sort(MyList list, Comparator comparator) { + + if (list == null || list.size() <= 1) { + return; + } + + int evenCount = 0; + + for (int i = 0; i < list.size(); i++) { + T element = list.get(i); + + if (valueExtractor.applyAsInt(element) % 2 == 0) { + evenCount++; + } + } + if (evenCount <= 1) { + return; + } + + @SuppressWarnings("unchecked") + T[] evenElements = (T[]) new Object[evenCount]; + + int[] evenIndexes = new int[evenCount]; + + int evenIndex = 0; + + for (int i = 0; i < list.size(); i++) { + T element = list.get(i); + + if (valueExtractor.applyAsInt(element) % 2 == 0) { + evenElements[evenIndex] = element; + evenIndexes[evenIndex] = i; + evenIndex++; + } + } + mergeSort(evenElements, 0, evenElements.length - 1, comparator); + + for (int i = 0; i < evenIndexes.length; i++) { + list.set(evenIndexes[i], evenElements[i]); + } + } + + private void mergeSort( + T[] elements, + int left, + int right, + Comparator comparator + ) { + if (left >= right) { + return; + } + + int mid = (left + right) / 2; + + mergeSort(elements, left, mid, comparator); + mergeSort(elements, mid + 1, right, comparator); + + merge(elements, left, mid, right, comparator); + } + private void merge( + T[] elements, + int left, + int mid, + int right, + Comparator comparator + ) { + int leftSize = mid - left + 1; + int rightSize = right - mid; + + @SuppressWarnings("unchecked") + T[] leftArray = (T[]) new Object[leftSize]; + + @SuppressWarnings("unchecked") + T[] rightArray = (T[]) new Object[rightSize]; + + for (int i = 0; i < leftSize; i++) { + leftArray[i] = elements[left + i]; + } + + for (int j = 0; j < rightSize; j++) { + rightArray[j] = elements[mid + 1 + j]; + } + + int i = 0; + int j = 0; + int k = left; + + while (i < leftSize && j < rightSize) { + if (comparator.compare(leftArray[i], rightArray[j]) <= 0) { + elements[k] = leftArray[i]; + i++; + } else { + elements[k] = rightArray[j]; + j++; + } + + k++; + } + + while (i < leftSize) { + elements[k] = leftArray[i]; + i++; + k++; + } + + while (j < rightSize) { + elements[k] = rightArray[j]; + j++; + k++; + } + } +} + From a9f578ad363dacf59018c76f2dfbec5886688524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Thu, 27 Aug 2026 23:09:20 +0300 Subject: [PATCH 49/77] fix(collection): add package declaration to MyLinkedList --- app/src/main/java/ru/project/collection/MyLinkedList.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/ru/project/collection/MyLinkedList.java b/app/src/main/java/ru/project/collection/MyLinkedList.java index 23b2b75..270689c 100644 --- a/app/src/main/java/ru/project/collection/MyLinkedList.java +++ b/app/src/main/java/ru/project/collection/MyLinkedList.java @@ -1,3 +1,4 @@ +package ru.project.collection; import java.util.Arrays; public class MyLinkedList implements MyList { From 62f45cea65b3088a6c65606531cc4efe40bc5cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Thu, 27 Aug 2026 23:54:34 +0300 Subject: [PATCH 50/77] fix(sorting): update Student imports --- .../java/ru/project/sorting/StudentAverageGradeComparator.java | 2 +- .../main/java/ru/project/sorting/StudentGroupComparator.java | 2 +- .../java/ru/project/sorting/StudentRecordBookComparator.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/ru/project/sorting/StudentAverageGradeComparator.java b/app/src/main/java/ru/project/sorting/StudentAverageGradeComparator.java index 488aff9..fea72f6 100644 --- a/app/src/main/java/ru/project/sorting/StudentAverageGradeComparator.java +++ b/app/src/main/java/ru/project/sorting/StudentAverageGradeComparator.java @@ -1,6 +1,6 @@ package ru.project.sorting; -import ru.project.model.Student; +import ru.project.student.Student; import java.util.Comparator; public class StudentAverageGradeComparator implements Comparator { diff --git a/app/src/main/java/ru/project/sorting/StudentGroupComparator.java b/app/src/main/java/ru/project/sorting/StudentGroupComparator.java index a43c0e1..b23496e 100644 --- a/app/src/main/java/ru/project/sorting/StudentGroupComparator.java +++ b/app/src/main/java/ru/project/sorting/StudentGroupComparator.java @@ -1,6 +1,6 @@ package ru.project.sorting; -import ru.project.model.Student; +import ru.project.student.Student; import java.util.Comparator; public class StudentGroupComparator implements Comparator { diff --git a/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java b/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java index fd72fb4..0e4a714 100644 --- a/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java +++ b/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java @@ -1,6 +1,6 @@ package ru.project.sorting; -import ru.project.model.Student; +import ru.project.student.Student; import java.util.Comparator; public class StudentRecordBookComparator implements Comparator { From da8a42d89523e16a440d49fda4bbf78c42d9aaf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Fri, 28 Aug 2026 00:07:16 +0300 Subject: [PATCH 51/77] test(sorting): add even-only merge sort tests --- .../EvenOnlyMergeSortStrategyTest.java | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 app/src/test/java/ru/project/sorting/EvenOnlyMergeSortStrategyTest.java diff --git a/app/src/test/java/ru/project/sorting/EvenOnlyMergeSortStrategyTest.java b/app/src/test/java/ru/project/sorting/EvenOnlyMergeSortStrategyTest.java new file mode 100644 index 0000000..b423cba --- /dev/null +++ b/app/src/test/java/ru/project/sorting/EvenOnlyMergeSortStrategyTest.java @@ -0,0 +1,149 @@ +package ru.project.sorting; + +import org.junit.jupiter.api.Test; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class EvenOnlyMergeSortStrategyTest { + + @Test + void shouldSortOnlyEvenElements() { + MyList list = new MyLinkedList<>(); + + list.add(7); + list.add(8); + list.add(3); + list.add(2); + list.add(5); + list.add(4); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(7, list.get(0)); + assertEquals(2, list.get(1)); + assertEquals(3, list.get(2)); + assertEquals(4, list.get(3)); + assertEquals(5, list.get(4)); + assertEquals(8, list.get(5)); + + assertEquals(6, list.size()); + } + + @Test + void shouldKeepAllOddElementsUnchanged() { + MyList list = new MyLinkedList<>(); + + list.add(7); + list.add(3); + list.add(5); + list.add(1); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(7, list.get(0)); + assertEquals(3, list.get(1)); + assertEquals(5, list.get(2)); + assertEquals(1, list.get(3)); + } + + @Test + void shouldSortAllEvenElements() { + MyList list = new MyLinkedList<>(); + + list.add(8); + list.add(2); + list.add(6); + list.add(4); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(2, list.get(0)); + assertEquals(4, list.get(1)); + assertEquals(6, list.get(2)); + assertEquals(8, list.get(3)); + } + + @Test + void shouldKeepSingleElementUnchanged() { + MyList list = new MyLinkedList<>(); + + list.add(4); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(4, list.get(0)); + assertEquals(1, list.size()); + } + + @Test + void shouldHandleEmptyList() { + MyList list = new MyLinkedList<>(); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(0, list.size()); + } + + @Test + void shouldUseValueExtractorToDetermineEvenElements() { + MyList list = new MyLinkedList<>(); + + list.add(4); + list.add(3); + list.add(2); + list.add(1); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(value -> value + 1); + + strategy.sort(list, Integer::compare); + + assertEquals(4, list.get(0)); + assertEquals(1, list.get(1)); + assertEquals(2, list.get(2)); + assertEquals(3, list.get(3)); + } + + @Test + void shouldUseProvidedComparator() { + MyList list = new MyLinkedList<>(); + + list.add(7); + list.add(2); + list.add(3); + list.add(8); + list.add(5); + list.add(4); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(value -> value); + + strategy.sort(list, (first, second) -> Integer.compare(second, first)); + + assertEquals(7, list.get(0)); + assertEquals(8, list.get(1)); + assertEquals(3, list.get(2)); + assertEquals(4, list.get(3)); + assertEquals(5, list.get(4)); + assertEquals(2, list.get(5)); + } +} + + From 9a670a7380280a0adebccb0d152cedc5e593373b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Thu, 27 Aug 2026 18:51:52 +0300 Subject: [PATCH 52/77] feat: add EvenOnlyMergeSortStrategy --- .../sorting/EvenOnlyMergeSortStrategy.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java diff --git a/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java b/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java new file mode 100644 index 0000000..dea539e --- /dev/null +++ b/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java @@ -0,0 +1,84 @@ +package ru.project.sorting; + +import ru.project.list.MyList; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.ToIntFunction; + +public class EvenOnlyMergeSortStrategy implements SortStrategy { + + private final ToIntFunction valueExtractor; + + public EvenOnlyMergeSortStrategy(ToIntFunction valueExtractor) { + this.valueExtractor = valueExtractor; + } + + @Override + public void sort(MyList list, Comparator comparator) { + if (list == null || list.size() <= 1) { + return; + } + + List evenIndices = new ArrayList<>(); + List evenElements = new ArrayList<>(); + + for (int i = 0; i < list.size(); i++) { + T element = list.get(i); + int value = valueExtractor.applyAsInt(element); + if (value % 2 == 0) { + evenIndices.add(i); + evenElements.add(element); + } + } + + if (evenElements.size() <= 1) { + return; + } + + List sortedEven = mergeSort(evenElements, comparator); + + for (int j = 0; j < evenIndices.size(); j++) { + list.set(evenIndices.get(j), sortedEven.get(j)); + } + } + + private List mergeSort(List list, Comparator comparator) { + if (list.size() <= 1) { + return list; + } + + int mid = list.size() / 2; + List left = mergeSort(list.subList(0, mid), comparator); + List right = mergeSort(list.subList(mid, list.size()), comparator); + + return merge(left, right, comparator); + } + + private List merge(List left, List right, Comparator comparator) { + List result = new ArrayList<>(); + int i = 0, j = 0; + + while (i < left.size() && j < right.size()) { + if (comparator.compare(left.get(i), right.get(j)) <= 0) { + result.add(left.get(i)); + i++; + } else { + result.add(right.get(j)); + j++; + } + } + + while (i < left.size()) { + result.add(left.get(i)); + i++; + } + + while (j < right.size()) { + result.add(right.get(j)); + j++; + } + + return result; + } +} \ No newline at end of file From 80ebc71366a1e45234b0b0be0fda4d6a694c3456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Fri, 28 Aug 2026 11:53:00 +0400 Subject: [PATCH 53/77] test: add comprehensive unit tests for Student model --- .../java/ru/project/student/StudentTest.java | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 app/src/main/java/ru/project/student/StudentTest.java diff --git a/app/src/main/java/ru/project/student/StudentTest.java b/app/src/main/java/ru/project/student/StudentTest.java new file mode 100644 index 0000000..cdce6d8 --- /dev/null +++ b/app/src/main/java/ru/project/student/StudentTest.java @@ -0,0 +1,139 @@ +package ru.project.student; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class StudentTest { + + @Test + void testValidStudentCreation() { + Student student = new Student("A12", 4.5, 12345); + assertEquals("A12", student.getGroupNumber()); + assertEquals(4.5, student.getAverageGrade()); + assertEquals(12345, student.getRecordBookNumber()); + } + + @Test + void testValidGroupNumbers() { + assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); + assertDoesNotThrow(() -> new Student("B32", 4.5, 1)); + assertDoesNotThrow(() -> new Student("Z00", 4.5, 1)); + } + + @Test + void testInvalidGroupNumbers() { + assertThrows(IllegalArgumentException.class, () -> new Student("a12", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("A1", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("A123", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("AA1", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("а1А", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("аа!!а123", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("23фи!12", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("№№###*()", 4.5, 1)); + } + + @Test + void testValidAverageGrades() { + assertDoesNotThrow(() -> new Student("A12", 0.0, 1)); + assertDoesNotThrow(() -> new Student("A12", 5.0, 1)); + } + + @Test + void testInvalidAverageGrades() { + assertThrows(IllegalArgumentException.class, () -> new Student("A12", -1.0, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("A12", 6.0, 1)); + } + + @Test + void testValidRecordBookNumbers() { + assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); + assertDoesNotThrow(() -> new Student("A12", 4.5, 12345)); + } + + @Test + void testInvalidRecordBookNumbers() { + assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, 0)); + assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, -1)); + } + + @Test + void testWithGroupNumber() { + Student original = new Student("A12", 4.5, 12345); + Student updated = original.withGroupNumber("B32"); + + assertNotSame(original, updated); + assertEquals("B32", updated.getGroupNumber()); + assertEquals(4.5, updated.getAverageGrade()); + assertEquals(12345, updated.getRecordBookNumber()); + + assertEquals("A12", original.getGroupNumber()); + } + + @Test + void testWithAverageGrade() { + Student original = new Student("A12", 4.5, 12345); + Student updated = original.withAverageGrade(4.8); + + assertNotSame(original, updated); + assertEquals(4.8, updated.getAverageGrade()); + assertEquals("A12", updated.getGroupNumber()); + assertEquals(12345, updated.getRecordBookNumber()); + + assertEquals(4.5, original.getAverageGrade()); + } + + @Test + void testWithRecordBookNumber() { + Student original = new Student("A12", 4.5, 12345); + Student updated = original.withRecordBookNumber(99999); + + assertNotSame(original, updated); + assertEquals(99999, updated.getRecordBookNumber()); + assertEquals("A12", updated.getGroupNumber()); + assertEquals(4.5, updated.getAverageGrade()); + + assertEquals(12345, original.getRecordBookNumber()); + } + + @Test + void testWithInvalidGroupNumber() { + Student original = new Student("A12", 4.5, 12345); + assertThrows(IllegalArgumentException.class, + () -> original.withGroupNumber("a12")); + assertEquals("A12", original.getGroupNumber()); // не изменился + } + + @Test + void testWithInvalidAverageGrade() { + Student original = new Student("A12", 4.5, 12345); + assertThrows(IllegalArgumentException.class, + () -> original.withAverageGrade(6.0)); + assertEquals(4.5, original.getAverageGrade()); + } + + @Test + void testWithInvalidRecordBookNumber() { + Student original = new Student("A12", 4.5, 12345); + assertThrows(IllegalArgumentException.class, + () -> original.withRecordBookNumber(0)); + assertEquals(12345, original.getRecordBookNumber()); + } + + @Test + void testEqualsAndHashCode() { + Student s1 = new Student("A12", 4.5, 12345); + Student s2 = new Student("A12", 4.5, 12345); + Student s3 = new Student("B32", 4.5, 12345); + Student s4 = new Student("A12", 4.8, 12345); + Student s5 = new Student("A12", 4.5, 99999); + + assertEquals(s1, s2); + assertEquals(s1.hashCode(), s2.hashCode()); + + assertNotEquals(s1, s3); + assertNotEquals(s1, s4); + assertNotEquals(s1, s5); + assertNotEquals(s1, null); + assertNotEquals(s1, "some string"); + } +} \ No newline at end of file From daa658347064bba0cff87a777e077b905c632bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Fri, 28 Aug 2026 12:06:00 +0400 Subject: [PATCH 54/77] test: reorganize Student tests and fix duplicate test file --- .../main/java/ru/project/student/Student.java | 2 - .../java/ru/project/student/StudentTest.java | 139 ------------ .../java/ru/project/student/StudentTest.java | 209 ++++++++---------- 3 files changed, 93 insertions(+), 257 deletions(-) delete mode 100644 app/src/main/java/ru/project/student/StudentTest.java diff --git a/app/src/main/java/ru/project/student/Student.java b/app/src/main/java/ru/project/student/Student.java index be6fa91..b5d64d2 100644 --- a/app/src/main/java/ru/project/student/Student.java +++ b/app/src/main/java/ru/project/student/Student.java @@ -55,8 +55,6 @@ public class Student { return new Student(this.groupNumber, this.averageGrade, recordBookNumber); } - } - public static double getMaxAverageGrade() { return MAX_AVERAGE_GRADE; } diff --git a/app/src/main/java/ru/project/student/StudentTest.java b/app/src/main/java/ru/project/student/StudentTest.java deleted file mode 100644 index cdce6d8..0000000 --- a/app/src/main/java/ru/project/student/StudentTest.java +++ /dev/null @@ -1,139 +0,0 @@ -package ru.project.student; - -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -class StudentTest { - - @Test - void testValidStudentCreation() { - Student student = new Student("A12", 4.5, 12345); - assertEquals("A12", student.getGroupNumber()); - assertEquals(4.5, student.getAverageGrade()); - assertEquals(12345, student.getRecordBookNumber()); - } - - @Test - void testValidGroupNumbers() { - assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); - assertDoesNotThrow(() -> new Student("B32", 4.5, 1)); - assertDoesNotThrow(() -> new Student("Z00", 4.5, 1)); - } - - @Test - void testInvalidGroupNumbers() { - assertThrows(IllegalArgumentException.class, () -> new Student("a12", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("A1", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("A123", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("AA1", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("а1А", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("аа!!а123", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("23фи!12", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("№№###*()", 4.5, 1)); - } - - @Test - void testValidAverageGrades() { - assertDoesNotThrow(() -> new Student("A12", 0.0, 1)); - assertDoesNotThrow(() -> new Student("A12", 5.0, 1)); - } - - @Test - void testInvalidAverageGrades() { - assertThrows(IllegalArgumentException.class, () -> new Student("A12", -1.0, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("A12", 6.0, 1)); - } - - @Test - void testValidRecordBookNumbers() { - assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); - assertDoesNotThrow(() -> new Student("A12", 4.5, 12345)); - } - - @Test - void testInvalidRecordBookNumbers() { - assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, 0)); - assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, -1)); - } - - @Test - void testWithGroupNumber() { - Student original = new Student("A12", 4.5, 12345); - Student updated = original.withGroupNumber("B32"); - - assertNotSame(original, updated); - assertEquals("B32", updated.getGroupNumber()); - assertEquals(4.5, updated.getAverageGrade()); - assertEquals(12345, updated.getRecordBookNumber()); - - assertEquals("A12", original.getGroupNumber()); - } - - @Test - void testWithAverageGrade() { - Student original = new Student("A12", 4.5, 12345); - Student updated = original.withAverageGrade(4.8); - - assertNotSame(original, updated); - assertEquals(4.8, updated.getAverageGrade()); - assertEquals("A12", updated.getGroupNumber()); - assertEquals(12345, updated.getRecordBookNumber()); - - assertEquals(4.5, original.getAverageGrade()); - } - - @Test - void testWithRecordBookNumber() { - Student original = new Student("A12", 4.5, 12345); - Student updated = original.withRecordBookNumber(99999); - - assertNotSame(original, updated); - assertEquals(99999, updated.getRecordBookNumber()); - assertEquals("A12", updated.getGroupNumber()); - assertEquals(4.5, updated.getAverageGrade()); - - assertEquals(12345, original.getRecordBookNumber()); - } - - @Test - void testWithInvalidGroupNumber() { - Student original = new Student("A12", 4.5, 12345); - assertThrows(IllegalArgumentException.class, - () -> original.withGroupNumber("a12")); - assertEquals("A12", original.getGroupNumber()); // не изменился - } - - @Test - void testWithInvalidAverageGrade() { - Student original = new Student("A12", 4.5, 12345); - assertThrows(IllegalArgumentException.class, - () -> original.withAverageGrade(6.0)); - assertEquals(4.5, original.getAverageGrade()); - } - - @Test - void testWithInvalidRecordBookNumber() { - Student original = new Student("A12", 4.5, 12345); - assertThrows(IllegalArgumentException.class, - () -> original.withRecordBookNumber(0)); - assertEquals(12345, original.getRecordBookNumber()); - } - - @Test - void testEqualsAndHashCode() { - Student s1 = new Student("A12", 4.5, 12345); - Student s2 = new Student("A12", 4.5, 12345); - Student s3 = new Student("B32", 4.5, 12345); - Student s4 = new Student("A12", 4.8, 12345); - Student s5 = new Student("A12", 4.5, 99999); - - assertEquals(s1, s2); - assertEquals(s1.hashCode(), s2.hashCode()); - - assertNotEquals(s1, s3); - assertNotEquals(s1, s4); - assertNotEquals(s1, s5); - assertNotEquals(s1, null); - assertNotEquals(s1, "some string"); - } -} \ No newline at end of file diff --git a/app/src/test/java/ru/project/student/StudentTest.java b/app/src/test/java/ru/project/student/StudentTest.java index e9a7434..09cf2e7 100644 --- a/app/src/test/java/ru/project/student/StudentTest.java +++ b/app/src/test/java/ru/project/student/StudentTest.java @@ -2,167 +2,144 @@ package ru.project.student; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; class StudentTest { - @Test - void shouldCreateValidStudent() { - Student student = new StudentBuilder() - .setGroupNumber("101") - .setAverageGrade(4.5) - .setRecordBookNumber("12345") - .build(); - assertEquals("101", student.getGroupNumber()); + @Test + void testValidStudentCreation() { + Student student = new Student("A12", 4.5, 12345); + assertEquals("A12", student.getGroupNumber()); assertEquals(4.5, student.getAverageGrade()); - assertEquals("12345", student.getRecordBookNumber()); + assertEquals(12345, student.getRecordBookNumber()); } @Test - void shouldThrowExceptionWhenGroupNumberIsEmpty() { - assertThrows(IllegalArgumentException.class, () -> { - new StudentBuilder() - .setGroupNumber("") - .setAverageGrade(4.5) - .setRecordBookNumber("12345") - .build(); - }); + void testValidGroupNumbers() { + assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); + assertDoesNotThrow(() -> new Student("B32", 4.5, 1)); + assertDoesNotThrow(() -> new Student("Z00", 4.5, 1)); } @Test - void shouldThrowExceptionWhenAverageGradeIsGreaterThanFive() { - assertThrows(IllegalArgumentException.class, () -> { - new StudentBuilder() - .setGroupNumber("101") - .setAverageGrade(6.0) - .setRecordBookNumber("12345") - .build(); - }); + void testInvalidGroupNumbers() { + assertThrows(IllegalArgumentException.class, () -> new Student("a12", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("A1", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("A123", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("AA1", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("а1А", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("аа!!а123", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("23фи!12", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("№№###*()", 4.5, 1)); } @Test - void shouldThrowExceptionWhenAverageGradeIsLessThanZero() { - assertThrows(IllegalArgumentException.class, () -> { - new StudentBuilder() - .setGroupNumber("101") - .setAverageGrade(-1.0) - .setRecordBookNumber("12345") - .build(); - }); + void testValidAverageGrades() { + assertDoesNotThrow(() -> new Student("A12", 0.0, 1)); + assertDoesNotThrow(() -> new Student("A12", 5.0, 1)); } @Test - void shouldCreateStudentWhenAverageGradeIsZero() { - Student student = new StudentBuilder() - .setGroupNumber("101") - .setAverageGrade(0.0) - .setRecordBookNumber("12345") - .build(); - - assertEquals(0.0, student.getAverageGrade()); + void testInvalidAverageGrades() { + assertThrows(IllegalArgumentException.class, () -> new Student("A12", -1.0, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("A12", 6.0, 1)); } @Test - void shouldCreateStudentWhenAverageGradeIsFive() { - Student student = new StudentBuilder() - .setGroupNumber("101") - .setAverageGrade(5.0) - .setRecordBookNumber("12345") - .build(); - - assertEquals(5.0, student.getAverageGrade()); + void testValidRecordBookNumbers() { + assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); + assertDoesNotThrow(() -> new Student("A12", 4.5, 12345)); } @Test - void shouldThrowExceptionWhenRecordBookNumberIsEmpty() { - assertThrows(IllegalArgumentException.class, () -> { - new StudentBuilder() - .setGroupNumber("101") - .setAverageGrade(4.5) - .setRecordBookNumber("") - .build(); - }); + void testInvalidRecordBookNumbers() { + assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, 0)); + assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, -1)); } @Test - void shouldThrowExceptionWhenGroupNumberIsNull() { - assertThrows(IllegalArgumentException.class, () -> { - new StudentBuilder() - .setGroupNumber(null) - .setAverageGrade(4.5) - .setRecordBookNumber("12345") - .build(); - }); + void testWithGroupNumber() { + Student original = new Student("A12", 4.5, 12345); + Student updated = original.withGroupNumber("B32"); + + assertNotSame(original, updated); + assertEquals("B32", updated.getGroupNumber()); + assertEquals(4.5, updated.getAverageGrade()); + assertEquals(12345, updated.getRecordBookNumber()); + + assertEquals("A12", original.getGroupNumber()); } @Test - void shouldThrowExceptionWhenGroupNumberIsBlank() { - assertThrows(IllegalArgumentException.class, () -> { - new StudentBuilder() - .setGroupNumber(" ") - .setAverageGrade(4.5) - .setRecordBookNumber("12345") - .build(); - }); + void testWithAverageGrade() { + Student original = new Student("A12", 4.5, 12345); + Student updated = original.withAverageGrade(4.8); + + assertNotSame(original, updated); + assertEquals(4.8, updated.getAverageGrade()); + assertEquals("A12", updated.getGroupNumber()); + assertEquals(12345, updated.getRecordBookNumber()); + + assertEquals(4.5, original.getAverageGrade()); } @Test - void shouldThrowExceptionWhenRecordBookNumberIsNull() { - assertThrows(IllegalArgumentException.class, () -> { - new StudentBuilder() - .setGroupNumber("101") - .setAverageGrade(4.5) - .setRecordBookNumber(null) - .build(); - }); + void testWithRecordBookNumber() { + Student original = new Student("A12", 4.5, 12345); + Student updated = original.withRecordBookNumber(99999); + + assertNotSame(original, updated); + assertEquals(99999, updated.getRecordBookNumber()); + assertEquals("A12", updated.getGroupNumber()); + assertEquals(4.5, updated.getAverageGrade()); + + assertEquals(12345, original.getRecordBookNumber()); } @Test - void shouldThrowExceptionWhenRecordBookNumberIsBlank() { - assertThrows(IllegalArgumentException.class, () -> { - new StudentBuilder() - .setGroupNumber("101") - .setAverageGrade(4.5) - .setRecordBookNumber(" ") - .build(); - }); + void testWithInvalidGroupNumber() { + Student original = new Student("A12", 4.5, 12345); + assertThrows(IllegalArgumentException.class, + () -> original.withGroupNumber("a12")); + assertEquals("A12", original.getGroupNumber()); // не изменился } @Test - void shouldReturnTrueWhenStudentsAreEqual() { - Student student1 = new StudentBuilder() - .setGroupNumber("101") - .setAverageGrade(4.5) - .setRecordBookNumber("12345") - .build(); - - Student student2 = new StudentBuilder() - .setGroupNumber("101") - .setAverageGrade(4.5) - .setRecordBookNumber("12345") - .build(); - - assertEquals(student1, student2); - assertEquals(student1.hashCode(), student2.hashCode()); + void testWithInvalidAverageGrade() { + Student original = new Student("A12", 4.5, 12345); + assertThrows(IllegalArgumentException.class, + () -> original.withAverageGrade(6.0)); + assertEquals(4.5, original.getAverageGrade()); } @Test - void shouldReturnFalseWhenStudentsAreDifferent() { - Student student1 = new StudentBuilder() - .setGroupNumber("101") - .setAverageGrade(4.5) - .setRecordBookNumber("12345") - .build(); + void testWithInvalidRecordBookNumber() { + Student original = new Student("A12", 4.5, 12345); + assertThrows(IllegalArgumentException.class, + () -> original.withRecordBookNumber(0)); + assertEquals(12345, original.getRecordBookNumber()); + } - Student student2 = new StudentBuilder() - .setGroupNumber("102") - .setAverageGrade(4.5) - .setRecordBookNumber("12345") - .build(); + @Test + void testEqualsAndHashCode() { + Student s1 = new Student("A12", 4.5, 12345); + Student s2 = new Student("A12", 4.5, 12345); + Student s3 = new Student("B32", 4.5, 12345); + Student s4 = new Student("A12", 4.8, 12345); + Student s5 = new Student("A12", 4.5, 99999); - assertNotEquals(student1, student2); + assertEquals(s1, s2); + assertEquals(s1.hashCode(), s2.hashCode()); + + assertNotEquals(s1, s3); + assertNotEquals(s1, s4); + assertNotEquals(s1, s5); + assertNotEquals(s1, null); + assertNotEquals(s1, "some string"); } } From e47732b9ec2d1758912b53de8445418d16248fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Fri, 28 Aug 2026 12:14:10 +0400 Subject: [PATCH 55/77] fix: correct student group comparator --- .../main/java/ru/project/sorting/StudentGroupComparator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ru/project/sorting/StudentGroupComparator.java b/app/src/main/java/ru/project/sorting/StudentGroupComparator.java index b23496e..75cae82 100644 --- a/app/src/main/java/ru/project/sorting/StudentGroupComparator.java +++ b/app/src/main/java/ru/project/sorting/StudentGroupComparator.java @@ -6,7 +6,7 @@ import java.util.Comparator; public class StudentGroupComparator implements Comparator { @Override - public String compare(Student s1, Student s2) { - return Integer.compare(s1.getGroupNumber(), s2.getGroupNumber()); + public int compare(Student s1, Student s2) { + return s1.getGroupNumber().compareTo(s2.getGroupNumber()); } } From e53e1798e59b825645923add2d40c0012ce0bf43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Fri, 28 Aug 2026 17:50:35 +0300 Subject: [PATCH 56/77] fix: make constants private, return hashCode check in equals --- .../main/java/ru/project/student/Student.java | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/ru/project/student/Student.java b/app/src/main/java/ru/project/student/Student.java index b5d64d2..1e271a4 100644 --- a/app/src/main/java/ru/project/student/Student.java +++ b/app/src/main/java/ru/project/student/Student.java @@ -3,11 +3,10 @@ package ru.project.student; import java.util.Objects; public class Student { - - public static final double MIN_AVERAGE_GRADE = 0.0; - public static final double MAX_AVERAGE_GRADE = 5.0; - public static final int MIN_RECORD_BOOK_NUMBER = 1; - public static final String GROUP_NUMBER_PATTERN = "^[A-Z][0-9]{2}$"; + private static final double MIN_AVERAGE_GRADE = 0.0; + private static final double MAX_AVERAGE_GRADE = 5.0; + private static final int MIN_RECORD_BOOK_NUMBER = 1; + private static final String GROUP_NUMBER_PATTERN = "^[A-Z][0-9]{2}$"; private final String groupNumber; private final double averageGrade; @@ -16,15 +15,18 @@ public class Student { public Student(String groupNumber, double averageGrade, int recordBookNumber) { if (groupNumber == null || !groupNumber.matches(GROUP_NUMBER_PATTERN)) { throw new IllegalArgumentException( - "Group number must match pattern: " + GROUP_NUMBER_PATTERN); + "Group number must match pattern: " + GROUP_NUMBER_PATTERN + ); } if (averageGrade < MIN_AVERAGE_GRADE || averageGrade > MAX_AVERAGE_GRADE) { throw new IllegalArgumentException( - "Average grade must be between " + MIN_AVERAGE_GRADE + " and " + MAX_AVERAGE_GRADE); + "Average grade must be between " + MIN_AVERAGE_GRADE + " and " + MAX_AVERAGE_GRADE + ); } if (recordBookNumber < MIN_RECORD_BOOK_NUMBER) { throw new IllegalArgumentException( - "Record book number must be at least " + MIN_RECORD_BOOK_NUMBER); + "Record book number must be at least " + MIN_RECORD_BOOK_NUMBER + ); } this.groupNumber = groupNumber; this.averageGrade = averageGrade; @@ -43,16 +45,8 @@ public class Student { return recordBookNumber; } - public Student withGroupNumber(String groupNumber) { - return new Student(groupNumber, this.averageGrade, this.recordBookNumber); - } - - public Student withAverageGrade(double averageGrade) { - return new Student(this.groupNumber, averageGrade, this.recordBookNumber); - } - - public Student withRecordBookNumber(int recordBookNumber) { - return new Student(this.groupNumber, this.averageGrade, recordBookNumber); + public static double getMinAverageGrade() { + return MIN_AVERAGE_GRADE; } public static double getMaxAverageGrade() { @@ -67,6 +61,19 @@ public class Student { return GROUP_NUMBER_PATTERN; } + public Student withGroupNumber(String groupNumber) { + return new Student(groupNumber, this.averageGrade, this.recordBookNumber); + } + + public Student withAverageGrade(double averageGrade) { + return new Student(this.groupNumber, averageGrade, this.recordBookNumber); + } + + public Student withRecordBookNumber(int recordBookNumber) { + return new Student(this.groupNumber, this.averageGrade, recordBookNumber); + } + + @Override public String toString() { return "Student{" + "groupNumber='" + groupNumber + '\'' + @@ -83,6 +90,9 @@ public class Student { if (obj == null || getClass() != obj.getClass()) { return false; } + if (this.hashCode() != obj.hashCode()) { + return false; + } Student student = (Student) obj; return Double.compare(averageGrade, student.averageGrade) == 0 && recordBookNumber == student.recordBookNumber @@ -93,4 +103,4 @@ public class Student { public int hashCode() { return Objects.hash(groupNumber, averageGrade, recordBookNumber); } -} +} \ No newline at end of file From 2e44ba8fd5d83ab8116b86d0b2bc4dafb2e7ee34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Fri, 28 Aug 2026 17:53:14 +0300 Subject: [PATCH 57/77] refactor: move StudentTest to test directory --- .../java/ru/project/student/StudentTest1.java | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 app/src/test/java/ru/project/student/StudentTest1.java diff --git a/app/src/test/java/ru/project/student/StudentTest1.java b/app/src/test/java/ru/project/student/StudentTest1.java new file mode 100644 index 0000000..cdce6d8 --- /dev/null +++ b/app/src/test/java/ru/project/student/StudentTest1.java @@ -0,0 +1,139 @@ +package ru.project.student; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class StudentTest { + + @Test + void testValidStudentCreation() { + Student student = new Student("A12", 4.5, 12345); + assertEquals("A12", student.getGroupNumber()); + assertEquals(4.5, student.getAverageGrade()); + assertEquals(12345, student.getRecordBookNumber()); + } + + @Test + void testValidGroupNumbers() { + assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); + assertDoesNotThrow(() -> new Student("B32", 4.5, 1)); + assertDoesNotThrow(() -> new Student("Z00", 4.5, 1)); + } + + @Test + void testInvalidGroupNumbers() { + assertThrows(IllegalArgumentException.class, () -> new Student("a12", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("A1", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("A123", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("AA1", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("а1А", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("аа!!а123", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("23фи!12", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("№№###*()", 4.5, 1)); + } + + @Test + void testValidAverageGrades() { + assertDoesNotThrow(() -> new Student("A12", 0.0, 1)); + assertDoesNotThrow(() -> new Student("A12", 5.0, 1)); + } + + @Test + void testInvalidAverageGrades() { + assertThrows(IllegalArgumentException.class, () -> new Student("A12", -1.0, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("A12", 6.0, 1)); + } + + @Test + void testValidRecordBookNumbers() { + assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); + assertDoesNotThrow(() -> new Student("A12", 4.5, 12345)); + } + + @Test + void testInvalidRecordBookNumbers() { + assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, 0)); + assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, -1)); + } + + @Test + void testWithGroupNumber() { + Student original = new Student("A12", 4.5, 12345); + Student updated = original.withGroupNumber("B32"); + + assertNotSame(original, updated); + assertEquals("B32", updated.getGroupNumber()); + assertEquals(4.5, updated.getAverageGrade()); + assertEquals(12345, updated.getRecordBookNumber()); + + assertEquals("A12", original.getGroupNumber()); + } + + @Test + void testWithAverageGrade() { + Student original = new Student("A12", 4.5, 12345); + Student updated = original.withAverageGrade(4.8); + + assertNotSame(original, updated); + assertEquals(4.8, updated.getAverageGrade()); + assertEquals("A12", updated.getGroupNumber()); + assertEquals(12345, updated.getRecordBookNumber()); + + assertEquals(4.5, original.getAverageGrade()); + } + + @Test + void testWithRecordBookNumber() { + Student original = new Student("A12", 4.5, 12345); + Student updated = original.withRecordBookNumber(99999); + + assertNotSame(original, updated); + assertEquals(99999, updated.getRecordBookNumber()); + assertEquals("A12", updated.getGroupNumber()); + assertEquals(4.5, updated.getAverageGrade()); + + assertEquals(12345, original.getRecordBookNumber()); + } + + @Test + void testWithInvalidGroupNumber() { + Student original = new Student("A12", 4.5, 12345); + assertThrows(IllegalArgumentException.class, + () -> original.withGroupNumber("a12")); + assertEquals("A12", original.getGroupNumber()); // не изменился + } + + @Test + void testWithInvalidAverageGrade() { + Student original = new Student("A12", 4.5, 12345); + assertThrows(IllegalArgumentException.class, + () -> original.withAverageGrade(6.0)); + assertEquals(4.5, original.getAverageGrade()); + } + + @Test + void testWithInvalidRecordBookNumber() { + Student original = new Student("A12", 4.5, 12345); + assertThrows(IllegalArgumentException.class, + () -> original.withRecordBookNumber(0)); + assertEquals(12345, original.getRecordBookNumber()); + } + + @Test + void testEqualsAndHashCode() { + Student s1 = new Student("A12", 4.5, 12345); + Student s2 = new Student("A12", 4.5, 12345); + Student s3 = new Student("B32", 4.5, 12345); + Student s4 = new Student("A12", 4.8, 12345); + Student s5 = new Student("A12", 4.5, 99999); + + assertEquals(s1, s2); + assertEquals(s1.hashCode(), s2.hashCode()); + + assertNotEquals(s1, s3); + assertNotEquals(s1, s4); + assertNotEquals(s1, s5); + assertNotEquals(s1, null); + assertNotEquals(s1, "some string"); + } +} \ No newline at end of file From 18e1608103f9c116fbb912a1bfe6af6878874c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Fri, 28 Aug 2026 18:17:53 +0300 Subject: [PATCH 58/77] test: add comprehensive tests for all SortStrategy implementations --- .../ru/project/Sorting/SortStrategyTest.java | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 app/src/test/java/ru/project/Sorting/SortStrategyTest.java diff --git a/app/src/test/java/ru/project/Sorting/SortStrategyTest.java b/app/src/test/java/ru/project/Sorting/SortStrategyTest.java new file mode 100644 index 0000000..6e0472c --- /dev/null +++ b/app/src/test/java/ru/project/Sorting/SortStrategyTest.java @@ -0,0 +1,163 @@ +package ru.project.sorting; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.project.list.MyList; +import ru.project.list.MyLinkedList; +import ru.project.student.Student; + +import java.util.Comparator; + +import static org.junit.jupiter.api.Assertions.*; + +class SortStrategyTest { + + private MyList students; + private final Comparator groupComparator = new StudentGroupComparator(); + private final Comparator gradeComparator = new StudentAverageGradeComparator(); + private final Comparator recordComparator = new StudentRecordBookComparator(); + + @BeforeEach + void setUp() { + students = new MyLinkedList<>(); + students.add(new Student("A12", 4.5, 12345)); + students.add(new Student("B32", 3.8, 12346)); + students.add(new Student("Z00", 4.9, 12347)); + students.add(new Student("A12", 4.2, 12348)); + students.add(new Student("B32", 5.0, 12349)); + } + + @Test + void testMergeSortByGroupNumber() { + SortStrategy strategy = new MergeSortStrategy<>(); + strategy.sort(students, groupComparator); + assertOrder(students, "A12", "A12", "B32", "B32", "Z00"); + } + + @Test + void testMergeSortByAverageGrade() { + SortStrategy strategy = new MergeSortStrategy<>(); + strategy.sort(students, gradeComparator); + assertOrder(students, 3.8, 4.2, 4.5, 4.9, 5.0); + } + + @Test + void testMergeSortByRecordBookNumber() { + SortStrategy strategy = new MergeSortStrategy<>(); + strategy.sort(students, recordComparator); + assertOrder(students, 12345, 12346, 12347, 12348, 12349); + } + + @Test + void testBubbleSortByGroupNumber() { + SortStrategy strategy = new BubbleSortStrategy<>(); + strategy.sort(students, groupComparator); + assertOrder(students, "A12", "A12", "B32", "B32", "Z00"); + } + + @Test + void testBubbleSortByAverageGrade() { + SortStrategy strategy = new BubbleSortStrategy<>(); + strategy.sort(students, gradeComparator); + assertOrder(students, 3.8, 4.2, 4.5, 4.9, 5.0); + } + + @Test + void testBubbleSortByRecordBookNumber() { + SortStrategy strategy = new BubbleSortStrategy<>(); + strategy.sort(students, recordComparator); + assertOrder(students, 12345, 12346, 12347, 12348, 12349); + } + + @Test + void testEvenOnlySortByRecordBookNumber() { + MyList list = new MyLinkedList<>(); + list.add(new Student("A01", 4.5, 7)); + list.add(new Student("A02", 4.5, 8)); + list.add(new Student("A03", 4.5, 3)); + list.add(new Student("A04", 4.5, 2)); + list.add(new Student("A05", 4.5, 5)); + list.add(new Student("A06", 4.5, 4)); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber); + strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber)); + + int[] expected = {7, 2, 3, 4, 5, 8}; + for (int i = 0; i < list.size(); i++) { + assertEquals(expected[i], list.get(i).getRecordBookNumber()); + } + } + + @Test + void testEvenOnlyWithNoEvenElements() { + MyList list = new MyLinkedList<>(); + list.add(new Student("A01", 4.5, 1)); + list.add(new Student("A02", 4.5, 3)); + list.add(new Student("A03", 4.5, 5)); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber); + strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber)); + + int[] expected = {1, 3, 5}; + for (int i = 0; i < list.size(); i++) { + assertEquals(expected[i], list.get(i).getRecordBookNumber()); + } + } + + @Test + void testEvenOnlyWithAllEvenElements() { + MyList list = new MyLinkedList<>(); + list.add(new Student("A01", 4.5, 4)); + list.add(new Student("A02", 4.5, 2)); + list.add(new Student("A03", 4.5, 6)); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber); + strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber)); + + int[] expected = {2, 4, 6}; + for (int i = 0; i < list.size(); i++) { + assertEquals(expected[i], list.get(i).getRecordBookNumber()); + } + } + + @Test + void testSortEmptyList() { + MyList empty = new MyLinkedList<>(); + SortStrategy strategy = new MergeSortStrategy<>(); + strategy.sort(empty, groupComparator); + assertEquals(0, empty.size()); + } + + @Test + void testSortSingleElement() { + MyList single = new MyLinkedList<>(); + single.add(new Student("A12", 4.5, 12345)); + SortStrategy strategy = new MergeSortStrategy<>(); + strategy.sort(single, groupComparator); + assertEquals(1, single.size()); + } + + private void assertOrder(MyList list, String... groups) { + assertEquals(groups.length, list.size()); + for (int i = 0; i < groups.length; i++) { + assertEquals(groups[i], list.get(i).getGroupNumber()); + } + } + + private void assertOrder(MyList list, double... grades) { + assertEquals(grades.length, list.size()); + for (int i = 0; i < grades.length; i++) { + assertEquals(grades[i], list.get(i).getAverageGrade(), 0.001); + } + } + + private void assertOrder(MyList list, int... records) { + assertEquals(records.length, list.size()); + for (int i = 0; i < records.length; i++) { + assertEquals(records[i], list.get(i).getRecordBookNumber()); + } + } +} \ No newline at end of file From faae240b1d9d57bc48f0a20cf159570c41d397af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Fri, 28 Aug 2026 19:34:56 +0400 Subject: [PATCH 59/77] refactor: rename MessageWindow create method to show --- app/src/main/java/ru/project/ui/window/MessageWindow.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/ru/project/ui/window/MessageWindow.java b/app/src/main/java/ru/project/ui/window/MessageWindow.java index ee56ea8..d6d592c 100644 --- a/app/src/main/java/ru/project/ui/window/MessageWindow.java +++ b/app/src/main/java/ru/project/ui/window/MessageWindow.java @@ -22,7 +22,7 @@ public class MessageWindow extends BaseWindow { setComponent(panel); } - public static void create(WindowBasedTextGUI gui, String message) { + public static void show(WindowBasedTextGUI gui, String message) { new MessageWindow(gui, message).show(); } } From bce5707f5e9725232086b59142f5bd775714af83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Fri, 28 Aug 2026 19:35:33 +0400 Subject: [PATCH 60/77] feat: add input value window Add a reusable window for entering and editing values. --- .../ui/window/CollectionSizeInputWindow.java | 54 ------------------ .../project/ui/window/InputValueWindow.java | 56 +++++++++++++++++++ 2 files changed, 56 insertions(+), 54 deletions(-) delete mode 100644 app/src/main/java/ru/project/ui/window/CollectionSizeInputWindow.java create mode 100644 app/src/main/java/ru/project/ui/window/InputValueWindow.java diff --git a/app/src/main/java/ru/project/ui/window/CollectionSizeInputWindow.java b/app/src/main/java/ru/project/ui/window/CollectionSizeInputWindow.java deleted file mode 100644 index 25b7775..0000000 --- a/app/src/main/java/ru/project/ui/window/CollectionSizeInputWindow.java +++ /dev/null @@ -1,54 +0,0 @@ -package ru.project.ui.window; - -import com.googlecode.lanterna.gui2.Button; -import com.googlecode.lanterna.gui2.Label; -import com.googlecode.lanterna.gui2.LinearLayout; -import com.googlecode.lanterna.gui2.Panel; -import com.googlecode.lanterna.gui2.TextBox; -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -import java.util.function.IntConsumer; -import ru.project.ui.base.BaseWindow; - -public class CollectionSizeInputWindow extends BaseWindow { - - private final TextBox sizeTextBox; - private final IntConsumer onSizeEntered; - - public CollectionSizeInputWindow(WindowBasedTextGUI gui, IntConsumer onSizeEntered) { - - super("Задать размер", gui); - - this.onSizeEntered = onSizeEntered; - - Panel panel = new Panel(); - panel.setLayoutManager(new LinearLayout()); - - panel.addComponent(new Label("Введите размер:")); - - sizeTextBox = new TextBox(); - panel.addComponent(sizeTextBox); - - panel.addComponent(new Button("Подтвердить", this::handleSizeEntered)); - - panel.addComponent(new Button("Выйти", this::close)); - - setComponent(panel); - } - - private void handleSizeEntered() { - try { - int size = Integer.parseInt(sizeTextBox.getText().trim()); - - if (size <= 0) { - MessageWindow.create(gui, "Размер должен быть больше 0."); - return; - } - - close(); - onSizeEntered.accept(size); - - } catch (NumberFormatException e) { - MessageWindow.create(gui, "Введите целое число."); - } - } -} diff --git a/app/src/main/java/ru/project/ui/window/InputValueWindow.java b/app/src/main/java/ru/project/ui/window/InputValueWindow.java new file mode 100644 index 0000000..d21a79b --- /dev/null +++ b/app/src/main/java/ru/project/ui/window/InputValueWindow.java @@ -0,0 +1,56 @@ +package ru.project.ui.window; + +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.TextBox; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.Predicate; +import ru.project.ui.base.BaseWindow; + +public class InputValueWindow extends BaseWindow { + + private final TextBox textBox; + private final Predicate onSave; + + public InputValueWindow( + WindowBasedTextGUI gui, + String title, + String prompt, + String initialValue, + Predicate onSave) { + + super(title, gui); + + this.onSave = onSave; + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + panel.addComponent(new Label(prompt)); + + textBox = new TextBox(initialValue == null ? "" : initialValue); + + panel.addComponent(textBox); + + panel.addComponent(new Button("Сохранить", this::save)); + + panel.addComponent(new Button("Отмена", this::close)); + + setComponent(panel); + } + + private void save() { + String value = textBox.getText().trim(); + + if (value.isEmpty()) { + MessageWindow.show(gui, "Значение не может быть пустым."); + return; + } + + if (onSave.test(value)) { + close(); + } + } +} From 5d0052805c98fd8ea5c190c0b043553eebeaac40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Fri, 28 Aug 2026 19:37:27 +0400 Subject: [PATCH 61/77] refactor: replace current collection window Replace CurrentCollectionWindow with CollectionDisplayWindow. --- .../ui/window/CollectionDisplayWindow.java | 102 ++++++++++++++++++ .../ui/window/CurrentCollectionWindow.java | 41 ------- 2 files changed, 102 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/ru/project/ui/window/CollectionDisplayWindow.java delete mode 100644 app/src/main/java/ru/project/ui/window/CurrentCollectionWindow.java diff --git a/app/src/main/java/ru/project/ui/window/CollectionDisplayWindow.java b/app/src/main/java/ru/project/ui/window/CollectionDisplayWindow.java new file mode 100644 index 0000000..4ca3c5e --- /dev/null +++ b/app/src/main/java/ru/project/ui/window/CollectionDisplayWindow.java @@ -0,0 +1,102 @@ +package ru.project.ui.window; + +import com.googlecode.lanterna.TerminalSize; +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Direction; +import com.googlecode.lanterna.gui2.EmptySpace; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.TextBox; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import com.googlecode.lanterna.input.KeyStroke; +import com.googlecode.lanterna.input.KeyType; +import ru.project.collection.MyList; +import ru.project.student.Student; +import ru.project.ui.base.BaseWindow; + +public class CollectionDisplayWindow extends BaseWindow { + + private final MyList collection; + + private TextBox collectionTextBox; + private Button exitButton; + + public CollectionDisplayWindow(WindowBasedTextGUI gui, MyList collection) { + + super("Просмотр коллекции", gui); + + this.collection = collection; + + Panel panel = createPanel(); + + setComponent(panel); + + exitButton.takeFocus(); + } + + @Override + public boolean handleInput(KeyStroke key) { + if (key.getKeyType() == KeyType.Escape && collectionTextBox.isFocused()) { + + exitButton.takeFocus(); + return true; + } + + return super.handleInput(key); + } + + private Panel createPanel() { + Panel panel = new Panel(); + + panel.setLayoutManager(new LinearLayout(Direction.VERTICAL)); + + addCollectionDisplay(panel); + addExitButton(panel); + + return panel; + } + + private void addCollectionDisplay(Panel panel) { + collectionTextBox = createCollectionScrollBox(); + + panel.addComponent(collectionTextBox); + } + + private TextBox createCollectionScrollBox() { + TextBox textBox = new TextBox(new TerminalSize(80, 20), TextBox.Style.MULTI_LINE); + + textBox.setReadOnly(true); + + if (collection == null || collection.isEmpty()) { + textBox.setText("Коллекция пуста."); + } else { + textBox.setText(buildCollectionText()); + } + + return textBox; + } + + private void addExitButton(Panel panel) { + panel.addComponent(new EmptySpace(new TerminalSize(0, 1))); + + exitButton = new Button("Выйти", this::close); + + panel.addComponent(exitButton); + } + + private String buildCollectionText() { + StringBuilder text = new StringBuilder(); + + for (int i = 0; i < collection.size(); i++) { + Student student = collection.get(i); + + text.append("[") + .append(i) + .append("] ") + .append(student == null ? "null" : student) + .append("\n"); + } + + return text.toString(); + } +} diff --git a/app/src/main/java/ru/project/ui/window/CurrentCollectionWindow.java b/app/src/main/java/ru/project/ui/window/CurrentCollectionWindow.java deleted file mode 100644 index 0fd7e29..0000000 --- a/app/src/main/java/ru/project/ui/window/CurrentCollectionWindow.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.project.ui.window; - -import com.googlecode.lanterna.gui2.Button; -import com.googlecode.lanterna.gui2.Label; -import com.googlecode.lanterna.gui2.LinearLayout; -import com.googlecode.lanterna.gui2.Panel; -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -import ru.project.collection.MyList; -import ru.project.model.AppState; -import ru.project.student.Student; -import ru.project.ui.base.BaseWindow; - -public class CurrentCollectionWindow extends BaseWindow { - - public CurrentCollectionWindow(WindowBasedTextGUI gui, AppState state) { - super("Текущая коллекция", gui); - - Panel panel = new Panel(); - panel.setLayoutManager(new LinearLayout()); - - MyList collection = state.getCollection(); - - if (collection == null || collection.isEmpty()) { - panel.addComponent(new Label("Текущая коллекция пуста.")); - } else { - addStudents(panel, collection); - } - - panel.addComponent(new Button("Выйти", this::close)); - - setComponent(panel); - } - - private void addStudents(Panel panel, MyList collection) { - for (int i = 0; i < collection.size(); i++) { - Student student = collection.get(i); - - panel.addComponent(new Label((i + 1) + ". " + student)); - } - } -} From ff0c063078ccedd59dca3c139a932ca48819d7d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Fri, 28 Aug 2026 19:51:47 +0400 Subject: [PATCH 62/77] feat: add custom data collection settings Add support for storing and configuring custom student data collections in AppState. Introduce a custom data settings window for entering and validating student fields, selecting an object index, previewing student data, and applying prepared data to the current collection. Refactor collection creation to use reusable input handling and update collection display and message window usage. --- .../main/java/ru/project/model/AppState.java | 23 +- .../project/ui/menu/CreateCollectionMenu.java | 73 +++-- .../java/ru/project/ui/menu/MainMenu.java | 18 +- .../ui/window/CustomDataSettingsWindow.java | 265 ++++++++++++++++++ 4 files changed, 350 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/ru/project/ui/window/CustomDataSettingsWindow.java diff --git a/app/src/main/java/ru/project/model/AppState.java b/app/src/main/java/ru/project/model/AppState.java index 9b2a61e..e430057 100644 --- a/app/src/main/java/ru/project/model/AppState.java +++ b/app/src/main/java/ru/project/model/AppState.java @@ -6,20 +6,31 @@ import ru.project.student.Student; public class AppState { - private MyList collection; + private MyList currentCollection; + private MyList customDataCollection; private String currentFilePath; public AppState() { - this.collection = new MyLinkedList<>(); + this.currentCollection = new MyLinkedList<>(); + this.customDataCollection = new MyLinkedList<>(); this.currentFilePath = null; } - public MyList getCollection() { - return collection; + public MyList getCurrentCollection() { + return currentCollection; } - public void setCollection(MyList collection) { - this.collection = collection; + public void setCurrentCollection(MyList currentCollection) { + this.currentCollection = currentCollection; + } + + public MyList getCustomDataCollection() { + return customDataCollection; + } + + public void setCustomDataCollection(MyList customDataCollection) { + + this.customDataCollection = customDataCollection; } public String getCurrentFilePath() { diff --git a/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java b/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java index 2f7facd..48d2f0c 100644 --- a/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java @@ -8,7 +8,8 @@ import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import ru.project.model.AppState; import ru.project.model.FillMethod; import ru.project.ui.base.BaseWindow; -import ru.project.ui.window.CollectionSizeInputWindow; +import ru.project.ui.window.CustomDataSettingsWindow; +import ru.project.ui.window.InputValueWindow; import ru.project.ui.window.MessageWindow; public class CreateCollectionMenu extends BaseWindow { @@ -19,9 +20,13 @@ public class CreateCollectionMenu extends BaseWindow { private final Label sizeLabel; private final Label fillMethodLabel; + private final AppState state; + public CreateCollectionMenu(WindowBasedTextGUI gui, AppState state) { super("Создание новой коллекции", gui); + this.state = state; + collectionSize = 0; selectedFillMethod = FillMethod.CUSTOM; @@ -35,9 +40,12 @@ public class CreateCollectionMenu extends BaseWindow { panel.addComponent(sizeLabel); panel.addComponent(fillMethodLabel); - panel.addComponent(new Button("Задать размер", this::openCollectionSizeInputWindow)); + panel.addComponent(new Button("Задать размер", this::openCollectionSizeInputValueWindow)); - panel.addComponent(new Button("Настроить способ заполнения", this::openFillMethodMenu)); + panel.addComponent(new Button("Выбрать способ заполнения", this::selectFillMethod)); + + panel.addComponent( + new Button("Настроить текущий способ заполнения", this::setupCurrentFillMethod)); panel.addComponent(new Button("Создать новую коллекцию", this::createCollection)); @@ -46,20 +54,53 @@ public class CreateCollectionMenu extends BaseWindow { setComponent(panel); } - private void openCollectionSizeInputWindow() { - CollectionSizeInputWindow window = - new CollectionSizeInputWindow( + private void openCollectionSizeInputValueWindow() { + InputValueWindow window = + new InputValueWindow( gui, - size -> { - collectionSize = size; - sizeLabel.setText("Размер: " + collectionSize); - }); + "Размер коллекции", + "Введите размер коллекции:", + collectionSize > 0 ? String.valueOf(collectionSize) : null, + this::handleSizeEntered); window.show(); } - private void openFillMethodMenu() { - FillMethodSelectionWindow menu = + private boolean handleSizeEntered(String value) { + try { + int enteredSize = Integer.parseInt(value); + if (enteredSize <= 0) { + MessageWindow.show(gui, "Размер должен быть больше 0."); + return false; + } + collectionSize = enteredSize; + sizeLabel.setText("Размер: " + collectionSize); + return true; + } catch (NumberFormatException e) { + MessageWindow.show(gui, "Введите целое число."); + return false; + } + } + + private void setupCurrentFillMethod() { + switch (selectedFillMethod) { + case CUSTOM -> { + BaseWindow window = new CustomDataSettingsWindow(gui, state, collectionSize); + + window.show(); + } + + case FILE -> + // TODO + MessageWindow.show(gui, "Не реализовано."); + + case RANDOM -> + MessageWindow.show(gui, "Для случайных данных дополнительные настройки не требуются."); + } + } + + private void selectFillMethod() { + BaseWindow menu = new FillMethodSelectionWindow( gui, fillMethod -> { @@ -73,7 +114,7 @@ public class CreateCollectionMenu extends BaseWindow { private void createCollection() { if (collectionSize <= 0) { - MessageWindow.create(gui, "Сначала задайте размер коллекции."); + MessageWindow.show(gui, "Сначала задайте размер коллекции."); return; } @@ -94,17 +135,17 @@ public class CreateCollectionMenu extends BaseWindow { private void createCollectionFromCustomData() { // TODO - MessageWindow.create(gui, "Не реализовано."); + MessageWindow.show(gui, "Не реализовано."); } private void createCollectionFromRandomData() { // TODO - MessageWindow.create(gui, "Не реализовано."); + MessageWindow.show(gui, "Не реализовано."); } private void createCollectionFromFileData() { // TODO - MessageWindow.create(gui, "Не реализовано."); + MessageWindow.show(gui, "Не реализовано."); } private String getFillMethodName(FillMethod fillMethod) { diff --git a/app/src/main/java/ru/project/ui/menu/MainMenu.java b/app/src/main/java/ru/project/ui/menu/MainMenu.java index 3b3fbcb..308277e 100644 --- a/app/src/main/java/ru/project/ui/menu/MainMenu.java +++ b/app/src/main/java/ru/project/ui/menu/MainMenu.java @@ -2,9 +2,11 @@ package ru.project.ui.menu; import com.googlecode.lanterna.gui2.ActionListBox; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.collection.MyList; import ru.project.model.AppState; +import ru.project.student.Student; import ru.project.ui.base.BaseWindow; -import ru.project.ui.window.CurrentCollectionWindow; +import ru.project.ui.window.CollectionDisplayWindow; import ru.project.ui.window.MessageWindow; public class MainMenu extends BaseWindow { @@ -37,35 +39,37 @@ public class MainMenu extends BaseWindow { } private void showCurrentCollection() { - CurrentCollectionWindow window = new CurrentCollectionWindow(gui, state); + MyList currentCollection = state.getCurrentCollection(); + + BaseWindow window = new CollectionDisplayWindow(gui, currentCollection); window.show(); } private void createCollection() { - CreateCollectionMenu window = new CreateCollectionMenu(gui, state); + BaseWindow window = new CreateCollectionMenu(gui, state); window.show(); } private void saveCollection() { // TODO - MessageWindow.create(gui, "Не реализовано."); + MessageWindow.show(gui, "Не реализовано."); } private void sortCollection() { // TODO - MessageWindow.create(gui, "Не реализовано."); + MessageWindow.show(gui, "Не реализовано."); } private void searchCollection() { // TODO - MessageWindow.create(gui, "Не реализовано."); + MessageWindow.show(gui, "Не реализовано."); } private void countOccurrences() { // TODO - MessageWindow.create(gui, "Не реализовано."); + MessageWindow.show(gui, "Не реализовано."); } private void exit() { diff --git a/app/src/main/java/ru/project/ui/window/CustomDataSettingsWindow.java b/app/src/main/java/ru/project/ui/window/CustomDataSettingsWindow.java new file mode 100644 index 0000000..b59f805 --- /dev/null +++ b/app/src/main/java/ru/project/ui/window/CustomDataSettingsWindow.java @@ -0,0 +1,265 @@ +package ru.project.ui.window; + +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.model.AppState; +import ru.project.student.Student; +import ru.project.student.StudentBuilder; +import ru.project.ui.base.BaseWindow; + +public class CustomDataSettingsWindow extends BaseWindow { + + private final AppState state; + private final int collectionSize; + private final StudentBuilder studentBuilder; + + private String groupNumber; + private Double averageGrade; + private Integer recordBookNumber; + private Integer selectedObjectIndex; + + private final Label groupNumberLabel; + private final Label averageGradeLabel; + private final Label recordBookNumberLabel; + private final Label indexLabel; + + private final MyList preparedCustomDataCollection; + + public CustomDataSettingsWindow(WindowBasedTextGUI gui, AppState state, int collectionSize) { + + super("Настройка пользовательских данных", gui); + + this.state = state; + this.collectionSize = collectionSize; + this.studentBuilder = new StudentBuilder(); + this.preparedCustomDataCollection = new MyLinkedList<>(); + + MyList customDataCollection = state.getCustomDataCollection(); + + int elementsToCopy = Math.min(customDataCollection.size(), collectionSize); + + for (int i = 0; i < elementsToCopy; i++) { + preparedCustomDataCollection.add(customDataCollection.get(i)); + } + + for (int i = elementsToCopy; i < collectionSize; i++) { + preparedCustomDataCollection.add(null); + } + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + groupNumberLabel = new Label("Номер группы: не задан"); + + averageGradeLabel = new Label("Средний балл: не задан"); + + recordBookNumberLabel = new Label("Номер зачётной книжки: не задан"); + + indexLabel = new Label("Индекс объекта: не задан"); + + panel.addComponent(groupNumberLabel); + panel.addComponent(averageGradeLabel); + panel.addComponent(recordBookNumberLabel); + panel.addComponent(indexLabel); + + panel.addComponent(new Button("Задать номер группы", this::setGroupNumber)); + + panel.addComponent(new Button("Задать средний балл", this::setAverageGrade)); + + panel.addComponent(new Button("Задать номер зачётной книжки", this::setRecordBookNumber)); + + panel.addComponent(new Button("Задать индекс объекта", this::setObjectIndex)); + + panel.addComponent(new Button("Задать данные объекта", this::editObject)); + + panel.addComponent(new Button("Предпросмотр", this::preview)); + + panel.addComponent(new Button("Применить", this::apply)); + + panel.addComponent(new Button("Назад", this::close)); + + setComponent(panel); + } + + private void setGroupNumber() { + InputValueWindow window = + new InputValueWindow( + gui, + "Номер группы", + "Введите номер группы (например, A12):", + groupNumber, + value -> { + if (!value.matches(Student.getGroupNumberPattern())) { + MessageWindow.show(gui, "Номер группы должен соответствовать формату A12."); + return false; + } + + groupNumber = value; + studentBuilder.setGroupNumber(value); + + groupNumberLabel.setText("Номер группы: " + groupNumber); + + return true; + }); + + window.show(); + } + + private void setAverageGrade() { + InputValueWindow window = + new InputValueWindow( + gui, + "Средний балл", + "Введите средний балл от 0.0 до 5.0:", + averageGrade == null ? null : String.valueOf(averageGrade), + value -> { + try { + double parsedValue = Double.parseDouble(value); + + if (parsedValue < Student.getMinAverageGrade() + || parsedValue > Student.getMaxAverageGrade()) { + + MessageWindow.show(gui, "Средний балл должен быть от 0.0 до 5.0."); + return false; + } + + averageGrade = parsedValue; + studentBuilder.setAverageGrade(parsedValue); + + averageGradeLabel.setText("Средний балл: " + averageGrade); + + } catch (NumberFormatException e) { + MessageWindow.show(gui, "Введите корректное число."); + return false; + } + + return true; + }); + + window.show(); + } + + private void setRecordBookNumber() { + InputValueWindow window = + new InputValueWindow( + gui, + "Номер зачётной книжки", + "Введите номер зачётной книжки:", + recordBookNumber == null ? null : String.valueOf(recordBookNumber), + value -> { + try { + int parsedValue = Integer.parseInt(value); + + if (parsedValue < Student.getMinRecordBookNumber()) { + MessageWindow.show(gui, "Номер зачётной книжки должен быть положительным."); + return false; + } + + recordBookNumber = parsedValue; + studentBuilder.setRecordBookNumber(parsedValue); + + recordBookNumberLabel.setText("Номер зачётной книжки: " + recordBookNumber); + + } catch (NumberFormatException e) { + MessageWindow.show(gui, "Введите целое число."); + return false; + } + + return true; + }); + + window.show(); + } + + private void setObjectIndex() { + InputValueWindow window = + new InputValueWindow( + gui, + "Индекс объекта", + "Введите индекс объекта:", + selectedObjectIndex == null ? null : String.valueOf(selectedObjectIndex), + value -> { + try { + int index = Integer.parseInt(value); + + if (index < 0) { + MessageWindow.show(gui, "Индекс объекта не может быть отрицательным."); + return false; + } + + if (index >= collectionSize) { + MessageWindow.show(gui, "Индекс должен быть меньше размера коллекции."); + return false; + } + + selectedObjectIndex = index; + + indexLabel.setText("Индекс объекта: " + selectedObjectIndex); + + return true; + + } catch (NumberFormatException e) { + MessageWindow.show(gui, "Введите целое число."); + return false; + } + }); + + window.show(); + } + + private void editObject() { + if (!hasStudentData()) { + MessageWindow.show(gui, "Сначала задайте все поля студента."); + return; + } + + if (selectedObjectIndex == null) { + MessageWindow.show(gui, "Сначала задайте индекс объекта."); + return; + } + + if (selectedObjectIndex >= preparedCustomDataCollection.size()) { + MessageWindow.show(gui, "Индекс объекта выходит за границы подготовленной коллекции."); + return; + } + + Student student = studentBuilder.build(); + + preparedCustomDataCollection.set(selectedObjectIndex, student); + + MessageWindow.show(gui, "Данные объекта успешно заданы."); + } + + private void apply() { + MyList currentCollection = new MyLinkedList<>(); + + for (int i = 0; i < preparedCustomDataCollection.size(); i++) { + currentCollection.add(preparedCustomDataCollection.get(i)); + } + + state.setCustomDataCollection(preparedCustomDataCollection); + state.setCurrentCollection(currentCollection); + + close(); + } + + private void preview() { + if (!hasStudentData()) { + MessageWindow.show(gui, "Для предпросмотра сначала задайте все поля студента."); + return; + } + + Student student = studentBuilder.build(); + + MessageWindow.show(gui, student.toString()); + } + + private boolean hasStudentData() { + return groupNumber != null && averageGrade != null && recordBookNumber != null; + } +} From 62ef9540bfdba4eba2adb5656722173fbe927a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Fri, 28 Aug 2026 22:54:04 +0300 Subject: [PATCH 63/77] refactor(sorting): reuse merge sort strategy --- .../sorting/EvenOnlyMergeSortStrategy.java | 82 ++----------------- 1 file changed, 7 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java b/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java index a784d7a..45f5b78 100644 --- a/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java +++ b/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java @@ -1,5 +1,6 @@ package ru.project.sorting; +import ru.project.collection.MyLinkedList; import ru.project.collection.MyList; import java.util.Comparator; @@ -7,9 +8,11 @@ import java.util.function.ToIntFunction; public class EvenOnlyMergeSortStrategy implements SortStrategy { private final ToIntFunction valueExtractor; + private final SortStrategy mergeSortStrategy; public EvenOnlyMergeSortStrategy(ToIntFunction valueExtractor) { this.valueExtractor = valueExtractor; + this.mergeSortStrategy = new MergeSortStrategy<>(); } @Override @@ -32,8 +35,7 @@ public class EvenOnlyMergeSortStrategy implements SortStrategy { return; } - @SuppressWarnings("unchecked") - T[] evenElements = (T[]) new Object[evenCount]; + MyList evenElements = new MyLinkedList<>(); int[] evenIndexes = new int[evenCount]; @@ -43,85 +45,15 @@ public class EvenOnlyMergeSortStrategy implements SortStrategy { T element = list.get(i); if (valueExtractor.applyAsInt(element) % 2 == 0) { - evenElements[evenIndex] = element; + evenElements.add(element); evenIndexes[evenIndex] = i; evenIndex++; } } - mergeSort(evenElements, 0, evenElements.length - 1, comparator); + mergeSortStrategy.sort(evenElements, comparator); for (int i = 0; i < evenIndexes.length; i++) { - list.set(evenIndexes[i], evenElements[i]); - } - } - - private void mergeSort( - T[] elements, - int left, - int right, - Comparator comparator - ) { - if (left >= right) { - return; - } - - int mid = (left + right) / 2; - - mergeSort(elements, left, mid, comparator); - mergeSort(elements, mid + 1, right, comparator); - - merge(elements, left, mid, right, comparator); - } - private void merge( - T[] elements, - int left, - int mid, - int right, - Comparator comparator - ) { - int leftSize = mid - left + 1; - int rightSize = right - mid; - - @SuppressWarnings("unchecked") - T[] leftArray = (T[]) new Object[leftSize]; - - @SuppressWarnings("unchecked") - T[] rightArray = (T[]) new Object[rightSize]; - - for (int i = 0; i < leftSize; i++) { - leftArray[i] = elements[left + i]; - } - - for (int j = 0; j < rightSize; j++) { - rightArray[j] = elements[mid + 1 + j]; - } - - int i = 0; - int j = 0; - int k = left; - - while (i < leftSize && j < rightSize) { - if (comparator.compare(leftArray[i], rightArray[j]) <= 0) { - elements[k] = leftArray[i]; - i++; - } else { - elements[k] = rightArray[j]; - j++; - } - - k++; - } - - while (i < leftSize) { - elements[k] = leftArray[i]; - i++; - k++; - } - - while (j < rightSize) { - elements[k] = rightArray[j]; - j++; - k++; + list.set(evenIndexes[i], evenElements.get(i)); } } } From 61280132f1f7948825742564c97b54f091d5fa62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Fri, 28 Aug 2026 22:55:25 +0300 Subject: [PATCH 64/77] test(sorting): add student record book case --- .../EvenOnlyMergeSortStrategyTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/src/test/java/ru/project/sorting/EvenOnlyMergeSortStrategyTest.java b/app/src/test/java/ru/project/sorting/EvenOnlyMergeSortStrategyTest.java index b423cba..ffaef36 100644 --- a/app/src/test/java/ru/project/sorting/EvenOnlyMergeSortStrategyTest.java +++ b/app/src/test/java/ru/project/sorting/EvenOnlyMergeSortStrategyTest.java @@ -3,6 +3,7 @@ package ru.project.sorting; import org.junit.jupiter.api.Test; import ru.project.collection.MyLinkedList; import ru.project.collection.MyList; +import ru.project.student.Student; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -144,6 +145,33 @@ class EvenOnlyMergeSortStrategyTest { assertEquals(5, list.get(4)); assertEquals(2, list.get(5)); } + + @Test + void shouldUseStudentRecordBookNumberToDetermineEvenElements() { + MyList students = new MyLinkedList<>(); + + students.add(new Student("A01", 4.0, 7)); + students.add(new Student("A02", 4.0, 8)); + students.add(new Student("A03", 4.0, 3)); + students.add(new Student("A04", 4.0, 2)); + students.add(new Student("A05", 4.0, 5)); + students.add(new Student("A06", 4.0, 4)); + + SortStrategy strategy = new EvenOnlyMergeSortStrategy<>( + Student::getRecordBookNumber + ); + + strategy.sort(students, new StudentRecordBookComparator()); + + assertEquals(7, students.get(0).getRecordBookNumber()); + assertEquals(2, students.get(1).getRecordBookNumber()); + assertEquals(3, students.get(2).getRecordBookNumber()); + assertEquals(4, students.get(3).getRecordBookNumber()); + assertEquals(5, students.get(4).getRecordBookNumber()); + assertEquals(8, students.get(5).getRecordBookNumber()); + + assertEquals(6, students.size()); + } } From b932636409c2a5807e66acd47969dc1fff7e0df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Sat, 29 Aug 2026 18:37:48 +0400 Subject: [PATCH 65/77] refactor(ui): rename BaseWindow to BaseModalWindow --- .../ui/base/{BaseWindow.java => BaseModalWindow.java} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename app/src/main/java/ru/project/ui/base/{BaseWindow.java => BaseModalWindow.java} (62%) diff --git a/app/src/main/java/ru/project/ui/base/BaseWindow.java b/app/src/main/java/ru/project/ui/base/BaseModalWindow.java similarity index 62% rename from app/src/main/java/ru/project/ui/base/BaseWindow.java rename to app/src/main/java/ru/project/ui/base/BaseModalWindow.java index 90fbb7c..0f0abde 100644 --- a/app/src/main/java/ru/project/ui/base/BaseWindow.java +++ b/app/src/main/java/ru/project/ui/base/BaseModalWindow.java @@ -3,17 +3,17 @@ package ru.project.ui.base; import com.googlecode.lanterna.gui2.BasicWindow; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -public abstract class BaseWindow extends BasicWindow { +public abstract class BaseModalWindow extends BasicWindow { protected final WindowBasedTextGUI gui; - protected BaseWindow(String title, WindowBasedTextGUI gui) { + protected BaseModalWindow(String title, WindowBasedTextGUI gui) { super(title); this.gui = gui; } - public void show() { + public void showModal() { gui.addWindowAndWait(this); } } From 266aaa4c7777374a21e846a52284982903a71916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Sat, 29 Aug 2026 18:38:45 +0400 Subject: [PATCH 66/77] refactor(model): rename currentCollection to mainCollection --- app/src/main/java/ru/project/model/AppState.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/ru/project/model/AppState.java b/app/src/main/java/ru/project/model/AppState.java index e430057..375877e 100644 --- a/app/src/main/java/ru/project/model/AppState.java +++ b/app/src/main/java/ru/project/model/AppState.java @@ -6,22 +6,22 @@ import ru.project.student.Student; public class AppState { - private MyList currentCollection; + private MyList mainCollection; private MyList customDataCollection; private String currentFilePath; public AppState() { - this.currentCollection = new MyLinkedList<>(); + this.mainCollection = new MyLinkedList<>(); this.customDataCollection = new MyLinkedList<>(); this.currentFilePath = null; } - public MyList getCurrentCollection() { - return currentCollection; + public MyList getMainCollection() { + return mainCollection; } - public void setCurrentCollection(MyList currentCollection) { - this.currentCollection = currentCollection; + public void setMainCollection(MyList currentCollection) { + this.mainCollection = currentCollection; } public MyList getCustomDataCollection() { From 5c459a101767d962879b617094c1a8ee75bd7575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Sat, 29 Aug 2026 18:39:24 +0400 Subject: [PATCH 67/77] refactor(ui): update MessageWindow for modal base class --- app/src/main/java/ru/project/ui/window/MessageWindow.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/ru/project/ui/window/MessageWindow.java b/app/src/main/java/ru/project/ui/window/MessageWindow.java index d6d592c..76ad9a1 100644 --- a/app/src/main/java/ru/project/ui/window/MessageWindow.java +++ b/app/src/main/java/ru/project/ui/window/MessageWindow.java @@ -5,9 +5,9 @@ import com.googlecode.lanterna.gui2.Label; import com.googlecode.lanterna.gui2.LinearLayout; import com.googlecode.lanterna.gui2.Panel; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -import ru.project.ui.base.BaseWindow; +import ru.project.ui.base.BaseModalWindow; -public class MessageWindow extends BaseWindow { +public class MessageWindow extends BaseModalWindow { public MessageWindow(WindowBasedTextGUI gui, String message) { super("Информация", gui); @@ -22,7 +22,7 @@ public class MessageWindow extends BaseWindow { setComponent(panel); } - public static void show(WindowBasedTextGUI gui, String message) { - new MessageWindow(gui, message).show(); + public static void showModal(WindowBasedTextGUI gui, String message) { + new MessageWindow(gui, message).showModal(); } } From 8f5548644988364bfe88c1009df48cbe56cffd92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Sat, 29 Aug 2026 18:42:22 +0400 Subject: [PATCH 68/77] feat(collection): implement custom collection creation flow - rename window base class usage to BaseModalWindow and update modal method calls - add persistent CustomDataSettingsWindow instance to CreateCollectionMenu - prepare custom data collection dynamically before showing the settings window - apply custom data to AppState without immediately replacing mainCollection - create mainCollection from prepared custom student data - replace student preview message with collection preview window - update input validation and StudentBuilder state handling - rename collection-related methods and variables for clearer semantics --- app/src/main/java/ru/project/App.java | 2 +- .../project/ui/menu/CreateCollectionMenu.java | 71 +++++----- .../ui/menu/FillMethodSelectionWindow.java | 4 +- .../java/ru/project/ui/menu/MainMenu.java | 35 +++-- .../ui/window/CollectionDisplayWindow.java | 4 +- .../ui/window/CustomDataSettingsWindow.java | 125 ++++++++++-------- .../project/ui/window/InputValueWindow.java | 6 +- 7 files changed, 139 insertions(+), 108 deletions(-) diff --git a/app/src/main/java/ru/project/App.java b/app/src/main/java/ru/project/App.java index 1e1d933..fa4ee64 100644 --- a/app/src/main/java/ru/project/App.java +++ b/app/src/main/java/ru/project/App.java @@ -25,7 +25,7 @@ public final class App { MainMenu mainMenu = new MainMenu(gui, state); - mainMenu.show(); + mainMenu.showModal(); } catch (Exception e) { System.err.println("Произошла ошибка при выполнении программы:"); diff --git a/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java b/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java index 48d2f0c..dd9785a 100644 --- a/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java @@ -5,14 +5,17 @@ import com.googlecode.lanterna.gui2.Label; import com.googlecode.lanterna.gui2.LinearLayout; import com.googlecode.lanterna.gui2.Panel; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; import ru.project.model.AppState; import ru.project.model.FillMethod; -import ru.project.ui.base.BaseWindow; +import ru.project.student.Student; +import ru.project.ui.base.BaseModalWindow; import ru.project.ui.window.CustomDataSettingsWindow; import ru.project.ui.window.InputValueWindow; import ru.project.ui.window.MessageWindow; -public class CreateCollectionMenu extends BaseWindow { +public class CreateCollectionMenu extends BaseModalWindow { private int collectionSize; private FillMethod selectedFillMethod; @@ -20,16 +23,16 @@ public class CreateCollectionMenu extends BaseWindow { private final Label sizeLabel; private final Label fillMethodLabel; - private final AppState state; + private final CustomDataSettingsWindow customDataSettingsWindow; public CreateCollectionMenu(WindowBasedTextGUI gui, AppState state) { super("Создание новой коллекции", gui); - this.state = state; - collectionSize = 0; selectedFillMethod = FillMethod.CUSTOM; + customDataSettingsWindow = new CustomDataSettingsWindow(gui, state, () -> collectionSize); + Panel panel = new Panel(); panel.setLayoutManager(new LinearLayout()); @@ -40,21 +43,21 @@ public class CreateCollectionMenu extends BaseWindow { panel.addComponent(sizeLabel); panel.addComponent(fillMethodLabel); - panel.addComponent(new Button("Задать размер", this::openCollectionSizeInputValueWindow)); + panel.addComponent(new Button("Задать размер", this::showCollectionSizeInputValueWindow)); panel.addComponent(new Button("Выбрать способ заполнения", this::selectFillMethod)); panel.addComponent( new Button("Настроить текущий способ заполнения", this::setupCurrentFillMethod)); - panel.addComponent(new Button("Создать новую коллекцию", this::createCollection)); + panel.addComponent(new Button("Создать новую коллекцию", () -> updateMainCollection(state))); panel.addComponent(new Button("Выйти", this::close)); setComponent(panel); } - private void openCollectionSizeInputValueWindow() { + private void showCollectionSizeInputValueWindow() { InputValueWindow window = new InputValueWindow( gui, @@ -63,21 +66,21 @@ public class CreateCollectionMenu extends BaseWindow { collectionSize > 0 ? String.valueOf(collectionSize) : null, this::handleSizeEntered); - window.show(); + window.showModal(); } private boolean handleSizeEntered(String value) { try { int enteredSize = Integer.parseInt(value); if (enteredSize <= 0) { - MessageWindow.show(gui, "Размер должен быть больше 0."); + MessageWindow.showModal(gui, "Размер должен быть больше 0."); return false; } collectionSize = enteredSize; sizeLabel.setText("Размер: " + collectionSize); return true; } catch (NumberFormatException e) { - MessageWindow.show(gui, "Введите целое число."); + MessageWindow.showModal(gui, "Введите целое число."); return false; } } @@ -85,22 +88,21 @@ public class CreateCollectionMenu extends BaseWindow { private void setupCurrentFillMethod() { switch (selectedFillMethod) { case CUSTOM -> { - BaseWindow window = new CustomDataSettingsWindow(gui, state, collectionSize); - - window.show(); + customDataSettingsWindow.showModal(); } case FILE -> // TODO - MessageWindow.show(gui, "Не реализовано."); + MessageWindow.showModal(gui, "Не реализовано."); case RANDOM -> - MessageWindow.show(gui, "Для случайных данных дополнительные настройки не требуются."); + MessageWindow.showModal( + gui, "Для случайных данных дополнительные настройки не требуются."); } } private void selectFillMethod() { - BaseWindow menu = + BaseModalWindow menu = new FillMethodSelectionWindow( gui, fillMethod -> { @@ -109,43 +111,52 @@ public class CreateCollectionMenu extends BaseWindow { "Способ заполнения: " + getFillMethodName(selectedFillMethod)); }); - menu.show(); + menu.showModal(); } - private void createCollection() { + private void updateMainCollection(AppState state) { if (collectionSize <= 0) { - MessageWindow.show(gui, "Сначала задайте размер коллекции."); + MessageWindow.showModal(gui, "Сначала задайте размер коллекции."); return; } switch (selectedFillMethod) { case CUSTOM: - createCollectionFromCustomData(); + createCollectionFromCustomData(state); break; case RANDOM: - createCollectionFromRandomData(); + createCollectionFromRandomData(state); break; case FILE: - createCollectionFromFileData(); + createCollectionFromFileData(state); break; } } - private void createCollectionFromCustomData() { - // TODO - MessageWindow.show(gui, "Не реализовано."); + private void createCollectionFromCustomData(AppState state) { + MyList customDataCollection = state.getCustomDataCollection(); + + MyList mainCollection = new MyLinkedList<>(); + + for (int i = 0; i < customDataCollection.size(); i++) { + mainCollection.add(customDataCollection.get(i)); + } + + state.setMainCollection(mainCollection); + + MessageWindow.showModal(gui, "Коллекция успешно создана."); } - private void createCollectionFromRandomData() { + private void createCollectionFromRandomData(AppState state) { // TODO - MessageWindow.show(gui, "Не реализовано."); + MessageWindow.showModal(gui, "Не реализовано."); } - private void createCollectionFromFileData() { + private void createCollectionFromFileData(AppState state) { // TODO - MessageWindow.show(gui, "Не реализовано."); + MessageWindow.showModal(gui, "Не реализовано."); } private String getFillMethodName(FillMethod fillMethod) { diff --git a/app/src/main/java/ru/project/ui/menu/FillMethodSelectionWindow.java b/app/src/main/java/ru/project/ui/menu/FillMethodSelectionWindow.java index 245fd06..f408210 100644 --- a/app/src/main/java/ru/project/ui/menu/FillMethodSelectionWindow.java +++ b/app/src/main/java/ru/project/ui/menu/FillMethodSelectionWindow.java @@ -4,9 +4,9 @@ import com.googlecode.lanterna.gui2.ActionListBox; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import java.util.function.Consumer; import ru.project.model.FillMethod; -import ru.project.ui.base.BaseWindow; +import ru.project.ui.base.BaseModalWindow; -public class FillMethodSelectionWindow extends BaseWindow { +public class FillMethodSelectionWindow extends BaseModalWindow { private final Consumer onFillMethodSelected; diff --git a/app/src/main/java/ru/project/ui/menu/MainMenu.java b/app/src/main/java/ru/project/ui/menu/MainMenu.java index 308277e..93cef48 100644 --- a/app/src/main/java/ru/project/ui/menu/MainMenu.java +++ b/app/src/main/java/ru/project/ui/menu/MainMenu.java @@ -5,25 +5,26 @@ import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import ru.project.collection.MyList; import ru.project.model.AppState; import ru.project.student.Student; -import ru.project.ui.base.BaseWindow; +import ru.project.ui.base.BaseModalWindow; import ru.project.ui.window.CollectionDisplayWindow; import ru.project.ui.window.MessageWindow; -public class MainMenu extends BaseWindow { +public class MainMenu extends BaseModalWindow { private final ActionListBox menu; - private final AppState state; + + private final BaseModalWindow createCollectionMenu; public MainMenu(WindowBasedTextGUI gui, AppState state) { super("Главное меню", gui); - this.state = state; + this.createCollectionMenu = new CreateCollectionMenu(gui, state); menu = new ActionListBox(); - menu.addItem("Показать текущую коллекцию", this::showCurrentCollection); + menu.addItem("Показать текущую коллекцию", () -> showMainCollection(state)); - menu.addItem("Меню создания новой коллекции", this::createCollection); + menu.addItem("Меню создания новой коллекции", this::showCreateCollectionMenu); menu.addItem("Меню сохранения текущей коллекции в файл", this::saveCollection); @@ -38,38 +39,36 @@ public class MainMenu extends BaseWindow { setComponent(menu); } - private void showCurrentCollection() { - MyList currentCollection = state.getCurrentCollection(); + private void showMainCollection(AppState state) { + MyList mainCollection = state.getMainCollection(); - BaseWindow window = new CollectionDisplayWindow(gui, currentCollection); + BaseModalWindow window = new CollectionDisplayWindow(gui, mainCollection); - window.show(); + window.showModal(); } - private void createCollection() { - BaseWindow window = new CreateCollectionMenu(gui, state); - - window.show(); + private void showCreateCollectionMenu() { + createCollectionMenu.showModal(); } private void saveCollection() { // TODO - MessageWindow.show(gui, "Не реализовано."); + MessageWindow.showModal(gui, "Не реализовано."); } private void sortCollection() { // TODO - MessageWindow.show(gui, "Не реализовано."); + MessageWindow.showModal(gui, "Не реализовано."); } private void searchCollection() { // TODO - MessageWindow.show(gui, "Не реализовано."); + MessageWindow.showModal(gui, "Не реализовано."); } private void countOccurrences() { // TODO - MessageWindow.show(gui, "Не реализовано."); + MessageWindow.showModal(gui, "Не реализовано."); } private void exit() { diff --git a/app/src/main/java/ru/project/ui/window/CollectionDisplayWindow.java b/app/src/main/java/ru/project/ui/window/CollectionDisplayWindow.java index 4ca3c5e..f5da901 100644 --- a/app/src/main/java/ru/project/ui/window/CollectionDisplayWindow.java +++ b/app/src/main/java/ru/project/ui/window/CollectionDisplayWindow.java @@ -12,9 +12,9 @@ import com.googlecode.lanterna.input.KeyStroke; import com.googlecode.lanterna.input.KeyType; import ru.project.collection.MyList; import ru.project.student.Student; -import ru.project.ui.base.BaseWindow; +import ru.project.ui.base.BaseModalWindow; -public class CollectionDisplayWindow extends BaseWindow { +public class CollectionDisplayWindow extends BaseModalWindow { private final MyList collection; diff --git a/app/src/main/java/ru/project/ui/window/CustomDataSettingsWindow.java b/app/src/main/java/ru/project/ui/window/CustomDataSettingsWindow.java index b59f805..377b417 100644 --- a/app/src/main/java/ru/project/ui/window/CustomDataSettingsWindow.java +++ b/app/src/main/java/ru/project/ui/window/CustomDataSettingsWindow.java @@ -5,17 +5,18 @@ import com.googlecode.lanterna.gui2.Label; import com.googlecode.lanterna.gui2.LinearLayout; import com.googlecode.lanterna.gui2.Panel; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.Supplier; import ru.project.collection.MyLinkedList; import ru.project.collection.MyList; import ru.project.model.AppState; import ru.project.student.Student; import ru.project.student.StudentBuilder; -import ru.project.ui.base.BaseWindow; +import ru.project.ui.base.BaseModalWindow; -public class CustomDataSettingsWindow extends BaseWindow { +public class CustomDataSettingsWindow extends BaseModalWindow { private final AppState state; - private final int collectionSize; + private final Supplier requestedCollectionSize; private final StudentBuilder studentBuilder; private String groupNumber; @@ -28,28 +29,17 @@ public class CustomDataSettingsWindow extends BaseWindow { private final Label recordBookNumberLabel; private final Label indexLabel; - private final MyList preparedCustomDataCollection; + private MyList preparedCustomDataCollection; - public CustomDataSettingsWindow(WindowBasedTextGUI gui, AppState state, int collectionSize) { + public CustomDataSettingsWindow( + WindowBasedTextGUI gui, AppState state, Supplier collectionSize) { super("Настройка пользовательских данных", gui); this.state = state; - this.collectionSize = collectionSize; + this.requestedCollectionSize = collectionSize; + this.studentBuilder = new StudentBuilder(); - this.preparedCustomDataCollection = new MyLinkedList<>(); - - MyList customDataCollection = state.getCustomDataCollection(); - - int elementsToCopy = Math.min(customDataCollection.size(), collectionSize); - - for (int i = 0; i < elementsToCopy; i++) { - preparedCustomDataCollection.add(customDataCollection.get(i)); - } - - for (int i = elementsToCopy; i < collectionSize; i++) { - preparedCustomDataCollection.add(null); - } Panel panel = new Panel(); panel.setLayoutManager(new LinearLayout()); @@ -86,6 +76,29 @@ public class CustomDataSettingsWindow extends BaseWindow { setComponent(panel); } + @Override + public void showModal() { + prepareCustomDataCollection(); + super.showModal(); + } + + private void prepareCustomDataCollection() { + preparedCustomDataCollection = new MyLinkedList<>(); + + int targetCollectionSize = requestedCollectionSize.get(); + int currentCustomDataCollectionSize = state.getCustomDataCollection().size(); + + int elementsToCopy = Math.min(currentCustomDataCollectionSize, targetCollectionSize); + + for (int i = 0; i < elementsToCopy; i++) { + preparedCustomDataCollection.add(state.getCustomDataCollection().get(i)); + } + + for (int i = elementsToCopy; i < targetCollectionSize; i++) { + preparedCustomDataCollection.add(null); + } + } + private void setGroupNumber() { InputValueWindow window = new InputValueWindow( @@ -95,7 +108,7 @@ public class CustomDataSettingsWindow extends BaseWindow { groupNumber, value -> { if (!value.matches(Student.getGroupNumberPattern())) { - MessageWindow.show(gui, "Номер группы должен соответствовать формату A12."); + MessageWindow.showModal(gui, "Номер группы должен соответствовать формату A12."); return false; } @@ -107,7 +120,7 @@ public class CustomDataSettingsWindow extends BaseWindow { return true; }); - window.show(); + window.showModal(); } private void setAverageGrade() { @@ -124,24 +137,27 @@ public class CustomDataSettingsWindow extends BaseWindow { if (parsedValue < Student.getMinAverageGrade() || parsedValue > Student.getMaxAverageGrade()) { - MessageWindow.show(gui, "Средний балл должен быть от 0.0 до 5.0."); + MessageWindow.showModal(gui, "Средний балл должен быть от 0.0 до 5.0."); + return false; } averageGrade = parsedValue; + studentBuilder.setAverageGrade(parsedValue); averageGradeLabel.setText("Средний балл: " + averageGrade); + return true; + } catch (NumberFormatException e) { - MessageWindow.show(gui, "Введите корректное число."); + MessageWindow.showModal(gui, "Введите корректное число."); + return false; } - - return true; }); - window.show(); + window.showModal(); } private void setRecordBookNumber() { @@ -156,24 +172,28 @@ public class CustomDataSettingsWindow extends BaseWindow { int parsedValue = Integer.parseInt(value); if (parsedValue < Student.getMinRecordBookNumber()) { - MessageWindow.show(gui, "Номер зачётной книжки должен быть положительным."); + + MessageWindow.showModal(gui, "Номер зачётной книжки должен быть положительным."); + return false; } recordBookNumber = parsedValue; + studentBuilder.setRecordBookNumber(parsedValue); recordBookNumberLabel.setText("Номер зачётной книжки: " + recordBookNumber); + return true; + } catch (NumberFormatException e) { - MessageWindow.show(gui, "Введите целое число."); + MessageWindow.showModal(gui, "Введите целое число."); + return false; } - - return true; }); - window.show(); + window.showModal(); } private void setObjectIndex() { @@ -188,12 +208,16 @@ public class CustomDataSettingsWindow extends BaseWindow { int index = Integer.parseInt(value); if (index < 0) { - MessageWindow.show(gui, "Индекс объекта не может быть отрицательным."); + MessageWindow.showModal(gui, "Индекс объекта не может быть отрицательным."); + return false; } - if (index >= collectionSize) { - MessageWindow.show(gui, "Индекс должен быть меньше размера коллекции."); + int size = requestedCollectionSize.get(); + + if (index >= size) { + MessageWindow.showModal(gui, "Индекс должен быть меньше размера коллекции."); + return false; } @@ -204,27 +228,28 @@ public class CustomDataSettingsWindow extends BaseWindow { return true; } catch (NumberFormatException e) { - MessageWindow.show(gui, "Введите целое число."); + MessageWindow.showModal(gui, "Введите целое число."); + return false; } }); - window.show(); + window.showModal(); } private void editObject() { if (!hasStudentData()) { - MessageWindow.show(gui, "Сначала задайте все поля студента."); + MessageWindow.showModal(gui, "Сначала задайте все поля студента."); return; } if (selectedObjectIndex == null) { - MessageWindow.show(gui, "Сначала задайте индекс объекта."); + MessageWindow.showModal(gui, "Сначала задайте индекс объекта."); return; } if (selectedObjectIndex >= preparedCustomDataCollection.size()) { - MessageWindow.show(gui, "Индекс объекта выходит за границы подготовленной коллекции."); + MessageWindow.showModal(gui, "Индекс объекта выходит за границы подготовленной коллекции."); return; } @@ -232,31 +257,27 @@ public class CustomDataSettingsWindow extends BaseWindow { preparedCustomDataCollection.set(selectedObjectIndex, student); - MessageWindow.show(gui, "Данные объекта успешно заданы."); + MessageWindow.showModal(gui, "Данные объекта успешно заданы."); } private void apply() { - MyList currentCollection = new MyLinkedList<>(); - - for (int i = 0; i < preparedCustomDataCollection.size(); i++) { - currentCollection.add(preparedCustomDataCollection.get(i)); - } - state.setCustomDataCollection(preparedCustomDataCollection); - state.setCurrentCollection(currentCollection); + prepareCustomDataCollection(); - close(); + MessageWindow.showModal(gui, "Пользовательские данные успешно применены."); } private void preview() { - if (!hasStudentData()) { - MessageWindow.show(gui, "Для предпросмотра сначала задайте все поля студента."); + if (preparedCustomDataCollection == null || preparedCustomDataCollection.isEmpty()) { + + MessageWindow.showModal(gui, "Подготовленная коллекция пуста."); + return; } - Student student = studentBuilder.build(); + CollectionDisplayWindow window = new CollectionDisplayWindow(gui, preparedCustomDataCollection); - MessageWindow.show(gui, student.toString()); + window.showModal(); } private boolean hasStudentData() { diff --git a/app/src/main/java/ru/project/ui/window/InputValueWindow.java b/app/src/main/java/ru/project/ui/window/InputValueWindow.java index d21a79b..dc16dbb 100644 --- a/app/src/main/java/ru/project/ui/window/InputValueWindow.java +++ b/app/src/main/java/ru/project/ui/window/InputValueWindow.java @@ -7,9 +7,9 @@ import com.googlecode.lanterna.gui2.Panel; import com.googlecode.lanterna.gui2.TextBox; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import java.util.function.Predicate; -import ru.project.ui.base.BaseWindow; +import ru.project.ui.base.BaseModalWindow; -public class InputValueWindow extends BaseWindow { +public class InputValueWindow extends BaseModalWindow { private final TextBox textBox; private final Predicate onSave; @@ -45,7 +45,7 @@ public class InputValueWindow extends BaseWindow { String value = textBox.getText().trim(); if (value.isEmpty()) { - MessageWindow.show(gui, "Значение не может быть пустым."); + MessageWindow.showModal(gui, "Значение не может быть пустым."); return; } From e02e519b2c630d1cd40e6397d1030774108c71aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Sat, 29 Aug 2026 12:47:02 +0300 Subject: [PATCH 69/77] feat(sorting): add even-only sort strategy --- .../project/sorting/EvenOnlySortStrategy.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 app/src/main/java/ru/project/sorting/EvenOnlySortStrategy.java diff --git a/app/src/main/java/ru/project/sorting/EvenOnlySortStrategy.java b/app/src/main/java/ru/project/sorting/EvenOnlySortStrategy.java new file mode 100644 index 0000000..32349c1 --- /dev/null +++ b/app/src/main/java/ru/project/sorting/EvenOnlySortStrategy.java @@ -0,0 +1,94 @@ +package ru.project.sorting; + +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; + +import java.util.Comparator; +import java.util.function.ToIntFunction; + +public class EvenOnlySortStrategy implements SortStrategy { + + private SortStrategy sortStrategy; + private ToIntFunction valueExtractor; + + public EvenOnlySortStrategy() { + } + + public EvenOnlySortStrategy(SortStrategy sortStrategy) { + setSortStrategy(sortStrategy); + } + + public EvenOnlySortStrategy( + SortStrategy sortStrategy, + ToIntFunction valueExtractor + ) { + setSortStrategy(sortStrategy); + setValueExtractor(valueExtractor); + } + + public void setSortStrategy(SortStrategy sortStrategy) { + if (sortStrategy == null) { + throw new IllegalArgumentException("Sort strategy must not be null"); + } + + this.sortStrategy = sortStrategy; + } + + public void setValueExtractor(ToIntFunction valueExtractor) { + if (valueExtractor == null) { + throw new IllegalArgumentException("Value extractor must not be null"); + } + + this.valueExtractor = valueExtractor; + } + + @Override + public void sort(MyList list, Comparator comparator) { + if (sortStrategy == null) { + throw new IllegalStateException("Sort strategy is not set"); + } + + if (valueExtractor == null) { + throw new IllegalStateException("Value extractor is not set"); + } + + if (list == null || list.size() <= 1) { + return; + } + + int evenCount = 0; + + for (int i = 0; i < list.size(); i++) { + T element = list.get(i); + + if (valueExtractor.applyAsInt(element) % 2 == 0) { + evenCount++; + } + } + + if (evenCount <= 1) { + return; + } + + MyList evenElements = new MyLinkedList<>(); + int[] evenIndexes = new int[evenCount]; + + int evenIndex = 0; + + for (int i = 0; i < list.size(); i++) { + T element = list.get(i); + + if (valueExtractor.applyAsInt(element) % 2 == 0) { + evenElements.add(element); + evenIndexes[evenIndex] = i; + evenIndex++; + } + } + + sortStrategy.sort(evenElements, comparator); + + for (int i = 0; i < evenIndexes.length; i++) { + list.set(evenIndexes[i], evenElements.get(i)); + } + } +} \ No newline at end of file From 715b411eeadc7009e46ffbd38f90852f3324d91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=8E=D1=89=D0=B8=D0=BA=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F?= Date: Sat, 29 Aug 2026 12:47:56 +0300 Subject: [PATCH 70/77] test(sorting): add even-only sort strategy tests --- .../sorting/EvenOnlySortStrategyTest.java | 301 ++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 app/src/test/java/ru/project/sorting/EvenOnlySortStrategyTest.java diff --git a/app/src/test/java/ru/project/sorting/EvenOnlySortStrategyTest.java b/app/src/test/java/ru/project/sorting/EvenOnlySortStrategyTest.java new file mode 100644 index 0000000..8d482bd --- /dev/null +++ b/app/src/test/java/ru/project/sorting/EvenOnlySortStrategyTest.java @@ -0,0 +1,301 @@ +package ru.project.sorting; + +import org.junit.jupiter.api.Test; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.student.Student; + +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.util.Comparator; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class EvenOnlySortStrategyTest { + + @Test + void shouldHandleEmptyList() { + MyList list = new MyLinkedList<>(); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>( + new MergeSortStrategy<>(), + value -> value + ); + + strategy.sort(list, Integer::compare); + + assertEquals(0, list.size()); + } + + @Test + void shouldKeepSingleEvenElementUnchanged() { + MyList list = new MyLinkedList<>(); + list.add(4); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>( + new MergeSortStrategy<>(), + value -> value + ); + + strategy.sort(list, Integer::compare); + + assertEquals(4, list.get(0)); + assertEquals(1, list.size()); + } + + @Test + void shouldKeepSingleOddElementUnchanged() { + MyList list = new MyLinkedList<>(); + list.add(7); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>( + new MergeSortStrategy<>(), + value -> value + ); + + strategy.sort(list, Integer::compare); + + assertEquals(7, list.get(0)); + assertEquals(1, list.size()); + } + + @Test + void shouldHandleDuplicateEvenValues() { + MyList list = new MyLinkedList<>(); + + list.add(8); + list.add(4); + list.add(8); + list.add(2); + list.add(4); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>( + new MergeSortStrategy<>(), + value -> value + ); + + strategy.sort(list, Integer::compare); + + assertEquals(2, list.get(0)); + assertEquals(4, list.get(1)); + assertEquals(4, list.get(2)); + assertEquals(8, list.get(3)); + assertEquals(8, list.get(4)); + } + + @Test + void shouldKeepOddStudentObjectsAtSameIndexes() { + Student oddFirst = new Student("A01", 4.0, 7); + Student evenFirst = new Student("A02", 4.0, 8); + Student oddSecond = new Student("A03", 4.0, 3); + Student evenSecond = new Student("A04", 4.0, 2); + Student oddThird = new Student("A05", 4.0, 5); + Student evenThird = new Student("A06", 4.0, 4); + + MyList students = new MyLinkedList<>(); + + students.add(oddFirst); + students.add(evenFirst); + students.add(oddSecond); + students.add(evenSecond); + students.add(oddThird); + students.add(evenThird); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>( + new MergeSortStrategy<>(), + Student::getRecordBookNumber + ); + + strategy.sort(students, new StudentRecordBookComparator()); + + assertSame(oddFirst, students.get(0)); + assertSame(oddSecond, students.get(2)); + assertSame(oddThird, students.get(4)); + + assertEquals(2, students.get(1).getRecordBookNumber()); + assertEquals(4, students.get(3).getRecordBookNumber()); + assertEquals(8, students.get(5).getRecordBookNumber()); + } + + @Test + void shouldSortWhenStrategyPassedThroughConstructor() { + MyList list = new MyLinkedList<>(); + + list.add(8); + list.add(2); + list.add(4); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>( + new MergeSortStrategy<>() + ); + + strategy.setValueExtractor(value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(2, list.get(0)); + assertEquals(4, list.get(1)); + assertEquals(8, list.get(2)); + } + + @Test + void shouldSortOnlyEvenElements() { + MyList students = new MyLinkedList<>(); + + students.add(new Student("A01", 4.0, 7)); + students.add(new Student("A02", 4.0, 8)); + students.add(new Student("A03", 4.0, 3)); + students.add(new Student("A04", 4.0, 2)); + students.add(new Student("A05", 4.0, 5)); + students.add(new Student("A06", 4.0, 4)); + + EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), Student::getRecordBookNumber); + + strategy.sort(students, new StudentRecordBookComparator()); + + assertEquals(7, students.get(0).getRecordBookNumber()); + assertEquals(2, students.get(1).getRecordBookNumber()); + assertEquals(3, students.get(2).getRecordBookNumber()); + assertEquals(4, students.get(3).getRecordBookNumber()); + assertEquals(5, students.get(4).getRecordBookNumber()); + assertEquals(8, students.get(5).getRecordBookNumber()); + + assertEquals(6, students.size()); + } + + @Test + void shouldSortAllEvenElements() { + MyList list = new MyLinkedList<>(); + + list.add(8); + list.add(2); + list.add(6); + list.add(4); + + EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(2, list.get(0)); + assertEquals(4, list.get(1)); + assertEquals(6, list.get(2)); + assertEquals(8, list.get(3)); + } + + @Test + void shouldKeepOddElementsUnchanged() { + MyList list = new MyLinkedList<>(); + + list.add(7); + list.add(3); + list.add(5); + list.add(1); + + EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(7, list.get(0)); + assertEquals(3, list.get(1)); + assertEquals(5, list.get(2)); + assertEquals(1, list.get(3)); + } + + @Test + void shouldThrowWhenSortStrategyIsNotSet() { + EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(); + + strategy.setValueExtractor(value -> value); + + MyList list = new MyLinkedList<>(); + list.add(2); + list.add(4); + + assertThrows(IllegalStateException.class, () -> strategy.sort(list, Integer::compare)); + } + + @Test + void shouldRejectNullSortStrategy() { + EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(); + + assertThrows(IllegalArgumentException.class, () -> strategy.setSortStrategy(null)); + } + + @Test + void shouldDelegateSortingToProvidedStrategy() { + MyList list = new MyLinkedList<>(); + + list.add(7); + list.add(8); + list.add(3); + list.add(2); + + RecordingSortStrategy recordingStrategy = new RecordingSortStrategy(); + + EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(recordingStrategy, value -> value); + + strategy.sort(list, Integer::compare); + + assertTrue(recordingStrategy.wasCalled); + assertEquals(2, recordingStrategy.receivedSize); + assertEquals(8, recordingStrategy.firstReceivedElement); + assertEquals(2, recordingStrategy.secondReceivedElement); + } + + @Test + void shouldUseNewStrategyAfterReplacement() { + RecordingSortStrategy firstStrategy = new RecordingSortStrategy(); + + RecordingSortStrategy secondStrategy = new RecordingSortStrategy(); + + EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(firstStrategy, value -> value); + + MyList firstList = new MyLinkedList<>(); + firstList.add(4); + firstList.add(2); + + strategy.sort(firstList, Integer::compare); + + assertTrue(firstStrategy.wasCalled); + + strategy.setSortStrategy(secondStrategy); + + MyList secondList = new MyLinkedList<>(); + secondList.add(8); + secondList.add(6); + + strategy.sort(secondList, Integer::compare); + + assertTrue(secondStrategy.wasCalled); + } + + private static class RecordingSortStrategy implements SortStrategy { + + private boolean wasCalled; + private int receivedSize; + private Integer firstReceivedElement; + private Integer secondReceivedElement; + + @Override + public void sort(MyList list, Comparator comparator) { + wasCalled = true; + receivedSize = list.size(); + + if (list.size() > 0) { + firstReceivedElement = list.get(0); + } + + if (list.size() > 1) { + secondReceivedElement = list.get(1); + } + } + } +} \ No newline at end of file From 56652fda719eeb95460aa2c60e175f7ebc6311e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Sat, 29 Aug 2026 21:21:53 +0400 Subject: [PATCH 71/77] refactor(sorting): remove redundant even-only merge sort strategy --- .../sorting/EvenOnlyMergeSortStrategy.java | 60 ------ .../EvenOnlyMergeSortStrategyTest.java | 177 ------------------ 2 files changed, 237 deletions(-) delete mode 100644 app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java delete mode 100644 app/src/test/java/ru/project/sorting/EvenOnlyMergeSortStrategyTest.java diff --git a/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java b/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java deleted file mode 100644 index 45f5b78..0000000 --- a/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java +++ /dev/null @@ -1,60 +0,0 @@ -package ru.project.sorting; - -import ru.project.collection.MyLinkedList; -import ru.project.collection.MyList; - -import java.util.Comparator; -import java.util.function.ToIntFunction; - -public class EvenOnlyMergeSortStrategy implements SortStrategy { - private final ToIntFunction valueExtractor; - private final SortStrategy mergeSortStrategy; - - public EvenOnlyMergeSortStrategy(ToIntFunction valueExtractor) { - this.valueExtractor = valueExtractor; - this.mergeSortStrategy = new MergeSortStrategy<>(); - } - - @Override - public void sort(MyList list, Comparator comparator) { - - if (list == null || list.size() <= 1) { - return; - } - - int evenCount = 0; - - for (int i = 0; i < list.size(); i++) { - T element = list.get(i); - - if (valueExtractor.applyAsInt(element) % 2 == 0) { - evenCount++; - } - } - if (evenCount <= 1) { - return; - } - - MyList evenElements = new MyLinkedList<>(); - - int[] evenIndexes = new int[evenCount]; - - int evenIndex = 0; - - for (int i = 0; i < list.size(); i++) { - T element = list.get(i); - - if (valueExtractor.applyAsInt(element) % 2 == 0) { - evenElements.add(element); - evenIndexes[evenIndex] = i; - evenIndex++; - } - } - mergeSortStrategy.sort(evenElements, comparator); - - for (int i = 0; i < evenIndexes.length; i++) { - list.set(evenIndexes[i], evenElements.get(i)); - } - } -} - diff --git a/app/src/test/java/ru/project/sorting/EvenOnlyMergeSortStrategyTest.java b/app/src/test/java/ru/project/sorting/EvenOnlyMergeSortStrategyTest.java deleted file mode 100644 index ffaef36..0000000 --- a/app/src/test/java/ru/project/sorting/EvenOnlyMergeSortStrategyTest.java +++ /dev/null @@ -1,177 +0,0 @@ -package ru.project.sorting; - -import org.junit.jupiter.api.Test; -import ru.project.collection.MyLinkedList; -import ru.project.collection.MyList; -import ru.project.student.Student; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class EvenOnlyMergeSortStrategyTest { - - @Test - void shouldSortOnlyEvenElements() { - MyList list = new MyLinkedList<>(); - - list.add(7); - list.add(8); - list.add(3); - list.add(2); - list.add(5); - list.add(4); - - EvenOnlyMergeSortStrategy strategy = - new EvenOnlyMergeSortStrategy<>(value -> value); - - strategy.sort(list, Integer::compare); - - assertEquals(7, list.get(0)); - assertEquals(2, list.get(1)); - assertEquals(3, list.get(2)); - assertEquals(4, list.get(3)); - assertEquals(5, list.get(4)); - assertEquals(8, list.get(5)); - - assertEquals(6, list.size()); - } - - @Test - void shouldKeepAllOddElementsUnchanged() { - MyList list = new MyLinkedList<>(); - - list.add(7); - list.add(3); - list.add(5); - list.add(1); - - EvenOnlyMergeSortStrategy strategy = - new EvenOnlyMergeSortStrategy<>(value -> value); - - strategy.sort(list, Integer::compare); - - assertEquals(7, list.get(0)); - assertEquals(3, list.get(1)); - assertEquals(5, list.get(2)); - assertEquals(1, list.get(3)); - } - - @Test - void shouldSortAllEvenElements() { - MyList list = new MyLinkedList<>(); - - list.add(8); - list.add(2); - list.add(6); - list.add(4); - - EvenOnlyMergeSortStrategy strategy = - new EvenOnlyMergeSortStrategy<>(value -> value); - - strategy.sort(list, Integer::compare); - - assertEquals(2, list.get(0)); - assertEquals(4, list.get(1)); - assertEquals(6, list.get(2)); - assertEquals(8, list.get(3)); - } - - @Test - void shouldKeepSingleElementUnchanged() { - MyList list = new MyLinkedList<>(); - - list.add(4); - - EvenOnlyMergeSortStrategy strategy = - new EvenOnlyMergeSortStrategy<>(value -> value); - - strategy.sort(list, Integer::compare); - - assertEquals(4, list.get(0)); - assertEquals(1, list.size()); - } - - @Test - void shouldHandleEmptyList() { - MyList list = new MyLinkedList<>(); - - EvenOnlyMergeSortStrategy strategy = - new EvenOnlyMergeSortStrategy<>(value -> value); - - strategy.sort(list, Integer::compare); - - assertEquals(0, list.size()); - } - - @Test - void shouldUseValueExtractorToDetermineEvenElements() { - MyList list = new MyLinkedList<>(); - - list.add(4); - list.add(3); - list.add(2); - list.add(1); - - EvenOnlyMergeSortStrategy strategy = - new EvenOnlyMergeSortStrategy<>(value -> value + 1); - - strategy.sort(list, Integer::compare); - - assertEquals(4, list.get(0)); - assertEquals(1, list.get(1)); - assertEquals(2, list.get(2)); - assertEquals(3, list.get(3)); - } - - @Test - void shouldUseProvidedComparator() { - MyList list = new MyLinkedList<>(); - - list.add(7); - list.add(2); - list.add(3); - list.add(8); - list.add(5); - list.add(4); - - EvenOnlyMergeSortStrategy strategy = - new EvenOnlyMergeSortStrategy<>(value -> value); - - strategy.sort(list, (first, second) -> Integer.compare(second, first)); - - assertEquals(7, list.get(0)); - assertEquals(8, list.get(1)); - assertEquals(3, list.get(2)); - assertEquals(4, list.get(3)); - assertEquals(5, list.get(4)); - assertEquals(2, list.get(5)); - } - - @Test - void shouldUseStudentRecordBookNumberToDetermineEvenElements() { - MyList students = new MyLinkedList<>(); - - students.add(new Student("A01", 4.0, 7)); - students.add(new Student("A02", 4.0, 8)); - students.add(new Student("A03", 4.0, 3)); - students.add(new Student("A04", 4.0, 2)); - students.add(new Student("A05", 4.0, 5)); - students.add(new Student("A06", 4.0, 4)); - - SortStrategy strategy = new EvenOnlyMergeSortStrategy<>( - Student::getRecordBookNumber - ); - - strategy.sort(students, new StudentRecordBookComparator()); - - assertEquals(7, students.get(0).getRecordBookNumber()); - assertEquals(2, students.get(1).getRecordBookNumber()); - assertEquals(3, students.get(2).getRecordBookNumber()); - assertEquals(4, students.get(3).getRecordBookNumber()); - assertEquals(5, students.get(4).getRecordBookNumber()); - assertEquals(8, students.get(5).getRecordBookNumber()); - - assertEquals(6, students.size()); - } -} - - From e7e709a5720042eb887a038e88d5209cb41f4d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D1=8E=D1=80=D1=8F=D0=B3=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB?= Date: Sat, 29 Aug 2026 21:18:14 +0300 Subject: [PATCH 72/77] remove obsolete test file StudentTest1 --- .../java/ru/project/student/StudentTest1.java | 139 ------------------ 1 file changed, 139 deletions(-) delete mode 100644 app/src/test/java/ru/project/student/StudentTest1.java diff --git a/app/src/test/java/ru/project/student/StudentTest1.java b/app/src/test/java/ru/project/student/StudentTest1.java deleted file mode 100644 index cdce6d8..0000000 --- a/app/src/test/java/ru/project/student/StudentTest1.java +++ /dev/null @@ -1,139 +0,0 @@ -package ru.project.student; - -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -class StudentTest { - - @Test - void testValidStudentCreation() { - Student student = new Student("A12", 4.5, 12345); - assertEquals("A12", student.getGroupNumber()); - assertEquals(4.5, student.getAverageGrade()); - assertEquals(12345, student.getRecordBookNumber()); - } - - @Test - void testValidGroupNumbers() { - assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); - assertDoesNotThrow(() -> new Student("B32", 4.5, 1)); - assertDoesNotThrow(() -> new Student("Z00", 4.5, 1)); - } - - @Test - void testInvalidGroupNumbers() { - assertThrows(IllegalArgumentException.class, () -> new Student("a12", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("A1", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("A123", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("AA1", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("а1А", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("аа!!а123", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("23фи!12", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("№№###*()", 4.5, 1)); - } - - @Test - void testValidAverageGrades() { - assertDoesNotThrow(() -> new Student("A12", 0.0, 1)); - assertDoesNotThrow(() -> new Student("A12", 5.0, 1)); - } - - @Test - void testInvalidAverageGrades() { - assertThrows(IllegalArgumentException.class, () -> new Student("A12", -1.0, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("A12", 6.0, 1)); - } - - @Test - void testValidRecordBookNumbers() { - assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); - assertDoesNotThrow(() -> new Student("A12", 4.5, 12345)); - } - - @Test - void testInvalidRecordBookNumbers() { - assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, 0)); - assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, -1)); - } - - @Test - void testWithGroupNumber() { - Student original = new Student("A12", 4.5, 12345); - Student updated = original.withGroupNumber("B32"); - - assertNotSame(original, updated); - assertEquals("B32", updated.getGroupNumber()); - assertEquals(4.5, updated.getAverageGrade()); - assertEquals(12345, updated.getRecordBookNumber()); - - assertEquals("A12", original.getGroupNumber()); - } - - @Test - void testWithAverageGrade() { - Student original = new Student("A12", 4.5, 12345); - Student updated = original.withAverageGrade(4.8); - - assertNotSame(original, updated); - assertEquals(4.8, updated.getAverageGrade()); - assertEquals("A12", updated.getGroupNumber()); - assertEquals(12345, updated.getRecordBookNumber()); - - assertEquals(4.5, original.getAverageGrade()); - } - - @Test - void testWithRecordBookNumber() { - Student original = new Student("A12", 4.5, 12345); - Student updated = original.withRecordBookNumber(99999); - - assertNotSame(original, updated); - assertEquals(99999, updated.getRecordBookNumber()); - assertEquals("A12", updated.getGroupNumber()); - assertEquals(4.5, updated.getAverageGrade()); - - assertEquals(12345, original.getRecordBookNumber()); - } - - @Test - void testWithInvalidGroupNumber() { - Student original = new Student("A12", 4.5, 12345); - assertThrows(IllegalArgumentException.class, - () -> original.withGroupNumber("a12")); - assertEquals("A12", original.getGroupNumber()); // не изменился - } - - @Test - void testWithInvalidAverageGrade() { - Student original = new Student("A12", 4.5, 12345); - assertThrows(IllegalArgumentException.class, - () -> original.withAverageGrade(6.0)); - assertEquals(4.5, original.getAverageGrade()); - } - - @Test - void testWithInvalidRecordBookNumber() { - Student original = new Student("A12", 4.5, 12345); - assertThrows(IllegalArgumentException.class, - () -> original.withRecordBookNumber(0)); - assertEquals(12345, original.getRecordBookNumber()); - } - - @Test - void testEqualsAndHashCode() { - Student s1 = new Student("A12", 4.5, 12345); - Student s2 = new Student("A12", 4.5, 12345); - Student s3 = new Student("B32", 4.5, 12345); - Student s4 = new Student("A12", 4.8, 12345); - Student s5 = new Student("A12", 4.5, 99999); - - assertEquals(s1, s2); - assertEquals(s1.hashCode(), s2.hashCode()); - - assertNotEquals(s1, s3); - assertNotEquals(s1, s4); - assertNotEquals(s1, s5); - assertNotEquals(s1, null); - assertNotEquals(s1, "some string"); - } -} \ No newline at end of file From 1de5a59e93a2b81028f943a29207279ee04c4833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Sat, 29 Aug 2026 22:58:09 +0400 Subject: [PATCH 73/77] feat(ui): add collection sorting menu and sort method selection --- .../java/ru/project/model/SortMethod.java | 6 ++ .../java/ru/project/ui/menu/MainMenu.java | 5 +- .../project/ui/menu/SortCollectionMenu.java | 101 ++++++++++++++++++ .../ui/window/SortMethodSelectionWindow.java | 33 ++++++ 4 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/ru/project/model/SortMethod.java create mode 100644 app/src/main/java/ru/project/ui/menu/SortCollectionMenu.java create mode 100644 app/src/main/java/ru/project/ui/window/SortMethodSelectionWindow.java diff --git a/app/src/main/java/ru/project/model/SortMethod.java b/app/src/main/java/ru/project/model/SortMethod.java new file mode 100644 index 0000000..8cf4c6e --- /dev/null +++ b/app/src/main/java/ru/project/model/SortMethod.java @@ -0,0 +1,6 @@ +package ru.project.model; + +public enum SortMethod { + NORMAL, + PARITY +} diff --git a/app/src/main/java/ru/project/ui/menu/MainMenu.java b/app/src/main/java/ru/project/ui/menu/MainMenu.java index 93cef48..32999d7 100644 --- a/app/src/main/java/ru/project/ui/menu/MainMenu.java +++ b/app/src/main/java/ru/project/ui/menu/MainMenu.java @@ -14,11 +14,13 @@ public class MainMenu extends BaseModalWindow { private final ActionListBox menu; private final BaseModalWindow createCollectionMenu; + private final BaseModalWindow sortCollectionMenu; public MainMenu(WindowBasedTextGUI gui, AppState state) { super("Главное меню", gui); this.createCollectionMenu = new CreateCollectionMenu(gui, state); + this.sortCollectionMenu = new SortCollectionMenu(gui, state); menu = new ActionListBox(); @@ -57,8 +59,7 @@ public class MainMenu extends BaseModalWindow { } private void sortCollection() { - // TODO - MessageWindow.showModal(gui, "Не реализовано."); + sortCollectionMenu.showModal(); } private void searchCollection() { diff --git a/app/src/main/java/ru/project/ui/menu/SortCollectionMenu.java b/app/src/main/java/ru/project/ui/menu/SortCollectionMenu.java new file mode 100644 index 0000000..5c9ea61 --- /dev/null +++ b/app/src/main/java/ru/project/ui/menu/SortCollectionMenu.java @@ -0,0 +1,101 @@ +package ru.project.ui.menu; + +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.model.AppState; +import ru.project.model.SortMethod; +import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.window.MessageWindow; +import ru.project.ui.window.SortMethodSelectionWindow; + +public class SortCollectionMenu extends BaseModalWindow { + + private final AppState state; + + private final Label sortMethodLabel; + private final SortMethodSelectionWindow sortMethodSelectionWindow; + + private SortMethod selectedSortMethod; + + public SortCollectionMenu(WindowBasedTextGUI gui, AppState state) { + + super("Меню сортировки текущей коллекции", gui); + + this.state = state; + this.selectedSortMethod = SortMethod.NORMAL; + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + this.sortMethodLabel = new Label("Способ сортировки: " + getSortMethodName(selectedSortMethod)); + + this.sortMethodSelectionWindow = + new SortMethodSelectionWindow( + gui, + sortMethod -> { + selectedSortMethod = sortMethod; + + sortMethodLabel.setText( + "Способ сортировки: " + getSortMethodName(selectedSortMethod)); + }); + + panel.addComponent(sortMethodLabel); + + panel.addComponent(new Button("Выбрать способ сортировки", this::selectSortMethod)); + + panel.addComponent( + new Button("Настроить текущий способ сортировки", this::setupCurrentSortMethod)); + + panel.addComponent(new Button("Сортировать текущую коллекцию", this::sortCurrentCollection)); + + panel.addComponent(new Button("Выйти", this::close)); + + setComponent(panel); + } + + private void setupCurrentSortMethod() { + MessageWindow.showModal(gui, "Настройка способа сортировки пока не реализована."); + } + + private void selectSortMethod() { + sortMethodSelectionWindow.showModal(); + } + + private void sortCurrentCollection() { + if (state.getMainCollection() == null || state.getMainCollection().isEmpty()) { + + MessageWindow.showModal(gui, "Текущая коллекция пуста."); + return; + } + + switch (selectedSortMethod) { + case NORMAL: + sortNormally(); + break; + + case PARITY: + sortByParity(); + break; + } + } + + private void sortNormally() { + // TODO + MessageWindow.showModal(gui, "Обычная сортировка не реализована."); + } + + private void sortByParity() { + // TODO + MessageWindow.showModal(gui, "Сортировка по чётности не реализована."); + } + + private String getSortMethodName(SortMethod sortMethod) { + return switch (sortMethod) { + case NORMAL -> "Обычная сортировка"; + case PARITY -> "Сортировка по чётности"; + }; + } +} diff --git a/app/src/main/java/ru/project/ui/window/SortMethodSelectionWindow.java b/app/src/main/java/ru/project/ui/window/SortMethodSelectionWindow.java new file mode 100644 index 0000000..ea234ef --- /dev/null +++ b/app/src/main/java/ru/project/ui/window/SortMethodSelectionWindow.java @@ -0,0 +1,33 @@ +package ru.project.ui.window; + +import com.googlecode.lanterna.gui2.ActionListBox; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.Consumer; +import ru.project.model.SortMethod; +import ru.project.ui.base.BaseModalWindow; + +public class SortMethodSelectionWindow extends BaseModalWindow { + + private final Consumer onSortMethodSelected; + + public SortMethodSelectionWindow( + WindowBasedTextGUI gui, Consumer onSortMethodSelected) { + + super("Выбрать способ сортировки", gui); + + this.onSortMethodSelected = onSortMethodSelected; + + ActionListBox menu = new ActionListBox(); + + menu.addItem("Обычная сортировка", () -> selectSortMethod(SortMethod.NORMAL)); + + menu.addItem("Сортировка по чётности", () -> selectSortMethod(SortMethod.PARITY)); + + setComponent(menu); + } + + private void selectSortMethod(SortMethod sortMethod) { + onSortMethodSelected.accept(sortMethod); + close(); + } +} From f655be382090773ad87a74a99685f21b1135a876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Sat, 29 Aug 2026 23:24:22 +0400 Subject: [PATCH 74/77] feat(ui): add exit action to sort method selection --- .../java/ru/project/ui/window/SortMethodSelectionWindow.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/ru/project/ui/window/SortMethodSelectionWindow.java b/app/src/main/java/ru/project/ui/window/SortMethodSelectionWindow.java index ea234ef..331f430 100644 --- a/app/src/main/java/ru/project/ui/window/SortMethodSelectionWindow.java +++ b/app/src/main/java/ru/project/ui/window/SortMethodSelectionWindow.java @@ -23,6 +23,8 @@ public class SortMethodSelectionWindow extends BaseModalWindow { menu.addItem("Сортировка по чётности", () -> selectSortMethod(SortMethod.PARITY)); + menu.addItem("Выйти", this::close); + setComponent(menu); } From 0f4df4b81157f3b098e1a2f2be8340426b19a5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Sat, 29 Aug 2026 23:25:37 +0400 Subject: [PATCH 75/77] feat(ui): add sort field configuration and parity sort settings --- .../main/java/ru/project/model/SortField.java | 7 +++ .../project/ui/menu/SortCollectionMenu.java | 44 +++++++++++++++--- .../ui/window/NormalSortSettingsWindow.java | 46 +++++++++++++++++++ .../ui/window/ParitySortSettingsWindow.java | 24 ++++++++++ 4 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/ru/project/model/SortField.java create mode 100644 app/src/main/java/ru/project/ui/window/NormalSortSettingsWindow.java create mode 100644 app/src/main/java/ru/project/ui/window/ParitySortSettingsWindow.java diff --git a/app/src/main/java/ru/project/model/SortField.java b/app/src/main/java/ru/project/model/SortField.java new file mode 100644 index 0000000..967a3ac --- /dev/null +++ b/app/src/main/java/ru/project/model/SortField.java @@ -0,0 +1,7 @@ +package ru.project.model; + +public enum SortField { + GROUP_NUMBER, + AVERAGE_GRADE, + RECORD_BOOK_NUMBER +} diff --git a/app/src/main/java/ru/project/ui/menu/SortCollectionMenu.java b/app/src/main/java/ru/project/ui/menu/SortCollectionMenu.java index 5c9ea61..577655a 100644 --- a/app/src/main/java/ru/project/ui/menu/SortCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/menu/SortCollectionMenu.java @@ -6,9 +6,12 @@ import com.googlecode.lanterna.gui2.LinearLayout; import com.googlecode.lanterna.gui2.Panel; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import ru.project.model.AppState; +import ru.project.model.SortField; import ru.project.model.SortMethod; import ru.project.ui.base.BaseModalWindow; import ru.project.ui.window.MessageWindow; +import ru.project.ui.window.NormalSortSettingsWindow; +import ru.project.ui.window.ParitySortSettingsWindow; import ru.project.ui.window.SortMethodSelectionWindow; public class SortCollectionMenu extends BaseModalWindow { @@ -16,9 +19,14 @@ public class SortCollectionMenu extends BaseModalWindow { private final AppState state; private final Label sortMethodLabel; + private final Label sortFieldLabel; + private final SortMethodSelectionWindow sortMethodSelectionWindow; + private final NormalSortSettingsWindow normalSortSettingsWindow; + private final ParitySortSettingsWindow paritySortSettingsWindow; private SortMethod selectedSortMethod; + private SortField selectedSortField; public SortCollectionMenu(WindowBasedTextGUI gui, AppState state) { @@ -26,12 +34,15 @@ public class SortCollectionMenu extends BaseModalWindow { this.state = state; this.selectedSortMethod = SortMethod.NORMAL; + this.selectedSortField = SortField.GROUP_NUMBER; Panel panel = new Panel(); panel.setLayoutManager(new LinearLayout()); this.sortMethodLabel = new Label("Способ сортировки: " + getSortMethodName(selectedSortMethod)); + this.sortFieldLabel = new Label("Сортировать по: " + getSortFieldName(selectedSortField)); + this.sortMethodSelectionWindow = new SortMethodSelectionWindow( gui, @@ -42,7 +53,17 @@ public class SortCollectionMenu extends BaseModalWindow { "Способ сортировки: " + getSortMethodName(selectedSortMethod)); }); + this.normalSortSettingsWindow = + new NormalSortSettingsWindow( + gui, + sortField -> { + selectedSortField = sortField; + }); + + this.paritySortSettingsWindow = new ParitySortSettingsWindow(gui); + panel.addComponent(sortMethodLabel); + panel.addComponent(sortFieldLabel); panel.addComponent(new Button("Выбрать способ сортировки", this::selectSortMethod)); @@ -57,7 +78,11 @@ public class SortCollectionMenu extends BaseModalWindow { } private void setupCurrentSortMethod() { - MessageWindow.showModal(gui, "Настройка способа сортировки пока не реализована."); + switch (selectedSortMethod) { + case NORMAL -> normalSortSettingsWindow.showModal(); + + case PARITY -> paritySortSettingsWindow.showModal(); + } } private void selectSortMethod() { @@ -68,17 +93,14 @@ public class SortCollectionMenu extends BaseModalWindow { if (state.getMainCollection() == null || state.getMainCollection().isEmpty()) { MessageWindow.showModal(gui, "Текущая коллекция пуста."); + return; } switch (selectedSortMethod) { - case NORMAL: - sortNormally(); - break; + case NORMAL -> sortNormally(); - case PARITY: - sortByParity(); - break; + case PARITY -> sortByParity(); } } @@ -98,4 +120,12 @@ public class SortCollectionMenu extends BaseModalWindow { case PARITY -> "Сортировка по чётности"; }; } + + private String getSortFieldName(SortField sortField) { + return switch (sortField) { + case GROUP_NUMBER -> "номеру группы"; + case AVERAGE_GRADE -> "среднему баллу"; + case RECORD_BOOK_NUMBER -> "номеру зачётной книжки"; + }; + } } diff --git a/app/src/main/java/ru/project/ui/window/NormalSortSettingsWindow.java b/app/src/main/java/ru/project/ui/window/NormalSortSettingsWindow.java new file mode 100644 index 0000000..5b6221b --- /dev/null +++ b/app/src/main/java/ru/project/ui/window/NormalSortSettingsWindow.java @@ -0,0 +1,46 @@ +package ru.project.ui.window; + +import com.googlecode.lanterna.gui2.ActionListBox; +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.Consumer; +import ru.project.model.SortField; +import ru.project.ui.base.BaseModalWindow; + +public class NormalSortSettingsWindow extends BaseModalWindow { + + private final Consumer onSortFieldSelected; + + public NormalSortSettingsWindow(WindowBasedTextGUI gui, Consumer onSortFieldSelected) { + + super("Обычная сортировка", gui); + + this.onSortFieldSelected = onSortFieldSelected; + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + ActionListBox menu = new ActionListBox(); + + menu.addItem("Сортировать по номеру группы", () -> selectSortField(SortField.GROUP_NUMBER)); + + menu.addItem("Сортировать по среднему баллу", () -> selectSortField(SortField.AVERAGE_GRADE)); + + menu.addItem( + "Сортировать по номеру зачётной книжки", + () -> selectSortField(SortField.RECORD_BOOK_NUMBER)); + + panel.addComponent(menu); + + panel.addComponent(new Button("Выйти", this::close)); + + setComponent(panel); + } + + private void selectSortField(SortField sortField) { + onSortFieldSelected.accept(sortField); + close(); + } +} diff --git a/app/src/main/java/ru/project/ui/window/ParitySortSettingsWindow.java b/app/src/main/java/ru/project/ui/window/ParitySortSettingsWindow.java new file mode 100644 index 0000000..1f8db08 --- /dev/null +++ b/app/src/main/java/ru/project/ui/window/ParitySortSettingsWindow.java @@ -0,0 +1,24 @@ +package ru.project.ui.window; + +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.ui.base.BaseModalWindow; + +public class ParitySortSettingsWindow extends BaseModalWindow { + + public ParitySortSettingsWindow(WindowBasedTextGUI gui) { + super("Настройка сортировки по чётности", gui); + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + panel.addComponent(new Label("Сортировка выполняется по номеру зачётной книжки.")); + + panel.addComponent(new Button("Назад", this::close)); + + setComponent(panel); + } +} From 545fae857a3ba4c80b6342e7d019a63465c61b4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Sat, 29 Aug 2026 23:37:58 +0400 Subject: [PATCH 76/77] refactor(ui): reorganize classes into logical feature packages --- app/src/main/java/ru/project/App.java | 2 +- .../ui/{window => common}/CollectionDisplayWindow.java | 2 +- .../project/ui/{window => common}/InputValueWindow.java | 2 +- .../ru/project/ui/{window => common}/MessageWindow.java | 2 +- .../ui/{menu => creation}/CreateCollectionMenu.java | 8 +++----- .../{window => creation}/CustomDataSettingsWindow.java | 5 ++++- .../ru/project/{model => ui/creation}/FillMethod.java | 2 +- .../ui/{menu => creation}/FillMethodSelectionWindow.java | 3 +-- .../main/java/ru/project/ui/{menu => main}/MainMenu.java | 8 +++++--- .../ui/{window => sorting}/NormalSortSettingsWindow.java | 3 +-- .../ui/{window => sorting}/ParitySortSettingsWindow.java | 2 +- .../project/ui/{menu => sorting}/SortCollectionMenu.java | 9 ++------- .../java/ru/project/{model => ui/sorting}/SortField.java | 2 +- .../ru/project/{model => ui/sorting}/SortMethod.java | 2 +- .../{window => sorting}/SortMethodSelectionWindow.java | 3 +-- 15 files changed, 25 insertions(+), 30 deletions(-) rename app/src/main/java/ru/project/ui/{window => common}/CollectionDisplayWindow.java (98%) rename app/src/main/java/ru/project/ui/{window => common}/InputValueWindow.java (97%) rename app/src/main/java/ru/project/ui/{window => common}/MessageWindow.java (96%) rename app/src/main/java/ru/project/ui/{menu => creation}/CreateCollectionMenu.java (96%) rename app/src/main/java/ru/project/ui/{window => creation}/CustomDataSettingsWindow.java (98%) rename app/src/main/java/ru/project/{model => ui/creation}/FillMethod.java (63%) rename app/src/main/java/ru/project/ui/{menu => creation}/FillMethodSelectionWindow.java (94%) rename app/src/main/java/ru/project/ui/{menu => main}/MainMenu.java (91%) rename app/src/main/java/ru/project/ui/{window => sorting}/NormalSortSettingsWindow.java (95%) rename app/src/main/java/ru/project/ui/{window => sorting}/ParitySortSettingsWindow.java (96%) rename app/src/main/java/ru/project/ui/{menu => sorting}/SortCollectionMenu.java (92%) rename app/src/main/java/ru/project/{model => ui/sorting}/SortField.java (72%) rename app/src/main/java/ru/project/{model => ui/sorting}/SortMethod.java (60%) rename app/src/main/java/ru/project/ui/{window => sorting}/SortMethodSelectionWindow.java (93%) diff --git a/app/src/main/java/ru/project/App.java b/app/src/main/java/ru/project/App.java index fa4ee64..73f5efa 100644 --- a/app/src/main/java/ru/project/App.java +++ b/app/src/main/java/ru/project/App.java @@ -4,7 +4,7 @@ import com.googlecode.lanterna.gui2.MultiWindowTextGUI; import com.googlecode.lanterna.screen.Screen; import com.googlecode.lanterna.terminal.DefaultTerminalFactory; import ru.project.model.AppState; -import ru.project.ui.menu.MainMenu; +import ru.project.ui.main.MainMenu; public final class App { diff --git a/app/src/main/java/ru/project/ui/window/CollectionDisplayWindow.java b/app/src/main/java/ru/project/ui/common/CollectionDisplayWindow.java similarity index 98% rename from app/src/main/java/ru/project/ui/window/CollectionDisplayWindow.java rename to app/src/main/java/ru/project/ui/common/CollectionDisplayWindow.java index f5da901..1d71808 100644 --- a/app/src/main/java/ru/project/ui/window/CollectionDisplayWindow.java +++ b/app/src/main/java/ru/project/ui/common/CollectionDisplayWindow.java @@ -1,4 +1,4 @@ -package ru.project.ui.window; +package ru.project.ui.common; import com.googlecode.lanterna.TerminalSize; import com.googlecode.lanterna.gui2.Button; diff --git a/app/src/main/java/ru/project/ui/window/InputValueWindow.java b/app/src/main/java/ru/project/ui/common/InputValueWindow.java similarity index 97% rename from app/src/main/java/ru/project/ui/window/InputValueWindow.java rename to app/src/main/java/ru/project/ui/common/InputValueWindow.java index dc16dbb..f3e3200 100644 --- a/app/src/main/java/ru/project/ui/window/InputValueWindow.java +++ b/app/src/main/java/ru/project/ui/common/InputValueWindow.java @@ -1,4 +1,4 @@ -package ru.project.ui.window; +package ru.project.ui.common; import com.googlecode.lanterna.gui2.Button; import com.googlecode.lanterna.gui2.Label; diff --git a/app/src/main/java/ru/project/ui/window/MessageWindow.java b/app/src/main/java/ru/project/ui/common/MessageWindow.java similarity index 96% rename from app/src/main/java/ru/project/ui/window/MessageWindow.java rename to app/src/main/java/ru/project/ui/common/MessageWindow.java index 76ad9a1..3f1905f 100644 --- a/app/src/main/java/ru/project/ui/window/MessageWindow.java +++ b/app/src/main/java/ru/project/ui/common/MessageWindow.java @@ -1,4 +1,4 @@ -package ru.project.ui.window; +package ru.project.ui.common; import com.googlecode.lanterna.gui2.Button; import com.googlecode.lanterna.gui2.Label; diff --git a/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java b/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java similarity index 96% rename from app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java rename to app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java index dd9785a..72820be 100644 --- a/app/src/main/java/ru/project/ui/menu/CreateCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java @@ -1,4 +1,4 @@ -package ru.project.ui.menu; +package ru.project.ui.creation; import com.googlecode.lanterna.gui2.Button; import com.googlecode.lanterna.gui2.Label; @@ -8,12 +8,10 @@ import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import ru.project.collection.MyLinkedList; import ru.project.collection.MyList; import ru.project.model.AppState; -import ru.project.model.FillMethod; import ru.project.student.Student; import ru.project.ui.base.BaseModalWindow; -import ru.project.ui.window.CustomDataSettingsWindow; -import ru.project.ui.window.InputValueWindow; -import ru.project.ui.window.MessageWindow; +import ru.project.ui.common.InputValueWindow; +import ru.project.ui.common.MessageWindow; public class CreateCollectionMenu extends BaseModalWindow { diff --git a/app/src/main/java/ru/project/ui/window/CustomDataSettingsWindow.java b/app/src/main/java/ru/project/ui/creation/CustomDataSettingsWindow.java similarity index 98% rename from app/src/main/java/ru/project/ui/window/CustomDataSettingsWindow.java rename to app/src/main/java/ru/project/ui/creation/CustomDataSettingsWindow.java index 377b417..5f0aa10 100644 --- a/app/src/main/java/ru/project/ui/window/CustomDataSettingsWindow.java +++ b/app/src/main/java/ru/project/ui/creation/CustomDataSettingsWindow.java @@ -1,4 +1,4 @@ -package ru.project.ui.window; +package ru.project.ui.creation; import com.googlecode.lanterna.gui2.Button; import com.googlecode.lanterna.gui2.Label; @@ -12,6 +12,9 @@ import ru.project.model.AppState; import ru.project.student.Student; import ru.project.student.StudentBuilder; import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.common.CollectionDisplayWindow; +import ru.project.ui.common.InputValueWindow; +import ru.project.ui.common.MessageWindow; public class CustomDataSettingsWindow extends BaseModalWindow { diff --git a/app/src/main/java/ru/project/model/FillMethod.java b/app/src/main/java/ru/project/ui/creation/FillMethod.java similarity index 63% rename from app/src/main/java/ru/project/model/FillMethod.java rename to app/src/main/java/ru/project/ui/creation/FillMethod.java index df682af..28567fe 100644 --- a/app/src/main/java/ru/project/model/FillMethod.java +++ b/app/src/main/java/ru/project/ui/creation/FillMethod.java @@ -1,4 +1,4 @@ -package ru.project.model; +package ru.project.ui.creation; public enum FillMethod { CUSTOM, diff --git a/app/src/main/java/ru/project/ui/menu/FillMethodSelectionWindow.java b/app/src/main/java/ru/project/ui/creation/FillMethodSelectionWindow.java similarity index 94% rename from app/src/main/java/ru/project/ui/menu/FillMethodSelectionWindow.java rename to app/src/main/java/ru/project/ui/creation/FillMethodSelectionWindow.java index f408210..9cf0fc7 100644 --- a/app/src/main/java/ru/project/ui/menu/FillMethodSelectionWindow.java +++ b/app/src/main/java/ru/project/ui/creation/FillMethodSelectionWindow.java @@ -1,9 +1,8 @@ -package ru.project.ui.menu; +package ru.project.ui.creation; import com.googlecode.lanterna.gui2.ActionListBox; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import java.util.function.Consumer; -import ru.project.model.FillMethod; import ru.project.ui.base.BaseModalWindow; public class FillMethodSelectionWindow extends BaseModalWindow { diff --git a/app/src/main/java/ru/project/ui/menu/MainMenu.java b/app/src/main/java/ru/project/ui/main/MainMenu.java similarity index 91% rename from app/src/main/java/ru/project/ui/menu/MainMenu.java rename to app/src/main/java/ru/project/ui/main/MainMenu.java index 32999d7..ff4899e 100644 --- a/app/src/main/java/ru/project/ui/menu/MainMenu.java +++ b/app/src/main/java/ru/project/ui/main/MainMenu.java @@ -1,4 +1,4 @@ -package ru.project.ui.menu; +package ru.project.ui.main; import com.googlecode.lanterna.gui2.ActionListBox; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; @@ -6,8 +6,10 @@ import ru.project.collection.MyList; import ru.project.model.AppState; import ru.project.student.Student; import ru.project.ui.base.BaseModalWindow; -import ru.project.ui.window.CollectionDisplayWindow; -import ru.project.ui.window.MessageWindow; +import ru.project.ui.common.CollectionDisplayWindow; +import ru.project.ui.common.MessageWindow; +import ru.project.ui.creation.CreateCollectionMenu; +import ru.project.ui.sorting.SortCollectionMenu; public class MainMenu extends BaseModalWindow { diff --git a/app/src/main/java/ru/project/ui/window/NormalSortSettingsWindow.java b/app/src/main/java/ru/project/ui/sorting/NormalSortSettingsWindow.java similarity index 95% rename from app/src/main/java/ru/project/ui/window/NormalSortSettingsWindow.java rename to app/src/main/java/ru/project/ui/sorting/NormalSortSettingsWindow.java index 5b6221b..0f3bbd2 100644 --- a/app/src/main/java/ru/project/ui/window/NormalSortSettingsWindow.java +++ b/app/src/main/java/ru/project/ui/sorting/NormalSortSettingsWindow.java @@ -1,4 +1,4 @@ -package ru.project.ui.window; +package ru.project.ui.sorting; import com.googlecode.lanterna.gui2.ActionListBox; import com.googlecode.lanterna.gui2.Button; @@ -6,7 +6,6 @@ import com.googlecode.lanterna.gui2.LinearLayout; import com.googlecode.lanterna.gui2.Panel; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import java.util.function.Consumer; -import ru.project.model.SortField; import ru.project.ui.base.BaseModalWindow; public class NormalSortSettingsWindow extends BaseModalWindow { diff --git a/app/src/main/java/ru/project/ui/window/ParitySortSettingsWindow.java b/app/src/main/java/ru/project/ui/sorting/ParitySortSettingsWindow.java similarity index 96% rename from app/src/main/java/ru/project/ui/window/ParitySortSettingsWindow.java rename to app/src/main/java/ru/project/ui/sorting/ParitySortSettingsWindow.java index 1f8db08..aa6d090 100644 --- a/app/src/main/java/ru/project/ui/window/ParitySortSettingsWindow.java +++ b/app/src/main/java/ru/project/ui/sorting/ParitySortSettingsWindow.java @@ -1,4 +1,4 @@ -package ru.project.ui.window; +package ru.project.ui.sorting; import com.googlecode.lanterna.gui2.Button; import com.googlecode.lanterna.gui2.Label; diff --git a/app/src/main/java/ru/project/ui/menu/SortCollectionMenu.java b/app/src/main/java/ru/project/ui/sorting/SortCollectionMenu.java similarity index 92% rename from app/src/main/java/ru/project/ui/menu/SortCollectionMenu.java rename to app/src/main/java/ru/project/ui/sorting/SortCollectionMenu.java index 577655a..ae72b4b 100644 --- a/app/src/main/java/ru/project/ui/menu/SortCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/sorting/SortCollectionMenu.java @@ -1,4 +1,4 @@ -package ru.project.ui.menu; +package ru.project.ui.sorting; import com.googlecode.lanterna.gui2.Button; import com.googlecode.lanterna.gui2.Label; @@ -6,13 +6,8 @@ import com.googlecode.lanterna.gui2.LinearLayout; import com.googlecode.lanterna.gui2.Panel; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import ru.project.model.AppState; -import ru.project.model.SortField; -import ru.project.model.SortMethod; import ru.project.ui.base.BaseModalWindow; -import ru.project.ui.window.MessageWindow; -import ru.project.ui.window.NormalSortSettingsWindow; -import ru.project.ui.window.ParitySortSettingsWindow; -import ru.project.ui.window.SortMethodSelectionWindow; +import ru.project.ui.common.MessageWindow; public class SortCollectionMenu extends BaseModalWindow { diff --git a/app/src/main/java/ru/project/model/SortField.java b/app/src/main/java/ru/project/ui/sorting/SortField.java similarity index 72% rename from app/src/main/java/ru/project/model/SortField.java rename to app/src/main/java/ru/project/ui/sorting/SortField.java index 967a3ac..6219f9b 100644 --- a/app/src/main/java/ru/project/model/SortField.java +++ b/app/src/main/java/ru/project/ui/sorting/SortField.java @@ -1,4 +1,4 @@ -package ru.project.model; +package ru.project.ui.sorting; public enum SortField { GROUP_NUMBER, diff --git a/app/src/main/java/ru/project/model/SortMethod.java b/app/src/main/java/ru/project/ui/sorting/SortMethod.java similarity index 60% rename from app/src/main/java/ru/project/model/SortMethod.java rename to app/src/main/java/ru/project/ui/sorting/SortMethod.java index 8cf4c6e..446f27e 100644 --- a/app/src/main/java/ru/project/model/SortMethod.java +++ b/app/src/main/java/ru/project/ui/sorting/SortMethod.java @@ -1,4 +1,4 @@ -package ru.project.model; +package ru.project.ui.sorting; public enum SortMethod { NORMAL, diff --git a/app/src/main/java/ru/project/ui/window/SortMethodSelectionWindow.java b/app/src/main/java/ru/project/ui/sorting/SortMethodSelectionWindow.java similarity index 93% rename from app/src/main/java/ru/project/ui/window/SortMethodSelectionWindow.java rename to app/src/main/java/ru/project/ui/sorting/SortMethodSelectionWindow.java index 331f430..121e5e2 100644 --- a/app/src/main/java/ru/project/ui/window/SortMethodSelectionWindow.java +++ b/app/src/main/java/ru/project/ui/sorting/SortMethodSelectionWindow.java @@ -1,9 +1,8 @@ -package ru.project.ui.window; +package ru.project.ui.sorting; import com.googlecode.lanterna.gui2.ActionListBox; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import java.util.function.Consumer; -import ru.project.model.SortMethod; import ru.project.ui.base.BaseModalWindow; public class SortMethodSelectionWindow extends BaseModalWindow { From e20820b78f8009f78f2a8acd69d1237f65b2dee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Sun, 30 Aug 2026 13:03:23 +0400 Subject: [PATCH 77/77] refactor: unify code style and fix minor merge issues --- .../project/sorting/BubbleSortStrategy.java | 34 +- .../sorting/EvenOnlyMergeSortStrategy.java | 130 ++-- .../project/sorting/EvenOnlySortStrategy.java | 141 +++-- .../ru/project/sorting/MergeSortStrategy.java | 111 ++-- .../java/ru/project/sorting/SortStrategy.java | 7 +- .../StudentAverageGradeComparator.java | 12 +- .../sorting/StudentGroupComparator.java | 10 +- .../sorting/StudentRecordBookComparator.java | 12 +- .../main/java/ru/project/student/Student.java | 4 + app/src/test/java/ru/project/AppTest.java | 16 +- .../ru/project/Sorting/SortStrategyTest.java | 163 ----- .../sorting/EvenOnlySortStrategyTest.java | 561 +++++++++--------- .../ru/project/sorting/SortStrategyTest.java | 162 +++++ .../java/ru/project/student/StudentTest.java | 222 ++++--- 14 files changed, 779 insertions(+), 806 deletions(-) delete mode 100644 app/src/test/java/ru/project/Sorting/SortStrategyTest.java create mode 100644 app/src/test/java/ru/project/sorting/SortStrategyTest.java diff --git a/app/src/main/java/ru/project/sorting/BubbleSortStrategy.java b/app/src/main/java/ru/project/sorting/BubbleSortStrategy.java index 38172db..3c482eb 100644 --- a/app/src/main/java/ru/project/sorting/BubbleSortStrategy.java +++ b/app/src/main/java/ru/project/sorting/BubbleSortStrategy.java @@ -1,24 +1,24 @@ package ru.project.sorting; -import ru.project.list.MyList; import java.util.Comparator; +import ru.project.collection.MyList; public class BubbleSortStrategy implements SortStrategy { - @Override - public void sort(MyList list, Comparator comparator) { - if (list == null || list.size() <= 1) { - return; - } - int n = list.size(); - for (int i = 0; i < n - 1; i++) { - for (int j = 0; j < n - i - 1; j++) { - if (comparator.compare(list.get(j), list.get(j + 1)) > 0) { - T temp = list.get(j); - list.set(j, list.get(j + 1)); - list.set(j + 1, temp); - } - } - } + @Override + public void sort(MyList list, Comparator comparator) { + if (list == null || list.size() <= 1) { + return; } -} \ No newline at end of file + int n = list.size(); + for (int i = 0; i < n - 1; i++) { + for (int j = 0; j < n - i - 1; j++) { + if (comparator.compare(list.get(j), list.get(j + 1)) > 0) { + T temp = list.get(j); + list.set(j, list.get(j + 1)); + list.set(j + 1, temp); + } + } + } + } +} diff --git a/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java b/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java index dea539e..021a70e 100644 --- a/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java +++ b/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java @@ -1,84 +1,84 @@ package ru.project.sorting; -import ru.project.list.MyList; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.function.ToIntFunction; +import ru.project.collection.MyList; public class EvenOnlyMergeSortStrategy implements SortStrategy { - private final ToIntFunction valueExtractor; + private final ToIntFunction valueExtractor; - public EvenOnlyMergeSortStrategy(ToIntFunction valueExtractor) { - this.valueExtractor = valueExtractor; + public EvenOnlyMergeSortStrategy(ToIntFunction valueExtractor) { + this.valueExtractor = valueExtractor; + } + + @Override + public void sort(MyList list, Comparator comparator) { + if (list == null || list.size() <= 1) { + return; } - @Override - public void sort(MyList list, Comparator comparator) { - if (list == null || list.size() <= 1) { - return; - } + List evenIndices = new ArrayList<>(); + List evenElements = new ArrayList<>(); - List evenIndices = new ArrayList<>(); - List evenElements = new ArrayList<>(); - - for (int i = 0; i < list.size(); i++) { - T element = list.get(i); - int value = valueExtractor.applyAsInt(element); - if (value % 2 == 0) { - evenIndices.add(i); - evenElements.add(element); - } - } - - if (evenElements.size() <= 1) { - return; - } - - List sortedEven = mergeSort(evenElements, comparator); - - for (int j = 0; j < evenIndices.size(); j++) { - list.set(evenIndices.get(j), sortedEven.get(j)); - } + for (int i = 0; i < list.size(); i++) { + T element = list.get(i); + int value = valueExtractor.applyAsInt(element); + if (value % 2 == 0) { + evenIndices.add(i); + evenElements.add(element); + } } - private List mergeSort(List list, Comparator comparator) { - if (list.size() <= 1) { - return list; - } - - int mid = list.size() / 2; - List left = mergeSort(list.subList(0, mid), comparator); - List right = mergeSort(list.subList(mid, list.size()), comparator); - - return merge(left, right, comparator); + if (evenElements.size() <= 1) { + return; } - private List merge(List left, List right, Comparator comparator) { - List result = new ArrayList<>(); - int i = 0, j = 0; + List sortedEven = mergeSort(evenElements, comparator); - while (i < left.size() && j < right.size()) { - if (comparator.compare(left.get(i), right.get(j)) <= 0) { - result.add(left.get(i)); - i++; - } else { - result.add(right.get(j)); - j++; - } - } - - while (i < left.size()) { - result.add(left.get(i)); - i++; - } - - while (j < right.size()) { - result.add(right.get(j)); - j++; - } - - return result; + for (int j = 0; j < evenIndices.size(); j++) { + list.set(evenIndices.get(j), sortedEven.get(j)); } -} \ No newline at end of file + } + + private List mergeSort(List list, Comparator comparator) { + if (list.size() <= 1) { + return list; + } + + int mid = list.size() / 2; + List left = mergeSort(list.subList(0, mid), comparator); + List right = mergeSort(list.subList(mid, list.size()), comparator); + + return merge(left, right, comparator); + } + + private List merge(List left, List right, Comparator comparator) { + List result = new ArrayList<>(); + int i = 0, j = 0; + + while (i < left.size() && j < right.size()) { + if (comparator.compare(left.get(i), right.get(j)) <= 0) { + result.add(left.get(i)); + i++; + } else { + result.add(right.get(j)); + j++; + } + } + + while (i < left.size()) { + result.add(left.get(i)); + i++; + } + + while (j < right.size()) { + result.add(right.get(j)); + j++; + } + + return result; + } +} diff --git a/app/src/main/java/ru/project/sorting/EvenOnlySortStrategy.java b/app/src/main/java/ru/project/sorting/EvenOnlySortStrategy.java index 32349c1..8d179ec 100644 --- a/app/src/main/java/ru/project/sorting/EvenOnlySortStrategy.java +++ b/app/src/main/java/ru/project/sorting/EvenOnlySortStrategy.java @@ -1,94 +1,89 @@ package ru.project.sorting; -import ru.project.collection.MyLinkedList; -import ru.project.collection.MyList; - import java.util.Comparator; import java.util.function.ToIntFunction; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; public class EvenOnlySortStrategy implements SortStrategy { - private SortStrategy sortStrategy; - private ToIntFunction valueExtractor; + private SortStrategy sortStrategy; + private ToIntFunction valueExtractor; - public EvenOnlySortStrategy() { + public EvenOnlySortStrategy() {} + + public EvenOnlySortStrategy(SortStrategy sortStrategy) { + setSortStrategy(sortStrategy); + } + + public EvenOnlySortStrategy(SortStrategy sortStrategy, ToIntFunction valueExtractor) { + setSortStrategy(sortStrategy); + setValueExtractor(valueExtractor); + } + + public void setSortStrategy(SortStrategy sortStrategy) { + if (sortStrategy == null) { + throw new IllegalArgumentException("Sort strategy must not be null"); } - public EvenOnlySortStrategy(SortStrategy sortStrategy) { - setSortStrategy(sortStrategy); + this.sortStrategy = sortStrategy; + } + + public void setValueExtractor(ToIntFunction valueExtractor) { + if (valueExtractor == null) { + throw new IllegalArgumentException("Value extractor must not be null"); } - public EvenOnlySortStrategy( - SortStrategy sortStrategy, - ToIntFunction valueExtractor - ) { - setSortStrategy(sortStrategy); - setValueExtractor(valueExtractor); + this.valueExtractor = valueExtractor; + } + + @Override + public void sort(MyList list, Comparator comparator) { + if (sortStrategy == null) { + throw new IllegalStateException("Sort strategy is not set"); } - public void setSortStrategy(SortStrategy sortStrategy) { - if (sortStrategy == null) { - throw new IllegalArgumentException("Sort strategy must not be null"); - } - - this.sortStrategy = sortStrategy; + if (valueExtractor == null) { + throw new IllegalStateException("Value extractor is not set"); } - public void setValueExtractor(ToIntFunction valueExtractor) { - if (valueExtractor == null) { - throw new IllegalArgumentException("Value extractor must not be null"); - } - - this.valueExtractor = valueExtractor; + if (list == null || list.size() <= 1) { + return; } - @Override - public void sort(MyList list, Comparator comparator) { - if (sortStrategy == null) { - throw new IllegalStateException("Sort strategy is not set"); - } + int evenCount = 0; - if (valueExtractor == null) { - throw new IllegalStateException("Value extractor is not set"); - } + for (int i = 0; i < list.size(); i++) { + T element = list.get(i); - if (list == null || list.size() <= 1) { - return; - } - - int evenCount = 0; - - for (int i = 0; i < list.size(); i++) { - T element = list.get(i); - - if (valueExtractor.applyAsInt(element) % 2 == 0) { - evenCount++; - } - } - - if (evenCount <= 1) { - return; - } - - MyList evenElements = new MyLinkedList<>(); - int[] evenIndexes = new int[evenCount]; - - int evenIndex = 0; - - for (int i = 0; i < list.size(); i++) { - T element = list.get(i); - - if (valueExtractor.applyAsInt(element) % 2 == 0) { - evenElements.add(element); - evenIndexes[evenIndex] = i; - evenIndex++; - } - } - - sortStrategy.sort(evenElements, comparator); - - for (int i = 0; i < evenIndexes.length; i++) { - list.set(evenIndexes[i], evenElements.get(i)); - } + if (valueExtractor.applyAsInt(element) % 2 == 0) { + evenCount++; + } } -} \ No newline at end of file + + if (evenCount <= 1) { + return; + } + + MyList evenElements = new MyLinkedList<>(); + int[] evenIndexes = new int[evenCount]; + + int evenIndex = 0; + + for (int i = 0; i < list.size(); i++) { + T element = list.get(i); + + if (valueExtractor.applyAsInt(element) % 2 == 0) { + evenElements.add(element); + evenIndexes[evenIndex] = i; + evenIndex++; + } + } + + sortStrategy.sort(evenElements, comparator); + + for (int i = 0; i < evenIndexes.length; i++) { + list.set(evenIndexes[i], evenElements.get(i)); + } + } +} diff --git a/app/src/main/java/ru/project/sorting/MergeSortStrategy.java b/app/src/main/java/ru/project/sorting/MergeSortStrategy.java index 598f754..420adee 100644 --- a/app/src/main/java/ru/project/sorting/MergeSortStrategy.java +++ b/app/src/main/java/ru/project/sorting/MergeSortStrategy.java @@ -1,67 +1,66 @@ package ru.project.sorting; -import ru.project.collection.MyList; - import java.util.Comparator; +import ru.project.collection.MyList; public class MergeSortStrategy implements SortStrategy { - @Override - public void sort(MyList list, Comparator comparator) { - if (list == null || list.size() <= 1) { - return; - } - mergeSort(list, 0, list.size() - 1, comparator); + @Override + public void sort(MyList list, Comparator comparator) { + if (list == null || list.size() <= 1) { + return; + } + mergeSort(list, 0, list.size() - 1, comparator); + } + + private void mergeSort(MyList list, int left, int right, Comparator comparator) { + if (left >= right) { + return; + } + int mid = (left + right) / 2; + mergeSort(list, left, mid, comparator); + mergeSort(list, mid + 1, right, comparator); + merge(list, left, mid, right, comparator); + } + + private void merge(MyList list, int left, int mid, int right, Comparator comparator) { + int leftSize = mid - left + 1; + int rightSize = right - mid; + + @SuppressWarnings("unchecked") + T[] leftArray = (T[]) new Object[leftSize]; + @SuppressWarnings("unchecked") + T[] rightArray = (T[]) new Object[rightSize]; + + for (int i = 0; i < leftSize; i++) { + leftArray[i] = list.get(left + i); + } + for (int j = 0; j < rightSize; j++) { + rightArray[j] = list.get(mid + 1 + j); } - private void mergeSort(MyList list, int left, int right, Comparator comparator) { - if (left >= right) { - return; - } - int mid = (left + right) / 2; - mergeSort(list, left, mid, comparator); - mergeSort(list, mid + 1, right, comparator); - merge(list, left, mid, right, comparator); + int i = 0, j = 0, k = left; + + while (i < leftSize && j < rightSize) { + if (comparator.compare(leftArray[i], rightArray[j]) <= 0) { + list.set(k, leftArray[i]); + i++; + } else { + list.set(k, rightArray[j]); + j++; + } + k++; } - private void merge(MyList list, int left, int mid, int right, Comparator comparator) { - int leftSize = mid - left + 1; - int rightSize = right - mid; - - @SuppressWarnings("unchecked") - T[] leftArray = (T[]) new Object[leftSize]; - @SuppressWarnings("unchecked") - T[] rightArray = (T[]) new Object[rightSize]; - - for (int i = 0; i < leftSize; i++) { - leftArray[i] = list.get(left + i); - } - for (int j = 0; j < rightSize; j++) { - rightArray[j] = list.get(mid + 1 + j); - } - - int i = 0, j = 0, k = left; - - while (i < leftSize && j < rightSize) { - if (comparator.compare(leftArray[i], rightArray[j]) <= 0) { - list.set(k, leftArray[i]); - i++; - } else { - list.set(k, rightArray[j]); - j++; - } - k++; - } - - while (i < leftSize) { - list.set(k, leftArray[i]); - i++; - k++; - } - while (j < rightSize) { - list.set(k, rightArray[j]); - j++; - k++; - } + while (i < leftSize) { + list.set(k, leftArray[i]); + i++; + k++; } -} \ No newline at end of file + while (j < rightSize) { + list.set(k, rightArray[j]); + j++; + k++; + } + } +} diff --git a/app/src/main/java/ru/project/sorting/SortStrategy.java b/app/src/main/java/ru/project/sorting/SortStrategy.java index 059d908..b9946e8 100644 --- a/app/src/main/java/ru/project/sorting/SortStrategy.java +++ b/app/src/main/java/ru/project/sorting/SortStrategy.java @@ -1,9 +1,8 @@ package ru.project.sorting; +import java.util.Comparator; import ru.project.collection.MyList; -import java.util.Comparator; - public interface SortStrategy { - void sort(MyList list, Comparator comparator); -} \ No newline at end of file + void sort(MyList list, Comparator comparator); +} diff --git a/app/src/main/java/ru/project/sorting/StudentAverageGradeComparator.java b/app/src/main/java/ru/project/sorting/StudentAverageGradeComparator.java index fea72f6..3553976 100644 --- a/app/src/main/java/ru/project/sorting/StudentAverageGradeComparator.java +++ b/app/src/main/java/ru/project/sorting/StudentAverageGradeComparator.java @@ -1,12 +1,12 @@ package ru.project.sorting; -import ru.project.student.Student; import java.util.Comparator; +import ru.project.student.Student; public class StudentAverageGradeComparator implements Comparator { - @Override - public int compare(Student s1, Student s2) { - return Double.compare(s1.getAverageGrade(), s2.getAverageGrade()); - } -} \ No newline at end of file + @Override + public int compare(Student s1, Student s2) { + return Double.compare(s1.getAverageGrade(), s2.getAverageGrade()); + } +} diff --git a/app/src/main/java/ru/project/sorting/StudentGroupComparator.java b/app/src/main/java/ru/project/sorting/StudentGroupComparator.java index 75cae82..9278e81 100644 --- a/app/src/main/java/ru/project/sorting/StudentGroupComparator.java +++ b/app/src/main/java/ru/project/sorting/StudentGroupComparator.java @@ -1,12 +1,12 @@ package ru.project.sorting; -import ru.project.student.Student; import java.util.Comparator; +import ru.project.student.Student; public class StudentGroupComparator implements Comparator { - @Override - public int compare(Student s1, Student s2) { - return s1.getGroupNumber().compareTo(s2.getGroupNumber()); - } + @Override + public int compare(Student s1, Student s2) { + return s1.getGroupNumber().compareTo(s2.getGroupNumber()); + } } diff --git a/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java b/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java index 0e4a714..ce65a5b 100644 --- a/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java +++ b/app/src/main/java/ru/project/sorting/StudentRecordBookComparator.java @@ -1,12 +1,12 @@ package ru.project.sorting; -import ru.project.student.Student; import java.util.Comparator; +import ru.project.student.Student; public class StudentRecordBookComparator implements Comparator { - @Override - public int compare(Student s1, Student s2) { - return Integer.compare(s1.getRecordBookNumber(), s2.getRecordBookNumber()); - } -} \ No newline at end of file + @Override + public int compare(Student s1, Student s2) { + return Integer.compare(s1.getRecordBookNumber(), s2.getRecordBookNumber()); + } +} diff --git a/app/src/main/java/ru/project/student/Student.java b/app/src/main/java/ru/project/student/Student.java index f4cd7fc..111e559 100644 --- a/app/src/main/java/ru/project/student/Student.java +++ b/app/src/main/java/ru/project/student/Student.java @@ -54,6 +54,10 @@ public class Student { return new Student(this.groupNumber, averageGrade, this.recordBookNumber); } + public Student withRecordBookNumber(int recordBookNumber) { + return new Student(this.groupNumber, this.averageGrade, recordBookNumber); + } + public static double getMinAverageGrade() { return MIN_AVERAGE_GRADE; } diff --git a/app/src/test/java/ru/project/AppTest.java b/app/src/test/java/ru/project/AppTest.java index c79d156..44a05ef 100644 --- a/app/src/test/java/ru/project/AppTest.java +++ b/app/src/test/java/ru/project/AppTest.java @@ -4,16 +4,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -/** - * Unit test for simple App. - */ +/** Unit test for simple App. */ public class AppTest { - /** - * Rigorous Test :-) - */ - @Test - public void shouldAnswerWithTrue() { - assertTrue(true); - } + /** Rigorous Test :-) */ + @Test + public void shouldAnswerWithTrue() { + assertTrue(true); + } } diff --git a/app/src/test/java/ru/project/Sorting/SortStrategyTest.java b/app/src/test/java/ru/project/Sorting/SortStrategyTest.java deleted file mode 100644 index 6e0472c..0000000 --- a/app/src/test/java/ru/project/Sorting/SortStrategyTest.java +++ /dev/null @@ -1,163 +0,0 @@ -package ru.project.sorting; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import ru.project.list.MyList; -import ru.project.list.MyLinkedList; -import ru.project.student.Student; - -import java.util.Comparator; - -import static org.junit.jupiter.api.Assertions.*; - -class SortStrategyTest { - - private MyList students; - private final Comparator groupComparator = new StudentGroupComparator(); - private final Comparator gradeComparator = new StudentAverageGradeComparator(); - private final Comparator recordComparator = new StudentRecordBookComparator(); - - @BeforeEach - void setUp() { - students = new MyLinkedList<>(); - students.add(new Student("A12", 4.5, 12345)); - students.add(new Student("B32", 3.8, 12346)); - students.add(new Student("Z00", 4.9, 12347)); - students.add(new Student("A12", 4.2, 12348)); - students.add(new Student("B32", 5.0, 12349)); - } - - @Test - void testMergeSortByGroupNumber() { - SortStrategy strategy = new MergeSortStrategy<>(); - strategy.sort(students, groupComparator); - assertOrder(students, "A12", "A12", "B32", "B32", "Z00"); - } - - @Test - void testMergeSortByAverageGrade() { - SortStrategy strategy = new MergeSortStrategy<>(); - strategy.sort(students, gradeComparator); - assertOrder(students, 3.8, 4.2, 4.5, 4.9, 5.0); - } - - @Test - void testMergeSortByRecordBookNumber() { - SortStrategy strategy = new MergeSortStrategy<>(); - strategy.sort(students, recordComparator); - assertOrder(students, 12345, 12346, 12347, 12348, 12349); - } - - @Test - void testBubbleSortByGroupNumber() { - SortStrategy strategy = new BubbleSortStrategy<>(); - strategy.sort(students, groupComparator); - assertOrder(students, "A12", "A12", "B32", "B32", "Z00"); - } - - @Test - void testBubbleSortByAverageGrade() { - SortStrategy strategy = new BubbleSortStrategy<>(); - strategy.sort(students, gradeComparator); - assertOrder(students, 3.8, 4.2, 4.5, 4.9, 5.0); - } - - @Test - void testBubbleSortByRecordBookNumber() { - SortStrategy strategy = new BubbleSortStrategy<>(); - strategy.sort(students, recordComparator); - assertOrder(students, 12345, 12346, 12347, 12348, 12349); - } - - @Test - void testEvenOnlySortByRecordBookNumber() { - MyList list = new MyLinkedList<>(); - list.add(new Student("A01", 4.5, 7)); - list.add(new Student("A02", 4.5, 8)); - list.add(new Student("A03", 4.5, 3)); - list.add(new Student("A04", 4.5, 2)); - list.add(new Student("A05", 4.5, 5)); - list.add(new Student("A06", 4.5, 4)); - - EvenOnlyMergeSortStrategy strategy = - new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber); - strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber)); - - int[] expected = {7, 2, 3, 4, 5, 8}; - for (int i = 0; i < list.size(); i++) { - assertEquals(expected[i], list.get(i).getRecordBookNumber()); - } - } - - @Test - void testEvenOnlyWithNoEvenElements() { - MyList list = new MyLinkedList<>(); - list.add(new Student("A01", 4.5, 1)); - list.add(new Student("A02", 4.5, 3)); - list.add(new Student("A03", 4.5, 5)); - - EvenOnlyMergeSortStrategy strategy = - new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber); - strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber)); - - int[] expected = {1, 3, 5}; - for (int i = 0; i < list.size(); i++) { - assertEquals(expected[i], list.get(i).getRecordBookNumber()); - } - } - - @Test - void testEvenOnlyWithAllEvenElements() { - MyList list = new MyLinkedList<>(); - list.add(new Student("A01", 4.5, 4)); - list.add(new Student("A02", 4.5, 2)); - list.add(new Student("A03", 4.5, 6)); - - EvenOnlyMergeSortStrategy strategy = - new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber); - strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber)); - - int[] expected = {2, 4, 6}; - for (int i = 0; i < list.size(); i++) { - assertEquals(expected[i], list.get(i).getRecordBookNumber()); - } - } - - @Test - void testSortEmptyList() { - MyList empty = new MyLinkedList<>(); - SortStrategy strategy = new MergeSortStrategy<>(); - strategy.sort(empty, groupComparator); - assertEquals(0, empty.size()); - } - - @Test - void testSortSingleElement() { - MyList single = new MyLinkedList<>(); - single.add(new Student("A12", 4.5, 12345)); - SortStrategy strategy = new MergeSortStrategy<>(); - strategy.sort(single, groupComparator); - assertEquals(1, single.size()); - } - - private void assertOrder(MyList list, String... groups) { - assertEquals(groups.length, list.size()); - for (int i = 0; i < groups.length; i++) { - assertEquals(groups[i], list.get(i).getGroupNumber()); - } - } - - private void assertOrder(MyList list, double... grades) { - assertEquals(grades.length, list.size()); - for (int i = 0; i < grades.length; i++) { - assertEquals(grades[i], list.get(i).getAverageGrade(), 0.001); - } - } - - private void assertOrder(MyList list, int... records) { - assertEquals(records.length, list.size()); - for (int i = 0; i < records.length; i++) { - assertEquals(records[i], list.get(i).getRecordBookNumber()); - } - } -} \ No newline at end of file diff --git a/app/src/test/java/ru/project/sorting/EvenOnlySortStrategyTest.java b/app/src/test/java/ru/project/sorting/EvenOnlySortStrategyTest.java index 8d482bd..daccad5 100644 --- a/app/src/test/java/ru/project/sorting/EvenOnlySortStrategyTest.java +++ b/app/src/test/java/ru/project/sorting/EvenOnlySortStrategyTest.java @@ -1,301 +1,286 @@ package ru.project.sorting; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Comparator; import org.junit.jupiter.api.Test; import ru.project.collection.MyLinkedList; import ru.project.collection.MyList; import ru.project.student.Student; -import static org.junit.jupiter.api.Assertions.assertSame; - -import java.util.Comparator; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - class EvenOnlySortStrategyTest { - @Test - void shouldHandleEmptyList() { - MyList list = new MyLinkedList<>(); + @Test + void shouldHandleEmptyList() { + MyList list = new MyLinkedList<>(); - EvenOnlySortStrategy strategy = - new EvenOnlySortStrategy<>( - new MergeSortStrategy<>(), - value -> value - ); + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value); - strategy.sort(list, Integer::compare); + strategy.sort(list, Integer::compare); - assertEquals(0, list.size()); + assertEquals(0, list.size()); + } + + @Test + void shouldKeepSingleEvenElementUnchanged() { + MyList list = new MyLinkedList<>(); + list.add(4); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(4, list.get(0)); + assertEquals(1, list.size()); + } + + @Test + void shouldKeepSingleOddElementUnchanged() { + MyList list = new MyLinkedList<>(); + list.add(7); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(7, list.get(0)); + assertEquals(1, list.size()); + } + + @Test + void shouldHandleDuplicateEvenValues() { + MyList list = new MyLinkedList<>(); + + list.add(8); + list.add(4); + list.add(8); + list.add(2); + list.add(4); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(2, list.get(0)); + assertEquals(4, list.get(1)); + assertEquals(4, list.get(2)); + assertEquals(8, list.get(3)); + assertEquals(8, list.get(4)); + } + + @Test + void shouldKeepOddStudentObjectsAtSameIndexes() { + Student oddFirst = new Student("A01", 4.0, 7); + Student evenFirst = new Student("A02", 4.0, 8); + Student oddSecond = new Student("A03", 4.0, 3); + Student evenSecond = new Student("A04", 4.0, 2); + Student oddThird = new Student("A05", 4.0, 5); + Student evenThird = new Student("A06", 4.0, 4); + + MyList students = new MyLinkedList<>(); + + students.add(oddFirst); + students.add(evenFirst); + students.add(oddSecond); + students.add(evenSecond); + students.add(oddThird); + students.add(evenThird); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), Student::getRecordBookNumber); + + strategy.sort(students, new StudentRecordBookComparator()); + + assertSame(oddFirst, students.get(0)); + assertSame(oddSecond, students.get(2)); + assertSame(oddThird, students.get(4)); + + assertEquals(2, students.get(1).getRecordBookNumber()); + assertEquals(4, students.get(3).getRecordBookNumber()); + assertEquals(8, students.get(5).getRecordBookNumber()); + } + + @Test + void shouldSortWhenStrategyPassedThroughConstructor() { + MyList list = new MyLinkedList<>(); + + list.add(8); + list.add(2); + list.add(4); + + EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(new MergeSortStrategy<>()); + + strategy.setValueExtractor(value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(2, list.get(0)); + assertEquals(4, list.get(1)); + assertEquals(8, list.get(2)); + } + + @Test + void shouldSortOnlyEvenElements() { + MyList students = new MyLinkedList<>(); + + students.add(new Student("A01", 4.0, 7)); + students.add(new Student("A02", 4.0, 8)); + students.add(new Student("A03", 4.0, 3)); + students.add(new Student("A04", 4.0, 2)); + students.add(new Student("A05", 4.0, 5)); + students.add(new Student("A06", 4.0, 4)); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), Student::getRecordBookNumber); + + strategy.sort(students, new StudentRecordBookComparator()); + + assertEquals(7, students.get(0).getRecordBookNumber()); + assertEquals(2, students.get(1).getRecordBookNumber()); + assertEquals(3, students.get(2).getRecordBookNumber()); + assertEquals(4, students.get(3).getRecordBookNumber()); + assertEquals(5, students.get(4).getRecordBookNumber()); + assertEquals(8, students.get(5).getRecordBookNumber()); + + assertEquals(6, students.size()); + } + + @Test + void shouldSortAllEvenElements() { + MyList list = new MyLinkedList<>(); + + list.add(8); + list.add(2); + list.add(6); + list.add(4); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(2, list.get(0)); + assertEquals(4, list.get(1)); + assertEquals(6, list.get(2)); + assertEquals(8, list.get(3)); + } + + @Test + void shouldKeepOddElementsUnchanged() { + MyList list = new MyLinkedList<>(); + + list.add(7); + list.add(3); + list.add(5); + list.add(1); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value); + + strategy.sort(list, Integer::compare); + + assertEquals(7, list.get(0)); + assertEquals(3, list.get(1)); + assertEquals(5, list.get(2)); + assertEquals(1, list.get(3)); + } + + @Test + void shouldThrowWhenSortStrategyIsNotSet() { + EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(); + + strategy.setValueExtractor(value -> value); + + MyList list = new MyLinkedList<>(); + list.add(2); + list.add(4); + + assertThrows(IllegalStateException.class, () -> strategy.sort(list, Integer::compare)); + } + + @Test + void shouldRejectNullSortStrategy() { + EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(); + + assertThrows(IllegalArgumentException.class, () -> strategy.setSortStrategy(null)); + } + + @Test + void shouldDelegateSortingToProvidedStrategy() { + MyList list = new MyLinkedList<>(); + + list.add(7); + list.add(8); + list.add(3); + list.add(2); + + RecordingSortStrategy recordingStrategy = new RecordingSortStrategy(); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>(recordingStrategy, value -> value); + + strategy.sort(list, Integer::compare); + + assertTrue(recordingStrategy.wasCalled); + assertEquals(2, recordingStrategy.receivedSize); + assertEquals(8, recordingStrategy.firstReceivedElement); + assertEquals(2, recordingStrategy.secondReceivedElement); + } + + @Test + void shouldUseNewStrategyAfterReplacement() { + RecordingSortStrategy firstStrategy = new RecordingSortStrategy(); + + RecordingSortStrategy secondStrategy = new RecordingSortStrategy(); + + EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>(firstStrategy, value -> value); + + MyList firstList = new MyLinkedList<>(); + firstList.add(4); + firstList.add(2); + + strategy.sort(firstList, Integer::compare); + + assertTrue(firstStrategy.wasCalled); + + strategy.setSortStrategy(secondStrategy); + + MyList secondList = new MyLinkedList<>(); + secondList.add(8); + secondList.add(6); + + strategy.sort(secondList, Integer::compare); + + assertTrue(secondStrategy.wasCalled); + } + + private static class RecordingSortStrategy implements SortStrategy { + + private boolean wasCalled; + private int receivedSize; + private Integer firstReceivedElement; + private Integer secondReceivedElement; + + @Override + public void sort(MyList list, Comparator comparator) { + wasCalled = true; + receivedSize = list.size(); + + if (list.size() > 0) { + firstReceivedElement = list.get(0); + } + + if (list.size() > 1) { + secondReceivedElement = list.get(1); + } } - - @Test - void shouldKeepSingleEvenElementUnchanged() { - MyList list = new MyLinkedList<>(); - list.add(4); - - EvenOnlySortStrategy strategy = - new EvenOnlySortStrategy<>( - new MergeSortStrategy<>(), - value -> value - ); - - strategy.sort(list, Integer::compare); - - assertEquals(4, list.get(0)); - assertEquals(1, list.size()); - } - - @Test - void shouldKeepSingleOddElementUnchanged() { - MyList list = new MyLinkedList<>(); - list.add(7); - - EvenOnlySortStrategy strategy = - new EvenOnlySortStrategy<>( - new MergeSortStrategy<>(), - value -> value - ); - - strategy.sort(list, Integer::compare); - - assertEquals(7, list.get(0)); - assertEquals(1, list.size()); - } - - @Test - void shouldHandleDuplicateEvenValues() { - MyList list = new MyLinkedList<>(); - - list.add(8); - list.add(4); - list.add(8); - list.add(2); - list.add(4); - - EvenOnlySortStrategy strategy = - new EvenOnlySortStrategy<>( - new MergeSortStrategy<>(), - value -> value - ); - - strategy.sort(list, Integer::compare); - - assertEquals(2, list.get(0)); - assertEquals(4, list.get(1)); - assertEquals(4, list.get(2)); - assertEquals(8, list.get(3)); - assertEquals(8, list.get(4)); - } - - @Test - void shouldKeepOddStudentObjectsAtSameIndexes() { - Student oddFirst = new Student("A01", 4.0, 7); - Student evenFirst = new Student("A02", 4.0, 8); - Student oddSecond = new Student("A03", 4.0, 3); - Student evenSecond = new Student("A04", 4.0, 2); - Student oddThird = new Student("A05", 4.0, 5); - Student evenThird = new Student("A06", 4.0, 4); - - MyList students = new MyLinkedList<>(); - - students.add(oddFirst); - students.add(evenFirst); - students.add(oddSecond); - students.add(evenSecond); - students.add(oddThird); - students.add(evenThird); - - EvenOnlySortStrategy strategy = - new EvenOnlySortStrategy<>( - new MergeSortStrategy<>(), - Student::getRecordBookNumber - ); - - strategy.sort(students, new StudentRecordBookComparator()); - - assertSame(oddFirst, students.get(0)); - assertSame(oddSecond, students.get(2)); - assertSame(oddThird, students.get(4)); - - assertEquals(2, students.get(1).getRecordBookNumber()); - assertEquals(4, students.get(3).getRecordBookNumber()); - assertEquals(8, students.get(5).getRecordBookNumber()); - } - - @Test - void shouldSortWhenStrategyPassedThroughConstructor() { - MyList list = new MyLinkedList<>(); - - list.add(8); - list.add(2); - list.add(4); - - EvenOnlySortStrategy strategy = - new EvenOnlySortStrategy<>( - new MergeSortStrategy<>() - ); - - strategy.setValueExtractor(value -> value); - - strategy.sort(list, Integer::compare); - - assertEquals(2, list.get(0)); - assertEquals(4, list.get(1)); - assertEquals(8, list.get(2)); - } - - @Test - void shouldSortOnlyEvenElements() { - MyList students = new MyLinkedList<>(); - - students.add(new Student("A01", 4.0, 7)); - students.add(new Student("A02", 4.0, 8)); - students.add(new Student("A03", 4.0, 3)); - students.add(new Student("A04", 4.0, 2)); - students.add(new Student("A05", 4.0, 5)); - students.add(new Student("A06", 4.0, 4)); - - EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), Student::getRecordBookNumber); - - strategy.sort(students, new StudentRecordBookComparator()); - - assertEquals(7, students.get(0).getRecordBookNumber()); - assertEquals(2, students.get(1).getRecordBookNumber()); - assertEquals(3, students.get(2).getRecordBookNumber()); - assertEquals(4, students.get(3).getRecordBookNumber()); - assertEquals(5, students.get(4).getRecordBookNumber()); - assertEquals(8, students.get(5).getRecordBookNumber()); - - assertEquals(6, students.size()); - } - - @Test - void shouldSortAllEvenElements() { - MyList list = new MyLinkedList<>(); - - list.add(8); - list.add(2); - list.add(6); - list.add(4); - - EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value); - - strategy.sort(list, Integer::compare); - - assertEquals(2, list.get(0)); - assertEquals(4, list.get(1)); - assertEquals(6, list.get(2)); - assertEquals(8, list.get(3)); - } - - @Test - void shouldKeepOddElementsUnchanged() { - MyList list = new MyLinkedList<>(); - - list.add(7); - list.add(3); - list.add(5); - list.add(1); - - EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value); - - strategy.sort(list, Integer::compare); - - assertEquals(7, list.get(0)); - assertEquals(3, list.get(1)); - assertEquals(5, list.get(2)); - assertEquals(1, list.get(3)); - } - - @Test - void shouldThrowWhenSortStrategyIsNotSet() { - EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(); - - strategy.setValueExtractor(value -> value); - - MyList list = new MyLinkedList<>(); - list.add(2); - list.add(4); - - assertThrows(IllegalStateException.class, () -> strategy.sort(list, Integer::compare)); - } - - @Test - void shouldRejectNullSortStrategy() { - EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(); - - assertThrows(IllegalArgumentException.class, () -> strategy.setSortStrategy(null)); - } - - @Test - void shouldDelegateSortingToProvidedStrategy() { - MyList list = new MyLinkedList<>(); - - list.add(7); - list.add(8); - list.add(3); - list.add(2); - - RecordingSortStrategy recordingStrategy = new RecordingSortStrategy(); - - EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(recordingStrategy, value -> value); - - strategy.sort(list, Integer::compare); - - assertTrue(recordingStrategy.wasCalled); - assertEquals(2, recordingStrategy.receivedSize); - assertEquals(8, recordingStrategy.firstReceivedElement); - assertEquals(2, recordingStrategy.secondReceivedElement); - } - - @Test - void shouldUseNewStrategyAfterReplacement() { - RecordingSortStrategy firstStrategy = new RecordingSortStrategy(); - - RecordingSortStrategy secondStrategy = new RecordingSortStrategy(); - - EvenOnlySortStrategy strategy = new EvenOnlySortStrategy<>(firstStrategy, value -> value); - - MyList firstList = new MyLinkedList<>(); - firstList.add(4); - firstList.add(2); - - strategy.sort(firstList, Integer::compare); - - assertTrue(firstStrategy.wasCalled); - - strategy.setSortStrategy(secondStrategy); - - MyList secondList = new MyLinkedList<>(); - secondList.add(8); - secondList.add(6); - - strategy.sort(secondList, Integer::compare); - - assertTrue(secondStrategy.wasCalled); - } - - private static class RecordingSortStrategy implements SortStrategy { - - private boolean wasCalled; - private int receivedSize; - private Integer firstReceivedElement; - private Integer secondReceivedElement; - - @Override - public void sort(MyList list, Comparator comparator) { - wasCalled = true; - receivedSize = list.size(); - - if (list.size() > 0) { - firstReceivedElement = list.get(0); - } - - if (list.size() > 1) { - secondReceivedElement = list.get(1); - } - } - } -} \ No newline at end of file + } +} diff --git a/app/src/test/java/ru/project/sorting/SortStrategyTest.java b/app/src/test/java/ru/project/sorting/SortStrategyTest.java new file mode 100644 index 0000000..7a6724c --- /dev/null +++ b/app/src/test/java/ru/project/sorting/SortStrategyTest.java @@ -0,0 +1,162 @@ +package ru.project.sorting; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Comparator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.student.Student; + +class SortStrategyTest { + + private MyList students; + private final Comparator groupComparator = new StudentGroupComparator(); + private final Comparator gradeComparator = new StudentAverageGradeComparator(); + private final Comparator recordComparator = new StudentRecordBookComparator(); + + @BeforeEach + void setUp() { + students = new MyLinkedList<>(); + students.add(new Student("A12", 4.5, 12345)); + students.add(new Student("B32", 3.8, 12346)); + students.add(new Student("Z00", 4.9, 12347)); + students.add(new Student("A12", 4.2, 12348)); + students.add(new Student("B32", 5.0, 12349)); + } + + @Test + void testMergeSortByGroupNumber() { + SortStrategy strategy = new MergeSortStrategy<>(); + strategy.sort(students, groupComparator); + assertOrder(students, "A12", "A12", "B32", "B32", "Z00"); + } + + @Test + void testMergeSortByAverageGrade() { + SortStrategy strategy = new MergeSortStrategy<>(); + strategy.sort(students, gradeComparator); + assertOrder(students, 3.8, 4.2, 4.5, 4.9, 5.0); + } + + @Test + void testMergeSortByRecordBookNumber() { + SortStrategy strategy = new MergeSortStrategy<>(); + strategy.sort(students, recordComparator); + assertOrder(students, 12345, 12346, 12347, 12348, 12349); + } + + @Test + void testBubbleSortByGroupNumber() { + SortStrategy strategy = new BubbleSortStrategy<>(); + strategy.sort(students, groupComparator); + assertOrder(students, "A12", "A12", "B32", "B32", "Z00"); + } + + @Test + void testBubbleSortByAverageGrade() { + SortStrategy strategy = new BubbleSortStrategy<>(); + strategy.sort(students, gradeComparator); + assertOrder(students, 3.8, 4.2, 4.5, 4.9, 5.0); + } + + @Test + void testBubbleSortByRecordBookNumber() { + SortStrategy strategy = new BubbleSortStrategy<>(); + strategy.sort(students, recordComparator); + assertOrder(students, 12345, 12346, 12347, 12348, 12349); + } + + @Test + void testEvenOnlySortByRecordBookNumber() { + MyList list = new MyLinkedList<>(); + list.add(new Student("A01", 4.5, 7)); + list.add(new Student("A02", 4.5, 8)); + list.add(new Student("A03", 4.5, 3)); + list.add(new Student("A04", 4.5, 2)); + list.add(new Student("A05", 4.5, 5)); + list.add(new Student("A06", 4.5, 4)); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber); + strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber)); + + int[] expected = {7, 2, 3, 4, 5, 8}; + for (int i = 0; i < list.size(); i++) { + assertEquals(expected[i], list.get(i).getRecordBookNumber()); + } + } + + @Test + void testEvenOnlyWithNoEvenElements() { + MyList list = new MyLinkedList<>(); + list.add(new Student("A01", 4.5, 1)); + list.add(new Student("A02", 4.5, 3)); + list.add(new Student("A03", 4.5, 5)); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber); + strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber)); + + int[] expected = {1, 3, 5}; + for (int i = 0; i < list.size(); i++) { + assertEquals(expected[i], list.get(i).getRecordBookNumber()); + } + } + + @Test + void testEvenOnlyWithAllEvenElements() { + MyList list = new MyLinkedList<>(); + list.add(new Student("A01", 4.5, 4)); + list.add(new Student("A02", 4.5, 2)); + list.add(new Student("A03", 4.5, 6)); + + EvenOnlyMergeSortStrategy strategy = + new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber); + strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber)); + + int[] expected = {2, 4, 6}; + for (int i = 0; i < list.size(); i++) { + assertEquals(expected[i], list.get(i).getRecordBookNumber()); + } + } + + @Test + void testSortEmptyList() { + MyList empty = new MyLinkedList<>(); + SortStrategy strategy = new MergeSortStrategy<>(); + strategy.sort(empty, groupComparator); + assertEquals(0, empty.size()); + } + + @Test + void testSortSingleElement() { + MyList single = new MyLinkedList<>(); + single.add(new Student("A12", 4.5, 12345)); + SortStrategy strategy = new MergeSortStrategy<>(); + strategy.sort(single, groupComparator); + assertEquals(1, single.size()); + } + + private void assertOrder(MyList list, String... groups) { + assertEquals(groups.length, list.size()); + for (int i = 0; i < groups.length; i++) { + assertEquals(groups[i], list.get(i).getGroupNumber()); + } + } + + private void assertOrder(MyList list, double... grades) { + assertEquals(grades.length, list.size()); + for (int i = 0; i < grades.length; i++) { + assertEquals(grades[i], list.get(i).getAverageGrade(), 0.001); + } + } + + private void assertOrder(MyList list, int... records) { + assertEquals(records.length, list.size()); + for (int i = 0; i < records.length; i++) { + assertEquals(records[i], list.get(i).getRecordBookNumber()); + } + } +} diff --git a/app/src/test/java/ru/project/student/StudentTest.java b/app/src/test/java/ru/project/student/StudentTest.java index 09cf2e7..a51e251 100644 --- a/app/src/test/java/ru/project/student/StudentTest.java +++ b/app/src/test/java/ru/project/student/StudentTest.java @@ -1,145 +1,141 @@ package ru.project.student; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; class StudentTest { - @Test - void testValidStudentCreation() { - Student student = new Student("A12", 4.5, 12345); - assertEquals("A12", student.getGroupNumber()); - assertEquals(4.5, student.getAverageGrade()); - assertEquals(12345, student.getRecordBookNumber()); - } + @Test + void testValidStudentCreation() { + Student student = new Student("A12", 4.5, 12345); + assertEquals("A12", student.getGroupNumber()); + assertEquals(4.5, student.getAverageGrade()); + assertEquals(12345, student.getRecordBookNumber()); + } - @Test - void testValidGroupNumbers() { - assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); - assertDoesNotThrow(() -> new Student("B32", 4.5, 1)); - assertDoesNotThrow(() -> new Student("Z00", 4.5, 1)); - } + @Test + void testValidGroupNumbers() { + assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); + assertDoesNotThrow(() -> new Student("B32", 4.5, 1)); + assertDoesNotThrow(() -> new Student("Z00", 4.5, 1)); + } - @Test - void testInvalidGroupNumbers() { - assertThrows(IllegalArgumentException.class, () -> new Student("a12", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("A1", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("A123", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("AA1", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("а1А", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("аа!!а123", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("23фи!12", 4.5, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("№№###*()", 4.5, 1)); - } + @Test + void testInvalidGroupNumbers() { + assertThrows(IllegalArgumentException.class, () -> new Student("a12", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("A1", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("A123", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("AA1", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("а1А", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("аа!!а123", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("23фи!12", 4.5, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("№№###*()", 4.5, 1)); + } - @Test - void testValidAverageGrades() { - assertDoesNotThrow(() -> new Student("A12", 0.0, 1)); - assertDoesNotThrow(() -> new Student("A12", 5.0, 1)); - } + @Test + void testValidAverageGrades() { + assertDoesNotThrow(() -> new Student("A12", 0.0, 1)); + assertDoesNotThrow(() -> new Student("A12", 5.0, 1)); + } - @Test - void testInvalidAverageGrades() { - assertThrows(IllegalArgumentException.class, () -> new Student("A12", -1.0, 1)); - assertThrows(IllegalArgumentException.class, () -> new Student("A12", 6.0, 1)); - } + @Test + void testInvalidAverageGrades() { + assertThrows(IllegalArgumentException.class, () -> new Student("A12", -1.0, 1)); + assertThrows(IllegalArgumentException.class, () -> new Student("A12", 6.0, 1)); + } - @Test - void testValidRecordBookNumbers() { - assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); - assertDoesNotThrow(() -> new Student("A12", 4.5, 12345)); - } + @Test + void testValidRecordBookNumbers() { + assertDoesNotThrow(() -> new Student("A12", 4.5, 1)); + assertDoesNotThrow(() -> new Student("A12", 4.5, 12345)); + } - @Test - void testInvalidRecordBookNumbers() { - assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, 0)); - assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, -1)); - } + @Test + void testInvalidRecordBookNumbers() { + assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, 0)); + assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, -1)); + } - @Test - void testWithGroupNumber() { - Student original = new Student("A12", 4.5, 12345); - Student updated = original.withGroupNumber("B32"); + @Test + void testWithGroupNumber() { + Student original = new Student("A12", 4.5, 12345); + Student updated = original.withGroupNumber("B32"); - assertNotSame(original, updated); - assertEquals("B32", updated.getGroupNumber()); - assertEquals(4.5, updated.getAverageGrade()); - assertEquals(12345, updated.getRecordBookNumber()); + assertNotSame(original, updated); + assertEquals("B32", updated.getGroupNumber()); + assertEquals(4.5, updated.getAverageGrade()); + assertEquals(12345, updated.getRecordBookNumber()); - assertEquals("A12", original.getGroupNumber()); - } + assertEquals("A12", original.getGroupNumber()); + } - @Test - void testWithAverageGrade() { - Student original = new Student("A12", 4.5, 12345); - Student updated = original.withAverageGrade(4.8); + @Test + void testWithAverageGrade() { + Student original = new Student("A12", 4.5, 12345); + Student updated = original.withAverageGrade(4.8); - assertNotSame(original, updated); - assertEquals(4.8, updated.getAverageGrade()); - assertEquals("A12", updated.getGroupNumber()); - assertEquals(12345, updated.getRecordBookNumber()); + assertNotSame(original, updated); + assertEquals(4.8, updated.getAverageGrade()); + assertEquals("A12", updated.getGroupNumber()); + assertEquals(12345, updated.getRecordBookNumber()); - assertEquals(4.5, original.getAverageGrade()); - } + assertEquals(4.5, original.getAverageGrade()); + } - @Test - void testWithRecordBookNumber() { - Student original = new Student("A12", 4.5, 12345); - Student updated = original.withRecordBookNumber(99999); + @Test + void testWithRecordBookNumber() { + Student original = new Student("A12", 4.5, 12345); + Student updated = original.withRecordBookNumber(99999); - assertNotSame(original, updated); - assertEquals(99999, updated.getRecordBookNumber()); - assertEquals("A12", updated.getGroupNumber()); - assertEquals(4.5, updated.getAverageGrade()); + assertNotSame(original, updated); + assertEquals(99999, updated.getRecordBookNumber()); + assertEquals("A12", updated.getGroupNumber()); + assertEquals(4.5, updated.getAverageGrade()); - assertEquals(12345, original.getRecordBookNumber()); - } + assertEquals(12345, original.getRecordBookNumber()); + } - @Test - void testWithInvalidGroupNumber() { - Student original = new Student("A12", 4.5, 12345); - assertThrows(IllegalArgumentException.class, - () -> original.withGroupNumber("a12")); - assertEquals("A12", original.getGroupNumber()); // не изменился - } + @Test + void testWithInvalidGroupNumber() { + Student original = new Student("A12", 4.5, 12345); + assertThrows(IllegalArgumentException.class, () -> original.withGroupNumber("a12")); + assertEquals("A12", original.getGroupNumber()); // не изменился + } - @Test - void testWithInvalidAverageGrade() { - Student original = new Student("A12", 4.5, 12345); - assertThrows(IllegalArgumentException.class, - () -> original.withAverageGrade(6.0)); - assertEquals(4.5, original.getAverageGrade()); - } + @Test + void testWithInvalidAverageGrade() { + Student original = new Student("A12", 4.5, 12345); + assertThrows(IllegalArgumentException.class, () -> original.withAverageGrade(6.0)); + assertEquals(4.5, original.getAverageGrade()); + } - @Test - void testWithInvalidRecordBookNumber() { - Student original = new Student("A12", 4.5, 12345); - assertThrows(IllegalArgumentException.class, - () -> original.withRecordBookNumber(0)); - assertEquals(12345, original.getRecordBookNumber()); - } + @Test + void testWithInvalidRecordBookNumber() { + Student original = new Student("A12", 4.5, 12345); + assertThrows(IllegalArgumentException.class, () -> original.withRecordBookNumber(0)); + assertEquals(12345, original.getRecordBookNumber()); + } - @Test - void testEqualsAndHashCode() { - Student s1 = new Student("A12", 4.5, 12345); - Student s2 = new Student("A12", 4.5, 12345); - Student s3 = new Student("B32", 4.5, 12345); - Student s4 = new Student("A12", 4.8, 12345); - Student s5 = new Student("A12", 4.5, 99999); + @Test + void testEqualsAndHashCode() { + Student s1 = new Student("A12", 4.5, 12345); + Student s2 = new Student("A12", 4.5, 12345); + Student s3 = new Student("B32", 4.5, 12345); + Student s4 = new Student("A12", 4.8, 12345); + Student s5 = new Student("A12", 4.5, 99999); - assertEquals(s1, s2); - assertEquals(s1.hashCode(), s2.hashCode()); + assertEquals(s1, s2); + assertEquals(s1.hashCode(), s2.hashCode()); - assertNotEquals(s1, s3); - assertNotEquals(s1, s4); - assertNotEquals(s1, s5); - assertNotEquals(s1, null); - assertNotEquals(s1, "some string"); - } + assertNotEquals(s1, s3); + assertNotEquals(s1, s4); + assertNotEquals(s1, s5); + assertNotEquals(s1, null); + assertNotEquals(s1, "some string"); + } }