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 001/156] 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 002/156] 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 003/156] 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 004/156] 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 005/156] 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 006/156] 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 007/156] 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 008/156] 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 009/156] 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 010/156] 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 011/156] 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 012/156] 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 013/156] 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 014/156] 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 015/156] 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 016/156] 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 017/156] 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 018/156] 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 019/156] 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 020/156] 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 021/156] 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 022/156] 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 023/156] 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 024/156] 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 025/156] 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 026/156] 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 027/156] 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 028/156] 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 029/156] 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 030/156] 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 031/156] 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 032/156] 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 033/156] 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 034/156] 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 035/156] 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 036/156] 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 037/156] 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 038/156] 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 039/156] 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 040/156] 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 041/156] 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 042/156] 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 043/156] 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 044/156] 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 045/156] 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 046/156] 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 047/156] 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 048/156] 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 049/156] 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 050/156] 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 051/156] 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 052/156] 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 053/156] 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 054/156] 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 055/156] 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 056/156] 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 057/156] 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 058/156] 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 059/156] 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 060/156] 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 061/156] 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 062/156] 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 063/156] 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 064/156] 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 065/156] 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 066/156] 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 067/156] 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 068/156] 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 0d1f0cbfc45618fb620947970f0a705c7f09e122 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 16:16:17 +0300 Subject: [PATCH 069/156] feat: implement composite pattern for student search --- .../search/AverageGradeRangeCriteria.java | 22 ++++++++ .../CompositeStudentSearchCriteria.java | 51 +++++++++++++++++++ .../search/GroupNumberRangeCriteria.java | 25 +++++++++ .../search/RecordBookNumberRangeCriteria.java | 22 ++++++++ .../ru/project/search/SearchOperation.java | 6 +++ .../project/search/StudentSearchCriteria.java | 7 +++ .../project/search/StudentSearchService.java | 30 +++++++++++ 7 files changed, 163 insertions(+) create mode 100644 app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java create mode 100644 app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java create mode 100644 app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java create mode 100644 app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java create mode 100644 app/src/main/java/ru/project/search/SearchOperation.java create mode 100644 app/src/main/java/ru/project/search/StudentSearchCriteria.java create mode 100644 app/src/main/java/ru/project/search/StudentSearchService.java diff --git a/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java b/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java new file mode 100644 index 0000000..a53a7c5 --- /dev/null +++ b/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java @@ -0,0 +1,22 @@ +package ru.project.search; + +import ru.project.student.Student; + +public class AverageGradeRangeCriteria implements StudentSearchCriteria { + private final double min; + private final double max; + + public AverageGradeRangeCriteria(double min, double max) { + if (min > max) { + throw new IllegalArgumentException("Min grade must be <= max grade"); + } + this.min = min; + this.max = max; + } + + @Override + public boolean matches(Student student) { + double grade = student.getAverageGrade(); + return grade >= min && grade <= max; + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java b/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java new file mode 100644 index 0000000..063d087 --- /dev/null +++ b/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java @@ -0,0 +1,51 @@ +package ru.project.search; + +import ru.project.student.Student; +import java.util.ArrayList; +import java.util.List; + +public class CompositeStudentSearchCriteria implements StudentSearchCriteria { + private final SearchOperation operation; + private final List criteriaList = new ArrayList<>(); + + public CompositeStudentSearchCriteria(SearchOperation operation) { + if (operation == null) { + throw new IllegalArgumentException("Operation must not be null"); + } + this.operation = operation; + } + + public void add(StudentSearchCriteria criteria) { + if (criteria == null) { + throw new IllegalArgumentException("Criteria must not be null"); + } + criteriaList.add(criteria); + } + + public void remove(StudentSearchCriteria criteria) { + criteriaList.remove(criteria); + } + + @Override + public boolean matches(Student student) { + if (criteriaList.isEmpty()) { + return operation == SearchOperation.INTERSECTION; + } + + if (operation == SearchOperation.UNION) { + for (StudentSearchCriteria c : criteriaList) { + if (c.matches(student)) { + return true; + } + } + return false; + } else { + for (StudentSearchCriteria c : criteriaList) { + if (!c.matches(student)) { + return false; + } + } + return true; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java b/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java new file mode 100644 index 0000000..61ea406 --- /dev/null +++ b/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java @@ -0,0 +1,25 @@ +package ru.project.search; + +import ru.project.student.Student; + +public class GroupNumberRangeCriteria implements StudentSearchCriteria { + private final String min; + private final String max; + + public GroupNumberRangeCriteria(String min, String max) { + if (min == null || max == null) { + throw new IllegalArgumentException("Min and max must not be null"); + } + if (min.compareTo(max) > 0) { + throw new IllegalArgumentException("Min group must be <= max group"); + } + this.min = min; + this.max = max; + } + + @Override + public boolean matches(Student student) { + String group = student.getGroupNumber(); + return group.compareTo(min) >= 0 && group.compareTo(max) <= 0; + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java b/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java new file mode 100644 index 0000000..41131b4 --- /dev/null +++ b/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java @@ -0,0 +1,22 @@ +package ru.project.search; + +import ru.project.student.Student; + +public class RecordBookNumberRangeCriteria implements StudentSearchCriteria { + private final int min; + private final int max; + + public RecordBookNumberRangeCriteria(int min, int max) { + if (min > max) { + throw new IllegalArgumentException("Min record number must be <= max"); + } + this.min = min; + this.max = max; + } + + @Override + public boolean matches(Student student) { + int number = student.getRecordBookNumber(); + return number >= min && number <= max; + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/search/SearchOperation.java b/app/src/main/java/ru/project/search/SearchOperation.java new file mode 100644 index 0000000..d616f97 --- /dev/null +++ b/app/src/main/java/ru/project/search/SearchOperation.java @@ -0,0 +1,6 @@ +package ru.project.search; + +public enum SearchOperation { + UNION, + INTERSECTION +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/search/StudentSearchCriteria.java b/app/src/main/java/ru/project/search/StudentSearchCriteria.java new file mode 100644 index 0000000..f157982 --- /dev/null +++ b/app/src/main/java/ru/project/search/StudentSearchCriteria.java @@ -0,0 +1,7 @@ +package ru.project.search; + +import ru.project.student.Student; + +public interface StudentSearchCriteria { + boolean matches(Student student); +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/search/StudentSearchService.java b/app/src/main/java/ru/project/search/StudentSearchService.java new file mode 100644 index 0000000..4ced3c9 --- /dev/null +++ b/app/src/main/java/ru/project/search/StudentSearchService.java @@ -0,0 +1,30 @@ +package ru.project.search; + +import ru.project.list.MyList; +import ru.project.list.MyLinkedList; +import ru.project.student.Student; + +public class StudentSearchService { + private final StudentSearchCriteria criteria; + + public StudentSearchService(StudentSearchCriteria criteria) { + if (criteria == null) { + throw new IllegalArgumentException("Criteria must not be null"); + } + this.criteria = criteria; + } + + public MyList find(MyList students) { + if (students == null) { + return new MyLinkedList<>(); + } + MyList result = new MyLinkedList<>(); + for (int i = 0; i < students.size(); i++) { + Student s = students.get(i); + if (criteria.matches(s)) { + result.add(s); + } + } + return result; + } +} \ No newline at end of file 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 070/156] 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 071/156] 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 072/156] 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 479ea98237789c646ad1c5c2a844f136c2603918 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: Sat, 29 Aug 2026 10:53:48 +0300 Subject: [PATCH 073/156] feat: add MyListCollector for streaming into MyList --- app/src/main/java/ru/project/MyListCollector.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 app/src/main/java/ru/project/MyListCollector.java diff --git a/app/src/main/java/ru/project/MyListCollector.java b/app/src/main/java/ru/project/MyListCollector.java new file mode 100644 index 0000000..c2b21c5 --- /dev/null +++ b/app/src/main/java/ru/project/MyListCollector.java @@ -0,0 +1,4 @@ +public class MyListCollector implements Collector, MyList> +{ + +} \ No newline at end of file From e4a48381f0f1915d0eef7f16451685da223d1b3a 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: Sat, 29 Aug 2026 11:11:12 +0300 Subject: [PATCH 074/156] feat:add supplier for MyListCollector --- app/src/main/java/ru/project/MyListCollector.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/ru/project/MyListCollector.java b/app/src/main/java/ru/project/MyListCollector.java index c2b21c5..eba69de 100644 --- a/app/src/main/java/ru/project/MyListCollector.java +++ b/app/src/main/java/ru/project/MyListCollector.java @@ -1,4 +1,16 @@ +package ru.test.maven.spring; +import java.util.Collections; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; public class MyListCollector implements Collector, MyList> { - + @Override + public Supplier> supplier() + { + return () -> new MyLinkedList<>(); + } } \ No newline at end of file From 501236f881eea9a7cbffa2d7d0a54df9f1e55a7b 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: Sat, 29 Aug 2026 11:13:45 +0300 Subject: [PATCH 075/156] feat: add accumulator to MyListCollector --- app/src/main/java/ru/project/MyListCollector.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/ru/project/MyListCollector.java b/app/src/main/java/ru/project/MyListCollector.java index eba69de..da5c8db 100644 --- a/app/src/main/java/ru/project/MyListCollector.java +++ b/app/src/main/java/ru/project/MyListCollector.java @@ -13,4 +13,9 @@ public class MyListCollector implements Collector, MyList> { return () -> new MyLinkedList<>(); } + @Override + public BiConsumer, T> accumulator() { + + return (list, element) -> list.add(element); + } } \ No newline at end of file From 02c11a040db307d980f7ae248e6afb80ab40057e 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: Sat, 29 Aug 2026 12:09:13 +0300 Subject: [PATCH 076/156] feat: add combiner to MyListCollector --- app/src/main/java/ru/project/MyListCollector.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/ru/project/MyListCollector.java b/app/src/main/java/ru/project/MyListCollector.java index da5c8db..7c79282 100644 --- a/app/src/main/java/ru/project/MyListCollector.java +++ b/app/src/main/java/ru/project/MyListCollector.java @@ -18,4 +18,13 @@ public class MyListCollector implements Collector, MyList> return (list, element) -> list.add(element); } + @Override + public BinaryOperator> combiner() { + return (list1, list2) -> { + for (int i = 0; i < list2.size(); i++) { + list1.add(list2.get(i)); + } + return list1; + }; + } } \ No newline at end of file From fe41b5d67da43c5294e5f19dd3b477bad1ece228 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: Sat, 29 Aug 2026 12:12:03 +0300 Subject: [PATCH 077/156] feat: implement identity finisher() for MyListCollector --- app/src/main/java/ru/project/MyListCollector.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/ru/project/MyListCollector.java b/app/src/main/java/ru/project/MyListCollector.java index 7c79282..c976681 100644 --- a/app/src/main/java/ru/project/MyListCollector.java +++ b/app/src/main/java/ru/project/MyListCollector.java @@ -27,4 +27,9 @@ public class MyListCollector implements Collector, MyList> return list1; }; } + @Override + public Function, MyList> finisher() + { + return list -> list; + } } \ No newline at end of file From 82713a80c6edb2274a1ef96bab979c4a498b1557 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: Sat, 29 Aug 2026 12:14:27 +0300 Subject: [PATCH 078/156] feat: add characteristics to MyListCollector --- app/src/main/java/ru/project/MyListCollector.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/ru/project/MyListCollector.java b/app/src/main/java/ru/project/MyListCollector.java index c976681..f731379 100644 --- a/app/src/main/java/ru/project/MyListCollector.java +++ b/app/src/main/java/ru/project/MyListCollector.java @@ -32,4 +32,10 @@ public class MyListCollector implements Collector, MyList> { return list -> list; } + @Override + public Set characteristics() { + return java.util.Set.of( + Collector.Characteristics.IDENTITY_FINISH + ); + } } \ No newline at end of file From a63a8fdaacdb6cee4f829bb1893bb9602c6aec37 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: Sat, 29 Aug 2026 12:17:22 +0300 Subject: [PATCH 079/156] feat: create MyCollectors with toMyList factory --- app/src/main/java/ru/project/MyCollectors.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 app/src/main/java/ru/project/MyCollectors.java diff --git a/app/src/main/java/ru/project/MyCollectors.java b/app/src/main/java/ru/project/MyCollectors.java new file mode 100644 index 0000000..20840c3 --- /dev/null +++ b/app/src/main/java/ru/project/MyCollectors.java @@ -0,0 +1,10 @@ +package ru.test.maven.spring; +import java.util.stream.Collector; +public class MyCollectors +{ + private MyCollectors() { + } + public static Collector, MyList> toMyList() { + return new MyListCollector<>(); + } +} \ No newline at end of file From 4e64c3e35b2957308fede8f8ef6d72cf05ea6006 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: Sat, 29 Aug 2026 12:23:37 +0300 Subject: [PATCH 080/156] test: add tests for MyListCollector --- .../java/ru/project/MyListCollectorTest.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 app/src/test/java/ru/project/MyListCollectorTest.java diff --git a/app/src/test/java/ru/project/MyListCollectorTest.java b/app/src/test/java/ru/project/MyListCollectorTest.java new file mode 100644 index 0000000..6155d57 --- /dev/null +++ b/app/src/test/java/ru/project/MyListCollectorTest.java @@ -0,0 +1,63 @@ +package ru.test.maven.spring; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import java.util.Set; +import java.util.stream.Collector; +import java.util.stream.IntStream; +import java.util.Collections; +public class MyListCollectorTest +{ + private final MyListCollector collector = new MyListCollector<>(); + + @Test + void shouldTestCollectInOrderSequential() { + MyList result = IntStream.range(0, 10) + .boxed() + .collect(MyCollectors.toMyList()); + + assertEquals(10, result.size()); + for (int i = 0; i < result.size(); i++) { + assertEquals(i, (int) result.get(i)); + } + + } + + @Test + void shouldTestCollectInOrderParallel() { + MyList result = IntStream.range(0, 100) + .parallel() + .boxed() + .collect(MyCollectors.toMyList()); + + assertEquals(100, result.size()); + for (int i = 0; i < result.size(); i++) { + assertEquals(i, (int) result.get(i)); + } + } + + @Test + void shouldTestHandleEmptyStream() { + MyList result = IntStream.empty() + .boxed() + .collect(MyCollectors.toMyList()); + + assertTrue(result.isEmpty()); + + } + @Test + void shouldTestNotUseStandardList() { + MyList result = IntStream.range(0, 5) + .mapToObj(i -> "item" + i) + .collect(MyCollectors.toMyList()); + + assertInstanceOf(MyList.class, result); + assertFalse(result instanceof java.util.List); + } + @Test + void shouldTestHaveCorrectCharacteristics() { + Set characteristics = collector.characteristics(); + assertTrue(characteristics.contains(java.util.stream.Collector.Characteristics.IDENTITY_FINISH)); + assertFalse(characteristics.contains(java.util.stream.Collector.Characteristics.UNORDERED)); + } + +} 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 081/156] 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 082/156] 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 083/156] 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 084/156] 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 085/156] 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 8783308efb3f62be6dc0cf1e629aa4421ec8ba1c 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, 30 Aug 2026 11:43:27 +0300 Subject: [PATCH 086/156] test: add unit tests for student search --- .../ru/project/search/StudentSearchTest.java | 314 ++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 app/src/test/java/ru/project/search/StudentSearchTest.java diff --git a/app/src/test/java/ru/project/search/StudentSearchTest.java b/app/src/test/java/ru/project/search/StudentSearchTest.java new file mode 100644 index 0000000..d12bd9c --- /dev/null +++ b/app/src/test/java/ru/project/search/StudentSearchTest.java @@ -0,0 +1,314 @@ +package ru.project.search; + +import org.junit.jupiter.api.Test; +import ru.project.list.MyList; +import ru.project.list.MyLinkedList; +import ru.project.student.Student; + +import static org.junit.jupiter.api.Assertions.*; + +class StudentSearchTest { + + @Test + void testGroupNumberRangeMatchesInside() { + StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); + Student student = new Student("B20", 4.5, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testGroupNumberRangeMatchesLowerBoundary() { + StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); + Student student = new Student("A10", 4.5, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testGroupNumberRangeMatchesUpperBoundary() { + StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); + Student student = new Student("C30", 4.5, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testGroupNumberRangeDoesNotMatchOutside() { + StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); + Student student = new Student("D00", 4.5, 123); + assertFalse(criteria.matches(student)); + } + + @Test + void testGroupNumberRangeInvalidMinMaxThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> new GroupNumberRangeCriteria("C30", "A10")); + } + + @Test + void testGroupNumberRangeNullValuesThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> new GroupNumberRangeCriteria(null, "A10")); + assertThrows(IllegalArgumentException.class, + () -> new GroupNumberRangeCriteria("A10", null)); + } + + @Test + void testAverageGradeRangeMatchesInside() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 4.5, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeMatchesLowerBoundary() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 4.0, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeMatchesUpperBoundary() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 5.0, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeDoesNotMatchBelow() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 3.9, 123); + assertFalse(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeDoesNotMatchAbove() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 5.1, 123); + assertFalse(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeInvalidMinMaxThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> new AverageGradeRangeCriteria(5.0, 4.0)); + } + + @Test + void testRecordBookNumberRangeMatchesInside() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 150); + assertTrue(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeMatchesLowerBoundary() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 100); + assertTrue(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeMatchesUpperBoundary() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 200); + assertTrue(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeDoesNotMatchBelow() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 99); + assertFalse(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeDoesNotMatchAbove() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 201); + assertFalse(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeInvalidMinMaxThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> new RecordBookNumberRangeCriteria(200, 100)); + } + + @Test + void testCompositeIntersectionEmptyReturnsTrue() { + CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + Student student = new Student("A12", 4.5, 123); + assertTrue(composite.matches(student)); + } + + @Test + void testCompositeUnionEmptyReturnsFalse() { + CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.UNION); + Student student = new Student("A12", 4.5, 123); + assertFalse(composite.matches(student)); + } + + @Test + void testCompositeIntersectionAllTrue() { + CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + composite.add(new GroupNumberRangeCriteria("A10", "C30")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + Student student = new Student("B20", 4.5, 123); + assertTrue(composite.matches(student)); + } + + @Test + void testCompositeIntersectionOneFalse() { + CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + composite.add(new GroupNumberRangeCriteria("A10", "C30")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + Student student = new Student("B20", 3.5, 123); + assertFalse(composite.matches(student)); + } + + @Test + void testCompositeUnionOneTrue() { + CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.UNION); + composite.add(new GroupNumberRangeCriteria("A10", "C30")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + Student student = new Student("D00", 4.5, 123); + assertTrue(composite.matches(student)); + } + + @Test + void testCompositeUnionAllFalse() { + CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.UNION); + composite.add(new GroupNumberRangeCriteria("A10", "C30")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + Student student = new Student("D00", 3.5, 123); + assertFalse(composite.matches(student)); + } + + @Test + void testCompositeWithNestedComposite() { + CompositeStudentSearchCriteria outer = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + outer.add(new GroupNumberRangeCriteria("A10", "C30")); + + CompositeStudentSearchCriteria inner = new CompositeStudentSearchCriteria(SearchOperation.UNION); + inner.add(new AverageGradeRangeCriteria(4.0, 5.0)); + inner.add(new RecordBookNumberRangeCriteria(100, 200)); + + outer.add(inner); + + Student student = new Student("B20", 4.5, 150); + assertTrue(outer.matches(student)); + student = new Student("B20", 3.5, 150); + assertFalse(outer.matches(student)); + student = new Student("D00", 4.5, 150); + assertFalse(outer.matches(student)); + } + + @Test + void testSearchServiceWithSingleCriteria() { + MyList students = new MyLinkedList<>(); + students.add(new Student("A10", 4.5, 1)); + students.add(new Student("B20", 3.8, 2)); + students.add(new Student("C30", 4.9, 3)); + + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + + assertEquals(2, result.size()); + assertEquals("A10", result.get(0).getGroupNumber()); + assertEquals("C30", result.get(1).getGroupNumber()); + } + + @Test + void testSearchServiceEmptyResult() { + MyList students = new MyLinkedList<>(); + students.add(new Student("A10", 3.5, 1)); + students.add(new Student("B20", 3.8, 2)); + + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + + assertEquals(0, result.size()); + } + + @Test + void testSearchServiceEmptyCollection() { + MyList students = new MyLinkedList<>(); + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + assertEquals(0, result.size()); + } + + @Test + void testSearchServicePreservesOrder() { + MyList students = new MyLinkedList<>(); + students.add(new Student("Z00", 4.5, 1)); + students.add(new Student("A10", 4.5, 2)); + students.add(new Student("M30", 4.5, 3)); + + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + + assertEquals(3, result.size()); + assertEquals("Z00", result.get(0).getGroupNumber()); + assertEquals("A10", result.get(1).getGroupNumber()); + assertEquals("M30", result.get(2).getGroupNumber()); + } + + @Test + void testSearchServiceDoesNotModifyOriginal() { + MyList original = new MyLinkedList<>(); + original.add(new Student("A10", 4.5, 1)); + original.add(new Student("B20", 3.8, 2)); + + MyList copy = new MyLinkedList<>(); + for (int i = 0; i < original.size(); i++) { + copy.add(original.get(i)); + } + + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + service.find(original); + + assertEquals(copy.size(), original.size()); + for (int i = 0; i < copy.size(); i++) { + assertEquals(copy.get(i).getGroupNumber(), original.get(i).getGroupNumber()); + } + } + + @Test + void testSearchServiceWithNullCriteriaThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> new StudentSearchService(null)); + } + + @Test + void testSearchServiceWithCompositeCriteria() { + MyList students = new MyLinkedList<>(); + students.add(new Student("A10", 4.5, 1)); + students.add(new Student("B20", 4.5, 2)); + students.add(new Student("C30", 3.5, 3)); + + CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + composite.add(new GroupNumberRangeCriteria("A10", "B20")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + + StudentSearchService service = new StudentSearchService(composite); + MyList result = service.find(students); + + assertEquals(2, result.size()); + assertEquals("A10", result.get(0).getGroupNumber()); + assertEquals("B20", result.get(1).getGroupNumber()); + } + + @Test + void testSearchServiceWithNullStudentListReturnsEmpty() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(null); + assertNotNull(result); + assertEquals(0, result.size()); + } +} \ No newline at end of file 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 087/156] 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"); + } } From e77b8c5956c0770765f8a8101a3988bd3a943ad8 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 14:49:34 +0400 Subject: [PATCH 088/156] docs: add Git Flow tutorial and organize Git guides --- .../git/GIT_BRANCH_DEPENDENCIES.ru.md | 0 .../git/GIT_CONFIG.ru.md | 0 tutorials/git/GIT_FLOW.ru.md | 1201 +++++++++++++++++ .../git/GIT_FORCE_WITH_LEASE_RECOVERY.ru.md | 0 4 files changed, 1201 insertions(+) rename GIT_BRANCH_DEPENDENCIES.ru.md => tutorials/git/GIT_BRANCH_DEPENDENCIES.ru.md (100%) rename GIT_CONFIG.ru.md => tutorials/git/GIT_CONFIG.ru.md (100%) create mode 100644 tutorials/git/GIT_FLOW.ru.md rename GIT_FORCE_WITH_LEASE_RECOVERY.ru.md => tutorials/git/GIT_FORCE_WITH_LEASE_RECOVERY.ru.md (100%) diff --git a/GIT_BRANCH_DEPENDENCIES.ru.md b/tutorials/git/GIT_BRANCH_DEPENDENCIES.ru.md similarity index 100% rename from GIT_BRANCH_DEPENDENCIES.ru.md rename to tutorials/git/GIT_BRANCH_DEPENDENCIES.ru.md diff --git a/GIT_CONFIG.ru.md b/tutorials/git/GIT_CONFIG.ru.md similarity index 100% rename from GIT_CONFIG.ru.md rename to tutorials/git/GIT_CONFIG.ru.md diff --git a/tutorials/git/GIT_FLOW.ru.md b/tutorials/git/GIT_FLOW.ru.md new file mode 100644 index 0000000..02f6063 --- /dev/null +++ b/tutorials/git/GIT_FLOW.ru.md @@ -0,0 +1,1201 @@ +# Git Flow: правила разработки в команде + +## Назначение + +Этот документ описывает принятый в команде порядок работы с Git Flow: + +- какие ветки используются; +- от каких веток создавать новые; +- куда вливать изменения; +- как работать с `feature`, `release`, `hotfix`; +- как работать с зависимыми feature; +- как синхронизировать локальные ветки; +- как безопасно публиковать изменения; +- что делать через Pull Request. + +Цель — сделать историю изменений предсказуемой и избежать ситуаций, когда изменения случайно попадают не в ту ветку. + +--- + +## 1. Основная модель веток + +В классическом Git Flow используются две основные долгоживущие ветки: + +- `main` — код, находящийся в production; +- `develop` — основная ветка текущей разработки. + +Временные ветки: + +- `feature/*` — разработка новой функциональности; +- `release/*` — подготовка релиза; +- `hotfix/*` — срочное исправление production; +- `bugfix/*` — исправление ошибок в процессе разработки; +- `support/*` — поддержка старых версий, если это необходимо проекту. + +Базовая схема: + +```text + feature/* + / \ + / \ + / \ +main ─────────────────●──────────────●───────────── + ↑ ↑ + │ │ + release hotfix + │ │ + └──────┐ ┌─────┘ + ↓ ↓ +develop ────────────────●────●─●────────────────── + ↑ + │ + feature +``` + +Главное правило: + +```text +feature/* → develop +release/* → main +release/* → develop +hotfix/* → main +hotfix/* → develop +``` + +--- + +# 2. Что является источником для каждой ветки + +## Feature + +Feature создаётся от: + +```text +develop → feature/* +``` + +Пример: + +```bash +git flow feature start user-profile +``` + +Получается: + +```text +develop + \ + feature/user-profile +``` + +Новые feature по умолчанию **не создаются от `main`**. + +--- + +## Release + +Release создаётся от: + +```text +develop → release/* +``` + +Пример: + +```bash +git flow release start 2.4.0 +``` + +Получается: + +```text +develop + \ + release/2.4.0 +``` + +Release используется для подготовки уже готового набора изменений к production. + +В release обычно не добавляют новые большие функциональные возможности. + +--- + +## Hotfix + +Hotfix создаётся от: + +```text +main → hotfix/* +``` + +Пример: + +```bash +git flow hotfix start 2.4.1 +``` + +Используйте `hotfix`, когда проблема уже находится в production и исправление нельзя ждать до следующего обычного релиза. + +--- + +## Bugfix + +`bugfix` используется для исправления ошибок в процессе разработки. + +В зависимости от конфигурации `git-flow` такая ветка обычно является веткой разработки и должна в конечном итоге попасть в `develop`. + +Пример: + +```bash +git flow bugfix start fix-registration +``` + +--- + +# 3. Основной рабочий цикл Feature + +Обычная разработка выглядит так: + +```text +develop + │ + ├── feature/login + │ + ├── feature/payment + │ + └── feature/profile + │ + └────────→ develop +``` + +## Шаг 1. Обновить develop + +Перед началом работы необходимо синхронизировать локальный `develop`: + +```bash +git checkout develop +git pull origin develop +``` + +## Шаг 2. Создать feature + +```bash +git flow feature start user-profile +``` + +## Шаг 3. Работать и делать коммиты + +```bash +git add . +git commit -m "feat: add user profile" +``` + +Коммиты должны быть небольшими и логически связанными. + +## Шаг 4. Опубликовать feature + +Если работа ведётся через remote: + +```bash +git flow feature publish user-profile +``` + +или: + +```bash +git push -u origin feature/user-profile +``` + +## Шаг 5. Создать Pull Request + +Из: + +```text +feature/user-profile +``` + +в: + +```text +develop +``` + +После ревью и успешного прохождения CI изменения можно вливать в `develop`. + +--- + +# 4. Как вливать Feature в develop + +Есть два допустимых варианта. + +## Вариант A — через Pull Request + +Для командной разработки это предпочтительный способ: + +```text +feature/user-profile + │ + │ Pull Request + ↓ + develop +``` + +Плюсы: + +- code review; +- автоматические проверки; +- прозрачная история; +- возможность обсуждать изменения до merge. + +--- + +## Вариант B — через git-flow локально + +Можно использовать: + +```bash +git flow feature finish user-profile +``` + +Эта команда завершает feature и вливает её в `develop`. + +После этого изменения нужно отправить в remote: + +```bash +git push origin develop +``` + +Если проект использует обязательный Pull Request, локальный `feature finish` обычно не используется для финального merge. + +--- + +# 5. Важно: не все Feature должны зависеть друг от друга + +Предпочтительная структура: + +```text +develop + ├── feature/A + ├── feature/B + └── feature/C +``` + +Нежелательная структура: + +```text +develop + └── feature/A + └── feature/B + └── feature/C +``` + +Каждая feature по возможности должна начинаться от `develop`. + +--- + +# 6. Что делать, если Feature зависит от другой Feature + +Иногда `feature/B` действительно требует незавершённых изменений из `feature/A`. + +Например: + +```text +develop + \ + feature/A + \ + feature/B +``` + +В этом случае временно допустимо создать `feature/B` от `feature/A`. + +Пример: + +```bash +git checkout feature/A +git checkout -b feature/B +``` + +Но нужно понимать, что теперь B зависит от A. + +После завершения A желательно вернуть B на обычную базу: + +```text +develop + ├── feature/A + └── feature/B +``` + +Например, после merge A в `develop`: + +```bash +git checkout feature/B +git rebase develop +``` + +или выполнить merge: + +```bash +git checkout feature/B +git merge develop +``` + +Выбор между `rebase` и `merge` зависит от правил проекта. + +Главное правило: + +> Зависимая feature — временное исключение, а не стандартная структура проекта. + +Подробнее о зависимостях см. `GIT_BRANCH_DEPENDENCIES.ru.md`. + +--- + +# 7. Как обновлять Feature от develop + +Пока feature разрабатывается, в `develop` могут появляться новые изменения. + +Периодически feature необходимо синхронизировать. + +### Вариант с rebase + +```bash +git checkout feature/user-profile +git fetch origin +git rebase origin/develop +``` + +### Вариант с merge + +```bash +git checkout feature/user-profile +git fetch origin +git merge origin/develop +``` + +Необходимо использовать тот вариант, который принят в правилах конкретного репозитория. + +Если используется `rebase` для уже опубликованной ветки, после него может понадобиться: + +```bash +git push --force-with-lease +``` + +Использовать: + +```bash +git push --force +``` + +не рекомендуется. + +Подробнее о безопасном использовании `--force-with-lease` см. в: + +`GIT_FORCE_WITH_LEASE_RECOVERY.ru.md` + +--- + +# 8. Release Flow + +Когда набор изменений готов к релизу: + +```text +develop + │ + └──→ release/2.4.0 +``` + +Создание: + +```bash +git flow release start 2.4.0 +``` + +Далее выполняется: + +- финальное тестирование; +- исправление найденных ошибок; +- обновление версии; +- подготовка release notes; +- подготовка production-конфигурации. + +Когда release готов: + +```text +release/2.4.0 → main +release/2.4.0 → develop +``` + +То есть результат релиза должен оказаться одновременно: + +```text +main +``` + +и + +```text +develop +``` + +Пример: + +```bash +git flow release finish 2.4.0 +``` + +После чего изменения необходимо отправить в remote: + +```bash +git push origin main +git push origin develop +git push origin --tags +``` + +Конкретная последовательность зависит от используемого процесса CI/CD. + +--- + +# 9. Почему release вливается и в main, и в develop + +Предположим: + +```text +develop + \ + release/2.4.0 +``` + +Во время подготовки релиза нашли bug и исправили его в release. + +Если отправить изменения только в `main`: + +```text +release → main +``` + +то исправление может не попасть обратно в будущую разработку. + +Поэтому используется: + +```text +release → main +release → develop +``` + +Таким образом, `develop` не теряет исправления, сделанные во время подготовки релиза. + +--- + +# 10. Hotfix Flow + +Если production сломан и исправление требуется срочно: + +```text +main + \ + hotfix/2.4.1 +``` + +Создание: + +```bash +git flow hotfix start 2.4.1 +``` + +После исправления hotfix должен попасть: + +```text +hotfix/2.4.1 → main +hotfix/2.4.1 → develop +``` + +Пример: + +```bash +git flow hotfix finish 2.4.1 +``` + +После этого изменения публикуются: + +```bash +git push origin main +git push origin develop +git push origin --tags +``` + +--- + +# 11. Bugfix Flow + +Если ошибка обнаружена во время разработки и не требует отдельного production hotfix: + +```text +develop + \ + bugfix/registration +``` + +Создание: + +```bash +git flow bugfix start registration +``` + +После завершения: + +```text +bugfix/registration → develop +``` + +Использование `bugfix` или обычного `feature` для исправления ошибки определяется соглашениями проекта. + +Главное — не создавать production hotfix от `develop`. + +--- + +# 12. Support Flow + +`support/*` нужен только тогда, когда проект действительно поддерживает старые версии. + +Например: + +```text +main + ├── 3.0 + └── support/2.x +``` + +Такие ветки позволяют поддерживать старую версию отдельно от основной разработки. + +Использование `support/*` должно быть явно согласовано командой. + +Если проект не требует поддержки нескольких версий одновременно, `support` лучше не использовать без необходимости. + +--- + +# 13. Публикация веток + +Для feature: + +```bash +git flow feature publish +``` + +Для остальных типов веток используются соответствующие `publish`/`push` команды согласно конфигурации `git-flow`. + +Перед публикацией необходимо убедиться, что ветка имеет правильное имя и базовую ветку. + +Проверить ветки можно: + +```bash +git branch +git branch -r +``` + +Полезно также посмотреть историю: + +```bash +git log --all --graph --decorate --oneline +``` + +Это один из самых удобных способов визуально проверить, куда действительно идут изменения. + +--- + +# 14. От какого типа ветки что создаём + +| Ветка | Создаётся от | Основное назначение | Куда вливается | +|---|---|---|---| +| `feature/*` | `develop` | Новая функциональность | `develop` | +| `bugfix/*` | `develop` | Исправление ошибки в разработке | `develop` | +| `release/*` | `develop` | Подготовка релиза | `main` и `develop` | +| `hotfix/*` | `main` | Срочное production-исправление | `main` и `develop` | +| `support/*` | зависит от процесса | Поддержка старой версии | зависит от процесса | + +--- + +# 15. Что не следует делать + +## Не создавать Feature от main + +Плохо: + +```text +main + \ + feature/new-payment +``` + +Правильно: + +```text +develop + \ + feature/new-payment +``` + +--- + +## Не вливать Feature напрямую в main + +Обычно: + +```text +feature → develop → release → main +``` + +а не: + +```text +feature → main +``` + +--- + +## Не создавать Hotfix от develop + +Плохо: + +```text +develop → hotfix +``` + +Правильно: + +```text +main → hotfix +``` + +--- + +## Не держать Feature зависимой от другой Feature без необходимости + +Плохо: + +```text +feature/A + \ + feature/B + \ + feature/C +``` + +Лучше: + +```text +develop + ├── feature/A + ├── feature/B + └── feature/C +``` + +--- + +## Не выполнять force push без необходимости + +Опасно: + +```bash +git push --force +``` + +Предпочтительно: + +```bash +git push --force-with-lease +``` + +при условии, что force push разрешён правилами репозитория. + +--- + +# 16. Рекомендуемый командный процесс + +Для обычной задачи: + +```text +1. develop + ↓ +2. feature/* + ↓ +3. commits + ↓ +4. push + ↓ +5. Pull Request + ↓ +6. review + CI + ↓ +7. develop + ↓ +8. release/* + ↓ +9. main +``` + +В виде схемы: + +```text + Pull Request +feature/login ─────────────────────────→ develop + │ + │ release + ↓ + release/2.4.0 + │ + ┌──────────┴──────────┐ + ↓ ↓ + main develop +``` + +--- + +# 17. Правила Pull Request + +Для командной работы рекомендуется: + +1. Один PR — одна логическая задача. +2. Целевая ветка PR должна быть заранее определена. +3. PR должен проходить CI до merge. +4. Автор PR не должен самостоятельно обходить обязательные проверки. +5. Конфликты необходимо разрешать в своей рабочей ветке, а не в `develop`/`main`. +6. Не следует смешивать в одном PR feature, рефакторинг и несвязанные исправления. +7. После изменения истории feature-ветки необходимо убедиться, что PR всё ещё содержит только нужные изменения. + +Рекомендуемая схема: + +```text +feature/* → Pull Request → develop +release/* → Pull Request → main +hotfix/* → Pull Request → main +``` + +Если политика проекта допускает отдельный merge release/hotfix в `develop`, это также должно быть явно зафиксировано в правилах репозитория. + +--- + +# 18. Конвенция именования веток + +Название ветки должно быть коротким и отражать задачу. + +Примеры: + +```text +feature/user-profile +feature/payment-api +feature/checkout-flow + +bugfix/registration-error +bugfix/incorrect-total + +release/2.4.0 + +hotfix/2.4.1 + +support/2.x +``` + +Не рекомендуется: + +```text +feature/test +feature/new +feature/fix +feature/ivan +feature/123 +``` + +Если в проекте используется трекер задач, предпочтительно включать идентификатор задачи: + +```text +feature/PROJ-123-user-profile +bugfix/PROJ-456-invalid-total +``` + +Точная схема именования должна быть одинаковой для всей команды. + +--- + +# 19. Коммиты + +Коммиты должны быть: + +- небольшими; +- законченными; +- логически связанными; +- понятными без чтения всего diff. + +Плохо: + +```bash +git commit -m "fix" +git commit -m "changes" +git commit -m "test" +``` + +Лучше: + +```bash +git commit -m "feat: add user profile endpoint" +git commit -m "fix: validate empty email" +git commit -m "test: cover invalid payment status" +``` + +Если проект использует Conventional Commits, придерживаемся его правил: + +```text +feat: +fix: +refactor: +test: +docs: +chore: +build: +ci: +``` + +--- + +# 20. Синхронизация перед началом работы + +Перед созданием новой feature: + +```bash +git fetch origin +git checkout develop +git pull --ff-only origin develop +``` + +После этого: + +```bash +git flow feature start my-feature +``` + +Для `hotfix`: + +```bash +git fetch origin +git checkout main +git pull --ff-only origin main +git flow hotfix start 2.4.1 +``` + +Использование `--ff-only` помогает не создавать случайные локальные merge-коммиты во время обычного обновления базовой ветки. + +--- + +# 21. Что делать при конфликтах + +Если конфликт возник при `rebase`: + +```bash +git status +``` + +Исправить конфликтующие файлы, затем: + +```bash +git add +git rebase --continue +``` + +Чтобы полностью отменить rebase: + +```bash +git rebase --abort +``` + +Если конфликт возник при merge: + +```bash +git status +``` + +После исправления: + +```bash +git add +git commit +``` + +Если merge необходимо отменить: + +```bash +git merge --abort +``` + +Не следует разрешать конфликты вслепую. После разрешения обязательно проверить diff и тесты. + +--- + +# 22. Удаление завершённых веток + +После merge завершённая feature обычно больше не нужна. + +Локальную ветку можно удалить: + +```bash +git branch -d feature/my-feature +``` + +Удалённую ветку: + +```bash +git push origin --delete feature/my-feature +``` + +Если репозиторий автоматически удаляет ветку после merge, вручную удалять её не нужно. + +Не удаляйте ветку до тех пор, пока не убедились, что нужные изменения действительно попали в целевую ветку. + +--- + +# 23. Как проверить поток коммитов + +Для просмотра всех локальных и remote веток: + +```bash +git log --all --graph --decorate --oneline --date-order +``` + +Полезно также: + +```bash +git branch -a +``` + +и: + +```bash +git show-branch --all +``` + +Пример графа: + +```text +* abc1234 (main) Merge release/2.4.0 +|\ +| * def5678 (release/2.4.0) Prepare release 2.4.0 +| * 123abcd Fix release configuration +|/ +* 456efgh (develop) Merge feature/payment +|\ +| * 789ijkl (feature/payment) Add payment service +| * 012mnop Add payment validation +|/ +* 345qrst Previous release +``` + +Такой граф позволяет быстро проверить: + +- от какой ветки была создана ветка; +- какие изменения в неё попали; +- где находится merge; +- не ушла ли feature в неправильную ветку. + +--- + +# 24. Частые ошибки + +## Feature создана от старого develop + +Симптом: + +```text +feature + \ + старый develop +``` + +Решение — сначала синхронизировать feature с актуальным `develop`. + +--- + +## Feature случайно создана от main + +Не продолжайте разработку, не проверив ситуацию. + +Сначала нужно убедиться, какие изменения уже появились в feature, и только после этого выбрать способ переноса на актуальный `develop`. + +В зависимости от ситуации это может быть: + +```bash +git rebase develop +``` + +или пересоздание ветки от правильной базы. + +--- + +## В hotfix попали незавершённые изменения из develop + +Это обычно означает, что hotfix создавался не от актуального `main`. + +Hotfix должен базироваться на том состоянии, которое реально находится в production. + +--- + +## Исправление сделано только в release + +Если release содержит важный bugfix, он должен попасть и в `main`, и обратно в `develop`. + +Нельзя оставлять важные исправления только внутри временной release-ветки. + +--- + +## Два разработчика одновременно переписали историю одной feature + +Не следует выполнять `rebase`/force push общей ветки без согласования. + +Для опубликованных веток безопаснее: + +```bash +git push --force-with-lease +``` + +Но даже `--force-with-lease` не отменяет необходимость договориться с остальными участниками. + +Подробнее: `GIT_FORCE_WITH_LEASE_RECOVERY.ru.md`. + +--- + +# 25. Полная схема Git Flow + +```text + feature/A + / + / + ● + / + / +develop ────────────────●────────●───────────────●──────── + \ / + \ / + \ / + release/2.4.0 + │ + ┌────────┴────────┐ + ↓ ↓ + main develop + │ + │ + └── hotfix/2.4.1 + │ + ┌────────┴────────┐ + ↓ ↓ + main develop +``` + +Для обычной разработки поток выглядит так: + +```text +develop + │ + ├── feature/A ──────────────┐ + │ ↓ + ├── feature/B ─────────→ develop + │ + └── feature/C ──────────────┘ + │ + ↓ + release/2.4.0 + │ + ┌─────┴─────┐ + ↓ ↓ + main develop +``` + +--- + +# 26. Краткая памятка + +Запоминаем: + +```text +feature ← develop +feature → develop + +bugfix ← develop +bugfix → develop + +release ← develop +release → main +release → develop + +hotfix ← main +hotfix → main +hotfix → develop +``` + +Основные команды: + +```bash +# Feature +git flow feature start +git flow feature publish +git flow feature finish + +# Bugfix +git flow bugfix start +git flow bugfix finish + +# Release +git flow release start +git flow release finish + +# Hotfix +git flow hotfix start +git flow hotfix finish + +# Support +git flow support start +``` + +Для просмотра графа: + +```bash +git log --all --graph --decorate --oneline --date-order +``` + +--- + +# 27. Наше командное правило + +Перед каждой операцией с ветками нужно ответить на три вопроса: + +**1. От какой ветки я создаю текущую ветку?** + +**2. В какую ветку должны попасть мои изменения?** + +**3. Не содержит ли моя ветка изменений, которые не должны попасть в целевую ветку?** + +Если на эти три вопроса есть однозначный ответ, операция с Git почти всегда становится предсказуемой. + +--- + +# 28. Итог + +Нормальный Git Flow в команде выглядит так: + +```text + feature + ↗ +develop ───────────────●─────────────── + │ + │ release + ↓ + main + │ + │ hotfix + ↓ + develop +``` + +Ключевая идея проста: + +> `develop` — источник обычной разработки, `main` — источник production-правок. Feature идут в `develop`, release — в `main` и обратно в `develop`, hotfix — из `main` в `main` и обратно в `develop`. + +Git Flow — это не набор обязательных магических команд. Это договорённость команды о том, **где рождаются изменения, через какие этапы проходят и в какую ветку попадают**. + +Перед merge всегда проверяем целевую ветку, актуальность базы, CI и содержимое diff. diff --git a/GIT_FORCE_WITH_LEASE_RECOVERY.ru.md b/tutorials/git/GIT_FORCE_WITH_LEASE_RECOVERY.ru.md similarity index 100% rename from GIT_FORCE_WITH_LEASE_RECOVERY.ru.md rename to tutorials/git/GIT_FORCE_WITH_LEASE_RECOVERY.ru.md From 79a0f7b3c2a9f8337194d2a1d86eb345c75bc8b7 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 16:07:49 +0400 Subject: [PATCH 089/156] refactor: move CustomDataSettingsWindow to manual package --- .../main/java/ru/project/ui/creation/CreateCollectionMenu.java | 1 + .../ui/creation/{ => manual}/CustomDataSettingsWindow.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename app/src/main/java/ru/project/ui/creation/{ => manual}/CustomDataSettingsWindow.java (99%) diff --git a/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java b/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java index 72820be..d8a1674 100644 --- a/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java @@ -12,6 +12,7 @@ import ru.project.student.Student; import ru.project.ui.base.BaseModalWindow; import ru.project.ui.common.InputValueWindow; import ru.project.ui.common.MessageWindow; +import ru.project.ui.creation.manual.CustomDataSettingsWindow; public class CreateCollectionMenu extends BaseModalWindow { diff --git a/app/src/main/java/ru/project/ui/creation/CustomDataSettingsWindow.java b/app/src/main/java/ru/project/ui/creation/manual/CustomDataSettingsWindow.java similarity index 99% rename from app/src/main/java/ru/project/ui/creation/CustomDataSettingsWindow.java rename to app/src/main/java/ru/project/ui/creation/manual/CustomDataSettingsWindow.java index 5f0aa10..19d31b1 100644 --- a/app/src/main/java/ru/project/ui/creation/CustomDataSettingsWindow.java +++ b/app/src/main/java/ru/project/ui/creation/manual/CustomDataSettingsWindow.java @@ -1,4 +1,4 @@ -package ru.project.ui.creation; +package ru.project.ui.creation.manual; import com.googlecode.lanterna.gui2.Button; import com.googlecode.lanterna.gui2.Label; From f7ac87c3f2d2d0667ef68cdf04101ee634df294d 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 23:46:53 +0400 Subject: [PATCH 090/156] refactor(creation): move fill settings out of application state - remove custom data collection and file path from AppState - add FillStrategy abstraction for collection creation - add ManualFillSettings and ManualFillStrategy - add FileFillSettings and FileFillStrategy - move collection size input into reusable CollectionSizeInputWindow - update ManualDataSettingsWindow to manage its own editing state --- .../main/java/ru/project/model/AppState.java | 21 --- .../ui/common/CollectionSizeInputWindow.java | 51 +++++++ .../ui/creation/CreateCollectionMenu.java | 123 +++++----------- .../ru/project/ui/creation/FillStrategy.java | 9 ++ ...dow.java => ManualDataSettingsWindow.java} | 135 ++++++++++++------ .../creation/manual/ManualFillSettings.java | 22 +++ .../creation/manual/ManualFillStrategy.java | 28 ++++ 7 files changed, 237 insertions(+), 152 deletions(-) create mode 100644 app/src/main/java/ru/project/ui/common/CollectionSizeInputWindow.java create mode 100644 app/src/main/java/ru/project/ui/creation/FillStrategy.java rename app/src/main/java/ru/project/ui/creation/manual/{CustomDataSettingsWindow.java => ManualDataSettingsWindow.java} (64%) create mode 100644 app/src/main/java/ru/project/ui/creation/manual/ManualFillSettings.java create mode 100644 app/src/main/java/ru/project/ui/creation/manual/ManualFillStrategy.java diff --git a/app/src/main/java/ru/project/model/AppState.java b/app/src/main/java/ru/project/model/AppState.java index 375877e..70f3beb 100644 --- a/app/src/main/java/ru/project/model/AppState.java +++ b/app/src/main/java/ru/project/model/AppState.java @@ -7,13 +7,9 @@ import ru.project.student.Student; public class AppState { private MyList mainCollection; - private MyList customDataCollection; - private String currentFilePath; public AppState() { this.mainCollection = new MyLinkedList<>(); - this.customDataCollection = new MyLinkedList<>(); - this.currentFilePath = null; } public MyList getMainCollection() { @@ -23,21 +19,4 @@ public class AppState { public void setMainCollection(MyList currentCollection) { this.mainCollection = currentCollection; } - - public MyList getCustomDataCollection() { - return customDataCollection; - } - - public void setCustomDataCollection(MyList customDataCollection) { - - this.customDataCollection = customDataCollection; - } - - public String getCurrentFilePath() { - return currentFilePath; - } - - public void setCurrentFilePath(String currentFilePath) { - this.currentFilePath = currentFilePath; - } } diff --git a/app/src/main/java/ru/project/ui/common/CollectionSizeInputWindow.java b/app/src/main/java/ru/project/ui/common/CollectionSizeInputWindow.java new file mode 100644 index 0000000..57b1d3a --- /dev/null +++ b/app/src/main/java/ru/project/ui/common/CollectionSizeInputWindow.java @@ -0,0 +1,51 @@ +package ru.project.ui.common; + +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.Consumer; + +public class CollectionSizeInputWindow { + + private final WindowBasedTextGUI gui; + private final Integer currentSize; + private final Consumer onSizeEntered; + + public CollectionSizeInputWindow( + WindowBasedTextGUI gui, Integer currentSize, Consumer onSizeEntered) { + + this.gui = gui; + this.currentSize = currentSize; + this.onSizeEntered = onSizeEntered; + } + + public void showModal() { + InputValueWindow window = + new InputValueWindow( + gui, + "Размер коллекции", + "Введите размер коллекции:", + currentSize != null && currentSize > 0 ? String.valueOf(currentSize) : null, + this::handleSizeEntered); + + window.showModal(); + } + + private boolean handleSizeEntered(String value) { + try { + int size = Integer.parseInt(value); + + if (size <= 0) { + MessageWindow.showModal(gui, "Размер должен быть больше 0."); + + return false; + } + + onSizeEntered.accept(size); + return true; + + } catch (NumberFormatException e) { + MessageWindow.showModal(gui, "Введите целое число."); + + return false; + } + } +} diff --git a/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java b/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java index d8a1674..e56c4aa 100644 --- a/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java @@ -5,94 +5,75 @@ 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.ui.base.BaseModalWindow; -import ru.project.ui.common.InputValueWindow; import ru.project.ui.common.MessageWindow; -import ru.project.ui.creation.manual.CustomDataSettingsWindow; +import ru.project.ui.creation.file.FileDataSettingsWindow; +import ru.project.ui.creation.file.FileFillSettings; +import ru.project.ui.creation.file.FileFillStrategy; +import ru.project.ui.creation.manual.ManualDataSettingsWindow; +import ru.project.ui.creation.manual.ManualFillSettings; +import ru.project.ui.creation.manual.ManualFillStrategy; public class CreateCollectionMenu extends BaseModalWindow { - private int collectionSize; - private FillMethod selectedFillMethod; + private final AppState state; + + private FillMethod selectedFillMethod; + private FillStrategy fillStrategy; - private final Label sizeLabel; private final Label fillMethodLabel; - private final CustomDataSettingsWindow customDataSettingsWindow; + private final ManualDataSettingsWindow manualDataSettingsWindow; + private final FileDataSettingsWindow fileDataSettingsWindow; public CreateCollectionMenu(WindowBasedTextGUI gui, AppState state) { + super("Создание новой коллекции", gui); - collectionSize = 0; - selectedFillMethod = FillMethod.CUSTOM; + this.state = state; + this.selectedFillMethod = FillMethod.CUSTOM; + this.fillStrategy = null; - customDataSettingsWindow = new CustomDataSettingsWindow(gui, state, () -> collectionSize); + this.manualDataSettingsWindow = new ManualDataSettingsWindow(gui, this::setManualFillStrategy); + this.fileDataSettingsWindow = new FileDataSettingsWindow(gui, this::setFileFillStrategy); 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::showCollectionSizeInputValueWindow)); - panel.addComponent(new Button("Выбрать способ заполнения", this::selectFillMethod)); panel.addComponent( new Button("Настроить текущий способ заполнения", this::setupCurrentFillMethod)); - panel.addComponent(new Button("Создать новую коллекцию", () -> updateMainCollection(state))); + panel.addComponent(new Button("Создать новую коллекцию", this::updateMainCollection)); panel.addComponent(new Button("Выйти", this::close)); setComponent(panel); } - private void showCollectionSizeInputValueWindow() { - InputValueWindow window = - new InputValueWindow( - gui, - "Размер коллекции", - "Введите размер коллекции:", - collectionSize > 0 ? String.valueOf(collectionSize) : null, - this::handleSizeEntered); - - window.showModal(); + private void setManualFillStrategy(ManualFillSettings settings) { + fillStrategy = new ManualFillStrategy(settings); } - private boolean handleSizeEntered(String value) { - try { - int enteredSize = Integer.parseInt(value); - if (enteredSize <= 0) { - MessageWindow.showModal(gui, "Размер должен быть больше 0."); - return false; - } - collectionSize = enteredSize; - sizeLabel.setText("Размер: " + collectionSize); - return true; - } catch (NumberFormatException e) { - MessageWindow.showModal(gui, "Введите целое число."); - return false; - } + private void setFileFillStrategy(FileFillSettings settings) { + fillStrategy = new FileFillStrategy(settings); } + // private void setRandomFillStrategy(RandomFillSettings settings) { + // fillStrategy = new RandomFillStrategy(settings); + // } + private void setupCurrentFillMethod() { switch (selectedFillMethod) { - case CUSTOM -> { - customDataSettingsWindow.showModal(); - } + case CUSTOM -> manualDataSettingsWindow.showModal(); - case FILE -> - // TODO - MessageWindow.showModal(gui, "Не реализовано."); + case FILE -> fileDataSettingsWindow.showModal(); case RANDOM -> MessageWindow.showModal( @@ -106,6 +87,7 @@ public class CreateCollectionMenu extends BaseModalWindow { gui, fillMethod -> { selectedFillMethod = fillMethod; + fillMethodLabel.setText( "Способ заполнения: " + getFillMethodName(selectedFillMethod)); }); @@ -113,57 +95,22 @@ public class CreateCollectionMenu extends BaseModalWindow { menu.showModal(); } - private void updateMainCollection(AppState state) { - if (collectionSize <= 0) { - MessageWindow.showModal(gui, "Сначала задайте размер коллекции."); + private void updateMainCollection() { + if (fillStrategy == null) { + MessageWindow.showModal(gui, "Сначала настройте способ заполнения."); + return; } - switch (selectedFillMethod) { - case CUSTOM: - createCollectionFromCustomData(state); - break; - - case RANDOM: - createCollectionFromRandomData(state); - break; - - case FILE: - createCollectionFromFileData(state); - break; - } - } - - 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); + state.setMainCollection(fillStrategy.create()); MessageWindow.showModal(gui, "Коллекция успешно создана."); } - private void createCollectionFromRandomData(AppState state) { - // TODO - MessageWindow.showModal(gui, "Не реализовано."); - } - - private void createCollectionFromFileData(AppState state) { - // TODO - MessageWindow.showModal(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/creation/FillStrategy.java b/app/src/main/java/ru/project/ui/creation/FillStrategy.java new file mode 100644 index 0000000..f4931d8 --- /dev/null +++ b/app/src/main/java/ru/project/ui/creation/FillStrategy.java @@ -0,0 +1,9 @@ +package ru.project.ui.creation; + +import ru.project.collection.MyList; +import ru.project.student.Student; + +public interface FillStrategy { + + MyList create(); +} diff --git a/app/src/main/java/ru/project/ui/creation/manual/CustomDataSettingsWindow.java b/app/src/main/java/ru/project/ui/creation/manual/ManualDataSettingsWindow.java similarity index 64% rename from app/src/main/java/ru/project/ui/creation/manual/CustomDataSettingsWindow.java rename to app/src/main/java/ru/project/ui/creation/manual/ManualDataSettingsWindow.java index 19d31b1..dd02eeb 100644 --- a/app/src/main/java/ru/project/ui/creation/manual/CustomDataSettingsWindow.java +++ b/app/src/main/java/ru/project/ui/creation/manual/ManualDataSettingsWindow.java @@ -5,28 +5,28 @@ 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 java.util.function.Consumer; 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.BaseModalWindow; import ru.project.ui.common.CollectionDisplayWindow; +import ru.project.ui.common.CollectionSizeInputWindow; import ru.project.ui.common.InputValueWindow; import ru.project.ui.common.MessageWindow; -public class CustomDataSettingsWindow extends BaseModalWindow { +public class ManualDataSettingsWindow extends BaseModalWindow { - private final AppState state; - private final Supplier requestedCollectionSize; private final StudentBuilder studentBuilder; - private String groupNumber; - private Double averageGrade; - private Integer recordBookNumber; - private Integer selectedObjectIndex; + private Integer lastRequestedCollectionSize; + private String lastRequestedGroupNumber; + private Double lastRequestedAverageGrade; + private Integer lastRequestedRecordBookNumber; + private Integer lastRequestedSelectedObjectIndex; + private final Label sizeLabel; private final Label groupNumberLabel; private final Label averageGradeLabel; private final Label recordBookNumberLabel; @@ -34,19 +34,23 @@ public class CustomDataSettingsWindow extends BaseModalWindow { private MyList preparedCustomDataCollection; - public CustomDataSettingsWindow( - WindowBasedTextGUI gui, AppState state, Supplier collectionSize) { + private final ManualFillSettings settings; + private final Consumer onApply; + + public ManualDataSettingsWindow(WindowBasedTextGUI gui, Consumer onApply) { super("Настройка пользовательских данных", gui); - this.state = state; - this.requestedCollectionSize = collectionSize; + this.onApply = onApply; + this.settings = new ManualFillSettings(); this.studentBuilder = new StudentBuilder(); Panel panel = new Panel(); panel.setLayoutManager(new LinearLayout()); + sizeLabel = new Label("Размер: не задан"); + groupNumberLabel = new Label("Номер группы: не задан"); averageGradeLabel = new Label("Средний балл: не задан"); @@ -55,11 +59,14 @@ public class CustomDataSettingsWindow extends BaseModalWindow { indexLabel = new Label("Индекс объекта: не задан"); + panel.addComponent(sizeLabel); panel.addComponent(groupNumberLabel); panel.addComponent(averageGradeLabel); panel.addComponent(recordBookNumberLabel); panel.addComponent(indexLabel); + panel.addComponent(new Button("Задать размер", this::showCollectionSizeInputWindow)); + panel.addComponent(new Button("Задать номер группы", this::setGroupNumber)); panel.addComponent(new Button("Задать средний балл", this::setAverageGrade)); @@ -79,25 +86,65 @@ public class CustomDataSettingsWindow extends BaseModalWindow { setComponent(panel); } + private void showCollectionSizeInputWindow() { + CollectionSizeInputWindow window = + new CollectionSizeInputWindow( + gui, preparedCustomDataCollection.size(), this::handleCollectionSizeEntered); + + window.showModal(); + } + + private void handleCollectionSizeEntered(int size) { + MyList resizedCollection = new MyLinkedList<>(); + + int currentCollectionSize = preparedCustomDataCollection.size(); + + int elementsToCopy = Math.min(currentCollectionSize, size); + + for (int i = 0; i < elementsToCopy; i++) { + resizedCollection.add(preparedCustomDataCollection.get(i)); + } + + for (int i = elementsToCopy; i < size; i++) { + resizedCollection.add(null); + } + + preparedCustomDataCollection = resizedCollection; + lastRequestedCollectionSize = size; + + if (lastRequestedSelectedObjectIndex != null + && lastRequestedSelectedObjectIndex >= lastRequestedCollectionSize) { + lastRequestedSelectedObjectIndex = null; + indexLabel.setText("Индекс объекта: не задан"); + } + + sizeLabel.setText("Размер: " + lastRequestedCollectionSize); + } + @Override public void showModal() { prepareCustomDataCollection(); + super.showModal(); } private void prepareCustomDataCollection() { + MyList currentCustomDataCollection = settings.getCollection(); + preparedCustomDataCollection = new MyLinkedList<>(); - int targetCollectionSize = requestedCollectionSize.get(); - int currentCustomDataCollectionSize = state.getCustomDataCollection().size(); + int currentCollectionSize = currentCustomDataCollection.size(); - int elementsToCopy = Math.min(currentCustomDataCollectionSize, targetCollectionSize); + int targetSize = + lastRequestedCollectionSize == null ? currentCollectionSize : lastRequestedCollectionSize; + + int elementsToCopy = Math.min(currentCollectionSize, targetSize); for (int i = 0; i < elementsToCopy; i++) { - preparedCustomDataCollection.add(state.getCustomDataCollection().get(i)); + preparedCustomDataCollection.add(currentCustomDataCollection.get(i)); } - for (int i = elementsToCopy; i < targetCollectionSize; i++) { + for (int i = elementsToCopy; i < targetSize; i++) { preparedCustomDataCollection.add(null); } } @@ -108,17 +155,17 @@ public class CustomDataSettingsWindow extends BaseModalWindow { gui, "Номер группы", "Введите номер группы (например, A12):", - groupNumber, + lastRequestedGroupNumber, value -> { if (!value.matches(Student.getGroupNumberPattern())) { MessageWindow.showModal(gui, "Номер группы должен соответствовать формату A12."); return false; } - groupNumber = value; + lastRequestedGroupNumber = value; studentBuilder.setGroupNumber(value); - groupNumberLabel.setText("Номер группы: " + groupNumber); + groupNumberLabel.setText("Номер группы: " + lastRequestedGroupNumber); return true; }); @@ -132,7 +179,7 @@ public class CustomDataSettingsWindow extends BaseModalWindow { gui, "Средний балл", "Введите средний балл от 0.0 до 5.0:", - averageGrade == null ? null : String.valueOf(averageGrade), + lastRequestedAverageGrade == null ? null : String.valueOf(lastRequestedAverageGrade), value -> { try { double parsedValue = Double.parseDouble(value); @@ -145,11 +192,11 @@ public class CustomDataSettingsWindow extends BaseModalWindow { return false; } - averageGrade = parsedValue; + lastRequestedAverageGrade = parsedValue; studentBuilder.setAverageGrade(parsedValue); - averageGradeLabel.setText("Средний балл: " + averageGrade); + averageGradeLabel.setText("Средний балл: " + lastRequestedAverageGrade); return true; @@ -169,7 +216,9 @@ public class CustomDataSettingsWindow extends BaseModalWindow { gui, "Номер зачётной книжки", "Введите номер зачётной книжки:", - recordBookNumber == null ? null : String.valueOf(recordBookNumber), + lastRequestedRecordBookNumber == null + ? null + : String.valueOf(lastRequestedRecordBookNumber), value -> { try { int parsedValue = Integer.parseInt(value); @@ -181,11 +230,12 @@ public class CustomDataSettingsWindow extends BaseModalWindow { return false; } - recordBookNumber = parsedValue; + lastRequestedRecordBookNumber = parsedValue; studentBuilder.setRecordBookNumber(parsedValue); - recordBookNumberLabel.setText("Номер зачётной книжки: " + recordBookNumber); + recordBookNumberLabel.setText( + "Номер зачётной книжки: " + lastRequestedRecordBookNumber); return true; @@ -205,34 +255,31 @@ public class CustomDataSettingsWindow extends BaseModalWindow { gui, "Индекс объекта", "Введите индекс объекта:", - selectedObjectIndex == null ? null : String.valueOf(selectedObjectIndex), + lastRequestedSelectedObjectIndex == null + ? null + : String.valueOf(lastRequestedSelectedObjectIndex), value -> { try { int index = Integer.parseInt(value); if (index < 0) { MessageWindow.showModal(gui, "Индекс объекта не может быть отрицательным."); - return false; } - int size = requestedCollectionSize.get(); - - if (index >= size) { + if (index >= preparedCustomDataCollection.size()) { MessageWindow.showModal(gui, "Индекс должен быть меньше размера коллекции."); - return false; } - selectedObjectIndex = index; + lastRequestedSelectedObjectIndex = index; - indexLabel.setText("Индекс объекта: " + selectedObjectIndex); + indexLabel.setText("Индекс объекта: " + lastRequestedSelectedObjectIndex); return true; } catch (NumberFormatException e) { MessageWindow.showModal(gui, "Введите целое число."); - return false; } }); @@ -246,28 +293,28 @@ public class CustomDataSettingsWindow extends BaseModalWindow { return; } - if (selectedObjectIndex == null) { + if (lastRequestedSelectedObjectIndex == null) { MessageWindow.showModal(gui, "Сначала задайте индекс объекта."); return; } - if (selectedObjectIndex >= preparedCustomDataCollection.size()) { + if (lastRequestedSelectedObjectIndex >= preparedCustomDataCollection.size()) { MessageWindow.showModal(gui, "Индекс объекта выходит за границы подготовленной коллекции."); return; } Student student = studentBuilder.build(); - preparedCustomDataCollection.set(selectedObjectIndex, student); + preparedCustomDataCollection.set(lastRequestedSelectedObjectIndex, student); MessageWindow.showModal(gui, "Данные объекта успешно заданы."); } private void apply() { - state.setCustomDataCollection(preparedCustomDataCollection); - prepareCustomDataCollection(); + settings.setCollection(preparedCustomDataCollection); + onApply.accept(settings); - MessageWindow.showModal(gui, "Пользовательские данные успешно применены."); + MessageWindow.showModal(gui, "Настройки создания коллекции вручную успешно применены."); } private void preview() { @@ -284,6 +331,8 @@ public class CustomDataSettingsWindow extends BaseModalWindow { } private boolean hasStudentData() { - return groupNumber != null && averageGrade != null && recordBookNumber != null; + return lastRequestedGroupNumber != null + && lastRequestedAverageGrade != null + && lastRequestedRecordBookNumber != null; } } diff --git a/app/src/main/java/ru/project/ui/creation/manual/ManualFillSettings.java b/app/src/main/java/ru/project/ui/creation/manual/ManualFillSettings.java new file mode 100644 index 0000000..ff82f46 --- /dev/null +++ b/app/src/main/java/ru/project/ui/creation/manual/ManualFillSettings.java @@ -0,0 +1,22 @@ +package ru.project.ui.creation.manual; + +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.student.Student; + +public class ManualFillSettings { + + private MyList manualDataCollection; + + public ManualFillSettings() { + this.manualDataCollection = new MyLinkedList<>(); + } + + public MyList getCollection() { + return manualDataCollection; + } + + public void setCollection(MyList collection) { + this.manualDataCollection = collection; + } +} diff --git a/app/src/main/java/ru/project/ui/creation/manual/ManualFillStrategy.java b/app/src/main/java/ru/project/ui/creation/manual/ManualFillStrategy.java new file mode 100644 index 0000000..1a5c54e --- /dev/null +++ b/app/src/main/java/ru/project/ui/creation/manual/ManualFillStrategy.java @@ -0,0 +1,28 @@ +package ru.project.ui.creation.manual; + +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.student.Student; +import ru.project.ui.creation.FillStrategy; + +public class ManualFillStrategy implements FillStrategy { + + private final ManualFillSettings settings; + + public ManualFillStrategy(ManualFillSettings settings) { + this.settings = settings; + } + + @Override + public MyList create() { + MyList mainCollection = new MyLinkedList<>(); + + MyList manualDataCollection = settings.getCollection(); + + for (int i = 0; i < manualDataCollection.size(); i++) { + mainCollection.add(manualDataCollection.get(i)); + } + + return mainCollection; + } +} From 57c20ab7fee37505e4ba49ff231d19be1beda3e5 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 23:47:53 +0400 Subject: [PATCH 091/156] feat(creation): add file-based fill settings and strategy - add FileFillSettings and FileFillStrategy - add file path type selection - add FileDataSettingsWindow for file configuration --- .../creation/file/FileDataSettingsWindow.java | 78 +++++++++++++++++++ .../ui/creation/file/FileFillSettings.java | 18 +++++ .../ui/creation/file/FileFillStrategy.java | 19 +++++ .../ui/creation/file/FilePathType.java | 6 ++ .../file/FilePathTypeSelectionWindow.java | 34 ++++++++ 5 files changed, 155 insertions(+) create mode 100644 app/src/main/java/ru/project/ui/creation/file/FileDataSettingsWindow.java create mode 100644 app/src/main/java/ru/project/ui/creation/file/FileFillSettings.java create mode 100644 app/src/main/java/ru/project/ui/creation/file/FileFillStrategy.java create mode 100644 app/src/main/java/ru/project/ui/creation/file/FilePathType.java create mode 100644 app/src/main/java/ru/project/ui/creation/file/FilePathTypeSelectionWindow.java diff --git a/app/src/main/java/ru/project/ui/creation/file/FileDataSettingsWindow.java b/app/src/main/java/ru/project/ui/creation/file/FileDataSettingsWindow.java new file mode 100644 index 0000000..bf1b7d4 --- /dev/null +++ b/app/src/main/java/ru/project/ui/creation/file/FileDataSettingsWindow.java @@ -0,0 +1,78 @@ +package ru.project.ui.creation.file; + +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 com.googlecode.lanterna.gui2.dialogs.FileDialog; +import com.googlecode.lanterna.gui2.dialogs.FileDialogBuilder; +import java.io.File; +import java.util.function.Consumer; +import ru.project.ui.base.BaseModalWindow; + +public class FileDataSettingsWindow extends BaseModalWindow { + + // private final StudentBuilder studentBuilder; + + private final FileFillSettings settings; + private final Consumer onApply; + + private final Label filePathLabel; + + public FileDataSettingsWindow(WindowBasedTextGUI gui, Consumer onApply) { + + super("Данные из файла", gui); + + this.onApply = onApply; + + this.settings = new FileFillSettings(); + // this.studentBuilder = new StudentBuilder(); + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + filePathLabel = new Label("Абсолютный путь: " + getFilePathLabel()); + + panel.addComponent(filePathLabel); + + panel.addComponent(new Button("Выбрать файл", this::selectFile)); + + panel.addComponent(new Button("Применить", this::apply)); + + panel.addComponent(new Button("Выйти", this::close)); + + setComponent(panel); + } + + private void selectFile() { + FileDialog fileDialog = + new FileDialogBuilder() + .setTitle("Выбор файла") + .setDescription("Выберите файл:") + .setActionLabel("Открыть") + .build(); + + File selectedFile = fileDialog.showDialog(gui); + + if (selectedFile == null) { + return; + } + + settings.setAbsoluteFilePath(selectedFile.getAbsolutePath()); + + filePathLabel.setText("Абсолютный путь: " + settings.getAbsoluteFilePath()); + } + + private void apply() { + onApply.accept(settings); + + close(); + } + + private String getFilePathLabel() { + String filePath = settings.getAbsoluteFilePath(); + + return filePath == null || filePath.isBlank() ? "не задан" : filePath; + } +} diff --git a/app/src/main/java/ru/project/ui/creation/file/FileFillSettings.java b/app/src/main/java/ru/project/ui/creation/file/FileFillSettings.java new file mode 100644 index 0000000..db7e9cc --- /dev/null +++ b/app/src/main/java/ru/project/ui/creation/file/FileFillSettings.java @@ -0,0 +1,18 @@ +package ru.project.ui.creation.file; + +public class FileFillSettings { + + private String absoluteFilePath; + + public FileFillSettings() { + this.absoluteFilePath = null; + } + + public String getAbsoluteFilePath() { + return absoluteFilePath; + } + + public void setAbsoluteFilePath(String absoluteFilePath) { + this.absoluteFilePath = absoluteFilePath; + } +} diff --git a/app/src/main/java/ru/project/ui/creation/file/FileFillStrategy.java b/app/src/main/java/ru/project/ui/creation/file/FileFillStrategy.java new file mode 100644 index 0000000..51f14f8 --- /dev/null +++ b/app/src/main/java/ru/project/ui/creation/file/FileFillStrategy.java @@ -0,0 +1,19 @@ +package ru.project.ui.creation.file; + +import ru.project.collection.MyList; +import ru.project.student.Student; +import ru.project.ui.creation.FillStrategy; + +public class FileFillStrategy implements FillStrategy { + + // private final FileFillSettings settings; + + public FileFillStrategy(FileFillSettings settings) { + // this.settings = settings; + } + + @Override + public MyList create() { + return null; + } +} diff --git a/app/src/main/java/ru/project/ui/creation/file/FilePathType.java b/app/src/main/java/ru/project/ui/creation/file/FilePathType.java new file mode 100644 index 0000000..a460446 --- /dev/null +++ b/app/src/main/java/ru/project/ui/creation/file/FilePathType.java @@ -0,0 +1,6 @@ +package ru.project.ui.creation.file; + +public enum FilePathType { + ABSOLUTE, + RELATIVE +} diff --git a/app/src/main/java/ru/project/ui/creation/file/FilePathTypeSelectionWindow.java b/app/src/main/java/ru/project/ui/creation/file/FilePathTypeSelectionWindow.java new file mode 100644 index 0000000..83dfd4a --- /dev/null +++ b/app/src/main/java/ru/project/ui/creation/file/FilePathTypeSelectionWindow.java @@ -0,0 +1,34 @@ +package ru.project.ui.creation.file; + +import com.googlecode.lanterna.gui2.ActionListBox; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.Consumer; +import ru.project.ui.base.BaseModalWindow; + +public class FilePathTypeSelectionWindow extends BaseModalWindow { + + private final Consumer onPathTypeSelected; + + public FilePathTypeSelectionWindow( + WindowBasedTextGUI gui, Consumer onPathTypeSelected) { + + super("Выбор типа пути", gui); + + this.onPathTypeSelected = onPathTypeSelected; + + ActionListBox menu = new ActionListBox(); + + menu.addItem("Абсолютный", () -> selectPathType(FilePathType.ABSOLUTE)); + + menu.addItem("Относительный", () -> selectPathType(FilePathType.RELATIVE)); + + menu.addItem("Выйти", this::close); + + setComponent(menu); + } + + private void selectPathType(FilePathType pathType) { + onPathTypeSelected.accept(pathType); + close(); + } +} From c5cf12827a2c8395587ec841220cdd995d3839e4 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, 31 Aug 2026 00:35:45 +0300 Subject: [PATCH 092/156] feat: added the get-all method and tests --- .../ru/project/collection/MyLinkedList.java | 8 ++ .../java/ru/project/collection/MyList.java | 1 + .../project/collection/MyLinkedListTest.java | 130 ++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 app/src/test/java/ru/project/collection/MyLinkedListTest.java diff --git a/app/src/main/java/ru/project/collection/MyLinkedList.java b/app/src/main/java/ru/project/collection/MyLinkedList.java index 23bbad0..a919309 100644 --- a/app/src/main/java/ru/project/collection/MyLinkedList.java +++ b/app/src/main/java/ru/project/collection/MyLinkedList.java @@ -79,6 +79,14 @@ public class MyLinkedList implements MyList { public boolean isEmpty() { return size == 0; } + public void addAll(MyList collection) { + if (collection == null || collection.isEmpty()) { + return; + } + for (int i = 0; i < collection.size(); i++) { + add(collection.get(i)); + } + } @Override public String toString() { diff --git a/app/src/main/java/ru/project/collection/MyList.java b/app/src/main/java/ru/project/collection/MyList.java index bd47567..27af25d 100644 --- a/app/src/main/java/ru/project/collection/MyList.java +++ b/app/src/main/java/ru/project/collection/MyList.java @@ -13,4 +13,5 @@ public interface MyList { int size(); boolean isEmpty(); + void addAll(MyList collection); } diff --git a/app/src/test/java/ru/project/collection/MyLinkedListTest.java b/app/src/test/java/ru/project/collection/MyLinkedListTest.java new file mode 100644 index 0000000..6c43673 --- /dev/null +++ b/app/src/test/java/ru/project/collection/MyLinkedListTest.java @@ -0,0 +1,130 @@ +package ru.test.maven.spring; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +public class MyLinkedListTest +{ + @Test + public void shouldTestAdd() { + MyLinkedList list = new MyLinkedList<>(); + list.add(10); + list.add(20); + list.add(30); + + assertEquals(10, list.get(0)); + assertEquals(20, list.get(1)); + assertEquals(30, list.get(2)); + } + @Test + public void shouldTestGet() + { + MyLinkedList list = new MyLinkedList<>(); + list.add(10); + assertEquals(10, list.get(0)); + } + @Test + public void shouldTestRemove() { + MyLinkedList list = new MyLinkedList<>(); + list.add(10); + list.add(20); + list.add(30); + list.remove(0); + + assertEquals(2, list.size()); + assertEquals(20, list.get(0)); + assertEquals(30, list.get(1)); + } + @Test + void shouldTestSet() { + MyLinkedList list = new MyLinkedList<>(); + list.add("A"); + list.add("B"); + list.add("C"); + + String oldValue = list.set(1, "Z"); + + assertEquals("B", oldValue); + assertEquals("Z", list.get(1)); + assertEquals(3, list.size()); + + } + @Test + void shouldTestSize() + { + MyLinkedList list = new MyLinkedList<>(); + list.add(1); + list.add(2); + list.add(3); + assertEquals(3, list.size()); + } + @Test + void shouldTestisEmpty() + { + MyLinkedList list = new MyLinkedList<>(); + assertTrue(list.isEmpty()); + } + @Test + void shouldTestGet_OutOfBounds_ThrowsException() { + MyLinkedList list = new MyLinkedList<>(); + list.add(1); + + assertThrows(IllegalArgumentException.class, () -> list.get(-1)); + assertThrows(IllegalArgumentException.class, () -> list.get(1)); // index == size + assertThrows(IllegalArgumentException.class, () -> list.get(5)); // index > size + } + @Test + void shouldTestSet_OutOfBounds_ThrowsException() { + MyLinkedList list = new MyLinkedList<>(); + list.add(1); + + assertThrows(IllegalArgumentException.class, () -> list.set(-1, 99)); + assertThrows(IllegalArgumentException.class, () -> list.set(1, 99)); + } + @Test + void shouldTestRemove_IndexGreaterThanSize_ThrowsException() { + MyLinkedList list = new MyLinkedList<>(); + list.add("man"); + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(5)); + } + @Test + void shouldTestRemove_NegativeIndex_ThrowsException() { + MyLinkedList list = new MyLinkedList<>(); + list.add("1"); + list.add("2"); + + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1)); + } + @Test + void shouldTestaddAll_nonEmptySource_toEmptyList_addsAllElementsInOrder() { + MyList list = new MyLinkedList<>(); + MyList source = new MyLinkedList<>(); + source.add("A"); + source.add("B"); + source.add("C"); + + list.addAll(source); + + assertEquals(3, list.size()); + assertEquals("A", list.get(0)); + assertEquals("B", list.get(1)); + assertEquals("C", list.get(2)); + } + + @Test + void shouldTestaddAll_toNonEmptyList_appendsAtEnd() { + MyList list = new MyLinkedList<>(); + list.add("X"); + list.add("Y"); + + MyList source = new MyLinkedList<>(); + source.add("A"); + source.add("B"); + + list.addAll(source); + + assertEquals(4, list.size()); + assertEquals("X", list.get(0)); + assertEquals("Y", list.get(1)); + assertEquals("A", list.get(2)); + assertEquals("B", list.get(3)); + } +} From 482184d9c8a0aead71f62e031b1f996642ecec31 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: Mon, 31 Aug 2026 11:05:58 +0400 Subject: [PATCH 093/156] refactor(creation): remove file path type selection --- .../ui/creation/file/FilePathType.java | 6 ---- .../file/FilePathTypeSelectionWindow.java | 34 ------------------- 2 files changed, 40 deletions(-) delete mode 100644 app/src/main/java/ru/project/ui/creation/file/FilePathType.java delete mode 100644 app/src/main/java/ru/project/ui/creation/file/FilePathTypeSelectionWindow.java diff --git a/app/src/main/java/ru/project/ui/creation/file/FilePathType.java b/app/src/main/java/ru/project/ui/creation/file/FilePathType.java deleted file mode 100644 index a460446..0000000 --- a/app/src/main/java/ru/project/ui/creation/file/FilePathType.java +++ /dev/null @@ -1,6 +0,0 @@ -package ru.project.ui.creation.file; - -public enum FilePathType { - ABSOLUTE, - RELATIVE -} diff --git a/app/src/main/java/ru/project/ui/creation/file/FilePathTypeSelectionWindow.java b/app/src/main/java/ru/project/ui/creation/file/FilePathTypeSelectionWindow.java deleted file mode 100644 index 83dfd4a..0000000 --- a/app/src/main/java/ru/project/ui/creation/file/FilePathTypeSelectionWindow.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.project.ui.creation.file; - -import com.googlecode.lanterna.gui2.ActionListBox; -import com.googlecode.lanterna.gui2.WindowBasedTextGUI; -import java.util.function.Consumer; -import ru.project.ui.base.BaseModalWindow; - -public class FilePathTypeSelectionWindow extends BaseModalWindow { - - private final Consumer onPathTypeSelected; - - public FilePathTypeSelectionWindow( - WindowBasedTextGUI gui, Consumer onPathTypeSelected) { - - super("Выбор типа пути", gui); - - this.onPathTypeSelected = onPathTypeSelected; - - ActionListBox menu = new ActionListBox(); - - menu.addItem("Абсолютный", () -> selectPathType(FilePathType.ABSOLUTE)); - - menu.addItem("Относительный", () -> selectPathType(FilePathType.RELATIVE)); - - menu.addItem("Выйти", this::close); - - setComponent(menu); - } - - private void selectPathType(FilePathType pathType) { - onPathTypeSelected.accept(pathType); - close(); - } -} From 6c97f036d84039b7a1c84c2ca8f0e66ae1a40d91 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: Mon, 31 Aug 2026 11:22:30 +0400 Subject: [PATCH 094/156] docs: add random student generator task --- docs/tasks/RandomStudentGenerator.md | 305 +++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 docs/tasks/RandomStudentGenerator.md diff --git a/docs/tasks/RandomStudentGenerator.md b/docs/tasks/RandomStudentGenerator.md new file mode 100644 index 0000000..3e902f9 --- /dev/null +++ b/docs/tasks/RandomStudentGenerator.md @@ -0,0 +1,305 @@ +# Техническое задание: генератор случайных студентов + +## Задача + +Реализовать класс для формирования `MyList` со случайными значениями. + +По умолчанию ограничения диапазона для генерации не задаются. В таком случае значения должны генерироваться во всём допустимом диапазоне, установленном правилами класса `Student`. + +Генерация объектов должна выполняться через существующий `StudentBuilder`. + +## Название класса + +```text id="m9s0q1" +RandomStudentGenerator +``` + +## Контракт класса + +Класс должен хранить текущие ограничения генерации для каждого поля `Student`. + +Для каждого поля необходимо предоставить по два метода: левое и правое ограничение. + +### `groupNumber` + +Тип поля: `String`. + +```java id="t2z8k4" +RandomStudentGenerator setMinGroupNumber(String minGroupNumber); +``` + +```java id="q7x5n1" +RandomStudentGenerator setMaxGroupNumber(String maxGroupNumber); +``` + +Ограничения применяются при генерации `groupNumber`. + +Сравнение границ выполнять в соответствии с правилами, используемыми для `groupNumber` в `Student`. + +### `averageGrade` + +Тип поля: `double`. + +```java id="v4n2j8" +RandomStudentGenerator setMinAverageGrade(double minAverageGrade); +``` + +```java id="r5c1p9" +RandomStudentGenerator setMaxAverageGrade(double maxAverageGrade); +``` + +Диапазон должен быть включительным: + +```text id="u3k7w2" +minAverageGrade <= averageGrade <= maxAverageGrade +``` + +### `recordBookNumber` + +Тип поля: `int`. + +```java id="d8m4x6" +RandomStudentGenerator setMinRecordBookNumber(int minRecordBookNumber); +``` + +```java id="e1q9s5" +RandomStudentGenerator setMaxRecordBookNumber(int maxRecordBookNumber); +``` + +Диапазон должен быть включительным: + +```text id="y6p2v8" +minRecordBookNumber <= recordBookNumber <= maxRecordBookNumber +``` + +## Метод генерации + +Основной метод: + +```java id="k3w7m1" +MyList generate(int size); +``` + +Контракт: + +- создаёт ровно `size` объектов `Student`; +- все объекты должны быть валидными; +- каждый объект создаётся через `StudentBuilder`; +- результат возвращается как `MyList`; +- стандартные `List`, `ArrayList`, `LinkedList` и другие контейнеры использовать нельзя. + +## Поведение диапазонов по умолчанию + +Если для поля ограничение не задано, используется весь допустимый диапазон этого поля. + +Необходимо учитывать существующие ограничения `Student`, в частности: + +```text id="z4r8t2" +averageGrade: +getMinAverageGrade() +getMaxAverageGrade() + +recordBookNumber: +getMinRecordBookNumber() +``` + +Для `groupNumber` использовать существующий шаблон: + +```text id="n5c7y3" +getGroupNumberPattern() +``` + +Генерируемое значение `groupNumber` должно соответствовать требованиям `Student`. + +## Проверка ограничений + +При установке границ необходимо проверять их корректность. + +Недопустимые случаи: + +```text id="h2k6p4" +min > max +``` + +Для них выбрасывать: + +```java id="c7v3n9" +IllegalArgumentException +``` + +Ограничение также не должно выходить за допустимые значения `Student`. + +## Рекомендуемая реализация + +Класс должен хранить шесть ограничений: + +```text id="b5q9x2" +minGroupNumber +maxGroupNumber + +minAverageGrade +maxAverageGrade + +minRecordBookNumber +maxRecordBookNumber +``` + +Для каждого поля значения генерируются независимо. + +Рекомендуется использовать стандартный генератор случайных чисел Java, например: + +```java id="a8f4k1" +Random +``` + +или: + +```java id="p3m7d5" +ThreadLocalRandom +``` + +При создании `Student`: + +```text id="w6j2r9" +сгенерировать поля + ↓ +StudentBuilder + ↓ +build() + ↓ +Student + ↓ +MyList +``` + +Дополнительную валидацию, дублирующую правила `Student`, в генераторе не реализовывать. + +## Примеры использования + +Без настройки диапазонов: + +```java id="s2k8m4" +RandomStudentGenerator generator = + new RandomStudentGenerator(); + +MyList students = + generator.generate(100); +``` + +С настройкой диапазонов: + +```java id="f7q3n1" +RandomStudentGenerator generator = + new RandomStudentGenerator() + .setMinGroupNumber("A00") + .setMaxGroupNumber("Z99") + .setMinAverageGrade(3.0) + .setMaxAverageGrade(5.0) + .setMinRecordBookNumber(10000) + .setMaxRecordBookNumber(99999); + +MyList students = + generator.generate(100); +``` + +## Тесты + +### Генерация без ограничений + +```java id="r1w5p8" +MyList result = generator.generate(100); +``` + +Проверить: + +- размер равен `100`; +- все элементы не равны `null`; +- каждый `Student` валиден. + +### Ограничение `averageGrade` + +Задать: + +```text id="x4n7k2" +3.0 — 4.0 +``` + +Проверить, что все значения находятся в указанном диапазоне. + +### Ограничение `recordBookNumber` + +Задать: + +```text id="q9m3v6" +10000 — 20000 +``` + +Проверить диапазон и включение границ. + +### Ограничение `groupNumber` + +Задать левую и правую границы. + +Проверить, что все сгенерированные значения находятся между ними согласно правилам сравнения `groupNumber`. + +### Граничные значения + +Установить: + +```text id="p8c4s1" +min == max +``` + +Проверить, что все сгенерированные значения равны указанной границе. + +### Некорректный диапазон + +Проверить выброс `IllegalArgumentException`, если: + +```text id="z6t2h5" +min > max +``` + +### Некорректное ограничение + +Передать диапазон, выходящий за допустимые значения `Student`. + +Проверить выброс исключения. + +### Генерация пустой коллекции + +Для: + +```java id="j5r8w3" +generate(0) +``` + +ожидается пустой `MyList`. + +### Некорректный размер + +Для отрицательного размера: + +```java id="n2k7p4" +generate(-1) +``` + +ожидается `IllegalArgumentException`. + +## Результат + +Необходимо реализовать: + +```text id="v8m3q6" +RandomStudentGenerator.java +``` + +Класс должен использовать существующие: + +```text id="f1j9x4" +Student.java +StudentBuilder.java +MyList.java +``` + +Изменять интерфейсы `Student`, `StudentBuilder` и `MyList` для выполнения данного задания не требуется. From 7d646c7fa716c3e2f11bd31fecf521e75fa5cd96 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: Mon, 31 Aug 2026 11:38:28 +0400 Subject: [PATCH 095/156] feat(creation): add random fill settings and strategy --- .../ui/creation/CreateCollectionMenu.java | 15 +- .../random/RandomDataSettingsWindow.java | 313 ++++++++++++++++++ .../creation/random/RandomFillSettings.java | 72 ++++ .../creation/random/RandomFillStrategy.java | 19 ++ 4 files changed, 413 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/ru/project/ui/creation/random/RandomDataSettingsWindow.java create mode 100644 app/src/main/java/ru/project/ui/creation/random/RandomFillSettings.java create mode 100644 app/src/main/java/ru/project/ui/creation/random/RandomFillStrategy.java diff --git a/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java b/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java index e56c4aa..da542b9 100644 --- a/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java @@ -14,6 +14,9 @@ import ru.project.ui.creation.file.FileFillStrategy; import ru.project.ui.creation.manual.ManualDataSettingsWindow; import ru.project.ui.creation.manual.ManualFillSettings; import ru.project.ui.creation.manual.ManualFillStrategy; +import ru.project.ui.creation.random.RandomDataSettingsWindow; +import ru.project.ui.creation.random.RandomFillSettings; +import ru.project.ui.creation.random.RandomFillStrategy; public class CreateCollectionMenu extends BaseModalWindow { @@ -26,6 +29,7 @@ public class CreateCollectionMenu extends BaseModalWindow { private final ManualDataSettingsWindow manualDataSettingsWindow; private final FileDataSettingsWindow fileDataSettingsWindow; + private final RandomDataSettingsWindow randomDataSettingsWindow; public CreateCollectionMenu(WindowBasedTextGUI gui, AppState state) { @@ -37,6 +41,7 @@ public class CreateCollectionMenu extends BaseModalWindow { this.manualDataSettingsWindow = new ManualDataSettingsWindow(gui, this::setManualFillStrategy); this.fileDataSettingsWindow = new FileDataSettingsWindow(gui, this::setFileFillStrategy); + this.randomDataSettingsWindow = new RandomDataSettingsWindow(gui, this::setRandomFillStrategy); Panel panel = new Panel(); panel.setLayoutManager(new LinearLayout()); @@ -65,9 +70,9 @@ public class CreateCollectionMenu extends BaseModalWindow { fillStrategy = new FileFillStrategy(settings); } - // private void setRandomFillStrategy(RandomFillSettings settings) { - // fillStrategy = new RandomFillStrategy(settings); - // } + private void setRandomFillStrategy(RandomFillSettings settings) { + fillStrategy = new RandomFillStrategy(settings); + } private void setupCurrentFillMethod() { switch (selectedFillMethod) { @@ -75,9 +80,7 @@ public class CreateCollectionMenu extends BaseModalWindow { case FILE -> fileDataSettingsWindow.showModal(); - case RANDOM -> - MessageWindow.showModal( - gui, "Для случайных данных дополнительные настройки не требуются."); + case RANDOM -> randomDataSettingsWindow.showModal(); } } diff --git a/app/src/main/java/ru/project/ui/creation/random/RandomDataSettingsWindow.java b/app/src/main/java/ru/project/ui/creation/random/RandomDataSettingsWindow.java new file mode 100644 index 0000000..fcba4e1 --- /dev/null +++ b/app/src/main/java/ru/project/ui/creation/random/RandomDataSettingsWindow.java @@ -0,0 +1,313 @@ +package ru.project.ui.creation.random; + +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 java.util.function.Consumer; +import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.common.InputValueWindow; +import ru.project.ui.common.MessageWindow; + +public class RandomDataSettingsWindow extends BaseModalWindow { + + private final RandomFillSettings settings; + private final Consumer onApply; + + private final Label minGroupNumberLabel; + private final Label maxGroupNumberLabel; + private final Label minAverageGradeLabel; + private final Label maxAverageGradeLabel; + private final Label minRecordBookNumberLabel; + private final Label maxRecordBookNumberLabel; + + public RandomDataSettingsWindow(WindowBasedTextGUI gui, Consumer onApply) { + + super("Настройка случайного заполнения", gui); + + this.settings = new RandomFillSettings(); + this.onApply = onApply; + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + minGroupNumberLabel = + new Label("Минимальный номер группы: " + getValueOrDefault(settings.getMinGroupNumber())); + + maxGroupNumberLabel = + new Label("Максимальный номер группы: " + getValueOrDefault(settings.getMaxGroupNumber())); + + minAverageGradeLabel = + new Label("Минимальный средний балл: " + getValueOrDefault(settings.getMinAverageGrade())); + + maxAverageGradeLabel = + new Label("Максимальный средний балл: " + getValueOrDefault(settings.getMaxAverageGrade())); + + minRecordBookNumberLabel = + new Label( + "Минимальный номер зачётной книжки: " + + getValueOrDefault(settings.getMinRecordBookNumber())); + + maxRecordBookNumberLabel = + new Label( + "Максимальный номер зачётной книжки: " + + getValueOrDefault(settings.getMaxRecordBookNumber())); + + panel.addComponent(minGroupNumberLabel); + panel.addComponent(maxGroupNumberLabel); + panel.addComponent(minAverageGradeLabel); + panel.addComponent(maxAverageGradeLabel); + panel.addComponent(minRecordBookNumberLabel); + panel.addComponent(maxRecordBookNumberLabel); + + panel.addComponent(new Button("Задать минимальный номер группы", this::setMinGroupNumber)); + + panel.addComponent(new Button("Задать максимальный номер группы", this::setMaxGroupNumber)); + + panel.addComponent(new Button("Задать минимальный средний балл", this::setMinAverageGrade)); + + panel.addComponent(new Button("Задать максимальный средний балл", this::setMaxAverageGrade)); + + panel.addComponent( + new Button("Задать минимальный номер зачётной книжки", this::setMinRecordBookNumber)); + + panel.addComponent( + new Button("Задать максимальный номер зачётной книжки", this::setMaxRecordBookNumber)); + + panel.addComponent(new Button("Применить", this::apply)); + + panel.addComponent(new Button("Назад", this::close)); + + setComponent(panel); + } + + private void setMinGroupNumber() { + showGroupNumberInput( + "Минимальный номер группы", + settings.getMinGroupNumber(), + value -> { + if (!value.matches("[A-Z]\\d{2}")) { + + MessageWindow.showModal(gui, "Номер группы должен соответствовать формату A12."); + + return false; + } + + if (settings.getMaxGroupNumber() != null + && value.compareTo(settings.getMaxGroupNumber()) > 0) { + + MessageWindow.showModal( + gui, "Минимальный номер группы не может быть больше максимального."); + + return false; + } + + settings.setMinGroupNumber(value); + + minGroupNumberLabel.setText("Минимальный номер группы: " + value); + + return true; + }); + } + + private void setMaxGroupNumber() { + showGroupNumberInput( + "Максимальный номер группы", + settings.getMaxGroupNumber(), + value -> { + if (!value.matches("[A-Z]\\d{2}")) { + + MessageWindow.showModal(gui, "Номер группы должен соответствовать формату A12."); + + return false; + } + + if (settings.getMinGroupNumber() != null + && value.compareTo(settings.getMinGroupNumber()) < 0) { + + MessageWindow.showModal( + gui, "Максимальный номер группы не может быть меньше минимального."); + + return false; + } + + settings.setMaxGroupNumber(value); + + maxGroupNumberLabel.setText("Максимальный номер группы: " + value); + + return true; + }); + } + + private void showGroupNumberInput( + String title, String initialValue, java.util.function.Predicate onSave) { + + InputValueWindow window = + new InputValueWindow( + gui, title, "Введите номер группы (например, A12):", initialValue, onSave); + + window.showModal(); + } + + private void setMinAverageGrade() { + showAverageGradeInput( + "Минимальный средний балл", + settings.getMinAverageGrade(), + value -> { + if (settings.getMaxAverageGrade() != null && value > settings.getMaxAverageGrade()) { + + MessageWindow.showModal( + gui, "Минимальный средний балл не может быть больше максимального."); + + return false; + } + + settings.setMinAverageGrade(value); + + minAverageGradeLabel.setText("Минимальный средний балл: " + value); + + return true; + }); + } + + private void setMaxAverageGrade() { + showAverageGradeInput( + "Максимальный средний балл", + settings.getMaxAverageGrade(), + value -> { + if (settings.getMinAverageGrade() != null && value < settings.getMinAverageGrade()) { + + MessageWindow.showModal( + gui, "Максимальный средний балл не может быть меньше минимального."); + + return false; + } + + settings.setMaxAverageGrade(value); + + maxAverageGradeLabel.setText("Максимальный средний балл: " + value); + + return true; + }); + } + + private void showAverageGradeInput( + String title, Double initialValue, java.util.function.Predicate onSave) { + + InputValueWindow window = + new InputValueWindow( + gui, + title, + "Введите значение от " + "0.0 до 5.0:", + initialValue == null ? null : String.valueOf(initialValue), + value -> { + try { + double parsedValue = Double.parseDouble(value); + + if (parsedValue < ru.project.student.Student.getMinAverageGrade() + || parsedValue > ru.project.student.Student.getMaxAverageGrade()) { + + MessageWindow.showModal(gui, "Средний балл должен быть от 0.0 до 5.0."); + + return false; + } + + return onSave.test(parsedValue); + + } catch (NumberFormatException e) { + MessageWindow.showModal(gui, "Введите корректное число."); + + return false; + } + }); + + window.showModal(); + } + + private void setMinRecordBookNumber() { + showRecordBookNumberInput( + "Минимальный номер зачётной книжки", + settings.getMinRecordBookNumber(), + value -> { + if (settings.getMaxRecordBookNumber() != null + && value > settings.getMaxRecordBookNumber()) { + + MessageWindow.showModal( + gui, "Минимальный номер зачётной книжки не может быть больше максимального."); + + return false; + } + + settings.setMinRecordBookNumber(value); + + minRecordBookNumberLabel.setText("Минимальный номер зачётной книжки: " + value); + + return true; + }); + } + + private void setMaxRecordBookNumber() { + showRecordBookNumberInput( + "Максимальный номер зачётной книжки", + settings.getMaxRecordBookNumber(), + value -> { + if (settings.getMinRecordBookNumber() != null + && value < settings.getMinRecordBookNumber()) { + + MessageWindow.showModal( + gui, "Максимальный номер зачётной книжки не может быть меньше минимального."); + + return false; + } + + settings.setMaxRecordBookNumber(value); + + maxRecordBookNumberLabel.setText("Максимальный номер зачётной книжки: " + value); + + return true; + }); + } + + private void showRecordBookNumberInput( + String title, Integer initialValue, java.util.function.Predicate onSave) { + + InputValueWindow window = + new InputValueWindow( + gui, + title, + "Введите номер зачётной книжки:", + initialValue == null ? null : String.valueOf(initialValue), + value -> { + try { + int parsedValue = Integer.parseInt(value); + + if (parsedValue < ru.project.student.Student.getMinRecordBookNumber()) { + + MessageWindow.showModal(gui, "Номер зачётной книжки должен быть положительным."); + + return false; + } + + return onSave.test(parsedValue); + + } catch (NumberFormatException e) { + MessageWindow.showModal(gui, "Введите целое число."); + + return false; + } + }); + + window.showModal(); + } + + private void apply() { + onApply.accept(settings); + + MessageWindow.showModal(gui, "Настройки создания коллекции случайно успешно применены."); + } + + private String getValueOrDefault(Object value) { + return value == null ? "не задан" : String.valueOf(value); + } +} diff --git a/app/src/main/java/ru/project/ui/creation/random/RandomFillSettings.java b/app/src/main/java/ru/project/ui/creation/random/RandomFillSettings.java new file mode 100644 index 0000000..eab4334 --- /dev/null +++ b/app/src/main/java/ru/project/ui/creation/random/RandomFillSettings.java @@ -0,0 +1,72 @@ +package ru.project.ui.creation.random; + +public class RandomFillSettings { + + private String minGroupNumber; + private String maxGroupNumber; + + private Double minAverageGrade; + private Double maxAverageGrade; + + private Integer minRecordBookNumber; + private Integer maxRecordBookNumber; + + public RandomFillSettings() { + this.minGroupNumber = null; + this.maxGroupNumber = null; + + this.minAverageGrade = null; + this.maxAverageGrade = null; + + this.minRecordBookNumber = null; + this.maxRecordBookNumber = null; + } + + public String getMinGroupNumber() { + return minGroupNumber; + } + + public void setMinGroupNumber(String minGroupNumber) { + this.minGroupNumber = minGroupNumber; + } + + public String getMaxGroupNumber() { + return maxGroupNumber; + } + + public void setMaxGroupNumber(String maxGroupNumber) { + this.maxGroupNumber = maxGroupNumber; + } + + public Double getMinAverageGrade() { + return minAverageGrade; + } + + public void setMinAverageGrade(Double minAverageGrade) { + this.minAverageGrade = minAverageGrade; + } + + public Double getMaxAverageGrade() { + return maxAverageGrade; + } + + public void setMaxAverageGrade(Double maxAverageGrade) { + this.maxAverageGrade = maxAverageGrade; + } + + public Integer getMinRecordBookNumber() { + return minRecordBookNumber; + } + + public void setMinRecordBookNumber(Integer minRecordBookNumber) { + this.minRecordBookNumber = minRecordBookNumber; + } + + public Integer getMaxRecordBookNumber() { + return maxRecordBookNumber; + } + + public void setMaxRecordBookNumber(Integer maxRecordBookNumber) { + this.maxRecordBookNumber = maxRecordBookNumber; + } +} diff --git a/app/src/main/java/ru/project/ui/creation/random/RandomFillStrategy.java b/app/src/main/java/ru/project/ui/creation/random/RandomFillStrategy.java new file mode 100644 index 0000000..bbebc93 --- /dev/null +++ b/app/src/main/java/ru/project/ui/creation/random/RandomFillStrategy.java @@ -0,0 +1,19 @@ +package ru.project.ui.creation.random; + +import ru.project.collection.MyList; +import ru.project.student.Student; +import ru.project.ui.creation.FillStrategy; + +public class RandomFillStrategy implements FillStrategy { + + // private final RandomFillSettings settings; + + public RandomFillStrategy(RandomFillSettings settings) { + // this.settings = settings; + } + + @Override + public MyList create() { + return null; + } +} From 32e53d8fc0ecea21a8eb1b959671f955f3856ed9 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: Mon, 31 Aug 2026 13:52:54 +0400 Subject: [PATCH 096/156] refactor(ui): reorganize collection menus and sorting - move MainMenu to the root ui package - add CurrentCollectionMenu for current collection operations - move sorting components under collection/sorting - add CollectionFileSaveWindow for saving collections to file - update imports and menu delegation --- app/src/main/java/ru/project/App.java | 2 +- app/src/main/java/ru/project/ui/MainMenu.java | 38 +++++++++ .../ui/collection/CurrentCollectionMenu.java | 64 +++++++++++++++ .../sorting/NormalSortSettingsWindow.java | 2 +- .../sorting/ParitySortSettingsWindow.java | 2 +- .../sorting/SortCollectionMenu.java | 2 +- .../{ => collection}/sorting/SortField.java | 2 +- .../{ => collection}/sorting/SortMethod.java | 2 +- .../sorting/SortMethodSelectionWindow.java | 2 +- .../ui/common/CollectionFileSaveWindow.java | 76 ++++++++++++++++++ .../java/ru/project/ui/main/MainMenu.java | 80 ------------------- 11 files changed, 185 insertions(+), 87 deletions(-) create mode 100644 app/src/main/java/ru/project/ui/MainMenu.java create mode 100644 app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java rename app/src/main/java/ru/project/ui/{ => collection}/sorting/NormalSortSettingsWindow.java (97%) rename app/src/main/java/ru/project/ui/{ => collection}/sorting/ParitySortSettingsWindow.java (94%) rename app/src/main/java/ru/project/ui/{ => collection}/sorting/SortCollectionMenu.java (98%) rename app/src/main/java/ru/project/ui/{ => collection}/sorting/SortField.java (65%) rename app/src/main/java/ru/project/ui/{ => collection}/sorting/SortMethod.java (52%) rename app/src/main/java/ru/project/ui/{ => collection}/sorting/SortMethodSelectionWindow.java (95%) create mode 100644 app/src/main/java/ru/project/ui/common/CollectionFileSaveWindow.java delete mode 100644 app/src/main/java/ru/project/ui/main/MainMenu.java diff --git a/app/src/main/java/ru/project/App.java b/app/src/main/java/ru/project/App.java index 73f5efa..2e2df6b 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.main.MainMenu; +import ru.project.ui.MainMenu; public final class App { 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..cf3c57f --- /dev/null +++ b/app/src/main/java/ru/project/ui/MainMenu.java @@ -0,0 +1,38 @@ +package ru.project.ui; + +import com.googlecode.lanterna.gui2.ActionListBox; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.model.AppState; +import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.collection.CurrentCollectionMenu; +import ru.project.ui.creation.CreateCollectionMenu; + +public class MainMenu extends BaseModalWindow { + + private final ActionListBox menu; + + private final BaseModalWindow currentCollectionMenu; + private final BaseModalWindow createCollectionMenu; + + public MainMenu(WindowBasedTextGUI gui, AppState state) { + super("Главное меню", gui); + + this.currentCollectionMenu = new CurrentCollectionMenu(gui, state); + + this.createCollectionMenu = new CreateCollectionMenu(gui, state); + + menu = new ActionListBox(); + + menu.addItem("Меню текущей коллекции", currentCollectionMenu::showModal); + + menu.addItem("Меню создания новой коллекции", createCollectionMenu::showModal); + + menu.addItem("Завершить выполнение программы", this::exit); + + setComponent(menu); + } + + private void exit() { + close(); + } +} diff --git a/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java b/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java new file mode 100644 index 0000000..8dad545 --- /dev/null +++ b/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java @@ -0,0 +1,64 @@ +package ru.project.ui.collection; + +import com.googlecode.lanterna.gui2.ActionListBox; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.model.AppState; +import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.collection.sorting.SortCollectionMenu; +import ru.project.ui.common.CollectionDisplayWindow; +import ru.project.ui.common.CollectionFileSaveWindow; +import ru.project.ui.common.MessageWindow; + +public class CurrentCollectionMenu extends BaseModalWindow { + + private final AppState state; + + private final SortCollectionMenu sortCollectionMenu; + + public CurrentCollectionMenu(WindowBasedTextGUI gui, AppState state) { + + super("Меню текущей коллекции", gui); + + this.state = state; + + this.sortCollectionMenu = new SortCollectionMenu(gui, state); + + ActionListBox menu = new ActionListBox(); + + menu.addItem("Показать коллекцию", this::showCurrentCollection); + + menu.addItem("Сохранить коллекцию в файл", this::saveCurrentCollection); + + menu.addItem("Сортировать коллекцию", sortCollectionMenu::showModal); + + menu.addItem("Поиск элемента в коллекции", this::searchCollection); + + menu.addItem("Подсчёт количества вхождений элемента", this::countOccurrences); + + menu.addItem("Выйти", this::close); + + setComponent(menu); + } + + private void showCurrentCollection() { + CollectionDisplayWindow window = new CollectionDisplayWindow(gui, state.getMainCollection()); + + window.showModal(); + } + + private void saveCurrentCollection() { + CollectionFileSaveWindow window = new CollectionFileSaveWindow(gui, state.getMainCollection()); + + window.showModal(); + } + + private void searchCollection() { + // TODO + MessageWindow.showModal(gui, "Не реализовано."); + } + + private void countOccurrences() { + // TODO + MessageWindow.showModal(gui, "Не реализовано."); + } +} diff --git a/app/src/main/java/ru/project/ui/sorting/NormalSortSettingsWindow.java b/app/src/main/java/ru/project/ui/collection/sorting/NormalSortSettingsWindow.java similarity index 97% rename from app/src/main/java/ru/project/ui/sorting/NormalSortSettingsWindow.java rename to app/src/main/java/ru/project/ui/collection/sorting/NormalSortSettingsWindow.java index 0f3bbd2..6e66e45 100644 --- a/app/src/main/java/ru/project/ui/sorting/NormalSortSettingsWindow.java +++ b/app/src/main/java/ru/project/ui/collection/sorting/NormalSortSettingsWindow.java @@ -1,4 +1,4 @@ -package ru.project.ui.sorting; +package ru.project.ui.collection.sorting; import com.googlecode.lanterna.gui2.ActionListBox; import com.googlecode.lanterna.gui2.Button; diff --git a/app/src/main/java/ru/project/ui/sorting/ParitySortSettingsWindow.java b/app/src/main/java/ru/project/ui/collection/sorting/ParitySortSettingsWindow.java similarity index 94% rename from app/src/main/java/ru/project/ui/sorting/ParitySortSettingsWindow.java rename to app/src/main/java/ru/project/ui/collection/sorting/ParitySortSettingsWindow.java index aa6d090..b458b43 100644 --- a/app/src/main/java/ru/project/ui/sorting/ParitySortSettingsWindow.java +++ b/app/src/main/java/ru/project/ui/collection/sorting/ParitySortSettingsWindow.java @@ -1,4 +1,4 @@ -package ru.project.ui.sorting; +package ru.project.ui.collection.sorting; import com.googlecode.lanterna.gui2.Button; import com.googlecode.lanterna.gui2.Label; diff --git a/app/src/main/java/ru/project/ui/sorting/SortCollectionMenu.java b/app/src/main/java/ru/project/ui/collection/sorting/SortCollectionMenu.java similarity index 98% rename from app/src/main/java/ru/project/ui/sorting/SortCollectionMenu.java rename to app/src/main/java/ru/project/ui/collection/sorting/SortCollectionMenu.java index ae72b4b..9694764 100644 --- a/app/src/main/java/ru/project/ui/sorting/SortCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/collection/sorting/SortCollectionMenu.java @@ -1,4 +1,4 @@ -package ru.project.ui.sorting; +package ru.project.ui.collection.sorting; import com.googlecode.lanterna.gui2.Button; import com.googlecode.lanterna.gui2.Label; diff --git a/app/src/main/java/ru/project/ui/sorting/SortField.java b/app/src/main/java/ru/project/ui/collection/sorting/SortField.java similarity index 65% rename from app/src/main/java/ru/project/ui/sorting/SortField.java rename to app/src/main/java/ru/project/ui/collection/sorting/SortField.java index 6219f9b..5b6124b 100644 --- a/app/src/main/java/ru/project/ui/sorting/SortField.java +++ b/app/src/main/java/ru/project/ui/collection/sorting/SortField.java @@ -1,4 +1,4 @@ -package ru.project.ui.sorting; +package ru.project.ui.collection.sorting; public enum SortField { GROUP_NUMBER, diff --git a/app/src/main/java/ru/project/ui/sorting/SortMethod.java b/app/src/main/java/ru/project/ui/collection/sorting/SortMethod.java similarity index 52% rename from app/src/main/java/ru/project/ui/sorting/SortMethod.java rename to app/src/main/java/ru/project/ui/collection/sorting/SortMethod.java index 446f27e..9e56a8d 100644 --- a/app/src/main/java/ru/project/ui/sorting/SortMethod.java +++ b/app/src/main/java/ru/project/ui/collection/sorting/SortMethod.java @@ -1,4 +1,4 @@ -package ru.project.ui.sorting; +package ru.project.ui.collection.sorting; public enum SortMethod { NORMAL, diff --git a/app/src/main/java/ru/project/ui/sorting/SortMethodSelectionWindow.java b/app/src/main/java/ru/project/ui/collection/sorting/SortMethodSelectionWindow.java similarity index 95% rename from app/src/main/java/ru/project/ui/sorting/SortMethodSelectionWindow.java rename to app/src/main/java/ru/project/ui/collection/sorting/SortMethodSelectionWindow.java index 121e5e2..dc49e68 100644 --- a/app/src/main/java/ru/project/ui/sorting/SortMethodSelectionWindow.java +++ b/app/src/main/java/ru/project/ui/collection/sorting/SortMethodSelectionWindow.java @@ -1,4 +1,4 @@ -package ru.project.ui.sorting; +package ru.project.ui.collection.sorting; import com.googlecode.lanterna.gui2.ActionListBox; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; diff --git a/app/src/main/java/ru/project/ui/common/CollectionFileSaveWindow.java b/app/src/main/java/ru/project/ui/common/CollectionFileSaveWindow.java new file mode 100644 index 0000000..9388da9 --- /dev/null +++ b/app/src/main/java/ru/project/ui/common/CollectionFileSaveWindow.java @@ -0,0 +1,76 @@ +package ru.project.ui.common; + +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import com.googlecode.lanterna.gui2.dialogs.FileDialogBuilder; +import com.googlecode.lanterna.gui2.dialogs.MessageDialog; +import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import ru.project.collection.MyList; +import ru.project.student.Student; + +public class CollectionFileSaveWindow { + + private final WindowBasedTextGUI gui; + private final MyList collection; + + public CollectionFileSaveWindow(WindowBasedTextGUI gui, MyList collection) { + + this.gui = gui; + this.collection = collection; + } + + public void showModal() { + File selectedFile = selectFile(); + + if (selectedFile == null) { + return; + } + + if (selectedFile.exists()) { + saveToFile(selectedFile); + return; + } + + MessageDialogButton result = + MessageDialog.showMessageDialog( + gui, + "Создание файла", + "Файл не существует. Создать новый файл?", + MessageDialogButton.Yes, + MessageDialogButton.No); + + if (result == MessageDialogButton.Yes) { + saveToFile(selectedFile); + } + } + + private File selectFile() { + return new FileDialogBuilder() + .setTitle("Сохранение коллекции") + .setDescription("Выберите файл:") + .setActionLabel("Сохранить") + .build() + .showDialog(gui); + } + + private void saveToFile(File file) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + + for (int i = 0; i < collection.size(); i++) { + Student student = collection.get(i); + + writer.write(student == null ? "null" : student.toString()); + + writer.newLine(); + } + + MessageWindow.showModal(gui, "Коллекция успешно сохранена."); + + } catch (IOException e) { + MessageWindow.showModal(gui, "Не удалось сохранить коллекцию."); + } + } +} diff --git a/app/src/main/java/ru/project/ui/main/MainMenu.java b/app/src/main/java/ru/project/ui/main/MainMenu.java deleted file mode 100644 index ff4899e..0000000 --- a/app/src/main/java/ru/project/ui/main/MainMenu.java +++ /dev/null @@ -1,80 +0,0 @@ -package ru.project.ui.main; - -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.BaseModalWindow; -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 { - - 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(); - - menu.addItem("Показать текущую коллекцию", () -> showMainCollection(state)); - - menu.addItem("Меню создания новой коллекции", this::showCreateCollectionMenu); - - menu.addItem("Меню сохранения текущей коллекции в файл", this::saveCollection); - - menu.addItem("Меню сортировки текущей коллекции", this::sortCollection); - - menu.addItem("Меню поиска элемента в текущей коллекции", this::searchCollection); - - menu.addItem("Меню подсчёта количества вхождений элемента", this::countOccurrences); - - menu.addItem("Завершить выполнение программы", this::exit); - - setComponent(menu); - } - - private void showMainCollection(AppState state) { - MyList mainCollection = state.getMainCollection(); - - BaseModalWindow window = new CollectionDisplayWindow(gui, mainCollection); - - window.showModal(); - } - - private void showCreateCollectionMenu() { - createCollectionMenu.showModal(); - } - - private void saveCollection() { - // TODO - MessageWindow.showModal(gui, "Не реализовано."); - } - - private void sortCollection() { - sortCollectionMenu.showModal(); - } - - private void searchCollection() { - // TODO - MessageWindow.showModal(gui, "Не реализовано."); - } - - private void countOccurrences() { - // TODO - MessageWindow.showModal(gui, "Не реализовано."); - } - - private void exit() { - close(); - } -} From 36baf5449842f01e4e551d42473bde4d68361052 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, 31 Aug 2026 00:47:49 +0300 Subject: [PATCH 097/156] refactor : changed the combiner --- .../ru/project/collectors/MyCollectors.java | 15 +++++ .../project/collectors/MyListCollector.java | 43 +++++++++++++ .../collectors/MyListCollectorTest.java | 64 +++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 app/src/main/java/ru/project/collectors/MyCollectors.java create mode 100644 app/src/main/java/ru/project/collectors/MyListCollector.java create mode 100644 app/src/test/java/ru/project/collectors/MyListCollectorTest.java diff --git a/app/src/main/java/ru/project/collectors/MyCollectors.java b/app/src/main/java/ru/project/collectors/MyCollectors.java new file mode 100644 index 0000000..d0d7cf5 --- /dev/null +++ b/app/src/main/java/ru/project/collectors/MyCollectors.java @@ -0,0 +1,15 @@ +package ru.project.collectors; +import java.util.stream.Collector; + +public class MyCollectors +{ + private MyCollectors() { + + } + + public static Collector, MyList> toMyList() + { + return new MyListCollector<>(); + + } +} diff --git a/app/src/main/java/ru/project/collectors/MyListCollector.java b/app/src/main/java/ru/project/collectors/MyListCollector.java new file mode 100644 index 0000000..bb168e4 --- /dev/null +++ b/app/src/main/java/ru/project/collectors/MyListCollector.java @@ -0,0 +1,43 @@ +package ru.project.collectors; +import java.util.Collections; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +public class MyListCollector implements Collector, MyList> +{ + @Override + public Supplier> supplier() + { + return () -> new MyLinkedList<>(); + } + + @Override + public BiConsumer, T> accumulator() { + + return (list, element) -> list.add(element); + } + + @Override + public BinaryOperator> combiner() { + return (list1, list2) -> { + list1.addAll(list2); + return list1; + }; + } + + @Override + public Function, MyList> finisher() + { + return list -> list; + } + + @Override + public Set characteristics() { + return java.util.Set.of( + Collector.Characteristics.IDENTITY_FINISH + ); + } +} diff --git a/app/src/test/java/ru/project/collectors/MyListCollectorTest.java b/app/src/test/java/ru/project/collectors/MyListCollectorTest.java new file mode 100644 index 0000000..3ca8d21 --- /dev/null +++ b/app/src/test/java/ru/project/collectors/MyListCollectorTest.java @@ -0,0 +1,64 @@ +package ru.test.maven.spring; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import java.util.Set; +import java.util.stream.Collector; +import java.util.stream.IntStream; +import java.util.Collections; + +public class MyListCollectorTest +{ + private final MyListCollector collector = new MyListCollector<>(); + + @Test + void shouldTestCollectInOrderSequential() { + MyList result = IntStream.range(0, 10) + .boxed() + .collect(MyCollectors.toMyList()); + + assertEquals(10, result.size()); + for (int i = 0; i < result.size(); i++) { + assertEquals(i, (int) result.get(i)); + } + + } + + @Test + void shouldTestCollectInOrderParallel() { + MyList result = IntStream.range(0, 100) + .parallel() + .boxed() + .collect(MyCollectors.toMyList()); + + assertEquals(100, result.size()); + for (int i = 0; i < result.size(); i++) { + assertEquals(i, (int) result.get(i)); + } + } + + @Test + void shouldTestHandleEmptyStream() { + MyList result = IntStream.empty() + .boxed() + .collect(MyCollectors.toMyList()); + + assertTrue(result.isEmpty()); + + } + @Test + void shouldTestNotUseStandardList() { + MyList result = IntStream.range(0, 5) + .mapToObj(i -> "item" + i) + .collect(MyCollectors.toMyList()); + + assertInstanceOf(MyList.class, result); + assertFalse(result instanceof java.util.List); + } + @Test + void shouldTestHaveCorrectCharacteristics() { + Set characteristics = collector.characteristics(); + assertTrue(characteristics.contains(java.util.stream.Collector.Characteristics.IDENTITY_FINISH)); + assertFalse(characteristics.contains(java.util.stream.Collector.Characteristics.UNORDERED)); + } + +} From ebaadf4fa18a6df33225aefdc36aa8fd66c16c8d 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, 31 Aug 2026 00:57:23 +0300 Subject: [PATCH 098/156] feat: add StudentOccurrenceIndexFinder for occurrence search and test --- .../finder/StudentOccurrenceIndexFinder.java | 125 ++++++++++++++++ .../StudentOccurrenceIndexFinderTest.java | 134 ++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java create mode 100644 app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java diff --git a/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java b/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java new file mode 100644 index 0000000..7e7c3d2 --- /dev/null +++ b/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java @@ -0,0 +1,125 @@ +package ru.project.finder; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +public class StudentOccurrenceIndexFinder +{ + public int[] findOccurrences(MyList students, Student target) { + if (students == null) { + throw new IllegalArgumentException(); + } + if (target == null) { + throw new IllegalArgumentException(); + } + if (students.isEmpty()) { + return new int[0]; + } + int size = students.size(); + int threadCount = calculateThreadCount(size); + int chunkSize = countChunkSize(size, threadCount); + + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + MyList>> futures = new MyLinkedList<>(); + + for (int i = 0; i < threadCount; i++) { + int fromIndex = i * chunkSize; + int toIndex = Math.min(fromIndex + chunkSize, size); + + if (fromIndex >= toIndex) { + break; + } + + final int start = fromIndex; + final int end = toIndex; + + Future> future = executor.submit(() -> { + return findInRange(students, target, start, end); + }); + futures.add(future); + } + MyList allIndices = new MyLinkedList<>(); + try { + for (int i = 0; i < futures.size(); i++) { + Future> future = futures.get(i); + MyList partialResult = future.get(); + + if (partialResult != null) { + for (int j = 0; j < partialResult.size(); j++) { + allIndices.add(partialResult.get(j)); + } + } + } + } catch (Exception e) { + throw new RuntimeException("Ошибка при поиске", e); + } finally { + executor.shutdown(); + } + + sortList(allIndices); + + int[] resultArray = new int[allIndices.size()]; + for (int i = 0; i < allIndices.size(); i++) { + resultArray[i] = allIndices.get(i); + } + + return resultArray; + } + + private void sortList(MyList list) { + if (list.size() <= 1) return; + + MyList sorted = new MyLinkedList<>(); + + for (int i = 0; i < list.size(); i++) { + int value = list.get(i); + int insertIndex = 0; + while (insertIndex < sorted.size()) { + if (sorted.get(insertIndex) > value) { + break; + } + insertIndex++; + } + + MyList temp = new MyLinkedList<>(); + for (int k = 0; k < insertIndex; k++) { + temp.add(sorted.get(k)); + } + temp.add(value); + for (int k = insertIndex; k < sorted.size(); k++) { + temp.add(sorted.get(k)); + } + sorted = temp; + } + + while (!list.isEmpty()) { + list.remove(0); + } + + for (int i = 0; i < sorted.size(); i++) { + list.add(sorted.get(i)); + } + } + + private int calculateThreadCount(int size) { + int processors = Runtime.getRuntime().availableProcessors(); + return Math.min(processors, size); + } + + private int countChunkSize(int size, int threadCount) { + if (threadCount == 0) return 0; + return (size + threadCount - 1) / threadCount; + } + + private MyList findInRange(MyList students, Student target, int fromIndex, int toIndex) { + MyList foundIndices = new MyLinkedList<>(); + + for (int i = fromIndex; i < toIndex; i++) { + Student current = students.get(i); + if (current != null && current.equals(target)) { + foundIndices.add(i); + } + } + + return foundIndices; + } +} diff --git a/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java b/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java new file mode 100644 index 0000000..b61ed58 --- /dev/null +++ b/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java @@ -0,0 +1,134 @@ +package ru.project.finder; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class StudentOccurrenceIndexFinderTest +{ + private StudentOccurrenceIndexFinder finder; + private MyList students; + + @BeforeEach + void setUp() { + finder = new StudentOccurrenceIndexFinder(); + students = new MyLinkedList<>(); + } + + @Test + void shouldTesfindOccurrences_emptyList_returnsEmptyArray() { + int[] result = finder.findOccurrences(students, new Student("A12", 4.5, 1)); + assertEquals(0, result.length); + } + @Test + void shouldTesfindOccurrences_noMatches_returnsEmptyArray() { + students.add(new Student("A12", 4.0, 101)); + students.add(new Student("B23", 3.5, 102)); + Student target = new Student("C34", 4.8, 103); // такого нет + + int[] result = finder.findOccurrences(students, target); + assertEquals(0, result.length); + } + @Test + void shouldTesfindOccurrences_singleMatch_returnsCorrectIndex() { + students.add(new Student("A12", 4.0, 101)); + students.add(new Student("B23", 3.5, 102)); // target здесь + students.add(new Student("C34", 4.8, 103)); + + Student target = new Student("B23", 3.5, 102); + int[] result = finder.findOccurrences(students, target); + + assertEquals(1, result.length); + assertEquals(1, result[0]); + } + @Test + void shouldTesfindOccurrences_multipleMatches_returnsAllIndicesSorted() { + // индексы: 0, 2, 4 — должны вернуться в порядке возрастания + students.add(new Student("A12", 4.5, 101)); // match + students.add(new Student("B23", 3.8, 102)); + students.add(new Student("A12", 4.5, 101)); // match + students.add(new Student("C34", 4.0, 103)); + students.add(new Student("A12", 4.5, 101)); // match + + Student target = new Student("A12", 4.5, 101); + int[] result = finder.findOccurrences(students, target); + + assertEquals(3, result.length); + assertArrayEquals(new int[]{0, 2, 4}, result); + } + + @Test + void shouldTesfindOccurrences_allElementsMatch_returnsAllIndices() { + Student s = new Student("X99", 5.0, 999); + students.add(s); + students.add(s); + students.add(s); + + int[] result = finder.findOccurrences(students, s); + assertEquals(3, result.length); + assertArrayEquals(new int[]{0, 1, 2}, result); + } + + @Test + void shouldTesfindOccurrences_differentAverageGrade_noMatch() { + students.add(new Student("A12", 4.50, 101)); + Student target = new Student("A12", 4.51, 101); // отличается балл + + int[] result = finder.findOccurrences(students, target); + assertEquals(0, result.length); + } + + @Test + void shouldTesfindOccurrences_differentGroupNumber_noMatch() { + students.add(new Student("A12", 4.5, 101)); + Student target = new Student("B12", 4.5, 101); // другая группа + + int[] result = finder.findOccurrences(students, target); + assertEquals(0, result.length); + } + + @Test + void shouldTesfindOccurrences_differentRecordBookNumber_noMatch() { + students.add(new Student("A12", 4.5, 101)); + Student target = new Student("A12", 4.5, 102); // другой номер зачетки + + int[] result = finder.findOccurrences(students, target); + assertEquals(0, result.length); + } + + + @Test + void shouldTesfindOccurrences_nullList_throwsException() { + Student target = new Student("A12", 4.5, 101); + assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(null, target)); + } + + + @Test + void shouldTesfindOccurrences_nullTarget_throwsException() { + assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(students, null)); + } + @Test + void shouldTesfindOccurrences_resultIsSorted() { + String[] validGroups = {"A00", "A01", "B12", "C99", "X55"}; + + for (int i = 0; i < 50; i++) { + String group = validGroups[i % validGroups.length]; + double grade = (3.0 + (i % 3)) / 2.0; + if (i % 3 == 0) { + + students.add(new Student("A12", 4.5, 101)); + } else { + students.add(new Student(group, grade, 200 + i)); + } + } + Student target = new Student("A12", 4.5, 101); + int[] result = finder.findOccurrences(students, target); + + + for (int i = 1; i < result.length; i++) { + assertTrue(result[i] > result[i - 1], "Результат должен быть отсортирован"); + } + } +} From 52d0f5e636b6ddef9ba0bbb32e1f6967cf33dd74 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: Mon, 31 Aug 2026 16:20:24 +0400 Subject: [PATCH 099/156] refactor(collectors): remove duplicate collector implementations --- .../main/java/ru/project/MyCollectors.java | 10 ----- .../main/java/ru/project/MyListCollector.java | 41 ------------------- 2 files changed, 51 deletions(-) delete mode 100644 app/src/main/java/ru/project/MyCollectors.java delete mode 100644 app/src/main/java/ru/project/MyListCollector.java diff --git a/app/src/main/java/ru/project/MyCollectors.java b/app/src/main/java/ru/project/MyCollectors.java deleted file mode 100644 index 20840c3..0000000 --- a/app/src/main/java/ru/project/MyCollectors.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.test.maven.spring; -import java.util.stream.Collector; -public class MyCollectors -{ - private MyCollectors() { - } - public static Collector, MyList> toMyList() { - return new MyListCollector<>(); - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/project/MyListCollector.java b/app/src/main/java/ru/project/MyListCollector.java deleted file mode 100644 index f731379..0000000 --- a/app/src/main/java/ru/project/MyListCollector.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.test.maven.spring; -import java.util.Collections; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.BinaryOperator; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collector; -public class MyListCollector implements Collector, MyList> -{ - @Override - public Supplier> supplier() - { - return () -> new MyLinkedList<>(); - } - @Override - public BiConsumer, T> accumulator() { - - return (list, element) -> list.add(element); - } - @Override - public BinaryOperator> combiner() { - return (list1, list2) -> { - for (int i = 0; i < list2.size(); i++) { - list1.add(list2.get(i)); - } - return list1; - }; - } - @Override - public Function, MyList> finisher() - { - return list -> list; - } - @Override - public Set characteristics() { - return java.util.Set.of( - Collector.Characteristics.IDENTITY_FINISH - ); - } -} \ No newline at end of file From b8f28830f73b26d784306494ae9443ebd0ad6393 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: Mon, 31 Aug 2026 17:57:20 +0400 Subject: [PATCH 100/156] test: reorganize and update collection tests --- app/src/test/java/ru/project/AppTest.java | 15 - .../java/ru/project/MyListCollectorTest.java | 63 -- .../project/collection/MyLinkedListTest.java | 236 +++---- .../collectors/MyListCollectorTest.java | 88 ++- .../StudentOccurrenceIndexFinderTest.java | 4 + .../ru/project/search/StudentSearchTest.java | 601 +++++++++--------- 6 files changed, 469 insertions(+), 538 deletions(-) delete mode 100644 app/src/test/java/ru/project/AppTest.java delete mode 100644 app/src/test/java/ru/project/MyListCollectorTest.java diff --git a/app/src/test/java/ru/project/AppTest.java b/app/src/test/java/ru/project/AppTest.java deleted file mode 100644 index 44a05ef..0000000 --- a/app/src/test/java/ru/project/AppTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package ru.project; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -/** Unit test for simple App. */ -public class AppTest { - - /** Rigorous Test :-) */ - @Test - public void shouldAnswerWithTrue() { - assertTrue(true); - } -} diff --git a/app/src/test/java/ru/project/MyListCollectorTest.java b/app/src/test/java/ru/project/MyListCollectorTest.java deleted file mode 100644 index 6155d57..0000000 --- a/app/src/test/java/ru/project/MyListCollectorTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package ru.test.maven.spring; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; -import java.util.Set; -import java.util.stream.Collector; -import java.util.stream.IntStream; -import java.util.Collections; -public class MyListCollectorTest -{ - private final MyListCollector collector = new MyListCollector<>(); - - @Test - void shouldTestCollectInOrderSequential() { - MyList result = IntStream.range(0, 10) - .boxed() - .collect(MyCollectors.toMyList()); - - assertEquals(10, result.size()); - for (int i = 0; i < result.size(); i++) { - assertEquals(i, (int) result.get(i)); - } - - } - - @Test - void shouldTestCollectInOrderParallel() { - MyList result = IntStream.range(0, 100) - .parallel() - .boxed() - .collect(MyCollectors.toMyList()); - - assertEquals(100, result.size()); - for (int i = 0; i < result.size(); i++) { - assertEquals(i, (int) result.get(i)); - } - } - - @Test - void shouldTestHandleEmptyStream() { - MyList result = IntStream.empty() - .boxed() - .collect(MyCollectors.toMyList()); - - assertTrue(result.isEmpty()); - - } - @Test - void shouldTestNotUseStandardList() { - MyList result = IntStream.range(0, 5) - .mapToObj(i -> "item" + i) - .collect(MyCollectors.toMyList()); - - assertInstanceOf(MyList.class, result); - assertFalse(result instanceof java.util.List); - } - @Test - void shouldTestHaveCorrectCharacteristics() { - Set characteristics = collector.characteristics(); - assertTrue(characteristics.contains(java.util.stream.Collector.Characteristics.IDENTITY_FINISH)); - assertFalse(characteristics.contains(java.util.stream.Collector.Characteristics.UNORDERED)); - } - -} diff --git a/app/src/test/java/ru/project/collection/MyLinkedListTest.java b/app/src/test/java/ru/project/collection/MyLinkedListTest.java index 6c43673..8137964 100644 --- a/app/src/test/java/ru/project/collection/MyLinkedListTest.java +++ b/app/src/test/java/ru/project/collection/MyLinkedListTest.java @@ -1,130 +1,138 @@ -package ru.test.maven.spring; -import org.junit.jupiter.api.Test; +package ru.project.collection; + import static org.junit.jupiter.api.Assertions.*; -public class MyLinkedListTest -{ - @Test - public void shouldTestAdd() { - MyLinkedList list = new MyLinkedList<>(); - list.add(10); - list.add(20); - list.add(30); - assertEquals(10, list.get(0)); - assertEquals(20, list.get(1)); - assertEquals(30, list.get(2)); - } - @Test - public void shouldTestGet() - { - MyLinkedList list = new MyLinkedList<>(); - list.add(10); - assertEquals(10, list.get(0)); - } - @Test - public void shouldTestRemove() { - MyLinkedList list = new MyLinkedList<>(); - list.add(10); - list.add(20); - list.add(30); - list.remove(0); +import org.junit.jupiter.api.Test; - assertEquals(2, list.size()); - assertEquals(20, list.get(0)); - assertEquals(30, list.get(1)); - } - @Test - void shouldTestSet() { - MyLinkedList list = new MyLinkedList<>(); - list.add("A"); - list.add("B"); - list.add("C"); +public class MyLinkedListTest { + @Test + public void shouldTestAdd() { + MyLinkedList list = new MyLinkedList<>(); + list.add(10); + list.add(20); + list.add(30); - String oldValue = list.set(1, "Z"); + assertEquals(10, list.get(0)); + assertEquals(20, list.get(1)); + assertEquals(30, list.get(2)); + } - assertEquals("B", oldValue); - assertEquals("Z", list.get(1)); - assertEquals(3, list.size()); + @Test + public void shouldTestGet() { + MyLinkedList list = new MyLinkedList<>(); + list.add(10); + assertEquals(10, list.get(0)); + } - } - @Test - void shouldTestSize() - { - MyLinkedList list = new MyLinkedList<>(); - list.add(1); - list.add(2); - list.add(3); - assertEquals(3, list.size()); - } - @Test - void shouldTestisEmpty() - { - MyLinkedList list = new MyLinkedList<>(); - assertTrue(list.isEmpty()); - } - @Test - void shouldTestGet_OutOfBounds_ThrowsException() { - MyLinkedList list = new MyLinkedList<>(); - list.add(1); + @Test + public void shouldTestRemove() { + MyLinkedList list = new MyLinkedList<>(); + list.add(10); + list.add(20); + list.add(30); + list.remove(0); - assertThrows(IllegalArgumentException.class, () -> list.get(-1)); - assertThrows(IllegalArgumentException.class, () -> list.get(1)); // index == size - assertThrows(IllegalArgumentException.class, () -> list.get(5)); // index > size - } - @Test - void shouldTestSet_OutOfBounds_ThrowsException() { - MyLinkedList list = new MyLinkedList<>(); - list.add(1); + assertEquals(2, list.size()); + assertEquals(20, list.get(0)); + assertEquals(30, list.get(1)); + } - assertThrows(IllegalArgumentException.class, () -> list.set(-1, 99)); - assertThrows(IllegalArgumentException.class, () -> list.set(1, 99)); - } - @Test - void shouldTestRemove_IndexGreaterThanSize_ThrowsException() { - MyLinkedList list = new MyLinkedList<>(); - list.add("man"); - assertThrows(IndexOutOfBoundsException.class, () -> list.remove(5)); - } - @Test - void shouldTestRemove_NegativeIndex_ThrowsException() { - MyLinkedList list = new MyLinkedList<>(); - list.add("1"); - list.add("2"); + @Test + void shouldTestSet() { + MyLinkedList list = new MyLinkedList<>(); + list.add("A"); + list.add("B"); + list.add("C"); - assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1)); - } - @Test - void shouldTestaddAll_nonEmptySource_toEmptyList_addsAllElementsInOrder() { - MyList list = new MyLinkedList<>(); - MyList source = new MyLinkedList<>(); - source.add("A"); - source.add("B"); - source.add("C"); + String oldValue = list.set(1, "Z"); - list.addAll(source); + assertEquals("B", oldValue); + assertEquals("Z", list.get(1)); + assertEquals(3, list.size()); + } - assertEquals(3, list.size()); - assertEquals("A", list.get(0)); - assertEquals("B", list.get(1)); - assertEquals("C", list.get(2)); - } + @Test + void shouldTestSize() { + MyLinkedList list = new MyLinkedList<>(); + list.add(1); + list.add(2); + list.add(3); + assertEquals(3, list.size()); + } - @Test - void shouldTestaddAll_toNonEmptyList_appendsAtEnd() { - MyList list = new MyLinkedList<>(); - list.add("X"); - list.add("Y"); + @Test + void shouldTestisEmpty() { + MyLinkedList list = new MyLinkedList<>(); + assertTrue(list.isEmpty()); + } - MyList source = new MyLinkedList<>(); - source.add("A"); - source.add("B"); + @Test + void shouldTestGet_OutOfBounds_ThrowsException() { + MyLinkedList list = new MyLinkedList<>(); + list.add(1); - list.addAll(source); + assertThrows(IllegalArgumentException.class, () -> list.get(-1)); + assertThrows(IllegalArgumentException.class, () -> list.get(1)); // index == size + assertThrows(IllegalArgumentException.class, () -> list.get(5)); // index > size + } - assertEquals(4, list.size()); - assertEquals("X", list.get(0)); - assertEquals("Y", list.get(1)); - assertEquals("A", list.get(2)); - assertEquals("B", list.get(3)); - } + @Test + void shouldTestSet_OutOfBounds_ThrowsException() { + MyLinkedList list = new MyLinkedList<>(); + list.add(1); + + assertThrows(IllegalArgumentException.class, () -> list.set(-1, 99)); + assertThrows(IllegalArgumentException.class, () -> list.set(1, 99)); + } + + @Test + void shouldTestRemove_IndexGreaterThanSize_ThrowsException() { + MyLinkedList list = new MyLinkedList<>(); + list.add("man"); + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(5)); + } + + @Test + void shouldTestRemove_NegativeIndex_ThrowsException() { + MyLinkedList list = new MyLinkedList<>(); + list.add("1"); + list.add("2"); + + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1)); + } + + @Test + void shouldTestaddAll_nonEmptySource_toEmptyList_addsAllElementsInOrder() { + MyList list = new MyLinkedList<>(); + MyList source = new MyLinkedList<>(); + source.add("A"); + source.add("B"); + source.add("C"); + + list.addAll(source); + + assertEquals(3, list.size()); + assertEquals("A", list.get(0)); + assertEquals("B", list.get(1)); + assertEquals("C", list.get(2)); + } + + @Test + void shouldTestaddAll_toNonEmptyList_appendsAtEnd() { + MyList list = new MyLinkedList<>(); + list.add("X"); + list.add("Y"); + + MyList source = new MyLinkedList<>(); + source.add("A"); + source.add("B"); + + list.addAll(source); + + assertEquals(4, list.size()); + assertEquals("X", list.get(0)); + assertEquals("Y", list.get(1)); + assertEquals("A", list.get(2)); + assertEquals("B", list.get(3)); + } } diff --git a/app/src/test/java/ru/project/collectors/MyListCollectorTest.java b/app/src/test/java/ru/project/collectors/MyListCollectorTest.java index 3ca8d21..35abcbc 100644 --- a/app/src/test/java/ru/project/collectors/MyListCollectorTest.java +++ b/app/src/test/java/ru/project/collectors/MyListCollectorTest.java @@ -1,64 +1,58 @@ -package ru.test.maven.spring; -import org.junit.jupiter.api.Test; +package ru.project.collectors; + import static org.junit.jupiter.api.Assertions.*; + import java.util.Set; import java.util.stream.Collector; import java.util.stream.IntStream; -import java.util.Collections; +import org.junit.jupiter.api.Test; +import ru.project.collection.MyList; -public class MyListCollectorTest -{ - private final MyListCollector collector = new MyListCollector<>(); +public class MyListCollectorTest { + private final MyListCollector collector = new MyListCollector<>(); - @Test - void shouldTestCollectInOrderSequential() { - MyList result = IntStream.range(0, 10) - .boxed() - .collect(MyCollectors.toMyList()); - - assertEquals(10, result.size()); - for (int i = 0; i < result.size(); i++) { - assertEquals(i, (int) result.get(i)); - } + @Test + void shouldTestCollectInOrderSequential() { + MyList result = IntStream.range(0, 10).boxed().collect(MyCollectors.toMyList()); + assertEquals(10, result.size()); + for (int i = 0; i < result.size(); i++) { + assertEquals(i, (int) result.get(i)); } + } - @Test - void shouldTestCollectInOrderParallel() { - MyList result = IntStream.range(0, 100) - .parallel() - .boxed() - .collect(MyCollectors.toMyList()); + @Test + void shouldTestCollectInOrderParallel() { + MyList result = + IntStream.range(0, 100).parallel().boxed().collect(MyCollectors.toMyList()); - assertEquals(100, result.size()); - for (int i = 0; i < result.size(); i++) { - assertEquals(i, (int) result.get(i)); - } + assertEquals(100, result.size()); + for (int i = 0; i < result.size(); i++) { + assertEquals(i, (int) result.get(i)); } + } - @Test - void shouldTestHandleEmptyStream() { - MyList result = IntStream.empty() - .boxed() - .collect(MyCollectors.toMyList()); + @Test + void shouldTestHandleEmptyStream() { + MyList result = IntStream.empty().boxed().collect(MyCollectors.toMyList()); - assertTrue(result.isEmpty()); + assertTrue(result.isEmpty()); + } - } - @Test - void shouldTestNotUseStandardList() { - MyList result = IntStream.range(0, 5) - .mapToObj(i -> "item" + i) - .collect(MyCollectors.toMyList()); + @Test + void shouldTestNotUseStandardList() { + MyList result = + IntStream.range(0, 5).mapToObj(i -> "item" + i).collect(MyCollectors.toMyList()); - assertInstanceOf(MyList.class, result); - assertFalse(result instanceof java.util.List); - } - @Test - void shouldTestHaveCorrectCharacteristics() { - Set characteristics = collector.characteristics(); - assertTrue(characteristics.contains(java.util.stream.Collector.Characteristics.IDENTITY_FINISH)); - assertFalse(characteristics.contains(java.util.stream.Collector.Characteristics.UNORDERED)); - } + assertInstanceOf(MyList.class, result); + assertFalse(result instanceof java.util.List); + } + @Test + void shouldTestHaveCorrectCharacteristics() { + Set characteristics = collector.characteristics(); + assertTrue( + characteristics.contains(java.util.stream.Collector.Characteristics.IDENTITY_FINISH)); + assertFalse(characteristics.contains(java.util.stream.Collector.Characteristics.UNORDERED)); + } } diff --git a/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java b/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java index b61ed58..b928992 100644 --- a/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java +++ b/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java @@ -3,6 +3,10 @@ package ru.project.finder; 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; + import static org.junit.jupiter.api.Assertions.*; public class StudentOccurrenceIndexFinderTest diff --git a/app/src/test/java/ru/project/search/StudentSearchTest.java b/app/src/test/java/ru/project/search/StudentSearchTest.java index d12bd9c..db43fa1 100644 --- a/app/src/test/java/ru/project/search/StudentSearchTest.java +++ b/app/src/test/java/ru/project/search/StudentSearchTest.java @@ -1,314 +1,317 @@ package ru.project.search; -import org.junit.jupiter.api.Test; -import ru.project.list.MyList; -import ru.project.list.MyLinkedList; -import ru.project.student.Student; - import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.student.Student; + class StudentSearchTest { - @Test - void testGroupNumberRangeMatchesInside() { - StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); - Student student = new Student("B20", 4.5, 123); - assertTrue(criteria.matches(student)); + @Test + void testGroupNumberRangeMatchesInside() { + StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); + Student student = new Student("B20", 4.5, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testGroupNumberRangeMatchesLowerBoundary() { + StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); + Student student = new Student("A10", 4.5, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testGroupNumberRangeMatchesUpperBoundary() { + StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); + Student student = new Student("C30", 4.5, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testGroupNumberRangeDoesNotMatchOutside() { + StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); + Student student = new Student("D00", 4.5, 123); + assertFalse(criteria.matches(student)); + } + + @Test + void testGroupNumberRangeInvalidMinMaxThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new GroupNumberRangeCriteria("C30", "A10")); + } + + @Test + void testGroupNumberRangeNullValuesThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new GroupNumberRangeCriteria(null, "A10")); + assertThrows(IllegalArgumentException.class, () -> new GroupNumberRangeCriteria("A10", null)); + } + + @Test + void testAverageGradeRangeMatchesInside() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 4.5, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeMatchesLowerBoundary() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 4.0, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeMatchesUpperBoundary() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 5.0, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeDoesNotMatchBelow() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 3.9, 123); + assertFalse(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeDoesNotMatchAbove() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 5.1, 123); + assertFalse(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeInvalidMinMaxThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new AverageGradeRangeCriteria(5.0, 4.0)); + } + + @Test + void testRecordBookNumberRangeMatchesInside() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 150); + assertTrue(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeMatchesLowerBoundary() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 100); + assertTrue(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeMatchesUpperBoundary() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 200); + assertTrue(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeDoesNotMatchBelow() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 99); + assertFalse(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeDoesNotMatchAbove() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 201); + assertFalse(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeInvalidMinMaxThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new RecordBookNumberRangeCriteria(200, 100)); + } + + @Test + void testCompositeIntersectionEmptyReturnsTrue() { + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + Student student = new Student("A12", 4.5, 123); + assertTrue(composite.matches(student)); + } + + @Test + void testCompositeUnionEmptyReturnsFalse() { + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.UNION); + Student student = new Student("A12", 4.5, 123); + assertFalse(composite.matches(student)); + } + + @Test + void testCompositeIntersectionAllTrue() { + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + composite.add(new GroupNumberRangeCriteria("A10", "C30")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + Student student = new Student("B20", 4.5, 123); + assertTrue(composite.matches(student)); + } + + @Test + void testCompositeIntersectionOneFalse() { + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + composite.add(new GroupNumberRangeCriteria("A10", "C30")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + Student student = new Student("B20", 3.5, 123); + assertFalse(composite.matches(student)); + } + + @Test + void testCompositeUnionOneTrue() { + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.UNION); + composite.add(new GroupNumberRangeCriteria("A10", "C30")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + Student student = new Student("D00", 4.5, 123); + assertTrue(composite.matches(student)); + } + + @Test + void testCompositeUnionAllFalse() { + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.UNION); + composite.add(new GroupNumberRangeCriteria("A10", "C30")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + Student student = new Student("D00", 3.5, 123); + assertFalse(composite.matches(student)); + } + + @Test + void testCompositeWithNestedComposite() { + CompositeStudentSearchCriteria outer = + new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + outer.add(new GroupNumberRangeCriteria("A10", "C30")); + + CompositeStudentSearchCriteria inner = + new CompositeStudentSearchCriteria(SearchOperation.UNION); + inner.add(new AverageGradeRangeCriteria(4.0, 5.0)); + inner.add(new RecordBookNumberRangeCriteria(100, 200)); + + outer.add(inner); + + Student student = new Student("B20", 4.5, 150); + assertTrue(outer.matches(student)); + student = new Student("B20", 3.5, 150); + assertFalse(outer.matches(student)); + student = new Student("D00", 4.5, 150); + assertFalse(outer.matches(student)); + } + + @Test + void testSearchServiceWithSingleCriteria() { + MyList students = new MyLinkedList<>(); + students.add(new Student("A10", 4.5, 1)); + students.add(new Student("B20", 3.8, 2)); + students.add(new Student("C30", 4.9, 3)); + + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + + assertEquals(2, result.size()); + assertEquals("A10", result.get(0).getGroupNumber()); + assertEquals("C30", result.get(1).getGroupNumber()); + } + + @Test + void testSearchServiceEmptyResult() { + MyList students = new MyLinkedList<>(); + students.add(new Student("A10", 3.5, 1)); + students.add(new Student("B20", 3.8, 2)); + + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + + assertEquals(0, result.size()); + } + + @Test + void testSearchServiceEmptyCollection() { + MyList students = new MyLinkedList<>(); + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + assertEquals(0, result.size()); + } + + @Test + void testSearchServicePreservesOrder() { + MyList students = new MyLinkedList<>(); + students.add(new Student("Z00", 4.5, 1)); + students.add(new Student("A10", 4.5, 2)); + students.add(new Student("M30", 4.5, 3)); + + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + + assertEquals(3, result.size()); + assertEquals("Z00", result.get(0).getGroupNumber()); + assertEquals("A10", result.get(1).getGroupNumber()); + assertEquals("M30", result.get(2).getGroupNumber()); + } + + @Test + void testSearchServiceDoesNotModifyOriginal() { + MyList original = new MyLinkedList<>(); + original.add(new Student("A10", 4.5, 1)); + original.add(new Student("B20", 3.8, 2)); + + MyList copy = new MyLinkedList<>(); + for (int i = 0; i < original.size(); i++) { + copy.add(original.get(i)); } - @Test - void testGroupNumberRangeMatchesLowerBoundary() { - StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); - Student student = new Student("A10", 4.5, 123); - assertTrue(criteria.matches(student)); + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + service.find(original); + + assertEquals(copy.size(), original.size()); + for (int i = 0; i < copy.size(); i++) { + assertEquals(copy.get(i).getGroupNumber(), original.get(i).getGroupNumber()); } + } - @Test - void testGroupNumberRangeMatchesUpperBoundary() { - StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); - Student student = new Student("C30", 4.5, 123); - assertTrue(criteria.matches(student)); - } + @Test + void testSearchServiceWithNullCriteriaThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new StudentSearchService(null)); + } - @Test - void testGroupNumberRangeDoesNotMatchOutside() { - StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); - Student student = new Student("D00", 4.5, 123); - assertFalse(criteria.matches(student)); - } + @Test + void testSearchServiceWithCompositeCriteria() { + MyList students = new MyLinkedList<>(); + students.add(new Student("A10", 4.5, 1)); + students.add(new Student("B20", 4.5, 2)); + students.add(new Student("C30", 3.5, 3)); - @Test - void testGroupNumberRangeInvalidMinMaxThrowsException() { - assertThrows(IllegalArgumentException.class, - () -> new GroupNumberRangeCriteria("C30", "A10")); - } + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + composite.add(new GroupNumberRangeCriteria("A10", "B20")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); - @Test - void testGroupNumberRangeNullValuesThrowsException() { - assertThrows(IllegalArgumentException.class, - () -> new GroupNumberRangeCriteria(null, "A10")); - assertThrows(IllegalArgumentException.class, - () -> new GroupNumberRangeCriteria("A10", null)); - } + StudentSearchService service = new StudentSearchService(composite); + MyList result = service.find(students); - @Test - void testAverageGradeRangeMatchesInside() { - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - Student student = new Student("A12", 4.5, 123); - assertTrue(criteria.matches(student)); - } + assertEquals(2, result.size()); + assertEquals("A10", result.get(0).getGroupNumber()); + assertEquals("B20", result.get(1).getGroupNumber()); + } - @Test - void testAverageGradeRangeMatchesLowerBoundary() { - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - Student student = new Student("A12", 4.0, 123); - assertTrue(criteria.matches(student)); - } - - @Test - void testAverageGradeRangeMatchesUpperBoundary() { - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - Student student = new Student("A12", 5.0, 123); - assertTrue(criteria.matches(student)); - } - - @Test - void testAverageGradeRangeDoesNotMatchBelow() { - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - Student student = new Student("A12", 3.9, 123); - assertFalse(criteria.matches(student)); - } - - @Test - void testAverageGradeRangeDoesNotMatchAbove() { - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - Student student = new Student("A12", 5.1, 123); - assertFalse(criteria.matches(student)); - } - - @Test - void testAverageGradeRangeInvalidMinMaxThrowsException() { - assertThrows(IllegalArgumentException.class, - () -> new AverageGradeRangeCriteria(5.0, 4.0)); - } - - @Test - void testRecordBookNumberRangeMatchesInside() { - StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); - Student student = new Student("A12", 4.5, 150); - assertTrue(criteria.matches(student)); - } - - @Test - void testRecordBookNumberRangeMatchesLowerBoundary() { - StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); - Student student = new Student("A12", 4.5, 100); - assertTrue(criteria.matches(student)); - } - - @Test - void testRecordBookNumberRangeMatchesUpperBoundary() { - StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); - Student student = new Student("A12", 4.5, 200); - assertTrue(criteria.matches(student)); - } - - @Test - void testRecordBookNumberRangeDoesNotMatchBelow() { - StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); - Student student = new Student("A12", 4.5, 99); - assertFalse(criteria.matches(student)); - } - - @Test - void testRecordBookNumberRangeDoesNotMatchAbove() { - StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); - Student student = new Student("A12", 4.5, 201); - assertFalse(criteria.matches(student)); - } - - @Test - void testRecordBookNumberRangeInvalidMinMaxThrowsException() { - assertThrows(IllegalArgumentException.class, - () -> new RecordBookNumberRangeCriteria(200, 100)); - } - - @Test - void testCompositeIntersectionEmptyReturnsTrue() { - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); - Student student = new Student("A12", 4.5, 123); - assertTrue(composite.matches(student)); - } - - @Test - void testCompositeUnionEmptyReturnsFalse() { - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.UNION); - Student student = new Student("A12", 4.5, 123); - assertFalse(composite.matches(student)); - } - - @Test - void testCompositeIntersectionAllTrue() { - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); - composite.add(new GroupNumberRangeCriteria("A10", "C30")); - composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); - Student student = new Student("B20", 4.5, 123); - assertTrue(composite.matches(student)); - } - - @Test - void testCompositeIntersectionOneFalse() { - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); - composite.add(new GroupNumberRangeCriteria("A10", "C30")); - composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); - Student student = new Student("B20", 3.5, 123); - assertFalse(composite.matches(student)); - } - - @Test - void testCompositeUnionOneTrue() { - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.UNION); - composite.add(new GroupNumberRangeCriteria("A10", "C30")); - composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); - Student student = new Student("D00", 4.5, 123); - assertTrue(composite.matches(student)); - } - - @Test - void testCompositeUnionAllFalse() { - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.UNION); - composite.add(new GroupNumberRangeCriteria("A10", "C30")); - composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); - Student student = new Student("D00", 3.5, 123); - assertFalse(composite.matches(student)); - } - - @Test - void testCompositeWithNestedComposite() { - CompositeStudentSearchCriteria outer = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); - outer.add(new GroupNumberRangeCriteria("A10", "C30")); - - CompositeStudentSearchCriteria inner = new CompositeStudentSearchCriteria(SearchOperation.UNION); - inner.add(new AverageGradeRangeCriteria(4.0, 5.0)); - inner.add(new RecordBookNumberRangeCriteria(100, 200)); - - outer.add(inner); - - Student student = new Student("B20", 4.5, 150); - assertTrue(outer.matches(student)); - student = new Student("B20", 3.5, 150); - assertFalse(outer.matches(student)); - student = new Student("D00", 4.5, 150); - assertFalse(outer.matches(student)); - } - - @Test - void testSearchServiceWithSingleCriteria() { - MyList students = new MyLinkedList<>(); - students.add(new Student("A10", 4.5, 1)); - students.add(new Student("B20", 3.8, 2)); - students.add(new Student("C30", 4.9, 3)); - - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - StudentSearchService service = new StudentSearchService(criteria); - MyList result = service.find(students); - - assertEquals(2, result.size()); - assertEquals("A10", result.get(0).getGroupNumber()); - assertEquals("C30", result.get(1).getGroupNumber()); - } - - @Test - void testSearchServiceEmptyResult() { - MyList students = new MyLinkedList<>(); - students.add(new Student("A10", 3.5, 1)); - students.add(new Student("B20", 3.8, 2)); - - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - StudentSearchService service = new StudentSearchService(criteria); - MyList result = service.find(students); - - assertEquals(0, result.size()); - } - - @Test - void testSearchServiceEmptyCollection() { - MyList students = new MyLinkedList<>(); - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - StudentSearchService service = new StudentSearchService(criteria); - MyList result = service.find(students); - assertEquals(0, result.size()); - } - - @Test - void testSearchServicePreservesOrder() { - MyList students = new MyLinkedList<>(); - students.add(new Student("Z00", 4.5, 1)); - students.add(new Student("A10", 4.5, 2)); - students.add(new Student("M30", 4.5, 3)); - - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - StudentSearchService service = new StudentSearchService(criteria); - MyList result = service.find(students); - - assertEquals(3, result.size()); - assertEquals("Z00", result.get(0).getGroupNumber()); - assertEquals("A10", result.get(1).getGroupNumber()); - assertEquals("M30", result.get(2).getGroupNumber()); - } - - @Test - void testSearchServiceDoesNotModifyOriginal() { - MyList original = new MyLinkedList<>(); - original.add(new Student("A10", 4.5, 1)); - original.add(new Student("B20", 3.8, 2)); - - MyList copy = new MyLinkedList<>(); - for (int i = 0; i < original.size(); i++) { - copy.add(original.get(i)); - } - - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - StudentSearchService service = new StudentSearchService(criteria); - service.find(original); - - assertEquals(copy.size(), original.size()); - for (int i = 0; i < copy.size(); i++) { - assertEquals(copy.get(i).getGroupNumber(), original.get(i).getGroupNumber()); - } - } - - @Test - void testSearchServiceWithNullCriteriaThrowsException() { - assertThrows(IllegalArgumentException.class, - () -> new StudentSearchService(null)); - } - - @Test - void testSearchServiceWithCompositeCriteria() { - MyList students = new MyLinkedList<>(); - students.add(new Student("A10", 4.5, 1)); - students.add(new Student("B20", 4.5, 2)); - students.add(new Student("C30", 3.5, 3)); - - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); - composite.add(new GroupNumberRangeCriteria("A10", "B20")); - composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); - - StudentSearchService service = new StudentSearchService(composite); - MyList result = service.find(students); - - assertEquals(2, result.size()); - assertEquals("A10", result.get(0).getGroupNumber()); - assertEquals("B20", result.get(1).getGroupNumber()); - } - - @Test - void testSearchServiceWithNullStudentListReturnsEmpty() { - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - StudentSearchService service = new StudentSearchService(criteria); - MyList result = service.find(null); - assertNotNull(result); - assertEquals(0, result.size()); - } -} \ No newline at end of file + @Test + void testSearchServiceWithNullStudentListReturnsEmpty() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(null); + assertNotNull(result); + assertEquals(0, result.size()); + } +} From a59d574c67fea20aea6847e7d5cf3e93c817a72e 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: Mon, 31 Aug 2026 17:58:15 +0400 Subject: [PATCH 101/156] refactor: reorganize collection utilities and search imports --- .../ru/project/collectors/MyCollectors.java | 17 +- .../project/collectors/MyListCollector.java | 58 +++-- .../finder/StudentOccurrenceIndexFinder.java | 211 +++++++++--------- .../project/search/StudentSearchService.java | 42 ++-- 4 files changed, 165 insertions(+), 163 deletions(-) diff --git a/app/src/main/java/ru/project/collectors/MyCollectors.java b/app/src/main/java/ru/project/collectors/MyCollectors.java index d0d7cf5..83b7ae8 100644 --- a/app/src/main/java/ru/project/collectors/MyCollectors.java +++ b/app/src/main/java/ru/project/collectors/MyCollectors.java @@ -1,15 +1,12 @@ package ru.project.collectors; + import java.util.stream.Collector; +import ru.project.collection.MyList; -public class MyCollectors -{ - private MyCollectors() { +public class MyCollectors { + private MyCollectors() {} - } - - public static Collector, MyList> toMyList() - { - return new MyListCollector<>(); - - } + public static Collector, MyList> toMyList() { + return new MyListCollector<>(); + } } diff --git a/app/src/main/java/ru/project/collectors/MyListCollector.java b/app/src/main/java/ru/project/collectors/MyListCollector.java index bb168e4..c5958b0 100644 --- a/app/src/main/java/ru/project/collectors/MyListCollector.java +++ b/app/src/main/java/ru/project/collectors/MyListCollector.java @@ -1,43 +1,41 @@ package ru.project.collectors; -import java.util.Collections; + import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; -public class MyListCollector implements Collector, MyList> -{ - @Override - public Supplier> supplier() - { - return () -> new MyLinkedList<>(); - } +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; - @Override - public BiConsumer, T> accumulator() { +public class MyListCollector implements Collector, MyList> { + @Override + public Supplier> supplier() { + return () -> new MyLinkedList<>(); + } - return (list, element) -> list.add(element); - } + @Override + public BiConsumer, T> accumulator() { - @Override - public BinaryOperator> combiner() { - return (list1, list2) -> { - list1.addAll(list2); - return list1; - }; - } + return (list, element) -> list.add(element); + } - @Override - public Function, MyList> finisher() - { - return list -> list; - } + @Override + public BinaryOperator> combiner() { + return (list1, list2) -> { + list1.addAll(list2); + return list1; + }; + } - @Override - public Set characteristics() { - return java.util.Set.of( - Collector.Characteristics.IDENTITY_FINISH - ); - } + @Override + public Function, MyList> finisher() { + return list -> list; + } + + @Override + public Set characteristics() { + return java.util.Set.of(Collector.Characteristics.IDENTITY_FINISH); + } } diff --git a/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java b/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java index 7e7c3d2..45ac263 100644 --- a/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java +++ b/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java @@ -1,125 +1,132 @@ package ru.project.finder; + import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -public class StudentOccurrenceIndexFinder -{ - public int[] findOccurrences(MyList students, Student target) { - if (students == null) { - throw new IllegalArgumentException(); - } - if (target == null) { - throw new IllegalArgumentException(); - } - if (students.isEmpty()) { - return new int[0]; - } - int size = students.size(); - int threadCount = calculateThreadCount(size); - int chunkSize = countChunkSize(size, threadCount); +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.student.Student; - ExecutorService executor = Executors.newFixedThreadPool(threadCount); - MyList>> futures = new MyLinkedList<>(); +public class StudentOccurrenceIndexFinder { + public int[] findOccurrences(MyList students, Student target) { + if (students == null) { + throw new IllegalArgumentException(); + } + if (target == null) { + throw new IllegalArgumentException(); + } + if (students.isEmpty()) { + return new int[0]; + } + int size = students.size(); + int threadCount = calculateThreadCount(size); + int chunkSize = countChunkSize(size, threadCount); - for (int i = 0; i < threadCount; i++) { - int fromIndex = i * chunkSize; - int toIndex = Math.min(fromIndex + chunkSize, size); + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + MyList>> futures = new MyLinkedList<>(); - if (fromIndex >= toIndex) { - break; - } + for (int i = 0; i < threadCount; i++) { + int fromIndex = i * chunkSize; + int toIndex = Math.min(fromIndex + chunkSize, size); - final int start = fromIndex; - final int end = toIndex; + if (fromIndex >= toIndex) { + break; + } - Future> future = executor.submit(() -> { + final int start = fromIndex; + final int end = toIndex; + + Future> future = + executor.submit( + () -> { return findInRange(students, target, start, end); - }); - futures.add(future); + }); + futures.add(future); + } + MyList allIndices = new MyLinkedList<>(); + try { + for (int i = 0; i < futures.size(); i++) { + Future> future = futures.get(i); + MyList partialResult = future.get(); + + if (partialResult != null) { + for (int j = 0; j < partialResult.size(); j++) { + allIndices.add(partialResult.get(j)); + } } - MyList allIndices = new MyLinkedList<>(); - try { - for (int i = 0; i < futures.size(); i++) { - Future> future = futures.get(i); - MyList partialResult = future.get(); - - if (partialResult != null) { - for (int j = 0; j < partialResult.size(); j++) { - allIndices.add(partialResult.get(j)); - } - } - } - } catch (Exception e) { - throw new RuntimeException("Ошибка при поиске", e); - } finally { - executor.shutdown(); - } - - sortList(allIndices); - - int[] resultArray = new int[allIndices.size()]; - for (int i = 0; i < allIndices.size(); i++) { - resultArray[i] = allIndices.get(i); - } - - return resultArray; + } + } catch (Exception e) { + throw new RuntimeException("Ошибка при поиске", e); + } finally { + executor.shutdown(); } - private void sortList(MyList list) { - if (list.size() <= 1) return; + sortList(allIndices); - MyList sorted = new MyLinkedList<>(); - - for (int i = 0; i < list.size(); i++) { - int value = list.get(i); - int insertIndex = 0; - while (insertIndex < sorted.size()) { - if (sorted.get(insertIndex) > value) { - break; - } - insertIndex++; - } - - MyList temp = new MyLinkedList<>(); - for (int k = 0; k < insertIndex; k++) { - temp.add(sorted.get(k)); - } - temp.add(value); - for (int k = insertIndex; k < sorted.size(); k++) { - temp.add(sorted.get(k)); - } - sorted = temp; - } - - while (!list.isEmpty()) { - list.remove(0); - } - - for (int i = 0; i < sorted.size(); i++) { - list.add(sorted.get(i)); - } + int[] resultArray = new int[allIndices.size()]; + for (int i = 0; i < allIndices.size(); i++) { + resultArray[i] = allIndices.get(i); } - private int calculateThreadCount(int size) { - int processors = Runtime.getRuntime().availableProcessors(); - return Math.min(processors, size); - } + return resultArray; + } - private int countChunkSize(int size, int threadCount) { - if (threadCount == 0) return 0; - return (size + threadCount - 1) / threadCount; - } + private void sortList(MyList list) { + if (list.size() <= 1) return; - private MyList findInRange(MyList students, Student target, int fromIndex, int toIndex) { - MyList foundIndices = new MyLinkedList<>(); + MyList sorted = new MyLinkedList<>(); - for (int i = fromIndex; i < toIndex; i++) { - Student current = students.get(i); - if (current != null && current.equals(target)) { - foundIndices.add(i); - } + for (int i = 0; i < list.size(); i++) { + int value = list.get(i); + int insertIndex = 0; + while (insertIndex < sorted.size()) { + if (sorted.get(insertIndex) > value) { + break; } + insertIndex++; + } - return foundIndices; + MyList temp = new MyLinkedList<>(); + for (int k = 0; k < insertIndex; k++) { + temp.add(sorted.get(k)); + } + temp.add(value); + for (int k = insertIndex; k < sorted.size(); k++) { + temp.add(sorted.get(k)); + } + sorted = temp; } + + while (!list.isEmpty()) { + list.remove(0); + } + + for (int i = 0; i < sorted.size(); i++) { + list.add(sorted.get(i)); + } + } + + private int calculateThreadCount(int size) { + int processors = Runtime.getRuntime().availableProcessors(); + return Math.min(processors, size); + } + + private int countChunkSize(int size, int threadCount) { + if (threadCount == 0) return 0; + return (size + threadCount - 1) / threadCount; + } + + private MyList findInRange( + MyList students, Student target, int fromIndex, int toIndex) { + MyList foundIndices = new MyLinkedList<>(); + + for (int i = fromIndex; i < toIndex; i++) { + Student current = students.get(i); + if (current != null && current.equals(target)) { + foundIndices.add(i); + } + } + + return foundIndices; + } } diff --git a/app/src/main/java/ru/project/search/StudentSearchService.java b/app/src/main/java/ru/project/search/StudentSearchService.java index 4ced3c9..dc3a366 100644 --- a/app/src/main/java/ru/project/search/StudentSearchService.java +++ b/app/src/main/java/ru/project/search/StudentSearchService.java @@ -1,30 +1,30 @@ package ru.project.search; -import ru.project.list.MyList; -import ru.project.list.MyLinkedList; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; import ru.project.student.Student; public class StudentSearchService { - private final StudentSearchCriteria criteria; + private final StudentSearchCriteria criteria; - public StudentSearchService(StudentSearchCriteria criteria) { - if (criteria == null) { - throw new IllegalArgumentException("Criteria must not be null"); - } - this.criteria = criteria; + public StudentSearchService(StudentSearchCriteria criteria) { + if (criteria == null) { + throw new IllegalArgumentException("Criteria must not be null"); } + this.criteria = criteria; + } - public MyList find(MyList students) { - if (students == null) { - return new MyLinkedList<>(); - } - MyList result = new MyLinkedList<>(); - for (int i = 0; i < students.size(); i++) { - Student s = students.get(i); - if (criteria.matches(s)) { - result.add(s); - } - } - return result; + public MyList find(MyList students) { + if (students == null) { + return new MyLinkedList<>(); } -} \ No newline at end of file + MyList result = new MyLinkedList<>(); + for (int i = 0; i < students.size(); i++) { + Student s = students.get(i); + if (criteria.matches(s)) { + result.add(s); + } + } + return result; + } +} From a143a535bab1164338fa57c272e12980a8ee4c99 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: Mon, 31 Aug 2026 18:01:58 +0400 Subject: [PATCH 102/156] feat(ui): improve menu action labels and sort display --- .../ui/collection/CurrentCollectionMenu.java | 10 +++++----- .../collection/sorting/SortCollectionMenu.java | 18 ++++++++++++++++-- .../ui/creation/CreateCollectionMenu.java | 4 ++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java b/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java index 8dad545..f6edd36 100644 --- a/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java @@ -25,15 +25,15 @@ public class CurrentCollectionMenu extends BaseModalWindow { ActionListBox menu = new ActionListBox(); - menu.addItem("Показать коллекцию", this::showCurrentCollection); + menu.addItem("Показать коллекцию...", this::showCurrentCollection); - menu.addItem("Сохранить коллекцию в файл", this::saveCurrentCollection); + menu.addItem("Сохранить коллекцию в файл...", this::saveCurrentCollection); - menu.addItem("Сортировать коллекцию", sortCollectionMenu::showModal); + menu.addItem("Меню сортировки коллекции", sortCollectionMenu::showModal); - menu.addItem("Поиск элемента в коллекции", this::searchCollection); + menu.addItem("Меню поиска элемента в коллекции", this::searchCollection); - menu.addItem("Подсчёт количества вхождений элемента", this::countOccurrences); + menu.addItem("Меню подсчёта количества вхождений элемента", this::countOccurrences); menu.addItem("Выйти", this::close); diff --git a/app/src/main/java/ru/project/ui/collection/sorting/SortCollectionMenu.java b/app/src/main/java/ru/project/ui/collection/sorting/SortCollectionMenu.java index 9694764..f405e2e 100644 --- a/app/src/main/java/ru/project/ui/collection/sorting/SortCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/collection/sorting/SortCollectionMenu.java @@ -36,7 +36,8 @@ public class SortCollectionMenu extends BaseModalWindow { this.sortMethodLabel = new Label("Способ сортировки: " + getSortMethodName(selectedSortMethod)); - this.sortFieldLabel = new Label("Сортировать по: " + getSortFieldName(selectedSortField)); + this.sortFieldLabel = + new Label("Сортировать по: " + getSortFieldName(selectedSortMethod, selectedSortField)); this.sortMethodSelectionWindow = new SortMethodSelectionWindow( @@ -46,6 +47,9 @@ public class SortCollectionMenu extends BaseModalWindow { sortMethodLabel.setText( "Способ сортировки: " + getSortMethodName(selectedSortMethod)); + + sortFieldLabel.setText( + "Сортировать по: " + getSortFieldName(selectedSortMethod, selectedSortField)); }); this.normalSortSettingsWindow = @@ -53,6 +57,9 @@ public class SortCollectionMenu extends BaseModalWindow { gui, sortField -> { selectedSortField = sortField; + + sortFieldLabel.setText( + "Сортировать по: " + getSortFieldName(selectedSortMethod, selectedSortField)); }); this.paritySortSettingsWindow = new ParitySortSettingsWindow(gui); @@ -116,7 +123,14 @@ public class SortCollectionMenu extends BaseModalWindow { }; } - private String getSortFieldName(SortField sortField) { + private String getSortFieldName(SortMethod sortMethod, SortField sortField) { + return switch (sortMethod) { + case NORMAL -> getNormalSortFieldName(sortField); + case PARITY -> "номеру зачётной книжки"; + }; + } + + private String getNormalSortFieldName(SortField sortField) { return switch (sortField) { case GROUP_NUMBER -> "номеру группы"; case AVERAGE_GRADE -> "среднему баллу"; diff --git a/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java b/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java index da542b9..76043ea 100644 --- a/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/creation/CreateCollectionMenu.java @@ -50,10 +50,10 @@ public class CreateCollectionMenu extends BaseModalWindow { panel.addComponent(fillMethodLabel); - panel.addComponent(new Button("Выбрать способ заполнения", this::selectFillMethod)); + panel.addComponent(new Button("Выбрать способ заполнения...", this::selectFillMethod)); panel.addComponent( - new Button("Настроить текущий способ заполнения", this::setupCurrentFillMethod)); + new Button("Настроить текущий способ заполнения...", this::setupCurrentFillMethod)); panel.addComponent(new Button("Создать новую коллекцию", this::updateMainCollection)); From db66ebbc071f6f2cca78496b02cae3d27fce340d 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: Mon, 31 Aug 2026 19:11:10 +0400 Subject: [PATCH 103/156] feat(sorting): add selectable sorting algorithms - add bubble and merge sort algorithm selection - add SortStrategySelectionWindow - apply selected strategy to normal and parity sorting - use dedicated comparators for student fields - remove obsolete EvenOnlyMergeSortStrategy - update collection menu labels and TODO placeholders --- .../sorting/EvenOnlyMergeSortStrategy.java | 84 ---------------- .../ui/collection/CurrentCollectionMenu.java | 17 +++- .../ui/collection/sorting/SortAlgorithm.java | 6 ++ .../sorting/SortCollectionMenu.java | 95 +++++++++++++++++-- .../sorting/SortStrategySelectionWindow.java | 34 +++++++ 5 files changed, 137 insertions(+), 99 deletions(-) delete mode 100644 app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java create mode 100644 app/src/main/java/ru/project/ui/collection/sorting/SortAlgorithm.java create mode 100644 app/src/main/java/ru/project/ui/collection/sorting/SortStrategySelectionWindow.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 021a70e..0000000 --- a/app/src/main/java/ru/project/sorting/EvenOnlyMergeSortStrategy.java +++ /dev/null @@ -1,84 +0,0 @@ -package ru.project.sorting; - -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; - - 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; - } -} diff --git a/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java b/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java index f6edd36..6f72f17 100644 --- a/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java @@ -7,7 +7,6 @@ import ru.project.ui.base.BaseModalWindow; import ru.project.ui.collection.sorting.SortCollectionMenu; import ru.project.ui.common.CollectionDisplayWindow; import ru.project.ui.common.CollectionFileSaveWindow; -import ru.project.ui.common.MessageWindow; public class CurrentCollectionMenu extends BaseModalWindow { @@ -15,6 +14,9 @@ public class CurrentCollectionMenu extends BaseModalWindow { private final SortCollectionMenu sortCollectionMenu; + // private final SearchCollectionMenu searchCollectionMenu; + // private final CountOccurrencesMenu countOccurrencesMenu; + public CurrentCollectionMenu(WindowBasedTextGUI gui, AppState state) { super("Меню текущей коллекции", gui); @@ -23,6 +25,9 @@ public class CurrentCollectionMenu extends BaseModalWindow { this.sortCollectionMenu = new SortCollectionMenu(gui, state); + // this.searchCollectionMenu = new SearchCollectionMenu(gui, state); + // this.countCollectionMenu = new CountOccurrencesMenu(gui, state); + ActionListBox menu = new ActionListBox(); menu.addItem("Показать коллекцию...", this::showCurrentCollection); @@ -53,12 +58,14 @@ public class CurrentCollectionMenu extends BaseModalWindow { } private void searchCollection() { - // TODO - MessageWindow.showModal(gui, "Не реализовано."); + // SearchCollectionMenu searchCollectionMenu = new SearchCollectionMenu(gui, state); + // + // searchCollectionMenu.showModal(); } private void countOccurrences() { - // TODO - MessageWindow.showModal(gui, "Не реализовано."); + // CountOccurrencesMenu countOccurrencesMenu = new CountOccurrencesMenu(gui, state); + // + // countOccurrencesMenu.showModal(); } } diff --git a/app/src/main/java/ru/project/ui/collection/sorting/SortAlgorithm.java b/app/src/main/java/ru/project/ui/collection/sorting/SortAlgorithm.java new file mode 100644 index 0000000..4f193a6 --- /dev/null +++ b/app/src/main/java/ru/project/ui/collection/sorting/SortAlgorithm.java @@ -0,0 +1,6 @@ +package ru.project.ui.collection.sorting; + +public enum SortAlgorithm { + BUBBLE, + MERGE +} diff --git a/app/src/main/java/ru/project/ui/collection/sorting/SortCollectionMenu.java b/app/src/main/java/ru/project/ui/collection/sorting/SortCollectionMenu.java index f405e2e..fc8c774 100644 --- a/app/src/main/java/ru/project/ui/collection/sorting/SortCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/collection/sorting/SortCollectionMenu.java @@ -5,7 +5,16 @@ 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.Comparator; import ru.project.model.AppState; +import ru.project.sorting.BubbleSortStrategy; +import ru.project.sorting.EvenOnlySortStrategy; +import ru.project.sorting.MergeSortStrategy; +import ru.project.sorting.SortStrategy; +import ru.project.sorting.StudentAverageGradeComparator; +import ru.project.sorting.StudentGroupComparator; +import ru.project.sorting.StudentRecordBookComparator; +import ru.project.student.Student; import ru.project.ui.base.BaseModalWindow; import ru.project.ui.common.MessageWindow; @@ -13,29 +22,37 @@ public class SortCollectionMenu extends BaseModalWindow { private final AppState state; - private final Label sortMethodLabel; + private final Label sortAlgorithmLabel; private final Label sortFieldLabel; + private final Label sortMethodLabel; - private final SortMethodSelectionWindow sortMethodSelectionWindow; private final NormalSortSettingsWindow normalSortSettingsWindow; private final ParitySortSettingsWindow paritySortSettingsWindow; + private final SortMethodSelectionWindow sortMethodSelectionWindow; + private final SortStrategySelectionWindow sortStrategySelectionWindow; - private SortMethod selectedSortMethod; + private SortAlgorithm selectedSortAlgorithm; private SortField selectedSortField; + private SortMethod selectedSortMethod; public SortCollectionMenu(WindowBasedTextGUI gui, AppState state) { super("Меню сортировки текущей коллекции", gui); this.state = state; - this.selectedSortMethod = SortMethod.NORMAL; + + this.selectedSortAlgorithm = SortAlgorithm.BUBBLE; this.selectedSortField = SortField.GROUP_NUMBER; + this.selectedSortMethod = SortMethod.NORMAL; Panel panel = new Panel(); panel.setLayoutManager(new LinearLayout()); this.sortMethodLabel = new Label("Способ сортировки: " + getSortMethodName(selectedSortMethod)); + this.sortAlgorithmLabel = + new Label("Алгоритм сортировки: " + getSortAlgorithmName(selectedSortAlgorithm)); + this.sortFieldLabel = new Label("Сортировать по: " + getSortFieldName(selectedSortMethod, selectedSortField)); @@ -52,6 +69,16 @@ public class SortCollectionMenu extends BaseModalWindow { "Сортировать по: " + getSortFieldName(selectedSortMethod, selectedSortField)); }); + this.sortStrategySelectionWindow = + new SortStrategySelectionWindow( + gui, + sortAlgorithm -> { + selectedSortAlgorithm = sortAlgorithm; + + sortAlgorithmLabel.setText( + "Алгоритм сортировки: " + getSortAlgorithmName(selectedSortAlgorithm)); + }); + this.normalSortSettingsWindow = new NormalSortSettingsWindow( gui, @@ -66,11 +93,14 @@ public class SortCollectionMenu extends BaseModalWindow { panel.addComponent(sortMethodLabel); panel.addComponent(sortFieldLabel); + panel.addComponent(sortAlgorithmLabel); - panel.addComponent(new Button("Выбрать способ сортировки", this::selectSortMethod)); + panel.addComponent(new Button("Выбрать способ сортировки...", this::selectSortMethod)); + + panel.addComponent(new Button("Выбрать алгоритм сортировки...", this::selectSortAlgorithm)); panel.addComponent( - new Button("Настроить текущий способ сортировки", this::setupCurrentSortMethod)); + new Button("Настроить текущий способ сортировки...", this::setupCurrentSortMethod)); panel.addComponent(new Button("Сортировать текущую коллекцию", this::sortCurrentCollection)); @@ -91,6 +121,10 @@ public class SortCollectionMenu extends BaseModalWindow { sortMethodSelectionWindow.showModal(); } + private void selectSortAlgorithm() { + sortStrategySelectionWindow.showModal(); + } + private void sortCurrentCollection() { if (state.getMainCollection() == null || state.getMainCollection().isEmpty()) { @@ -107,25 +141,64 @@ public class SortCollectionMenu extends BaseModalWindow { } private void sortNormally() { - // TODO - MessageWindow.showModal(gui, "Обычная сортировка не реализована."); + SortStrategy sortStrategy = getSortStrategy(); + + Comparator comparator = getSortComparator(); + + sortStrategy.sort(state.getMainCollection(), comparator); + + MessageWindow.showModal(gui, "Обычная сортировка успешно выполнена."); } private void sortByParity() { - // TODO - MessageWindow.showModal(gui, "Сортировка по чётности не реализована."); + SortStrategy sortStrategy = + new EvenOnlySortStrategy<>(getSortStrategy(), Student::getRecordBookNumber); + + Comparator comparator = new StudentRecordBookComparator(); + + sortStrategy.sort(state.getMainCollection(), comparator); + + MessageWindow.showModal(gui, "Сортировка по чётности успешно выполнена."); + } + + private SortStrategy getSortStrategy() { + return switch (selectedSortAlgorithm) { + case BUBBLE -> new BubbleSortStrategy<>(); + + case MERGE -> new MergeSortStrategy<>(); + }; + } + + private Comparator getSortComparator() { + return switch (selectedSortField) { + case GROUP_NUMBER -> new StudentGroupComparator(); + + case AVERAGE_GRADE -> new StudentAverageGradeComparator(); + + case RECORD_BOOK_NUMBER -> new StudentRecordBookComparator(); + }; } private String getSortMethodName(SortMethod sortMethod) { return switch (sortMethod) { case NORMAL -> "Обычная сортировка"; + case PARITY -> "Сортировка по чётности"; }; } + private String getSortAlgorithmName(SortAlgorithm sortAlgorithm) { + return switch (sortAlgorithm) { + case BUBBLE -> "Сортировка пузырьком"; + + case MERGE -> "Сортировка слиянием"; + }; + } + private String getSortFieldName(SortMethod sortMethod, SortField sortField) { return switch (sortMethod) { case NORMAL -> getNormalSortFieldName(sortField); + case PARITY -> "номеру зачётной книжки"; }; } @@ -133,7 +206,9 @@ public class SortCollectionMenu extends BaseModalWindow { private String getNormalSortFieldName(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/collection/sorting/SortStrategySelectionWindow.java b/app/src/main/java/ru/project/ui/collection/sorting/SortStrategySelectionWindow.java new file mode 100644 index 0000000..2084e4e --- /dev/null +++ b/app/src/main/java/ru/project/ui/collection/sorting/SortStrategySelectionWindow.java @@ -0,0 +1,34 @@ +package ru.project.ui.collection.sorting; + +import com.googlecode.lanterna.gui2.ActionListBox; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.Consumer; +import ru.project.ui.base.BaseModalWindow; + +public class SortStrategySelectionWindow extends BaseModalWindow { + + private final Consumer onSortAlgorithmSelected; + + public SortStrategySelectionWindow( + WindowBasedTextGUI gui, Consumer onSortAlgorithmSelected) { + + super("Выбор алгоритма сортировки", gui); + + this.onSortAlgorithmSelected = onSortAlgorithmSelected; + + ActionListBox menu = new ActionListBox(); + + menu.addItem("Сортировка пузырьком", () -> selectSortAlgorithm(SortAlgorithm.BUBBLE)); + + menu.addItem("Сортировка слиянием", () -> selectSortAlgorithm(SortAlgorithm.MERGE)); + + menu.addItem("Выйти", this::close); + + setComponent(menu); + } + + private void selectSortAlgorithm(SortAlgorithm sortAlgorithm) { + onSortAlgorithmSelected.accept(sortAlgorithm); + close(); + } +} From ac8792eda4ed962d3ae9ab674c9a798b1a0b1614 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: Mon, 31 Aug 2026 21:57:38 +0400 Subject: [PATCH 104/156] docs: update csv student writer contract --- docs/tasks/CsvStudentIO.md | 169 +++++++++++++++++++++++++++++++------ 1 file changed, 143 insertions(+), 26 deletions(-) diff --git a/docs/tasks/CsvStudentIO.md b/docs/tasks/CsvStudentIO.md index 89e2ad5..76ea466 100644 --- a/docs/tasks/CsvStudentIO.md +++ b/docs/tasks/CsvStudentIO.md @@ -42,13 +42,13 @@ C25,4.9,10003 --- -# `StudentCsvWriter` +## `StudentCsvWriter` -## Назначение +### Назначение -Дозаписывать одного студента в указанный CSV-файл. +Дозаписывать одного студента в **существующий** CSV-файл. -## Контракт +### Контракт Конструктор: @@ -65,41 +65,158 @@ public void write(Student student) throws IOException ### Требования 1. Метод принимает одного `Student`. -2. Запись выполняется в режиме добавления. -3. Существующие записи не изменяются. -4. Если файл существует, заголовок повторно не записывается. -5. Если файл не существует, необходимо создать файл. -6. При создании нового файла имя должно соответствовать формату: +2. Указанный файл должен уже существовать. +3. Запись выполняется в режиме добавления. +4. Существующие записи не должны изменяться. +5. Заголовок CSV не должен записываться повторно. +6. Если файл не существует, новый файл **не создавать**. +7. Если файл не существует, метод должен выбросить `IOException`. +8. При успешной записи метод завершается без исключения. +9. Метод не возвращает значение. +10. Исключения, связанные с записью файла, не обрабатывать внутри метода. +11. Исключения `Student` и `StudentBuilder` также не обрабатывать. + +## Невозможность записи + +Если указанный файл не существует, метод должен выбросить `IOException`. + +Также `IOException` должен возникать и передаваться вызывающему коду, если: + +- файл недоступен для записи; +- отсутствуют права на запись; +- произошла ошибка файловой системы; +- запись в файл невозможна по другой причине. + +Создание файла с именем вида: ```text students-yyyy-MM-dd-HH-mm.csv ``` -Например: +`StudentCsvWriter` **не выполняет**. -```text -students-2026-08-28-20-45.csv +## Пример теста: несуществующий файл + +Передать путь к несуществующему файлу: + +```java +writer.write(student); ``` -1. Новый файл создаётся в директории, указанной абсолютным путём. -2. При создании нового файла сначала записывается заголовок. -3. При успешной записи метод завершается без исключения. -4. Метод не возвращает значение. -5. Исключения, связанные с записью файла, не обрабатывать внутри метода — они должны передаваться вызывающему коду. -6. Исключения `Student` и `StudentBuilder` также не обрабатывать. +Проверить: -## Невозможность записи +```text +IOException +``` -Если студент не может быть записан, например: +Проверить также, что новый файл не был создан. -- отсутствуют права на запись; -- невозможно создать файл; -- файл недоступен; -- произошла ошибка файловой системы; +## Изменение теста существующего файла -метод должен выбросить соответствующее исключение, например `IOException`. +Для существующего файла: -Запись не должна возвращать специальное значение вроде `0` для обозначения ошибки. +```text +groupNumber,averageGrade,recordBookNumber +A12,4.5,10001 +``` + +выполнить: + +```java +writer.write(student2); +``` + +Проверить, что результат: + +```text +groupNumber,averageGrade,recordBookNumber +A12,4.5,10001 +B07,3.8,10002 +``` + +Заголовок присутствует только один раз. + +## Дополнительный метод `StudentCsvWriter` + +Кроме метода записи одного студента: + +```java +public void write(Student student) throws IOException; +``` + +необходимо реализовать метод: + +```java +public void write(MyList[] students) throws IOException; +``` + +### Контракт + +Метод должен: + +1. Принимать массив `MyList`. +2. Последовательно обрабатывать все переданные коллекции. +3. Записывать каждого студента в указанный CSV-файл. +4. Сохранять порядок: + - коллекций в массиве; + - студентов внутри каждой `MyList`. +5. Использовать тот же формат CSV и те же правила записи, что и `write(Student)`. +6. Работать только с уже существующим файлом. +7. Не создавать файл, если он отсутствует. +8. При отсутствии файла выбрасывать `IOException`. +9. Не изменять переданные `MyList`. +10. Не обрабатывать исключения, возникающие при создании или валидации `Student`. +11. При ошибке записи прекращать операцию и передавать исключение вызывающему коду. + +### Пример + +```java +MyList[] studentLists = ...; + +writer.write(studentLists); +``` + +Если: + +```text +studentLists[0] = [Student A, Student B] +studentLists[1] = [Student C] +studentLists[2] = [Student D, Student E] +``` + +в файл должны последовательно добавиться: + +```text +Student A +Student B +Student C +Student D +Student E +``` + +### Проверка пустых коллекций + +Пустые `MyList` внутри массива не должны приводить к ошибке и не должны добавлять записей в файл. + +### Проверка пустого массива + +Для: + +```java +MyList[] studentLists = ...; // размер 0 +``` + +метод не должен добавлять никаких записей и не должен изменять файл. + +### Проверка отсутствующего файла + +Для несуществующего файла: + +```java +writer.write(studentLists); +``` + +должен выбрасываться `IOException`. Файл создавать запрещено. --- From 26f124bd0fb7c03f705131e4adc006d759f26f81 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: Mon, 31 Aug 2026 22:17:35 +0400 Subject: [PATCH 105/156] build: add Apache Commons CSV dependency - add commons-csv for CSV read and write support - configure version 1.14.1 in app/pom.xml --- app/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/pom.xml b/app/pom.xml index 5afca24..3094443 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -48,6 +48,12 @@ 3.1.5 + + org.apache.commons + commons-csv + 1.14.1 + + From 4dadefd87f644426d53ef8a47581a4e3a197997e 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: Mon, 31 Aug 2026 22:17:51 +0400 Subject: [PATCH 106/156] feat(io): add StudentCsvWriter - add CSV writer for individual students - support sequential writing of student lists - preserve existing CSV data and avoid duplicate headers - reject missing files with IOException --- .../java/ru/project/io/StudentCsvWriter.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 app/src/main/java/ru/project/io/StudentCsvWriter.java diff --git a/app/src/main/java/ru/project/io/StudentCsvWriter.java b/app/src/main/java/ru/project/io/StudentCsvWriter.java new file mode 100644 index 0000000..8795fbc --- /dev/null +++ b/app/src/main/java/ru/project/io/StudentCsvWriter.java @@ -0,0 +1,88 @@ +package ru.project.io; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import ru.project.collection.MyList; +import ru.project.student.Student; + +public class StudentCsvWriter { + + private static final String GROUP_NUMBER_HEADER = "groupNumber"; + private static final String AVERAGE_GRADE_HEADER = "averageGrade"; + private static final String RECORD_BOOK_NUMBER_HEADER = "recordBookNumber"; + + private final String absoluteFilePath; + + public StudentCsvWriter(String absoluteFilePath) { + this.absoluteFilePath = absoluteFilePath; + } + + public void write(Student student) throws IOException { + ensureFileExists(); + + boolean fileIsEmpty = isFileEmpty(); + + try (CSVPrinter printer = createPrinter()) { + if (fileIsEmpty) { + writeHeader(printer); + } + + writeStudent(printer, student); + } + } + + public void write(MyList[] students) throws IOException { + ensureFileExists(); + + boolean fileIsEmpty = isFileEmpty(); + + try (CSVPrinter printer = createPrinter()) { + if (fileIsEmpty) { + writeHeader(printer); + } + + for (MyList studentList : students) { + for (int i = 0; i < studentList.size(); i++) { + writeStudent(printer, studentList.get(i)); + } + } + } + } + + private CSVPrinter createPrinter() throws IOException { + BufferedWriter writer = + new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream(absoluteFilePath, true), StandardCharsets.UTF_8)); + + return new CSVPrinter(writer, CSVFormat.DEFAULT); + } + + private void writeHeader(CSVPrinter printer) throws IOException { + printer.printRecord(GROUP_NUMBER_HEADER, AVERAGE_GRADE_HEADER, RECORD_BOOK_NUMBER_HEADER); + } + + private void writeStudent(CSVPrinter printer, Student student) throws IOException { + + printer.printRecord( + student.getGroupNumber(), student.getAverageGrade(), student.getRecordBookNumber()); + } + + private void ensureFileExists() throws IOException { + File file = new File(absoluteFilePath); + + if (!file.isFile()) { + throw new IOException("CSV file does not exist: " + absoluteFilePath); + } + } + + private boolean isFileEmpty() { + return new File(absoluteFilePath).length() == 0; + } +} From be73b0fbf85eda561fce03e891ebfb1fc867ced2 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: Mon, 31 Aug 2026 22:18:22 +0400 Subject: [PATCH 107/156] test(io): add StudentCsvWriter tests - test writing to empty and existing CSV files - verify append behavior and header handling - test writing arrays of student lists - verify student order and empty lists - test IOException for missing files --- .../ru/project/io/StudentCsvWriterTest.java | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 app/src/test/java/ru/project/io/StudentCsvWriterTest.java diff --git a/app/src/test/java/ru/project/io/StudentCsvWriterTest.java b/app/src/test/java/ru/project/io/StudentCsvWriterTest.java new file mode 100644 index 0000000..d5da367 --- /dev/null +++ b/app/src/test/java/ru/project/io/StudentCsvWriterTest.java @@ -0,0 +1,194 @@ +package ru.project.io; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.student.Student; + +class StudentCsvWriterTest { + + @TempDir Path tempDir; + + @Test + void writeShouldWriteHeaderAndStudentToEmptyFile() throws Exception { + Path file = tempDir.resolve("students.csv"); + Files.createFile(file); + + Student student = new Student("A12", 4.5, 10001); + + StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString()); + + writer.write(student); + + List lines = Files.readAllLines(file); + + assertEquals(List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001"), lines); + } + + @Test + void writeShouldAppendStudentToExistingFile() throws Exception { + Path file = tempDir.resolve("students.csv"); + + Files.write(file, List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001")); + + Student student = new Student("B07", 3.8, 10002); + + StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString()); + + writer.write(student); + + List lines = Files.readAllLines(file); + + assertEquals( + List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001", "B07,3.8,10002"), + lines); + } + + @Test + void writeShouldNotCreateMissingFile() { + Path file = tempDir.resolve("students.csv"); + + Student student = new Student("A12", 4.5, 10001); + + StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString()); + + assertThrows(IOException.class, () -> writer.write(student)); + + assertFalse(Files.exists(file)); + } + + @Test + void writeShouldAppendSeveralStudentsInOrder() throws Exception { + Path file = tempDir.resolve("students.csv"); + Files.createFile(file); + + Student student1 = new Student("A12", 4.5, 10001); + Student student2 = new Student("B07", 3.8, 10002); + Student student3 = new Student("C25", 4.9, 10003); + + StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString()); + + writer.write(student1); + writer.write(student2); + writer.write(student3); + + List lines = Files.readAllLines(file); + + assertEquals( + List.of( + "groupNumber,averageGrade,recordBookNumber", + "A12,4.5,10001", + "B07,3.8,10002", + "C25,4.9,10003"), + lines); + } + + @Test + void writeArrayShouldAppendStudentsFromAllListsInOrder() throws Exception { + + Path file = tempDir.resolve("students.csv"); + Files.createFile(file); + + Student student1 = new Student("A12", 4.5, 10001); + Student student2 = new Student("B07", 3.8, 10002); + Student student3 = new Student("C25", 4.9, 10003); + Student student4 = new Student("D15", 4.2, 10004); + Student student5 = new Student("E20", 3.6, 10005); + + MyList firstList = new MyLinkedList<>(); + firstList.add(student1); + firstList.add(student2); + + MyList secondList = new MyLinkedList<>(); + secondList.add(student3); + + MyList thirdList = new MyLinkedList<>(); + thirdList.add(student4); + thirdList.add(student5); + + @SuppressWarnings("unchecked") + MyList[] students = new MyList[] {firstList, secondList, thirdList}; + + StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString()); + + writer.write(students); + + List lines = Files.readAllLines(file); + + assertEquals( + List.of( + "groupNumber,averageGrade,recordBookNumber", + "A12,4.5,10001", + "B07,3.8,10002", + "C25,4.9,10003", + "D15,4.2,10004", + "E20,3.6,10005"), + lines); + } + + @Test + void writeArrayShouldSkipEmptyLists() throws Exception { + Path file = tempDir.resolve("students.csv"); + Files.createFile(file); + + Student student = new Student("A12", 4.5, 10001); + + MyList emptyList = new MyLinkedList<>(); + + MyList nonEmptyList = new MyLinkedList<>(); + nonEmptyList.add(student); + + @SuppressWarnings("unchecked") + MyList[] students = new MyList[] {emptyList, nonEmptyList, new MyLinkedList<>()}; + + StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString()); + + writer.write(students); + + List lines = Files.readAllLines(file); + + assertEquals(List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001"), lines); + } + + @Test + void writeEmptyArrayShouldNotModifyFile() throws Exception { + Path file = tempDir.resolve("students.csv"); + + List initialContent = + List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001"); + + Files.write(file, initialContent); + + @SuppressWarnings("unchecked") + MyList[] students = new MyList[0]; + + StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString()); + + writer.write(students); + + assertEquals(initialContent, Files.readAllLines(file)); + } + + @Test + void writeEmptyArrayShouldNotCreateMissingFile() { + Path file = tempDir.resolve("students.csv"); + + @SuppressWarnings("unchecked") + MyList[] students = new MyList[0]; + + StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString()); + + assertThrows(IOException.class, () -> writer.write(students)); + + assertFalse(Files.exists(file)); + } +} From 84f9e2aa8d809737c54f881461cb42ad792258e2 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, 1 Sep 2026 13:27:21 +0400 Subject: [PATCH 108/156] feat(search): add collection search menu and filter settings - add search menu with filter configuration and result actions - add support for AND and OR filter relations - add range filters for group number, average grade, and record book number - update current collection menu to delegate search operations --- .../ui/collection/CurrentCollectionMenu.java | 15 +- .../ui/collection/search/FilterRelation.java | 6 + .../search/SearchCollectionMenu.java | 191 ++++++++++ .../ui/collection/search/SearchSettings.java | 116 ++++++ .../search/SearchSettingsWindow.java | 355 ++++++++++++++++++ 5 files changed, 673 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/ru/project/ui/collection/search/FilterRelation.java create mode 100644 app/src/main/java/ru/project/ui/collection/search/SearchCollectionMenu.java create mode 100644 app/src/main/java/ru/project/ui/collection/search/SearchSettings.java create mode 100644 app/src/main/java/ru/project/ui/collection/search/SearchSettingsWindow.java diff --git a/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java b/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java index 6f72f17..516c020 100644 --- a/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java @@ -4,6 +4,7 @@ import com.googlecode.lanterna.gui2.ActionListBox; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import ru.project.model.AppState; import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.collection.search.SearchCollectionMenu; import ru.project.ui.collection.sorting.SortCollectionMenu; import ru.project.ui.common.CollectionDisplayWindow; import ru.project.ui.common.CollectionFileSaveWindow; @@ -13,8 +14,8 @@ public class CurrentCollectionMenu extends BaseModalWindow { private final AppState state; private final SortCollectionMenu sortCollectionMenu; + private final SearchCollectionMenu searchCollectionMenu; - // private final SearchCollectionMenu searchCollectionMenu; // private final CountOccurrencesMenu countOccurrencesMenu; public CurrentCollectionMenu(WindowBasedTextGUI gui, AppState state) { @@ -25,18 +26,18 @@ public class CurrentCollectionMenu extends BaseModalWindow { this.sortCollectionMenu = new SortCollectionMenu(gui, state); - // this.searchCollectionMenu = new SearchCollectionMenu(gui, state); + this.searchCollectionMenu = new SearchCollectionMenu(gui, state); + // this.countCollectionMenu = new CountOccurrencesMenu(gui, state); ActionListBox menu = new ActionListBox(); - menu.addItem("Показать коллекцию...", this::showCurrentCollection); menu.addItem("Сохранить коллекцию в файл...", this::saveCurrentCollection); menu.addItem("Меню сортировки коллекции", sortCollectionMenu::showModal); - menu.addItem("Меню поиска элемента в коллекции", this::searchCollection); + menu.addItem("Меню поиска элемента в коллекции", searchCollectionMenu::showModal); menu.addItem("Меню подсчёта количества вхождений элемента", this::countOccurrences); @@ -57,12 +58,6 @@ public class CurrentCollectionMenu extends BaseModalWindow { window.showModal(); } - private void searchCollection() { - // SearchCollectionMenu searchCollectionMenu = new SearchCollectionMenu(gui, state); - // - // searchCollectionMenu.showModal(); - } - private void countOccurrences() { // CountOccurrencesMenu countOccurrencesMenu = new CountOccurrencesMenu(gui, state); // diff --git a/app/src/main/java/ru/project/ui/collection/search/FilterRelation.java b/app/src/main/java/ru/project/ui/collection/search/FilterRelation.java new file mode 100644 index 0000000..71c6cd2 --- /dev/null +++ b/app/src/main/java/ru/project/ui/collection/search/FilterRelation.java @@ -0,0 +1,6 @@ +package ru.project.ui.collection.search; + +public enum FilterRelation { + AND, + OR +} diff --git a/app/src/main/java/ru/project/ui/collection/search/SearchCollectionMenu.java b/app/src/main/java/ru/project/ui/collection/search/SearchCollectionMenu.java new file mode 100644 index 0000000..e820849 --- /dev/null +++ b/app/src/main/java/ru/project/ui/collection/search/SearchCollectionMenu.java @@ -0,0 +1,191 @@ +package ru.project.ui.collection.search; + +import com.googlecode.lanterna.gui2.ActionListBox; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.model.AppState; +import ru.project.search.AverageGradeRangeCriteria; +import ru.project.search.CompositeStudentSearchCriteria; +import ru.project.search.GroupNumberRangeCriteria; +import ru.project.search.RecordBookNumberRangeCriteria; +import ru.project.search.SearchOperation; +import ru.project.search.StudentSearchCriteria; +import ru.project.search.StudentSearchService; +import ru.project.student.Student; +import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.common.CollectionDisplayWindow; +import ru.project.ui.common.CollectionFileSaveWindow; +import ru.project.ui.common.MessageWindow; + +public class SearchCollectionMenu extends BaseModalWindow { + + private final AppState state; + + private final SearchSettingsWindow searchSettingsWindow; + + private SearchSettings searchSettings; + private MyList searchResultCollection; + + public SearchCollectionMenu(WindowBasedTextGUI gui, AppState state) { + + super("Меню поиска элемента в коллекции", gui); + + this.state = state; + this.searchSettings = new SearchSettings(); + this.searchResultCollection = new MyLinkedList<>(); + + this.searchSettingsWindow = + new SearchSettingsWindow(gui, settings -> this.searchSettings = settings); + + ActionListBox menu = new ActionListBox(); + + menu.addItem("Выполнить поиск", this::updateSearchResult); + + menu.addItem("Показать результат поиска", this::showSearchResult); + + menu.addItem("Сохранить результат поиска в файл", this::saveSearchResult); + + menu.addItem("Настроить процесс поиска...", searchSettingsWindow::showModal); + + menu.addItem("Выйти", this::close); + + setComponent(menu); + } + + private void updateSearchResult() { + MyList collection = state.getMainCollection(); + + if (collection == null || collection.isEmpty()) { + MessageWindow.showModal(gui, "Коллекция для поиска пуста."); + return; + } + + if (!hasEnabledFilters()) { + MessageWindow.showModal(gui, "Не выбран ни один фильтр."); + return; + } + + if (!hasValidFilterRanges()) { + return; + } + + StudentSearchCriteria criteria = createSearchCriteria(); + + StudentSearchService searchService = new StudentSearchService(criteria); + + searchResultCollection = searchService.find(collection); + + MessageWindow.showModal(gui, "Результат поиска обновлён."); + } + + private StudentSearchCriteria createSearchCriteria() { + SearchOperation operation = + searchSettings.getRelation() == FilterRelation.AND + ? SearchOperation.INTERSECTION + : SearchOperation.UNION; + + CompositeStudentSearchCriteria compositeCriteria = + new CompositeStudentSearchCriteria(operation); + + if (searchSettings.isGroupNumberFilterEnabled()) { + compositeCriteria.add( + new GroupNumberRangeCriteria( + searchSettings.getMinGroupNumber(), searchSettings.getMaxGroupNumber())); + } + + if (searchSettings.isAverageGradeFilterEnabled()) { + compositeCriteria.add( + new AverageGradeRangeCriteria( + searchSettings.getMinAverageGrade(), searchSettings.getMaxAverageGrade())); + } + + if (searchSettings.isRecordBookNumberFilterEnabled()) { + compositeCriteria.add( + new RecordBookNumberRangeCriteria( + searchSettings.getMinRecordBookNumber(), searchSettings.getMaxRecordBookNumber())); + } + + return compositeCriteria; + } + + private boolean hasEnabledFilters() { + return searchSettings.isGroupNumberFilterEnabled() + || searchSettings.isAverageGradeFilterEnabled() + || searchSettings.isRecordBookNumberFilterEnabled(); + } + + private boolean hasValidFilterRanges() { + StringBuilder errors = new StringBuilder(); + + if (!hasValidGroupNumberRange()) { + errors.append("Для фильтра по номеру группы задайте минимальное и максимальное значения.\n"); + } + + if (!hasValidAverageGradeRange()) { + errors.append("Для фильтра по среднему баллу задайте минимальное и максимальное значения.\n"); + } + + if (!hasValidRecordBookNumberRange()) { + errors.append( + "Для фильтра по номеру зачётной книжки задайте минимальное и максимальное значения.\n"); + } + + if (errors.length() > 0) { + MessageWindow.showModal(gui, errors.toString()); + return false; + } + + return true; + } + + private boolean hasValidGroupNumberRange() { + if (!searchSettings.isGroupNumberFilterEnabled()) { + return true; + } + + return searchSettings.getMinGroupNumber() != null && searchSettings.getMaxGroupNumber() != null; + } + + private boolean hasValidAverageGradeRange() { + if (!searchSettings.isAverageGradeFilterEnabled()) { + return true; + } + + return searchSettings.getMinAverageGrade() != null + && searchSettings.getMaxAverageGrade() != null; + } + + private boolean hasValidRecordBookNumberRange() { + if (!searchSettings.isRecordBookNumberFilterEnabled()) { + return true; + } + + return searchSettings.getMinRecordBookNumber() != null + && searchSettings.getMaxRecordBookNumber() != null; + } + + private void showSearchResult() { + if (searchResultCollection == null || searchResultCollection.isEmpty()) { + + MessageWindow.showModal(gui, "Результат поиска пуст."); + return; + } + + CollectionDisplayWindow window = new CollectionDisplayWindow(gui, searchResultCollection); + + window.showModal(); + } + + private void saveSearchResult() { + if (searchResultCollection == null || searchResultCollection.isEmpty()) { + + MessageWindow.showModal(gui, "Результат поиска пуст."); + return; + } + + CollectionFileSaveWindow window = new CollectionFileSaveWindow(gui, searchResultCollection); + + window.showModal(); + } +} diff --git a/app/src/main/java/ru/project/ui/collection/search/SearchSettings.java b/app/src/main/java/ru/project/ui/collection/search/SearchSettings.java new file mode 100644 index 0000000..058a59a --- /dev/null +++ b/app/src/main/java/ru/project/ui/collection/search/SearchSettings.java @@ -0,0 +1,116 @@ +package ru.project.ui.collection.search; + +public class SearchSettings { + + private FilterRelation relation; + + private boolean groupNumberFilterEnabled; + private boolean averageGradeFilterEnabled; + private boolean recordBookNumberFilterEnabled; + + private String minGroupNumber; + private String maxGroupNumber; + + private Double minAverageGrade; + private Double maxAverageGrade; + + private Integer minRecordBookNumber; + private Integer maxRecordBookNumber; + + public SearchSettings() { + this.relation = FilterRelation.AND; + + this.groupNumberFilterEnabled = false; + this.averageGradeFilterEnabled = false; + this.recordBookNumberFilterEnabled = false; + + this.minGroupNumber = null; + this.maxGroupNumber = null; + + this.minAverageGrade = null; + this.maxAverageGrade = null; + + this.minRecordBookNumber = null; + this.maxRecordBookNumber = null; + } + + public FilterRelation getRelation() { + return relation; + } + + public void setRelation(FilterRelation relation) { + this.relation = relation; + } + + public boolean isGroupNumberFilterEnabled() { + return groupNumberFilterEnabled; + } + + public void setGroupNumberFilterEnabled(boolean enabled) { + this.groupNumberFilterEnabled = enabled; + } + + public boolean isAverageGradeFilterEnabled() { + return averageGradeFilterEnabled; + } + + public void setAverageGradeFilterEnabled(boolean enabled) { + this.averageGradeFilterEnabled = enabled; + } + + public boolean isRecordBookNumberFilterEnabled() { + return recordBookNumberFilterEnabled; + } + + public void setRecordBookNumberFilterEnabled(boolean enabled) { + this.recordBookNumberFilterEnabled = enabled; + } + + public String getMinGroupNumber() { + return minGroupNumber; + } + + public void setMinGroupNumber(String minGroupNumber) { + this.minGroupNumber = minGroupNumber; + } + + public String getMaxGroupNumber() { + return maxGroupNumber; + } + + public void setMaxGroupNumber(String maxGroupNumber) { + this.maxGroupNumber = maxGroupNumber; + } + + public Double getMinAverageGrade() { + return minAverageGrade; + } + + public void setMinAverageGrade(Double minAverageGrade) { + this.minAverageGrade = minAverageGrade; + } + + public Double getMaxAverageGrade() { + return maxAverageGrade; + } + + public void setMaxAverageGrade(Double maxAverageGrade) { + this.maxAverageGrade = maxAverageGrade; + } + + public Integer getMinRecordBookNumber() { + return minRecordBookNumber; + } + + public void setMinRecordBookNumber(Integer minRecordBookNumber) { + this.minRecordBookNumber = minRecordBookNumber; + } + + public Integer getMaxRecordBookNumber() { + return maxRecordBookNumber; + } + + public void setMaxRecordBookNumber(Integer maxRecordBookNumber) { + this.maxRecordBookNumber = maxRecordBookNumber; + } +} diff --git a/app/src/main/java/ru/project/ui/collection/search/SearchSettingsWindow.java b/app/src/main/java/ru/project/ui/collection/search/SearchSettingsWindow.java new file mode 100644 index 0000000..059352c --- /dev/null +++ b/app/src/main/java/ru/project/ui/collection/search/SearchSettingsWindow.java @@ -0,0 +1,355 @@ +package ru.project.ui.collection.search; + +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.CheckBox; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.LinearLayout; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.RadioBoxList; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import java.util.function.Consumer; +import java.util.function.Predicate; +import ru.project.student.Student; +import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.common.InputValueWindow; +import ru.project.ui.common.MessageWindow; + +public class SearchSettingsWindow extends BaseModalWindow { + + private final SearchSettings settings; + private final Consumer onApply; + + private final CheckBox groupNumberFilterCheckBox; + private final CheckBox averageGradeFilterCheckBox; + private final CheckBox recordBookNumberFilterCheckBox; + + private final Label minGroupNumberLabel; + private final Label maxGroupNumberLabel; + + private final Label minAverageGradeLabel; + private final Label maxAverageGradeLabel; + + private final Label minRecordBookNumberLabel; + private final Label maxRecordBookNumberLabel; + + private final RadioBoxList relationList; + + public SearchSettingsWindow(WindowBasedTextGUI gui, Consumer onApply) { + + super("Настройка процесса поиска", gui); + + this.settings = new SearchSettings(); + this.onApply = onApply; + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + panel.addComponent(new Label("Отношение фильтров:")); + + relationList = new RadioBoxList<>(); + relationList.addItem("И"); + relationList.addItem("ИЛИ"); + relationList.setCheckedItemIndex(0); + + panel.addComponent(relationList); + + groupNumberFilterCheckBox = new CheckBox("Фильтр по номеру группы"); + + averageGradeFilterCheckBox = new CheckBox("Фильтр по среднему баллу"); + + recordBookNumberFilterCheckBox = new CheckBox("Фильтр по номеру зачётной книжки"); + + panel.addComponent(groupNumberFilterCheckBox); + + minGroupNumberLabel = + new Label("Минимум номера группы: " + getValueOrDefault(settings.getMinGroupNumber())); + + maxGroupNumberLabel = + new Label("Максимум номера группы: " + getValueOrDefault(settings.getMaxGroupNumber())); + + panel.addComponent(minGroupNumberLabel); + panel.addComponent(maxGroupNumberLabel); + + panel.addComponent(new Button("Задать минимум номера группы...", this::setMinGroupNumber)); + + panel.addComponent(new Button("Задать максимум номера группы...", this::setMaxGroupNumber)); + + panel.addComponent(averageGradeFilterCheckBox); + + minAverageGradeLabel = + new Label("Минимум среднего балла: " + getValueOrDefault(settings.getMinAverageGrade())); + + maxAverageGradeLabel = + new Label("Максимум среднего балла: " + getValueOrDefault(settings.getMaxAverageGrade())); + + panel.addComponent(minAverageGradeLabel); + panel.addComponent(maxAverageGradeLabel); + + panel.addComponent(new Button("Задать минимум среднего балла...", this::setMinAverageGrade)); + + panel.addComponent(new Button("Задать максимум среднего балла...", this::setMaxAverageGrade)); + + panel.addComponent(recordBookNumberFilterCheckBox); + + minRecordBookNumberLabel = + new Label( + "Минимум номера зачётной книжки: " + + getValueOrDefault(settings.getMinRecordBookNumber())); + + maxRecordBookNumberLabel = + new Label( + "Максимум номера зачётной книжки: " + + getValueOrDefault(settings.getMaxRecordBookNumber())); + + panel.addComponent(minRecordBookNumberLabel); + panel.addComponent(maxRecordBookNumberLabel); + + panel.addComponent( + new Button("Задать минимум номера зачётной книжки...", this::setMinRecordBookNumber)); + + panel.addComponent( + new Button("Задать максимум номера зачётной книжки...", this::setMaxRecordBookNumber)); + + panel.addComponent(new Button("Применить", this::apply)); + + panel.addComponent(new Button("Выйти", this::close)); + + setComponent(panel); + } + + private void setMinGroupNumber() { + showGroupNumberInput( + "Минимальный номер группы", + settings.getMinGroupNumber(), + value -> { + if (!value.matches(Student.getGroupNumberPattern())) { + + MessageWindow.showModal(gui, "Номер группы должен соответствовать формату A12."); + + return false; + } + + if (settings.getMaxGroupNumber() != null + && value.compareTo(settings.getMaxGroupNumber()) > 0) { + + MessageWindow.showModal( + gui, "Минимальный номер группы не может быть больше максимального."); + + return false; + } + + settings.setMinGroupNumber(value); + + minGroupNumberLabel.setText("Минимум номера группы: " + value); + + return true; + }); + } + + private void setMaxGroupNumber() { + showGroupNumberInput( + "Максимальный номер группы", + settings.getMaxGroupNumber(), + value -> { + if (!value.matches(Student.getGroupNumberPattern())) { + + MessageWindow.showModal(gui, "Номер группы должен соответствовать формату A12."); + + return false; + } + + if (settings.getMinGroupNumber() != null + && value.compareTo(settings.getMinGroupNumber()) < 0) { + + MessageWindow.showModal( + gui, "Максимальный номер группы не может быть меньше минимального."); + + return false; + } + + settings.setMaxGroupNumber(value); + + maxGroupNumberLabel.setText("Максимум номера группы: " + value); + + return true; + }); + } + + private void showGroupNumberInput(String title, String initialValue, Predicate onSave) { + + InputValueWindow window = + new InputValueWindow( + gui, title, "Введите номер группы (например, A12):", initialValue, onSave); + + window.showModal(); + } + + private void setMinAverageGrade() { + showAverageGradeInput( + "Минимальный средний балл", + settings.getMinAverageGrade(), + value -> { + if (settings.getMaxAverageGrade() != null && value > settings.getMaxAverageGrade()) { + + MessageWindow.showModal( + gui, "Минимальный средний балл не может быть больше максимального."); + + return false; + } + + settings.setMinAverageGrade(value); + + minAverageGradeLabel.setText("Минимум среднего балла: " + value); + + return true; + }); + } + + private void setMaxAverageGrade() { + showAverageGradeInput( + "Максимальный средний балл", + settings.getMaxAverageGrade(), + value -> { + if (settings.getMinAverageGrade() != null && value < settings.getMinAverageGrade()) { + + MessageWindow.showModal( + gui, "Максимальный средний балл не может быть меньше минимального."); + + return false; + } + + settings.setMaxAverageGrade(value); + + maxAverageGradeLabel.setText("Максимум среднего балла: " + value); + + return true; + }); + } + + private void showAverageGradeInput(String title, Double initialValue, Predicate onSave) { + + InputValueWindow window = + new InputValueWindow( + gui, + title, + "Введите значение от 0.0 до 5.0:", + initialValue == null ? null : String.valueOf(initialValue), + value -> { + try { + double parsedValue = Double.parseDouble(value); + + if (parsedValue < Student.getMinAverageGrade() + || parsedValue > Student.getMaxAverageGrade()) { + + MessageWindow.showModal(gui, "Средний балл должен быть от 0.0 до 5.0."); + + return false; + } + + return onSave.test(parsedValue); + + } catch (NumberFormatException e) { + MessageWindow.showModal(gui, "Введите корректное число."); + + return false; + } + }); + + window.showModal(); + } + + private void setMinRecordBookNumber() { + showRecordBookNumberInput( + "Минимальный номер зачётной книжки", + settings.getMinRecordBookNumber(), + value -> { + if (settings.getMaxRecordBookNumber() != null + && value > settings.getMaxRecordBookNumber()) { + + MessageWindow.showModal( + gui, "Минимальный номер зачётной книжки не может быть больше максимального."); + + return false; + } + + settings.setMinRecordBookNumber(value); + + minRecordBookNumberLabel.setText("Минимум номера зачётной книжки: " + value); + + return true; + }); + } + + private void setMaxRecordBookNumber() { + showRecordBookNumberInput( + "Максимальный номер зачётной книжки", + settings.getMaxRecordBookNumber(), + value -> { + if (settings.getMinRecordBookNumber() != null + && value < settings.getMinRecordBookNumber()) { + + MessageWindow.showModal( + gui, "Максимальный номер зачётной книжки не может быть меньше минимального."); + + return false; + } + + settings.setMaxRecordBookNumber(value); + + maxRecordBookNumberLabel.setText("Максимум номера зачётной книжки: " + value); + + return true; + }); + } + + private void showRecordBookNumberInput( + String title, Integer initialValue, Predicate onSave) { + + InputValueWindow window = + new InputValueWindow( + gui, + title, + "Введите номер зачётной книжки:", + initialValue == null ? null : String.valueOf(initialValue), + value -> { + try { + int parsedValue = Integer.parseInt(value); + + if (parsedValue < Student.getMinRecordBookNumber()) { + + MessageWindow.showModal(gui, "Номер зачётной книжки должен быть положительным."); + + return false; + } + + return onSave.test(parsedValue); + + } catch (NumberFormatException e) { + MessageWindow.showModal(gui, "Введите целое число."); + + return false; + } + }); + + window.showModal(); + } + + private void apply() { + settings.setGroupNumberFilterEnabled(groupNumberFilterCheckBox.isChecked()); + + settings.setAverageGradeFilterEnabled(averageGradeFilterCheckBox.isChecked()); + + settings.setRecordBookNumberFilterEnabled(recordBookNumberFilterCheckBox.isChecked()); + + settings.setRelation( + relationList.getCheckedItemIndex() == 0 ? FilterRelation.AND : FilterRelation.OR); + + onApply.accept(settings); + + MessageWindow.showModal(gui, "Настройки процесса поиска успешно применены."); + } + + private String getValueOrDefault(Object value) { + return value == null ? "не задан" : String.valueOf(value); + } +} From 390d31d0802c279adc4da95a21bcc9aa97f4cc16 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, 1 Sep 2026 13:58:05 +0300 Subject: [PATCH 109/156] feat: add random student generator --- .../random/RandomStudentGenerator.java | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 app/src/main/java/ru/project/random/RandomStudentGenerator.java diff --git a/app/src/main/java/ru/project/random/RandomStudentGenerator.java b/app/src/main/java/ru/project/random/RandomStudentGenerator.java new file mode 100644 index 0000000..84e619e --- /dev/null +++ b/app/src/main/java/ru/project/random/RandomStudentGenerator.java @@ -0,0 +1,152 @@ +package ru.test.maven.spring; + +import java.util.Random; +import java.util.regex.Pattern; + +public class RandomStudentGenerator +{ + private static final Random RANDOM = new Random(); + private String minGroupNumber; + private String maxGroupNumber; + private Double minAverageGrade; + private Double maxAverageGrade; + private Integer minRecordBookNumber; + private Integer maxRecordBookNumber; + private static final double DEFAULT_MIN_GRADE = 0.0; + private static final double DEFAULT_MAX_GRADE = 5.0; + private static final int DEFAULT_MIN_RECORD_BOOK = 1; + private static final String GROUP_PATTERN_REGEX = "^[A-Z][0-9]{2}$"; + private static final Pattern GROUP_PATTERN = Pattern.compile(GROUP_PATTERN_REGEX); + public RandomStudentGenerator setMinGroupNumber(String minGroupNumber) { + if (minGroupNumber != null && !GROUP_PATTERN.matcher(minGroupNumber).matches()) { + throw new IllegalArgumentException("minGroupNumber does not match Student group pattern: " + GROUP_PATTERN_REGEX); + } + this.minGroupNumber = minGroupNumber; + return this; + } + public RandomStudentGenerator setMaxGroupNumber(String maxGroupNumber) { + if (maxGroupNumber != null && !GROUP_PATTERN.matcher(maxGroupNumber).matches()) { + throw new IllegalArgumentException("maxGroupNumber does not match Student group pattern: " + GROUP_PATTERN_REGEX); + } + this.maxGroupNumber = maxGroupNumber; + return this; + } + + public RandomStudentGenerator setMinAverageGrade(double minAverageGrade) { + if (minAverageGrade < DEFAULT_MIN_GRADE || minAverageGrade > DEFAULT_MAX_GRADE) { + throw new IllegalArgumentException( + "minAverageGrade out of Student allowed range [" + DEFAULT_MIN_GRADE + ", " + DEFAULT_MAX_GRADE + "]" + ); + } + this.minAverageGrade = minAverageGrade; + return this; + } + + public RandomStudentGenerator setMaxAverageGrade(double maxAverageGrade) { + if (maxAverageGrade < DEFAULT_MIN_GRADE || maxAverageGrade > DEFAULT_MAX_GRADE) { + throw new IllegalArgumentException( + "maxAverageGrade out of Student allowed range [" + DEFAULT_MIN_GRADE + ", " + DEFAULT_MAX_GRADE + "]" + ); + } + this.maxAverageGrade = maxAverageGrade; + return this; + } + + public RandomStudentGenerator setMinRecordBookNumber(int minRecordBookNumber) { + if (minRecordBookNumber < DEFAULT_MIN_RECORD_BOOK) { + throw new IllegalArgumentException( + "minRecordBookNumber must be >= " + DEFAULT_MIN_RECORD_BOOK + ); + } + this.minRecordBookNumber = minRecordBookNumber; + return this; + } + + public RandomStudentGenerator setMaxRecordBookNumber(int maxRecordBookNumber) { + if (maxRecordBookNumber < DEFAULT_MIN_RECORD_BOOK) { + throw new IllegalArgumentException( + "maxRecordBookNumber must be >= " + DEFAULT_MIN_RECORD_BOOK + ); + } + this.maxRecordBookNumber = maxRecordBookNumber; + return this; + } + public MyList generate(int size) { + if (size < 0) { + throw new IllegalArgumentException("size must be non-negative"); + } + validateRanges(); + MyList result = new MyLinkedList<>(); + for (int i = 0; i < size; i++) { + String group = generateGroupNumber(); + double grade = generateAverageGrade(); + int recordBook = generateRecordBookNumber(); + + Student student = new StudentBuilder() + .setGroupNumber(group) + .setAverageGrade(grade) + .setRecordBookNumber(recordBook) + .build(); + + result.add(student); + } + return result; + } + private void validateRanges() { + + double effectiveMinGrade = (minAverageGrade != null) ? minAverageGrade : DEFAULT_MIN_GRADE; + double effectiveMaxGrade = (maxAverageGrade != null) ? maxAverageGrade : DEFAULT_MAX_GRADE; + if (effectiveMinGrade > effectiveMaxGrade) { + throw new IllegalArgumentException("minAverageGrade cannot be greater than maxAverageGrade"); + } + int effectiveMinRecord = (minRecordBookNumber != null) ? minRecordBookNumber : DEFAULT_MIN_RECORD_BOOK; + int effectiveMaxRecord = (maxRecordBookNumber != null) ? maxRecordBookNumber : Integer.MAX_VALUE; + if (effectiveMaxRecord == Integer.MAX_VALUE) { + effectiveMaxRecord = effectiveMinRecord + 999_999; + } + if (effectiveMinRecord > effectiveMaxRecord) { + throw new IllegalArgumentException("minRecordBookNumber cannot be greater than maxRecordBookNumber"); + } + if (minGroupNumber != null && maxGroupNumber != null) { + if (minGroupNumber.compareTo(maxGroupNumber) > 0) { + throw new IllegalArgumentException("minGroupNumber cannot be greater than maxGroupNumber"); + } + } + } + private String generateGroupNumber() { + String min = (minGroupNumber != null) ? minGroupNumber : "A00"; + String max = (maxGroupNumber != null) ? maxGroupNumber : "Z99"; + char minLetter = min.charAt(0); + char maxLetter = max.charAt(0); + int minDigit = Integer.parseInt(min.substring(1)); + int maxDigit = Integer.parseInt(max.substring(1)); + char letter = (char) (minLetter + RANDOM.nextInt((maxLetter - minLetter) + 1)); + int digit; + if (letter == minLetter && letter == maxLetter) { + digit = minDigit + RANDOM.nextInt((maxDigit - minDigit) + 1); + } else if (letter == minLetter) { + digit = minDigit + RANDOM.nextInt(100 - minDigit); + } else if (letter == maxLetter) { + digit = RANDOM.nextInt(maxDigit + 1); + } else { + digit = RANDOM.nextInt(100); + } + return letter + String.format("%02d", digit); + } + + private double generateAverageGrade() { + double min = (minAverageGrade != null) ? minAverageGrade : DEFAULT_MIN_GRADE; + double max = (maxAverageGrade != null) ? maxAverageGrade : DEFAULT_MAX_GRADE; + + double range = max - min; + return min + (range * RANDOM.nextDouble()); + } + private int generateRecordBookNumber() { + int min = (minRecordBookNumber != null) ? minRecordBookNumber : DEFAULT_MIN_RECORD_BOOK; + int max = (maxRecordBookNumber != null) ? maxRecordBookNumber : min + 999_999; // разумный максимум + if (max < min) { + max = min; + } + return min + RANDOM.nextInt((max - min) + 1); + } +} From a53e8f9a534c7edc2f0f0c1a6a0d2da817ffadcf 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, 1 Sep 2026 14:06:29 +0300 Subject: [PATCH 110/156] test: add unit test for random student generator --- .../random/RandomStudentGenerator.java | 7 +- .../random/RandomStudentGeneratorTest.java | 157 ++++++++++++++++++ 2 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 app/src/test/java/ru/project/random/RandomStudentGeneratorTest.java diff --git a/app/src/main/java/ru/project/random/RandomStudentGenerator.java b/app/src/main/java/ru/project/random/RandomStudentGenerator.java index 84e619e..c7b1812 100644 --- a/app/src/main/java/ru/project/random/RandomStudentGenerator.java +++ b/app/src/main/java/ru/project/random/RandomStudentGenerator.java @@ -1,5 +1,8 @@ -package ru.test.maven.spring; - +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.student.Student; import java.util.Random; import java.util.regex.Pattern; diff --git a/app/src/test/java/ru/project/random/RandomStudentGeneratorTest.java b/app/src/test/java/ru/project/random/RandomStudentGeneratorTest.java new file mode 100644 index 0000000..2b5a4be --- /dev/null +++ b/app/src/test/java/ru/project/random/RandomStudentGeneratorTest.java @@ -0,0 +1,157 @@ +package ru.project.random; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.student.Student; +import java.util.regex.Pattern; +import static org.junit.jupiter.api.Assertions.*; +public class RandomStudentGeneratorTest +{ + private static final Pattern GROUP_PATTERN = Pattern.compile("^[A-Z][0-9]{2}$"); + private static final double MIN_GRADE = 0.0; + private static final double MAX_GRADE = 5.0; + @Test + void shouldTestgenerate_withoutConstraints_returnsCorrectSizeAndValidStudents() { + int size = 100; + RandomStudentGenerator generator = new RandomStudentGenerator(); + MyList students = generator.generate(size); + assertEquals(size, students.size(), "Размер списка должен совпадать с запрошенным"); + for (int i = 0; i < students.size(); i++) { + Student s = students.get(i); + assertTrue(GROUP_PATTERN.matcher(s.getGroupNumber()).matches(), + "groupNumber должен соответствовать шаблону A00–Z99"); + assertTrue(s.getAverageGrade() >= MIN_GRADE && s.getAverageGrade() <= MAX_GRADE, + "averageGrade должен быть в диапазоне [0.0, 5.0]"); + assertTrue(s.getRecordBookNumber() >= 1, + "recordBookNumber должен быть >= 1"); + } + } + @Test + void shouldTestgenerate_withGroupRange_respectsLexicographicalBounds() { + RandomStudentGenerator generator = new RandomStudentGenerator() + .setMinGroupNumber("A00") + .setMaxGroupNumber("B99"); + MyList students = generator.generate(200); + assertEquals(200, students.size()); + for (int i = 0; i < students.size(); i++) { + Student s = students.get(i); + String g = s.getGroupNumber(); + assertTrue(g.compareTo("A00") >= 0, "Группа не должна быть меньше A00"); + assertTrue(g.compareTo("B99") <= 0, "Группа не должна быть больше B99"); + assertTrue(GROUP_PATTERN.matcher(g).matches(), "Группа должна соответствовать шаблону"); + } + } + @Test + void shouldTestgenerate_withGradeRange_respectsBounds() { + double minGrade = 3.0; + double maxGrade = 4.0; + + RandomStudentGenerator generator = new RandomStudentGenerator() + .setMinAverageGrade(minGrade) + .setMaxAverageGrade(maxGrade); + MyList students = generator.generate(100); + + for (int i = 0; i < students.size(); i++) { + Student s = students.get(i); + double g = s.getAverageGrade(); + assertTrue(g >= minGrade && g <= maxGrade, + "averageGrade должен быть в заданном диапазоне"); + } + } + @Test + void shouldTestgenerate_withRecordBookRange_respectsBounds() { + int minRec = 10_000; + int maxRec = 20_000; + + RandomStudentGenerator generator = new RandomStudentGenerator() + .setMinRecordBookNumber(minRec) + .setMaxRecordBookNumber(maxRec); + MyList students = generator.generate(100); + + for (int i = 0; i < students.size(); i++) { + Student s = students.get(i); + int r = s.getRecordBookNumber(); + assertTrue(r >= minRec && r <= maxRec, + "recordBookNumber должен быть в заданном диапазоне"); + } + } + + @Test + void shouldTestgenerate_negativeSize_throwsException() { + RandomStudentGenerator generator = new RandomStudentGenerator(); + assertThrows(IllegalArgumentException.class, () -> generator.generate(-1)); + } + @Test + void shouldTestsetMinGroupNumber_invalidPattern_throwsException() { + RandomStudentGenerator generator = new RandomStudentGenerator(); + assertThrows(IllegalArgumentException.class, + () -> generator.setMinGroupNumber("invalid")); + } + @Test + void shouldTestsetMaxGroupNumber_invalidPattern_throwsException() { + RandomStudentGenerator generator = new RandomStudentGenerator(); + assertThrows(IllegalArgumentException.class, + () -> generator.setMaxGroupNumber("ZZ")); // не 2 цифры + } + @Test + void shouldTestsetMinMaxGroupNumber_minGreaterThanMax_throwsException() { + RandomStudentGenerator generator = new RandomStudentGenerator(); + assertThrows(IllegalArgumentException.class, + () -> generator.setMinGroupNumber("C00").setMaxGroupNumber("A99").generate(1)); + } + @Test + void shouldTestsetMinAverageGrade_outOfRange_throwsException() { + RandomStudentGenerator generator = new RandomStudentGenerator(); + assertThrows(IllegalArgumentException.class, + () -> generator.setMinAverageGrade(-0.1)); + assertThrows(IllegalArgumentException.class, + () -> generator.setMinAverageGrade(5.1)); + } + @Test + void shouldTestsetMaxAverageGrade_outOfRange_throwsException() { + RandomStudentGenerator generator = new RandomStudentGenerator(); + assertThrows(IllegalArgumentException.class, + () -> generator.setMaxAverageGrade(-0.1)); + assertThrows(IllegalArgumentException.class, + () -> generator.setMaxAverageGrade(5.1)); + } + @Test + void shouldTestsetMinMaxAverageGrade_minGreaterThanMax_throwsException() { + RandomStudentGenerator generator = new RandomStudentGenerator(); + assertThrows(IllegalArgumentException.class, + () -> generator.setMinAverageGrade(4.0).setMaxAverageGrade(3.0).generate(1)); + } + @Test + void shouldTestsetMinRecordBookNumber_lessThanAllowed_throwsException() { + RandomStudentGenerator generator = new RandomStudentGenerator(); + assertThrows(IllegalArgumentException.class, + () -> generator.setMinRecordBookNumber(0)); + } + @Test + void shouldTestsetMinMaxRecordBookNumber_minGreaterThanMax_throwsException() { + RandomStudentGenerator generator = new RandomStudentGenerator(); + assertThrows(IllegalArgumentException.class, + () -> generator.setMinRecordBookNumber(20_000).setMaxRecordBookNumber(10_000).generate(1)); + } + @Test + void shouldTestchainedBuilderStyle_worksAsExpected() { + RandomStudentGenerator generator = new RandomStudentGenerator() + .setMinGroupNumber("A00") + .setMaxGroupNumber("Z99") + .setMinAverageGrade(2.0) + .setMaxAverageGrade(4.0) + .setMinRecordBookNumber(1_000) + .setMaxRecordBookNumber(9_999); + MyList students = generator.generate(50); + assertEquals(50, students.size()); + for (int i = 0; i < students.size(); i++) { + Student s = students.get(i); + assertTrue(s.getGroupNumber().compareTo("A00") >= 0 + && s.getGroupNumber().compareTo("Z99") <= 0); + assertTrue(s.getAverageGrade() >= 2.0 && s.getAverageGrade() <= 4.0); + assertTrue(s.getRecordBookNumber() >= 1_000 && s.getRecordBookNumber() <= 9_999); + } + } +} From ba59fa12dac6419014f8cfe652a9c50bc99e42db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Sun, 30 Aug 2026 20:34:10 +0700 Subject: [PATCH 111/156] feat: reworked classes StudentCsvWriter and StudentCsvReader --- .../project/datasource/StudentCsvReader.java | 79 +++++++++++++++++++ .../project/datasource/StudentCsvWriter.java | 73 +++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 app/src/main/java/ru/project/datasource/StudentCsvReader.java create mode 100644 app/src/main/java/ru/project/datasource/StudentCsvWriter.java diff --git a/app/src/main/java/ru/project/datasource/StudentCsvReader.java b/app/src/main/java/ru/project/datasource/StudentCsvReader.java new file mode 100644 index 0000000..7403caa --- /dev/null +++ b/app/src/main/java/ru/project/datasource/StudentCsvReader.java @@ -0,0 +1,79 @@ +package ru.project.datasource; + +import ru.project.student.Student; +import ru.project.student.StudentBuilder; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +public class StudentCsvReader implements Iterator { + private static final String GROUP_NUMBER = "groupNumber"; + private static final String AVERAGE_GRADE = "averageGrade"; + private static final String RECORD_BOOK_NUMBER = "recordBookNumber"; + + private final CSVParser parser; + private final Iterator records; + + public StudentCsvReader(String absoluteFilePath) { + Path path = Paths.get(absoluteFilePath); + if (!path.isAbsolute()) { + throw new IllegalArgumentException("File path must be absolute"); + } + + try { + CSVFormat format = CSVFormat.DEFAULT.builder() + .setHeader() + .setSkipHeaderRecord(true) + .build(); + this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); + validateHeader(parser.getHeaderMap()); + this.records = parser.iterator(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public boolean hasNext() { + return records.hasNext(); + } + + @Override + public Student next() { + if (!records.hasNext()) { + throw new NoSuchElementException(); + } + + CSVRecord record = records.next(); + if (record.size() != 3) { + throw new IllegalArgumentException( + "Expected 3 columns, but got " + record.size()); + } + + double averageGrade = Double.parseDouble(record.get(AVERAGE_GRADE)); + return new StudentBuilder() + .setGroupNumber(record.get(GROUP_NUMBER)) + .setAverageGrade(averageGrade) + .setRecordBookNumber(record.get(RECORD_BOOK_NUMBER)) + .build(); + } + + private static void validateHeader(Map headerMap) { + String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER}; + if (headerMap.size() != expected.length + || !headerMap.keySet().containsAll(Arrays.asList(expected))) { + throw new IllegalArgumentException("Invalid CSV header"); + } + } +} diff --git a/app/src/main/java/ru/project/datasource/StudentCsvWriter.java b/app/src/main/java/ru/project/datasource/StudentCsvWriter.java new file mode 100644 index 0000000..c086ec0 --- /dev/null +++ b/app/src/main/java/ru/project/datasource/StudentCsvWriter.java @@ -0,0 +1,73 @@ +package ru.project.datasource; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import ru.project.student.Student; +import ru.project.collection.MyList; +import ru.project.collection.MyLinkedList; + + +public class StudentCsvWriter{ + private static final String[] HEADER = { + "groupNumber", "averageGrade", "recordBookNumber" + }; + private static final DateTimeFormatter FILE_NAME_FORMAT = + DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm"); + + private final Path requestedPath; + private Path actualPath; + + public StudentCsvWriter(String absoluteFilePath) { + Path path = Paths.get(absoluteFilePath); + if (!path.isAbsolute()) { + throw new IllegalArgumentException("File path must be absolute"); + } + this.requestedPath = path; + } + + public void write(Student student) throws IOException { + Path target = resolveTargetPath(); + boolean fileExists = Files.exists(target); + + StandardOpenOption[] options = fileExists + ? new StandardOpenOption[]{StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND} + : new StandardOpenOption[]{StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE}; + + try (BufferedWriter writer = Files.newBufferedWriter(target, StandardCharsets.UTF_8, options); + CSVPrinter printer = new CSVPrinter(writer, CSVFormat.DEFAULT)) { + if (!fileExists) { + printer.printRecord((Object[]) HEADER); + } + printer.printRecord( + student.getGroupNumber(), + Double.toString(student.getAverageGrade()), + student.getRecordBookNumber() + ); + } + } + + private Path resolveTargetPath() { + if (actualPath != null) { + return actualPath; + } + if (Files.exists(requestedPath)) { + actualPath = requestedPath; + return actualPath; + } + + Path parent = requestedPath.getParent(); + String fileName = "students-" + LocalDateTime.now().format(FILE_NAME_FORMAT) + ".csv"; + actualPath = parent == null ? Paths.get(fileName).toAbsolutePath() : parent.resolve(fileName); + return actualPath; + } +} \ No newline at end of file From cb5068465d8749555ba822af177862187f9ca0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Sun, 30 Aug 2026 22:38:01 +0700 Subject: [PATCH 112/156] refactor: integrate StudentCsvParser into FileDataSource --- .../ru/project/datasource/FileDataSource.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 app/src/main/java/ru/project/datasource/FileDataSource.java diff --git a/app/src/main/java/ru/project/datasource/FileDataSource.java b/app/src/main/java/ru/project/datasource/FileDataSource.java new file mode 100644 index 0000000..62c1d83 --- /dev/null +++ b/app/src/main/java/ru/project/datasource/FileDataSource.java @@ -0,0 +1,39 @@ +package ru.project.datasource; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Collector; +import ru.project.student.Student; +import ru.project.collection.MyList; +import ru.project.collection.MyLinkedList; + +public class FileDataSource implements DataSource{ + private final String fileName; + + public FileDataSource(String fileName) { + this.fileName = fileName; + } + + @Override + public MyList create() { + MyList resultList = new MyLinkedList<>(); + try { + StudentCsvReader reader = new StudentCsvReader(fileName); + while reader.hasNext() { + result.append(reader.next()); + } + } + catch (IllegalArgumentException e){ + System.out.println(e.getMessage()); + return null; + } + catch (NoSuchElementException e){ + System.out.println("No such element"); + return null; + } + } +} From 0e6ff523e8aa721909ec7f3f17eb7d92ec298c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Sun, 30 Aug 2026 22:46:05 +0700 Subject: [PATCH 113/156] fix: import java.util.NoSuchElementException into FileDataSource --- app/src/main/java/ru/project/datasource/FileDataSource.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/ru/project/datasource/FileDataSource.java b/app/src/main/java/ru/project/datasource/FileDataSource.java index 62c1d83..b2c70d9 100644 --- a/app/src/main/java/ru/project/datasource/FileDataSource.java +++ b/app/src/main/java/ru/project/datasource/FileDataSource.java @@ -7,6 +7,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Collector; +import java.util.NoSuchElementException; import ru.project.student.Student; import ru.project.collection.MyList; import ru.project.collection.MyLinkedList; From 808e1eadbb5e8d0b23a38db38bd72994f950b12f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Sun, 23 Aug 2026 23:10:10 +0700 Subject: [PATCH 114/156] feat: adding class student and data source interface --- app/src/main/java/ru/project/DataSource.java | 3 +++ .../main/java/ru/project/FileDataSource.java | 16 +++++++++++++ app/src/main/java/ru/project/Main.java | 15 ++++++++++++ .../java/ru/project/ManualDataSource.java | 16 +++++++++++++ app/src/main/java/ru/project/MyList.java | 2 ++ .../java/ru/project/RandomDataSource.java | 6 +++++ app/src/main/java/ru/project/Student.java | 11 +++++++++ .../main/java/ru/project/StudentBuilder.java | 23 +++++++++++++++++++ 8 files changed, 92 insertions(+) create mode 100644 app/src/main/java/ru/project/DataSource.java create mode 100644 app/src/main/java/ru/project/FileDataSource.java create mode 100644 app/src/main/java/ru/project/Main.java create mode 100644 app/src/main/java/ru/project/ManualDataSource.java create mode 100644 app/src/main/java/ru/project/MyList.java create mode 100644 app/src/main/java/ru/project/RandomDataSource.java create mode 100644 app/src/main/java/ru/project/Student.java create mode 100644 app/src/main/java/ru/project/StudentBuilder.java diff --git a/app/src/main/java/ru/project/DataSource.java b/app/src/main/java/ru/project/DataSource.java new file mode 100644 index 0000000..38f09f8 --- /dev/null +++ b/app/src/main/java/ru/project/DataSource.java @@ -0,0 +1,3 @@ +public interface DataSource { + MyList fill(int size); +} diff --git a/app/src/main/java/ru/project/FileDataSource.java b/app/src/main/java/ru/project/FileDataSource.java new file mode 100644 index 0000000..298e375 --- /dev/null +++ b/app/src/main/java/ru/project/FileDataSource.java @@ -0,0 +1,16 @@ +public class FileDataSource implements DataSource{ + private String path; + + public FileDataSource(String path) { + this.path = path; + } + + public void setPath(String path) { + this.path = path; + } + + @Override + public MyList fill(int size) { + return null; + } +} diff --git a/app/src/main/java/ru/project/Main.java b/app/src/main/java/ru/project/Main.java new file mode 100644 index 0000000..930198c --- /dev/null +++ b/app/src/main/java/ru/project/Main.java @@ -0,0 +1,15 @@ +//TIP To Run code, press or +// click the icon in the gutter. +public class Main { + public static void main(String[] args) { + //TIP Press with your caret at the highlighted text + // to see how IntelliJ IDEA suggests fixing it. + System.out.printf("Hello and welcome!"); + + for (int i = 1; i <= 5; i++) { + //TIP Press to start debugging your code. We have set one breakpoint + // for you, but you can always add more by pressing . + System.out.println("i = " + i); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/ManualDataSource.java b/app/src/main/java/ru/project/ManualDataSource.java new file mode 100644 index 0000000..c373daf --- /dev/null +++ b/app/src/main/java/ru/project/ManualDataSource.java @@ -0,0 +1,16 @@ +import java.util.Scanner; + +public class ManualDataSource implements DataSource{ + @Override + public MyList fill(int size) { + String strToParse; + Scanner scanner = new Scanner(System.in); + while(true){ + strToParse = scanner.next(); + if (strToParse.equals("0")) + break; + } + scanner.close(); + return null; + } +} diff --git a/app/src/main/java/ru/project/MyList.java b/app/src/main/java/ru/project/MyList.java new file mode 100644 index 0000000..153dca1 --- /dev/null +++ b/app/src/main/java/ru/project/MyList.java @@ -0,0 +1,2 @@ +public class MyList { +} diff --git a/app/src/main/java/ru/project/RandomDataSource.java b/app/src/main/java/ru/project/RandomDataSource.java new file mode 100644 index 0000000..021df21 --- /dev/null +++ b/app/src/main/java/ru/project/RandomDataSource.java @@ -0,0 +1,6 @@ +public class RandomDataSource implements DataSource{ + @Override + public MyList fill(int size) { + return null; + } +} diff --git a/app/src/main/java/ru/project/Student.java b/app/src/main/java/ru/project/Student.java new file mode 100644 index 0000000..d3fc714 --- /dev/null +++ b/app/src/main/java/ru/project/Student.java @@ -0,0 +1,11 @@ +public class Student { + private final String groupNumber; + private final double averageGrade; + private final String recordBookNumber; + + public Student(String groupNumber, double averageGrade, String recordBookNumber) { + this.groupNumber = groupNumber; + this.averageGrade = averageGrade; + this.recordBookNumber = recordBookNumber; + } +} diff --git a/app/src/main/java/ru/project/StudentBuilder.java b/app/src/main/java/ru/project/StudentBuilder.java new file mode 100644 index 0000000..9a5146b --- /dev/null +++ b/app/src/main/java/ru/project/StudentBuilder.java @@ -0,0 +1,23 @@ +public class StudentBuilder { + private String groupNumber; + private double averageGrade; + private String recordBookNumber; + + public void setGroupNumber(String groupNumber) { + this.groupNumber = groupNumber; + } + + public void setAverageGrade(double averageGrade) { + this.averageGrade = averageGrade; + } + + public void setRecordBookNumber(String recordBookNumber) { + this.recordBookNumber = recordBookNumber; + } + + public Student build(){ + //TODO: Реализовать проверку на корректность введенных значений + + return new Student(groupNumber,averageGrade, recordBookNumber); + } +} From 8fa66b10d2ff0553a88b881b3e90b81128d7fae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Mon, 24 Aug 2026 22:54:19 +0700 Subject: [PATCH 115/156] feat: add StringParser and reworking project structure --- app/src/main/java/ru/project/DataSource.java | 3 -- .../main/java/ru/project/FileDataSource.java | 16 --------- app/src/main/java/ru/project/Main.java | 15 -------- .../java/ru/project/ManualDataSource.java | 16 --------- app/src/main/java/ru/project/MyList.java | 2 -- app/src/main/java/ru/project/Student.java | 11 ------ .../main/java/ru/project/StudentBuilder.java | 23 ------------ .../ru/project/datasource/DataSource.java | 9 +++++ .../ru/project/datasource/FileDataSource.java | 36 ++++++++++--------- .../project/datasource/ManualDataSource.java | 29 +++++++++++++++ .../{ => datasource}/RandomDataSource.java | 5 +++ .../ru/project/datasource/StringParser.java | 20 +++++++++++ 12 files changed, 82 insertions(+), 103 deletions(-) delete mode 100644 app/src/main/java/ru/project/DataSource.java delete mode 100644 app/src/main/java/ru/project/FileDataSource.java delete mode 100644 app/src/main/java/ru/project/Main.java delete mode 100644 app/src/main/java/ru/project/ManualDataSource.java delete mode 100644 app/src/main/java/ru/project/MyList.java delete mode 100644 app/src/main/java/ru/project/Student.java delete mode 100644 app/src/main/java/ru/project/StudentBuilder.java create mode 100644 app/src/main/java/ru/project/datasource/DataSource.java create mode 100644 app/src/main/java/ru/project/datasource/ManualDataSource.java rename app/src/main/java/ru/project/{ => datasource}/RandomDataSource.java (57%) create mode 100644 app/src/main/java/ru/project/datasource/StringParser.java diff --git a/app/src/main/java/ru/project/DataSource.java b/app/src/main/java/ru/project/DataSource.java deleted file mode 100644 index 38f09f8..0000000 --- a/app/src/main/java/ru/project/DataSource.java +++ /dev/null @@ -1,3 +0,0 @@ -public interface DataSource { - MyList fill(int size); -} diff --git a/app/src/main/java/ru/project/FileDataSource.java b/app/src/main/java/ru/project/FileDataSource.java deleted file mode 100644 index 298e375..0000000 --- a/app/src/main/java/ru/project/FileDataSource.java +++ /dev/null @@ -1,16 +0,0 @@ -public class FileDataSource implements DataSource{ - private String path; - - public FileDataSource(String path) { - this.path = path; - } - - public void setPath(String path) { - this.path = path; - } - - @Override - public MyList fill(int size) { - return null; - } -} diff --git a/app/src/main/java/ru/project/Main.java b/app/src/main/java/ru/project/Main.java deleted file mode 100644 index 930198c..0000000 --- a/app/src/main/java/ru/project/Main.java +++ /dev/null @@ -1,15 +0,0 @@ -//TIP To Run code, press or -// click the icon in the gutter. -public class Main { - public static void main(String[] args) { - //TIP Press with your caret at the highlighted text - // to see how IntelliJ IDEA suggests fixing it. - System.out.printf("Hello and welcome!"); - - for (int i = 1; i <= 5; i++) { - //TIP Press to start debugging your code. We have set one breakpoint - // for you, but you can always add more by pressing . - System.out.println("i = " + i); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/project/ManualDataSource.java b/app/src/main/java/ru/project/ManualDataSource.java deleted file mode 100644 index c373daf..0000000 --- a/app/src/main/java/ru/project/ManualDataSource.java +++ /dev/null @@ -1,16 +0,0 @@ -import java.util.Scanner; - -public class ManualDataSource implements DataSource{ - @Override - public MyList fill(int size) { - String strToParse; - Scanner scanner = new Scanner(System.in); - while(true){ - strToParse = scanner.next(); - if (strToParse.equals("0")) - break; - } - scanner.close(); - return null; - } -} diff --git a/app/src/main/java/ru/project/MyList.java b/app/src/main/java/ru/project/MyList.java deleted file mode 100644 index 153dca1..0000000 --- a/app/src/main/java/ru/project/MyList.java +++ /dev/null @@ -1,2 +0,0 @@ -public class MyList { -} diff --git a/app/src/main/java/ru/project/Student.java b/app/src/main/java/ru/project/Student.java deleted file mode 100644 index d3fc714..0000000 --- a/app/src/main/java/ru/project/Student.java +++ /dev/null @@ -1,11 +0,0 @@ -public class Student { - private final String groupNumber; - private final double averageGrade; - private final String recordBookNumber; - - public Student(String groupNumber, double averageGrade, String recordBookNumber) { - this.groupNumber = groupNumber; - this.averageGrade = averageGrade; - this.recordBookNumber = recordBookNumber; - } -} diff --git a/app/src/main/java/ru/project/StudentBuilder.java b/app/src/main/java/ru/project/StudentBuilder.java deleted file mode 100644 index 9a5146b..0000000 --- a/app/src/main/java/ru/project/StudentBuilder.java +++ /dev/null @@ -1,23 +0,0 @@ -public class StudentBuilder { - private String groupNumber; - private double averageGrade; - private String recordBookNumber; - - public void setGroupNumber(String groupNumber) { - this.groupNumber = groupNumber; - } - - public void setAverageGrade(double averageGrade) { - this.averageGrade = averageGrade; - } - - public void setRecordBookNumber(String recordBookNumber) { - this.recordBookNumber = recordBookNumber; - } - - public Student build(){ - //TODO: Реализовать проверку на корректность введенных значений - - return new Student(groupNumber,averageGrade, recordBookNumber); - } -} diff --git a/app/src/main/java/ru/project/datasource/DataSource.java b/app/src/main/java/ru/project/datasource/DataSource.java new file mode 100644 index 0000000..d8ad27f --- /dev/null +++ b/app/src/main/java/ru/project/datasource/DataSource.java @@ -0,0 +1,9 @@ +package ru.project.datasource; + +import ru.project.student.Student; +import ru.project.collection.MyList; + + +public interface DataSource { + MyList fill(int size); +} diff --git a/app/src/main/java/ru/project/datasource/FileDataSource.java b/app/src/main/java/ru/project/datasource/FileDataSource.java index b2c70d9..4b2c048 100644 --- a/app/src/main/java/ru/project/datasource/FileDataSource.java +++ b/app/src/main/java/ru/project/datasource/FileDataSource.java @@ -7,34 +7,36 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Collector; -import java.util.NoSuchElementException; import ru.project.student.Student; import ru.project.collection.MyList; -import ru.project.collection.MyLinkedList; public class FileDataSource implements DataSource{ - private final String fileName; + private String fileName; public FileDataSource(String fileName) { this.fileName = fileName; } + public void setFileName(String fileName) { + this.fileName = fileName; + } + @Override - public MyList create() { - MyList resultList = new MyLinkedList<>(); - try { - StudentCsvReader reader = new StudentCsvReader(fileName); - while reader.hasNext() { - result.append(reader.next()); - } - } - catch (IllegalArgumentException e){ - System.out.println(e.getMessage()); - return null; - } - catch (NoSuchElementException e){ - System.out.println("No such element"); + public MyList fill(int size) { + Path path = Paths.get(fileName); + try(var lines = Files.lines(path)){ + return lines.skip(1) + .limit(size) + .map(e -> StringParser.parseString(e)) + .filter(e -> !(e == null)) + .collect(Collector.of(MyList::new, + MyList::add, + (left, right) -> left.addAll(right))); + }catch(FileNotFoundException e){ + System.out.println("File not found."); return null; + } catch (IOException e) { + throw new RuntimeException(e); } } } diff --git a/app/src/main/java/ru/project/datasource/ManualDataSource.java b/app/src/main/java/ru/project/datasource/ManualDataSource.java new file mode 100644 index 0000000..f24c73a --- /dev/null +++ b/app/src/main/java/ru/project/datasource/ManualDataSource.java @@ -0,0 +1,29 @@ +package ru.project.datasource; + +import java.util.Scanner; +import ru.project.student.Student; +import ru.project.collection.MyList; + +public class ManualDataSource implements DataSource{ + @Override + public MyList fill(int size) { + MyList result = new MyList<>(); + String strToParse; + Student student; + Scanner scanner = new Scanner(System.in); + for (short i = 0; i < size; i++){ + strToParse = scanner.next(); + if (strToParse.equals("0")) + break; + student = StringParser.parseString(strToParse); + if (student == null){ + System.out.println("Oops! Invalid input"); + } + else{ + result.add(student); + } + } + scanner.close(); + return null; + } +} diff --git a/app/src/main/java/ru/project/RandomDataSource.java b/app/src/main/java/ru/project/datasource/RandomDataSource.java similarity index 57% rename from app/src/main/java/ru/project/RandomDataSource.java rename to app/src/main/java/ru/project/datasource/RandomDataSource.java index 021df21..8d5442e 100644 --- a/app/src/main/java/ru/project/RandomDataSource.java +++ b/app/src/main/java/ru/project/datasource/RandomDataSource.java @@ -1,3 +1,8 @@ +package ru.project.datasource; + +import ru.project.student.Student; +import ru.project.collection.MyList; + public class RandomDataSource implements DataSource{ @Override public MyList fill(int size) { diff --git a/app/src/main/java/ru/project/datasource/StringParser.java b/app/src/main/java/ru/project/datasource/StringParser.java new file mode 100644 index 0000000..2bfdd26 --- /dev/null +++ b/app/src/main/java/ru/project/datasource/StringParser.java @@ -0,0 +1,20 @@ +package ru.project.datasource; + +import ru.project.student.Student; +import ru.project.student.StudentBuilder; +import ru.project.collection.MyList; + +public class StringParser { + public static Student parseString(String str){ + String [] parts = str.split(";"); + try{ + return new StudentBuilder() + .setGroupNumber(parts[0]) + .setAverageGrade(Double.valueOf(parts[1])) + .setRecordBookNumber(parts[2]) + .build(); + }catch(NumberFormatException e){ + return null; + } + } +} From 02e14a987a9e2f2c43732814b5c7316b9ffa05b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Tue, 25 Aug 2026 22:18:22 +0700 Subject: [PATCH 116/156] feat: add random generation instances of class Student --- .../project/datasource/RandomDataSource.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/src/main/java/ru/project/datasource/RandomDataSource.java b/app/src/main/java/ru/project/datasource/RandomDataSource.java index 8d5442e..f81cf77 100644 --- a/app/src/main/java/ru/project/datasource/RandomDataSource.java +++ b/app/src/main/java/ru/project/datasource/RandomDataSource.java @@ -1,11 +1,36 @@ package ru.project.datasource; import ru.project.student.Student; +import ru.project.student.StudentBuilder; import ru.project.collection.MyList; +import java.util.Random; public class RandomDataSource implements DataSource{ + private final String CHARS = "АБВГДЕЖХИКЛМНОПРСТУФХЦЧЩЭЮЯ"; + + private String getRandomGroupNumber(Random random){ + StringBuilder buildRes = new StringBuilder(CHARS.charAt(random.nextInt(CHARS.length()))); + buildRes.append('-'); + buildRes.append(String.format("%03d", random.nextInt(999))); + return buildRes.toString(); + } + private String getRandomRecordBookNumber(Random random){ + StringBuilder buildRes = new StringBuilder(CHARS.charAt(random.nextInt(CHARS.length()))); + buildRes.append(String.format("%08d", random.nextInt(99999999))); + return buildRes.toString(); + } + @Override public MyList fill(int size) { + Random random = new Random(); + MyList result = new MyList<>(); + for (short i = 0; i Date: Fri, 28 Aug 2026 22:52:52 +0700 Subject: [PATCH 117/156] feat: add class FileWriter to append data to file --- .../ru/project/datasource/DataSource.java | 2 +- .../ru/project/datasource/FileDataSource.java | 15 ++++---- .../ru/project/datasource/FileWriter.java | 36 ++++++++++++++++++ .../project/datasource/ManualDataSource.java | 18 ++++++++- .../project/datasource/RandomDataSource.java | 34 +++++++++++------ .../ru/project/datasource/StudentBuffer.java | 37 +++++++++++++++++++ 6 files changed, 121 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/ru/project/datasource/FileWriter.java create mode 100644 app/src/main/java/ru/project/datasource/StudentBuffer.java diff --git a/app/src/main/java/ru/project/datasource/DataSource.java b/app/src/main/java/ru/project/datasource/DataSource.java index d8ad27f..08530a2 100644 --- a/app/src/main/java/ru/project/datasource/DataSource.java +++ b/app/src/main/java/ru/project/datasource/DataSource.java @@ -5,5 +5,5 @@ import ru.project.collection.MyList; public interface DataSource { - MyList fill(int size); + MyList create(); } diff --git a/app/src/main/java/ru/project/datasource/FileDataSource.java b/app/src/main/java/ru/project/datasource/FileDataSource.java index 4b2c048..88de258 100644 --- a/app/src/main/java/ru/project/datasource/FileDataSource.java +++ b/app/src/main/java/ru/project/datasource/FileDataSource.java @@ -9,6 +9,7 @@ import java.nio.file.Paths; import java.util.stream.Collector; import ru.project.student.Student; import ru.project.collection.MyList; +import ru.project.collection.MyLinkedList; public class FileDataSource implements DataSource{ private String fileName; @@ -22,16 +23,16 @@ public class FileDataSource implements DataSource{ } @Override - public MyList fill(int size) { - Path path = Paths.get(fileName); + public MyList create() { + Path path = Paths.of(fileName); try(var lines = Files.lines(path)){ - return lines.skip(1) - .limit(size) + return lines + //.skip(1) .map(e -> StringParser.parseString(e)) .filter(e -> !(e == null)) - .collect(Collector.of(MyList::new, - MyList::add, - (left, right) -> left.addAll(right))); + .collect(Collector.of(MyLinkedList::new, + MyLinkedList::add, + (left, right) -> left; }catch(FileNotFoundException e){ System.out.println("File not found."); return null; diff --git a/app/src/main/java/ru/project/datasource/FileWriter.java b/app/src/main/java/ru/project/datasource/FileWriter.java new file mode 100644 index 0000000..7b2f366 --- /dev/null +++ b/app/src/main/java/ru/project/datasource/FileWriter.java @@ -0,0 +1,36 @@ +package ru.project.datasource; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import ru.project.student.Student; +import ru.project.collection.MyList; +import ru.project.collection.MyLinkedList; +import ru.project.collection.Iterator; + +public class FileWriter { + private Path path; + + public FileWriter(String absolutePath) { + this.path = Path.of(absolutePath); + } + + public void write(MyList iist){ + //int sizeOfCollection = list.size(); + try (BufferedWriter writer = Files.newBufferedWriter( + path, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND + )) { + Iterator iterator = list.getIterator(); + while (iterator.hasNext()){ + writer.write(iterator.next().toFileString()); + writer.newLine(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/app/src/main/java/ru/project/datasource/ManualDataSource.java b/app/src/main/java/ru/project/datasource/ManualDataSource.java index f24c73a..d7c46e7 100644 --- a/app/src/main/java/ru/project/datasource/ManualDataSource.java +++ b/app/src/main/java/ru/project/datasource/ManualDataSource.java @@ -3,11 +3,25 @@ package ru.project.datasource; import java.util.Scanner; import ru.project.student.Student; import ru.project.collection.MyList; +import ru.project.collection.MyLinkedList; public class ManualDataSource implements DataSource{ + private int size; + private MyList bufferMyList = new MyLinkedList<>(); + + public ManualDataSource(int size) { + this.size = size; + } + + public boolean setStudent( + int index, + String groupNumber, + double averageGrade, + int recordBookNumber + ){} @Override - public MyList fill(int size) { - MyList result = new MyList<>(); + public MyList create() { + MyList result = new MyLinkedList<>(); String strToParse; Student student; Scanner scanner = new Scanner(System.in); diff --git a/app/src/main/java/ru/project/datasource/RandomDataSource.java b/app/src/main/java/ru/project/datasource/RandomDataSource.java index f81cf77..d109fa2 100644 --- a/app/src/main/java/ru/project/datasource/RandomDataSource.java +++ b/app/src/main/java/ru/project/datasource/RandomDataSource.java @@ -3,34 +3,46 @@ package ru.project.datasource; import ru.project.student.Student; import ru.project.student.StudentBuilder; import ru.project.collection.MyList; +import ru.project.collection.MyLinkedList; import java.util.Random; public class RandomDataSource implements DataSource{ - private final String CHARS = "АБВГДЕЖХИКЛМНОПРСТУФХЦЧЩЭЮЯ"; + private final int size; + //private final String CHARS = "АБВГДЕЖХИКЛМНОПРСТУФХЦЧЩЭЮЯ"; + private int upperBounRecordBookNumber; + + public RandomDataSource(int size) { + this.size = size; + } + + public void setUpperBounRecordBookNumber(int upperBounRecordBookNumber) { + this.upperBounRecordBookNumber = upperBounRecordBookNumber; + } private String getRandomGroupNumber(Random random){ - StringBuilder buildRes = new StringBuilder(CHARS.charAt(random.nextInt(CHARS.length()))); - buildRes.append('-'); - buildRes.append(String.format("%03d", random.nextInt(999))); - return buildRes.toString(); + char prefix = (char)(random.nextInt(26) + (int)'A')4 + String result = prefix + String.format("%02d", random.nextInt(100)); + return result; } - private String getRandomRecordBookNumber(Random random){ + /*private String getRandomRecordBookNumber(Random random){ StringBuilder buildRes = new StringBuilder(CHARS.charAt(random.nextInt(CHARS.length()))); buildRes.append(String.format("%08d", random.nextInt(99999999))); return buildRes.toString(); - } + }*/ @Override - public MyList fill(int size) { + public MyList create() { + if (upperBounRecordBookNumber < 0) + throw new IllegalException("The upper bound is negative!!!"); Random random = new Random(); - MyList result = new MyList<>(); + MyList result = new MyLinkedList<>(); for (short i = 0; i Date: Sun, 30 Aug 2026 22:38:01 +0700 Subject: [PATCH 118/156] refactor: integrate StudentCsvParser into FileDataSource --- .../ru/project/datasource/FileDataSource.java | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/ru/project/datasource/FileDataSource.java b/app/src/main/java/ru/project/datasource/FileDataSource.java index 88de258..62c1d83 100644 --- a/app/src/main/java/ru/project/datasource/FileDataSource.java +++ b/app/src/main/java/ru/project/datasource/FileDataSource.java @@ -12,32 +12,28 @@ import ru.project.collection.MyList; import ru.project.collection.MyLinkedList; public class FileDataSource implements DataSource{ - private String fileName; + private final String fileName; public FileDataSource(String fileName) { this.fileName = fileName; } - public void setFileName(String fileName) { - this.fileName = fileName; - } - @Override public MyList create() { - Path path = Paths.of(fileName); - try(var lines = Files.lines(path)){ - return lines - //.skip(1) - .map(e -> StringParser.parseString(e)) - .filter(e -> !(e == null)) - .collect(Collector.of(MyLinkedList::new, - MyLinkedList::add, - (left, right) -> left; - }catch(FileNotFoundException e){ - System.out.println("File not found."); + MyList resultList = new MyLinkedList<>(); + try { + StudentCsvReader reader = new StudentCsvReader(fileName); + while reader.hasNext() { + result.append(reader.next()); + } + } + catch (IllegalArgumentException e){ + System.out.println(e.getMessage()); + return null; + } + catch (NoSuchElementException e){ + System.out.println("No such element"); return null; - } catch (IOException e) { - throw new RuntimeException(e); } } } From bf256e8247f63732d3b2b08bd7c17cab6d37fc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Sun, 30 Aug 2026 22:46:05 +0700 Subject: [PATCH 119/156] fix: import java.util.NoSuchElementException into FileDataSource --- app/src/main/java/ru/project/datasource/FileDataSource.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/ru/project/datasource/FileDataSource.java b/app/src/main/java/ru/project/datasource/FileDataSource.java index 62c1d83..b2c70d9 100644 --- a/app/src/main/java/ru/project/datasource/FileDataSource.java +++ b/app/src/main/java/ru/project/datasource/FileDataSource.java @@ -7,6 +7,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Collector; +import java.util.NoSuchElementException; import ru.project.student.Student; import ru.project.collection.MyList; import ru.project.collection.MyLinkedList; From 4f1064ddafccacab04b32f8867669767af3df7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Mon, 31 Aug 2026 01:29:12 +0700 Subject: [PATCH 120/156] fix: fixing some syntax errors (part 1) --- app/pom.xml | 5 +++++ app/src/main/java/ru/project/datasource/FileDataSource.java | 5 +++-- app/src/main/java/ru/project/datasource/FileWriter.java | 6 +++--- .../main/java/ru/project/datasource/ManualDataSource.java | 4 +++- .../main/java/ru/project/datasource/RandomDataSource.java | 4 ++-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/pom.xml b/app/pom.xml index 509e563..9caf260 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -41,6 +41,11 @@ junit-jupiter-params test + + org.apache.commons + commons-csv + 1.14.1 + diff --git a/app/src/main/java/ru/project/datasource/FileDataSource.java b/app/src/main/java/ru/project/datasource/FileDataSource.java index b2c70d9..1c80464 100644 --- a/app/src/main/java/ru/project/datasource/FileDataSource.java +++ b/app/src/main/java/ru/project/datasource/FileDataSource.java @@ -24,9 +24,10 @@ public class FileDataSource implements DataSource{ MyList resultList = new MyLinkedList<>(); try { StudentCsvReader reader = new StudentCsvReader(fileName); - while reader.hasNext() { - result.append(reader.next()); + while (reader.hasNext()) { + resultList.add(reader.next()); } + return resultList; } catch (IllegalArgumentException e){ System.out.println(e.getMessage()); diff --git a/app/src/main/java/ru/project/datasource/FileWriter.java b/app/src/main/java/ru/project/datasource/FileWriter.java index 7b2f366..5d78d89 100644 --- a/app/src/main/java/ru/project/datasource/FileWriter.java +++ b/app/src/main/java/ru/project/datasource/FileWriter.java @@ -8,7 +8,7 @@ import java.nio.file.StandardOpenOption; import ru.project.student.Student; import ru.project.collection.MyList; import ru.project.collection.MyLinkedList; -import ru.project.collection.Iterator; +//import ru.project.collection.Iterator; public class FileWriter { private Path path; @@ -24,11 +24,11 @@ public class FileWriter { StandardOpenOption.CREATE, StandardOpenOption.APPEND )) { - Iterator iterator = list.getIterator(); + /*Iterator iterator = list.getIterator(); while (iterator.hasNext()){ writer.write(iterator.next().toFileString()); writer.newLine(); - } + }*/ } catch (IOException e) { throw new RuntimeException(e); } diff --git a/app/src/main/java/ru/project/datasource/ManualDataSource.java b/app/src/main/java/ru/project/datasource/ManualDataSource.java index d7c46e7..a6ae64c 100644 --- a/app/src/main/java/ru/project/datasource/ManualDataSource.java +++ b/app/src/main/java/ru/project/datasource/ManualDataSource.java @@ -18,7 +18,9 @@ public class ManualDataSource implements DataSource{ String groupNumber, double averageGrade, int recordBookNumber - ){} + ){ + return true; + } @Override public MyList create() { MyList result = new MyLinkedList<>(); diff --git a/app/src/main/java/ru/project/datasource/RandomDataSource.java b/app/src/main/java/ru/project/datasource/RandomDataSource.java index d109fa2..9239aad 100644 --- a/app/src/main/java/ru/project/datasource/RandomDataSource.java +++ b/app/src/main/java/ru/project/datasource/RandomDataSource.java @@ -20,7 +20,7 @@ public class RandomDataSource implements DataSource{ } private String getRandomGroupNumber(Random random){ - char prefix = (char)(random.nextInt(26) + (int)'A')4 + char prefix = (char)(random.nextInt(26) + (int)'A'); String result = prefix + String.format("%02d", random.nextInt(100)); return result; } @@ -33,7 +33,7 @@ public class RandomDataSource implements DataSource{ @Override public MyList create() { if (upperBounRecordBookNumber < 0) - throw new IllegalException("The upper bound is negative!!!"); + throw new IllegalArgumentException("The upper bound is negative!!!"); Random random = new Random(); MyList result = new MyLinkedList<>(); for (short i = 0; i Date: Mon, 31 Aug 2026 16:22:03 +0700 Subject: [PATCH 121/156] refactor: removing unused file StringParser.java --- .../ru/project/datasource/StringParser.java | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 app/src/main/java/ru/project/datasource/StringParser.java diff --git a/app/src/main/java/ru/project/datasource/StringParser.java b/app/src/main/java/ru/project/datasource/StringParser.java deleted file mode 100644 index 2bfdd26..0000000 --- a/app/src/main/java/ru/project/datasource/StringParser.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.project.datasource; - -import ru.project.student.Student; -import ru.project.student.StudentBuilder; -import ru.project.collection.MyList; - -public class StringParser { - public static Student parseString(String str){ - String [] parts = str.split(";"); - try{ - return new StudentBuilder() - .setGroupNumber(parts[0]) - .setAverageGrade(Double.valueOf(parts[1])) - .setRecordBookNumber(parts[2]) - .build(); - }catch(NumberFormatException e){ - return null; - } - } -} From 7408b0fd418aa222991b89b8aa8f1b49eb806016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Mon, 31 Aug 2026 16:39:56 +0700 Subject: [PATCH 122/156] fix: fixing syntax errors (part 2) --- app/src/main/java/ru/project/datasource/ManualDataSource.java | 4 ++-- app/src/main/java/ru/project/datasource/StudentCsvReader.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/ru/project/datasource/ManualDataSource.java b/app/src/main/java/ru/project/datasource/ManualDataSource.java index a6ae64c..a5bdad7 100644 --- a/app/src/main/java/ru/project/datasource/ManualDataSource.java +++ b/app/src/main/java/ru/project/datasource/ManualDataSource.java @@ -23,7 +23,7 @@ public class ManualDataSource implements DataSource{ } @Override public MyList create() { - MyList result = new MyLinkedList<>(); + /*MyList result = new MyLinkedList<>(); String strToParse; Student student; Scanner scanner = new Scanner(System.in); @@ -39,7 +39,7 @@ public class ManualDataSource implements DataSource{ result.add(student); } } - scanner.close(); + scanner.close();*/ return null; } } diff --git a/app/src/main/java/ru/project/datasource/StudentCsvReader.java b/app/src/main/java/ru/project/datasource/StudentCsvReader.java index 7403caa..7e39bee 100644 --- a/app/src/main/java/ru/project/datasource/StudentCsvReader.java +++ b/app/src/main/java/ru/project/datasource/StudentCsvReader.java @@ -62,10 +62,11 @@ public class StudentCsvReader implements Iterator { } double averageGrade = Double.parseDouble(record.get(AVERAGE_GRADE)); + int recordBookNumber = Integer.parseInt(record.get(RECORD_BOOK_NUMBER)); return new StudentBuilder() .setGroupNumber(record.get(GROUP_NUMBER)) .setAverageGrade(averageGrade) - .setRecordBookNumber(record.get(RECORD_BOOK_NUMBER)) + .setRecordBookNumber(recordBookNumber) .build(); } From eef7b45172bd26a28c8e7a023e360601a0bb00e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Mon, 31 Aug 2026 21:28:16 +0700 Subject: [PATCH 123/156] test: add test class DataSourceReaderTest --- .../datasource/DataSourceReaderTest.java | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 app/src/test/java/ru/project/datasource/DataSourceReaderTest.java diff --git a/app/src/test/java/ru/project/datasource/DataSourceReaderTest.java b/app/src/test/java/ru/project/datasource/DataSourceReaderTest.java new file mode 100644 index 0000000..740904c --- /dev/null +++ b/app/src/test/java/ru/project/datasource/DataSourceReaderTest.java @@ -0,0 +1,116 @@ +package ru.project.datasource; + +import java.io.UncheckedIOException; +import java.util.NoSuchElementException; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +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 static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; + +import ru.project.collection.MyList; +import ru.project.collection.MyLinkedList; + +import ru.project.student.Student; +import ru.project.student.StudentBuilder; + +class DataSourceReaderTest{ + private StudentCsvReader valideReader; + //Testing StudentCsvReader + @BeforeEach + void setUp(){ + valideReader = new StudentCsvReader("C:\\Users\\Dexp\\TestData\\sample.csv"); + } + + @Test + void readerTestRejectRelativePath(){ + assertThrows(IllegalArgumentException.class, () -> new StudentCsvReader("/Users/Dexp/TestData/sample.csv")); + } + + @Test + void readerRejectPathIsDir(){ + assertThrows(UncheckedIOException.class, () -> new StudentCsvReader("C:\\Users\\Dexp\\TestData")); + } + + @Test + void readerTestFileNotExist(){ + assertThrows(UncheckedIOException.class, () -> new StudentCsvReader("C:\\Users\\Dexp\\TestData\\sss.csv")); + } + + @Test + void testSkipsHeader(){ + Student student = valideReader.next(); + assertTrue(!(student.getGroupNumber()=="groupNumber")); + } + + @Test + void testCreationStudentOnValidData(){ + Student student = valideReader.next(); + assertEquals(student.getGroupNumber(), "A21"); + assertEquals(student.getAverageGrade(), 5.0); + assertEquals(student.getRecordBookNumber(), 331); + } + + @Test + void testNotSkippingFirstStudent(){ + boolean hn = valideReader.hasNext(); + hn = valideReader.hasNext(); + Student student = valideReader.next(); + assertEquals(student.getGroupNumber(), "A21"); + assertEquals(student.getAverageGrade(), 5.0); + assertEquals(student.getRecordBookNumber(), 331); + } + + @Test + void testValidEndsOfFile(){ + Student student = valideReader.next(); + student = valideReader.next(); + assertTrue(!valideReader.hasNext()); + } + + @Test + void testOutOfIndex(){ + assertThrows(NoSuchElementException.class, () -> { + Student student = valideReader.next(); + student = valideReader.next(); + student = valideReader.next(); + }); + } + + @Test + void readerTestRejectFewParametres(){ + assertThrows(IllegalArgumentException.class, () -> { + StudentCsvReader reader = new StudentCsvReader("C:\\Users\\Dexp\\TestData\\fewparameters.csv"); + Student student = reader.next(); + }); + } + + @Test + void readerTestRejectInvalidGroupNumber(){ + assertThrows(IllegalArgumentException.class, () -> { + StudentCsvReader reader = new StudentCsvReader("C:\\Users\\Dexp\\TestData\\invalid_groupnumber.csv"); + Student student = reader.next(); + }); + } + + @Test + void readerTestRejectInvalidAverageGrade(){ + assertThrows(IllegalArgumentException.class, () -> { + StudentCsvReader reader = new StudentCsvReader("C:\\Users\\Dexp\\TestData\\invalid_averagegrade.csv"); + Student student = reader.next(); + }); + } + + @Test + void readerTestRejectInvalidRecordBookNumber(){ + assertThrows(IllegalArgumentException.class, () -> { + StudentCsvReader reader = new StudentCsvReader("C:\\Users\\Dexp\\TestData\\invalid_recordbooknumber.csv"); + Student student = reader.next(); + }); + } +} \ No newline at end of file From 3b09d15163ab8d838b393219cc5d750b5d33daf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Mon, 31 Aug 2026 22:30:45 +0700 Subject: [PATCH 124/156] feat: reworking StudentCsvReader. Added the aability to specify row number when an exception is thrown out --- .../project/datasource/StudentCsvReader.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/ru/project/datasource/StudentCsvReader.java b/app/src/main/java/ru/project/datasource/StudentCsvReader.java index 7e39bee..156608e 100644 --- a/app/src/main/java/ru/project/datasource/StudentCsvReader.java +++ b/app/src/main/java/ru/project/datasource/StudentCsvReader.java @@ -25,7 +25,10 @@ public class StudentCsvReader implements Iterator { private final CSVParser parser; private final Iterator records; + private int lineOfFile; + public StudentCsvReader(String absoluteFilePath) { + lineOfFile = 0; Path path = Paths.get(absoluteFilePath); if (!path.isAbsolute()) { throw new IllegalArgumentException("File path must be absolute"); @@ -51,6 +54,7 @@ public class StudentCsvReader implements Iterator { @Override public Student next() { + lineOfFile++; if (!records.hasNext()) { throw new NoSuchElementException(); } @@ -58,16 +62,21 @@ public class StudentCsvReader implements Iterator { CSVRecord record = records.next(); if (record.size() != 3) { throw new IllegalArgumentException( - "Expected 3 columns, but got " + record.size()); + "Problem at line " + lineOfFile + ".Expected 3 columns, but got " + record.size()); + } + try { + double averageGrade = Double.parseDouble(record.get(AVERAGE_GRADE)); + int recordBookNumber = Integer.parseInt(record.get(RECORD_BOOK_NUMBER)); + return new StudentBuilder() + .setGroupNumber(record.get(GROUP_NUMBER)) + .setAverageGrade(averageGrade) + .setRecordBookNumber(recordBookNumber) + .build(); + } + catch (NumberFormatException e){ + throw new IllegalArgumentException( + "Problem at line " + lineOfFile + ". Unacceptable type convertation"); } - - double averageGrade = Double.parseDouble(record.get(AVERAGE_GRADE)); - int recordBookNumber = Integer.parseInt(record.get(RECORD_BOOK_NUMBER)); - return new StudentBuilder() - .setGroupNumber(record.get(GROUP_NUMBER)) - .setAverageGrade(averageGrade) - .setRecordBookNumber(recordBookNumber) - .build(); } private static void validateHeader(Map headerMap) { From fad97d8e9573f714b9235bd929321c5558428ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Mon, 31 Aug 2026 22:33:56 +0700 Subject: [PATCH 125/156] refactor: removing unused class ManualDataSource --- .../project/datasource/ManualDataSource.java | 45 ------------------- 1 file changed, 45 deletions(-) delete mode 100644 app/src/main/java/ru/project/datasource/ManualDataSource.java diff --git a/app/src/main/java/ru/project/datasource/ManualDataSource.java b/app/src/main/java/ru/project/datasource/ManualDataSource.java deleted file mode 100644 index a5bdad7..0000000 --- a/app/src/main/java/ru/project/datasource/ManualDataSource.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.project.datasource; - -import java.util.Scanner; -import ru.project.student.Student; -import ru.project.collection.MyList; -import ru.project.collection.MyLinkedList; - -public class ManualDataSource implements DataSource{ - private int size; - private MyList bufferMyList = new MyLinkedList<>(); - - public ManualDataSource(int size) { - this.size = size; - } - - public boolean setStudent( - int index, - String groupNumber, - double averageGrade, - int recordBookNumber - ){ - return true; - } - @Override - public MyList create() { - /*MyList result = new MyLinkedList<>(); - String strToParse; - Student student; - Scanner scanner = new Scanner(System.in); - for (short i = 0; i < size; i++){ - strToParse = scanner.next(); - if (strToParse.equals("0")) - break; - student = StringParser.parseString(strToParse); - if (student == null){ - System.out.println("Oops! Invalid input"); - } - else{ - result.add(student); - } - } - scanner.close();*/ - return null; - } -} From 3cca9402bb94c9933f4a443f782495ae4ef4adcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Mon, 31 Aug 2026 22:56:53 +0700 Subject: [PATCH 126/156] feature: improved handling exception NoSuchElementException --- app/src/main/java/ru/project/datasource/FileDataSource.java | 2 +- app/src/main/java/ru/project/datasource/StudentCsvReader.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ru/project/datasource/FileDataSource.java b/app/src/main/java/ru/project/datasource/FileDataSource.java index 1c80464..d15f63e 100644 --- a/app/src/main/java/ru/project/datasource/FileDataSource.java +++ b/app/src/main/java/ru/project/datasource/FileDataSource.java @@ -34,7 +34,7 @@ public class FileDataSource implements DataSource{ return null; } catch (NoSuchElementException e){ - System.out.println("No such element"); + System.out.println(e.getMessage() + "No such element"); return null; } } diff --git a/app/src/main/java/ru/project/datasource/StudentCsvReader.java b/app/src/main/java/ru/project/datasource/StudentCsvReader.java index 156608e..6e04c97 100644 --- a/app/src/main/java/ru/project/datasource/StudentCsvReader.java +++ b/app/src/main/java/ru/project/datasource/StudentCsvReader.java @@ -56,7 +56,7 @@ public class StudentCsvReader implements Iterator { public Student next() { lineOfFile++; if (!records.hasNext()) { - throw new NoSuchElementException(); + throw new NoSuchElementException("Problem at line " + lineOfFile + "."); } CSVRecord record = records.next(); From f1c6aa1443a66ea0cbf34b079877ed38f25db717 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: Mon, 31 Aug 2026 22:58:32 +0400 Subject: [PATCH 127/156] refactor: remove obsolete data source infrastructure - remove legacy DataSource implementations - remove obsolete file and random data source classes --- .../ru/project/datasource/DataSource.java | 9 --- .../ru/project/datasource/FileDataSource.java | 41 ----------- .../ru/project/datasource/FileWriter.java | 36 --------- .../project/datasource/RandomDataSource.java | 48 ------------ .../ru/project/datasource/StudentBuffer.java | 37 ---------- .../project/datasource/StudentCsvWriter.java | 73 ------------------- 6 files changed, 244 deletions(-) delete mode 100644 app/src/main/java/ru/project/datasource/DataSource.java delete mode 100644 app/src/main/java/ru/project/datasource/FileDataSource.java delete mode 100644 app/src/main/java/ru/project/datasource/FileWriter.java delete mode 100644 app/src/main/java/ru/project/datasource/RandomDataSource.java delete mode 100644 app/src/main/java/ru/project/datasource/StudentBuffer.java delete mode 100644 app/src/main/java/ru/project/datasource/StudentCsvWriter.java diff --git a/app/src/main/java/ru/project/datasource/DataSource.java b/app/src/main/java/ru/project/datasource/DataSource.java deleted file mode 100644 index 08530a2..0000000 --- a/app/src/main/java/ru/project/datasource/DataSource.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.project.datasource; - -import ru.project.student.Student; -import ru.project.collection.MyList; - - -public interface DataSource { - MyList create(); -} diff --git a/app/src/main/java/ru/project/datasource/FileDataSource.java b/app/src/main/java/ru/project/datasource/FileDataSource.java deleted file mode 100644 index d15f63e..0000000 --- a/app/src/main/java/ru/project/datasource/FileDataSource.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.project.datasource; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.stream.Collector; -import java.util.NoSuchElementException; -import ru.project.student.Student; -import ru.project.collection.MyList; -import ru.project.collection.MyLinkedList; - -public class FileDataSource implements DataSource{ - private final String fileName; - - public FileDataSource(String fileName) { - this.fileName = fileName; - } - - @Override - public MyList create() { - MyList resultList = new MyLinkedList<>(); - try { - StudentCsvReader reader = new StudentCsvReader(fileName); - while (reader.hasNext()) { - resultList.add(reader.next()); - } - return resultList; - } - catch (IllegalArgumentException e){ - System.out.println(e.getMessage()); - return null; - } - catch (NoSuchElementException e){ - System.out.println(e.getMessage() + "No such element"); - return null; - } - } -} diff --git a/app/src/main/java/ru/project/datasource/FileWriter.java b/app/src/main/java/ru/project/datasource/FileWriter.java deleted file mode 100644 index 5d78d89..0000000 --- a/app/src/main/java/ru/project/datasource/FileWriter.java +++ /dev/null @@ -1,36 +0,0 @@ -package ru.project.datasource; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import ru.project.student.Student; -import ru.project.collection.MyList; -import ru.project.collection.MyLinkedList; -//import ru.project.collection.Iterator; - -public class FileWriter { - private Path path; - - public FileWriter(String absolutePath) { - this.path = Path.of(absolutePath); - } - - public void write(MyList iist){ - //int sizeOfCollection = list.size(); - try (BufferedWriter writer = Files.newBufferedWriter( - path, - StandardOpenOption.CREATE, - StandardOpenOption.APPEND - )) { - /*Iterator iterator = list.getIterator(); - while (iterator.hasNext()){ - writer.write(iterator.next().toFileString()); - writer.newLine(); - }*/ - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/app/src/main/java/ru/project/datasource/RandomDataSource.java b/app/src/main/java/ru/project/datasource/RandomDataSource.java deleted file mode 100644 index 9239aad..0000000 --- a/app/src/main/java/ru/project/datasource/RandomDataSource.java +++ /dev/null @@ -1,48 +0,0 @@ -package ru.project.datasource; - -import ru.project.student.Student; -import ru.project.student.StudentBuilder; -import ru.project.collection.MyList; -import ru.project.collection.MyLinkedList; -import java.util.Random; - -public class RandomDataSource implements DataSource{ - private final int size; - //private final String CHARS = "АБВГДЕЖХИКЛМНОПРСТУФХЦЧЩЭЮЯ"; - private int upperBounRecordBookNumber; - - public RandomDataSource(int size) { - this.size = size; - } - - public void setUpperBounRecordBookNumber(int upperBounRecordBookNumber) { - this.upperBounRecordBookNumber = upperBounRecordBookNumber; - } - - private String getRandomGroupNumber(Random random){ - char prefix = (char)(random.nextInt(26) + (int)'A'); - String result = prefix + String.format("%02d", random.nextInt(100)); - return result; - } - /*private String getRandomRecordBookNumber(Random random){ - StringBuilder buildRes = new StringBuilder(CHARS.charAt(random.nextInt(CHARS.length()))); - buildRes.append(String.format("%08d", random.nextInt(99999999))); - return buildRes.toString(); - }*/ - - @Override - public MyList create() { - if (upperBounRecordBookNumber < 0) - throw new IllegalArgumentException("The upper bound is negative!!!"); - Random random = new Random(); - MyList result = new MyLinkedList<>(); - for (short i = 0; i Date: Mon, 31 Aug 2026 23:08:50 +0400 Subject: [PATCH 128/156] refactor(io): rename StudentCsvReader package --- .../project/datasource/StudentCsvReader.java | 89 ------------------- .../java/ru/project/io/StudentCsvReader.java | 83 +++++++++++++++++ 2 files changed, 83 insertions(+), 89 deletions(-) delete mode 100644 app/src/main/java/ru/project/datasource/StudentCsvReader.java create mode 100644 app/src/main/java/ru/project/io/StudentCsvReader.java diff --git a/app/src/main/java/ru/project/datasource/StudentCsvReader.java b/app/src/main/java/ru/project/datasource/StudentCsvReader.java deleted file mode 100644 index 6e04c97..0000000 --- a/app/src/main/java/ru/project/datasource/StudentCsvReader.java +++ /dev/null @@ -1,89 +0,0 @@ -package ru.project.datasource; - -import ru.project.student.Student; -import ru.project.student.StudentBuilder; - -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVParser; -import org.apache.commons.csv.CSVRecord; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Iterator; -import java.util.Map; -import java.util.NoSuchElementException; - -public class StudentCsvReader implements Iterator { - private static final String GROUP_NUMBER = "groupNumber"; - private static final String AVERAGE_GRADE = "averageGrade"; - private static final String RECORD_BOOK_NUMBER = "recordBookNumber"; - - private final CSVParser parser; - private final Iterator records; - - private int lineOfFile; - - public StudentCsvReader(String absoluteFilePath) { - lineOfFile = 0; - Path path = Paths.get(absoluteFilePath); - if (!path.isAbsolute()) { - throw new IllegalArgumentException("File path must be absolute"); - } - - try { - CSVFormat format = CSVFormat.DEFAULT.builder() - .setHeader() - .setSkipHeaderRecord(true) - .build(); - this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); - validateHeader(parser.getHeaderMap()); - this.records = parser.iterator(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public boolean hasNext() { - return records.hasNext(); - } - - @Override - public Student next() { - lineOfFile++; - if (!records.hasNext()) { - throw new NoSuchElementException("Problem at line " + lineOfFile + "."); - } - - CSVRecord record = records.next(); - if (record.size() != 3) { - throw new IllegalArgumentException( - "Problem at line " + lineOfFile + ".Expected 3 columns, but got " + record.size()); - } - try { - double averageGrade = Double.parseDouble(record.get(AVERAGE_GRADE)); - int recordBookNumber = Integer.parseInt(record.get(RECORD_BOOK_NUMBER)); - return new StudentBuilder() - .setGroupNumber(record.get(GROUP_NUMBER)) - .setAverageGrade(averageGrade) - .setRecordBookNumber(recordBookNumber) - .build(); - } - catch (NumberFormatException e){ - throw new IllegalArgumentException( - "Problem at line " + lineOfFile + ". Unacceptable type convertation"); - } - } - - private static void validateHeader(Map headerMap) { - String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER}; - if (headerMap.size() != expected.length - || !headerMap.keySet().containsAll(Arrays.asList(expected))) { - throw new IllegalArgumentException("Invalid CSV header"); - } - } -} diff --git a/app/src/main/java/ru/project/io/StudentCsvReader.java b/app/src/main/java/ru/project/io/StudentCsvReader.java new file mode 100644 index 0000000..586fb5b --- /dev/null +++ b/app/src/main/java/ru/project/io/StudentCsvReader.java @@ -0,0 +1,83 @@ +package ru.project.io; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import ru.project.student.Student; +import ru.project.student.StudentBuilder; + +public class StudentCsvReader implements Iterator { + private static final String GROUP_NUMBER = "groupNumber"; + private static final String AVERAGE_GRADE = "averageGrade"; + private static final String RECORD_BOOK_NUMBER = "recordBookNumber"; + + private final CSVParser parser; + private final Iterator records; + + private int lineOfFile; + + public StudentCsvReader(String absoluteFilePath) { + lineOfFile = 0; + Path path = Paths.get(absoluteFilePath); + if (!path.isAbsolute()) { + throw new IllegalArgumentException("File path must be absolute"); + } + + try { + CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).build(); + this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); + validateHeader(parser.getHeaderMap()); + this.records = parser.iterator(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public boolean hasNext() { + return records.hasNext(); + } + + @Override + public Student next() { + lineOfFile++; + if (!records.hasNext()) { + throw new NoSuchElementException("Problem at line " + lineOfFile + "."); + } + + CSVRecord record = records.next(); + if (record.size() != 3) { + throw new IllegalArgumentException( + "Problem at line " + lineOfFile + ".Expected 3 columns, but got " + record.size()); + } + try { + double averageGrade = Double.parseDouble(record.get(AVERAGE_GRADE)); + int recordBookNumber = Integer.parseInt(record.get(RECORD_BOOK_NUMBER)); + return new StudentBuilder() + .setGroupNumber(record.get(GROUP_NUMBER)) + .setAverageGrade(averageGrade) + .setRecordBookNumber(recordBookNumber) + .build(); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Problem at line " + lineOfFile + ". Unacceptable type convertation"); + } + } + + private static void validateHeader(Map headerMap) { + String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER}; + if (headerMap.size() != expected.length + || !headerMap.keySet().containsAll(Arrays.asList(expected))) { + throw new IllegalArgumentException("Invalid CSV header"); + } + } +} From 217f9594bdb7c45f7abc9621211b93de220242a0 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: Mon, 31 Aug 2026 23:13:45 +0400 Subject: [PATCH 129/156] refactor(test): replace DataSourceReader tests with StudentCsvReader tests --- .../datasource/DataSourceReaderTest.java | 116 ---------------- .../ru/project/io/StudentCsvReaderTest.java | 127 ++++++++++++++++++ 2 files changed, 127 insertions(+), 116 deletions(-) delete mode 100644 app/src/test/java/ru/project/datasource/DataSourceReaderTest.java create mode 100644 app/src/test/java/ru/project/io/StudentCsvReaderTest.java diff --git a/app/src/test/java/ru/project/datasource/DataSourceReaderTest.java b/app/src/test/java/ru/project/datasource/DataSourceReaderTest.java deleted file mode 100644 index 740904c..0000000 --- a/app/src/test/java/ru/project/datasource/DataSourceReaderTest.java +++ /dev/null @@ -1,116 +0,0 @@ -package ru.project.datasource; - -import java.io.UncheckedIOException; -import java.util.NoSuchElementException; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -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 static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.BeforeEach; - -import ru.project.collection.MyList; -import ru.project.collection.MyLinkedList; - -import ru.project.student.Student; -import ru.project.student.StudentBuilder; - -class DataSourceReaderTest{ - private StudentCsvReader valideReader; - //Testing StudentCsvReader - @BeforeEach - void setUp(){ - valideReader = new StudentCsvReader("C:\\Users\\Dexp\\TestData\\sample.csv"); - } - - @Test - void readerTestRejectRelativePath(){ - assertThrows(IllegalArgumentException.class, () -> new StudentCsvReader("/Users/Dexp/TestData/sample.csv")); - } - - @Test - void readerRejectPathIsDir(){ - assertThrows(UncheckedIOException.class, () -> new StudentCsvReader("C:\\Users\\Dexp\\TestData")); - } - - @Test - void readerTestFileNotExist(){ - assertThrows(UncheckedIOException.class, () -> new StudentCsvReader("C:\\Users\\Dexp\\TestData\\sss.csv")); - } - - @Test - void testSkipsHeader(){ - Student student = valideReader.next(); - assertTrue(!(student.getGroupNumber()=="groupNumber")); - } - - @Test - void testCreationStudentOnValidData(){ - Student student = valideReader.next(); - assertEquals(student.getGroupNumber(), "A21"); - assertEquals(student.getAverageGrade(), 5.0); - assertEquals(student.getRecordBookNumber(), 331); - } - - @Test - void testNotSkippingFirstStudent(){ - boolean hn = valideReader.hasNext(); - hn = valideReader.hasNext(); - Student student = valideReader.next(); - assertEquals(student.getGroupNumber(), "A21"); - assertEquals(student.getAverageGrade(), 5.0); - assertEquals(student.getRecordBookNumber(), 331); - } - - @Test - void testValidEndsOfFile(){ - Student student = valideReader.next(); - student = valideReader.next(); - assertTrue(!valideReader.hasNext()); - } - - @Test - void testOutOfIndex(){ - assertThrows(NoSuchElementException.class, () -> { - Student student = valideReader.next(); - student = valideReader.next(); - student = valideReader.next(); - }); - } - - @Test - void readerTestRejectFewParametres(){ - assertThrows(IllegalArgumentException.class, () -> { - StudentCsvReader reader = new StudentCsvReader("C:\\Users\\Dexp\\TestData\\fewparameters.csv"); - Student student = reader.next(); - }); - } - - @Test - void readerTestRejectInvalidGroupNumber(){ - assertThrows(IllegalArgumentException.class, () -> { - StudentCsvReader reader = new StudentCsvReader("C:\\Users\\Dexp\\TestData\\invalid_groupnumber.csv"); - Student student = reader.next(); - }); - } - - @Test - void readerTestRejectInvalidAverageGrade(){ - assertThrows(IllegalArgumentException.class, () -> { - StudentCsvReader reader = new StudentCsvReader("C:\\Users\\Dexp\\TestData\\invalid_averagegrade.csv"); - Student student = reader.next(); - }); - } - - @Test - void readerTestRejectInvalidRecordBookNumber(){ - assertThrows(IllegalArgumentException.class, () -> { - StudentCsvReader reader = new StudentCsvReader("C:\\Users\\Dexp\\TestData\\invalid_recordbooknumber.csv"); - Student student = reader.next(); - }); - } -} \ No newline at end of file diff --git a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java new file mode 100644 index 0000000..60db7de --- /dev/null +++ b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java @@ -0,0 +1,127 @@ +package ru.project.io; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.UncheckedIOException; +import java.util.NoSuchElementException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.project.student.Student; + +class StudentCsvReaderTest { + private StudentCsvReader valideReader; + + // Testing StudentCsvReader + @BeforeEach + void setUp() { + valideReader = new StudentCsvReader("C:\\Users\\Dexp\\TestData\\sample.csv"); + } + + @Test + void readerTestRejectRelativePath() { + assertThrows( + IllegalArgumentException.class, + () -> new StudentCsvReader("/Users/Dexp/TestData/sample.csv")); + } + + @Test + void readerRejectPathIsDir() { + assertThrows( + UncheckedIOException.class, () -> new StudentCsvReader("C:\\Users\\Dexp\\TestData")); + } + + @Test + void readerTestFileNotExist() { + assertThrows( + UncheckedIOException.class, + () -> new StudentCsvReader("C:\\Users\\Dexp\\TestData\\sss.csv")); + } + + @Test + void testSkipsHeader() { + Student student = valideReader.next(); + assertTrue(!(student.getGroupNumber() == "groupNumber")); + } + + @Test + void testCreationStudentOnValidData() { + Student student = valideReader.next(); + assertEquals(student.getGroupNumber(), "A21"); + assertEquals(student.getAverageGrade(), 5.0); + assertEquals(student.getRecordBookNumber(), 331); + } + + @Test + void testNotSkippingFirstStudent() { + boolean hn = valideReader.hasNext(); + hn = valideReader.hasNext(); + Student student = valideReader.next(); + assertEquals(student.getGroupNumber(), "A21"); + assertEquals(student.getAverageGrade(), 5.0); + assertEquals(student.getRecordBookNumber(), 331); + } + + @Test + void testValidEndsOfFile() { + Student student = valideReader.next(); + student = valideReader.next(); + assertTrue(!valideReader.hasNext()); + } + + @Test + void testOutOfIndex() { + assertThrows( + NoSuchElementException.class, + () -> { + Student student = valideReader.next(); + student = valideReader.next(); + student = valideReader.next(); + }); + } + + @Test + void readerTestRejectFewParametres() { + assertThrows( + IllegalArgumentException.class, + () -> { + StudentCsvReader reader = + new StudentCsvReader("C:\\Users\\Dexp\\TestData\\fewparameters.csv"); + Student student = reader.next(); + }); + } + + @Test + void readerTestRejectInvalidGroupNumber() { + assertThrows( + IllegalArgumentException.class, + () -> { + StudentCsvReader reader = + new StudentCsvReader("C:\\Users\\Dexp\\TestData\\invalid_groupnumber.csv"); + Student student = reader.next(); + }); + } + + @Test + void readerTestRejectInvalidAverageGrade() { + assertThrows( + IllegalArgumentException.class, + () -> { + StudentCsvReader reader = + new StudentCsvReader("C:\\Users\\Dexp\\TestData\\invalid_averagegrade.csv"); + Student student = reader.next(); + }); + } + + @Test + void readerTestRejectInvalidRecordBookNumber() { + assertThrows( + IllegalArgumentException.class, + () -> { + StudentCsvReader reader = + new StudentCsvReader("C:\\Users\\Dexp\\TestData\\invalid_recordbooknumber.csv"); + Student student = reader.next(); + }); + } +} From a06f36683dbe9b30c4bf0534b9de0ddbb6c201e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Tue, 1 Sep 2026 13:56:52 +0700 Subject: [PATCH 130/156] refactor: replace org.apache.commons.csv.CSVFormat.Builder.build() with org.apache.commons.csv.CSVFormat.Builder.get() --- app/src/main/java/ru/project/io/StudentCsvReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/ru/project/io/StudentCsvReader.java b/app/src/main/java/ru/project/io/StudentCsvReader.java index 586fb5b..b44a738 100644 --- a/app/src/main/java/ru/project/io/StudentCsvReader.java +++ b/app/src/main/java/ru/project/io/StudentCsvReader.java @@ -33,7 +33,7 @@ public class StudentCsvReader implements Iterator { } try { - CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).build(); + CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).get(); this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); validateHeader(parser.getHeaderMap()); this.records = parser.iterator(); From fc847750e8be3bef980c7e8caa0a197f2f8ca60f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Tue, 1 Sep 2026 14:20:45 +0700 Subject: [PATCH 131/156] refactor (test): remove variables at StudentCsvReaderTest never used --- .../test/java/ru/project/io/StudentCsvReaderTest.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java index 60db7de..d8355bc 100644 --- a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java +++ b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java @@ -1,6 +1,7 @@ package ru.project.io; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -42,7 +43,7 @@ class StudentCsvReaderTest { @Test void testSkipsHeader() { Student student = valideReader.next(); - assertTrue(!(student.getGroupNumber() == "groupNumber")); + assertNotEquals(student.getGroupNumber(), "groupNumber"); } @Test @@ -55,8 +56,8 @@ class StudentCsvReaderTest { @Test void testNotSkippingFirstStudent() { - boolean hn = valideReader.hasNext(); - hn = valideReader.hasNext(); + valideReader.hasNext(); + valideReader.hasNext(); Student student = valideReader.next(); assertEquals(student.getGroupNumber(), "A21"); assertEquals(student.getAverageGrade(), 5.0); @@ -65,8 +66,8 @@ class StudentCsvReaderTest { @Test void testValidEndsOfFile() { - Student student = valideReader.next(); - student = valideReader.next(); + valideReader.next(); + valideReader.next(); assertTrue(!valideReader.hasNext()); } From d0bf5f84fcaa9f7e08516756eb471d4a49e96eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Tue, 1 Sep 2026 14:54:01 +0700 Subject: [PATCH 132/156] refactor (test): add final String ABSOLUTE_DIR into StudentCsvReaderTest --- .../java/ru/project/io/StudentCsvReaderTest.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java index d8355bc..2ba9ed1 100644 --- a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java +++ b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java @@ -12,12 +12,13 @@ import org.junit.jupiter.api.Test; import ru.project.student.Student; class StudentCsvReaderTest { + private final String ABSOLUTE_DIR = "C:\\Users\\Dexp\\TestData\\" private StudentCsvReader valideReader; // Testing StudentCsvReader @BeforeEach void setUp() { - valideReader = new StudentCsvReader("C:\\Users\\Dexp\\TestData\\sample.csv"); + valideReader = new StudentCsvReader(ABSOLUTE_DIR + "sample.csv"); } @Test @@ -30,14 +31,14 @@ class StudentCsvReaderTest { @Test void readerRejectPathIsDir() { assertThrows( - UncheckedIOException.class, () -> new StudentCsvReader("C:\\Users\\Dexp\\TestData")); + UncheckedIOException.class, () -> new StudentCsvReader(ABSOLUTE_DIR)); } @Test void readerTestFileNotExist() { assertThrows( UncheckedIOException.class, - () -> new StudentCsvReader("C:\\Users\\Dexp\\TestData\\sss.csv")); + () -> new StudentCsvReader(ABSOLUTE_DIR + "sss.csv")); } @Test @@ -88,7 +89,7 @@ class StudentCsvReaderTest { IllegalArgumentException.class, () -> { StudentCsvReader reader = - new StudentCsvReader("C:\\Users\\Dexp\\TestData\\fewparameters.csv"); + new StudentCsvReader(ABSOLUTE_DIR + "fewparameters.csv"); Student student = reader.next(); }); } @@ -99,7 +100,7 @@ class StudentCsvReaderTest { IllegalArgumentException.class, () -> { StudentCsvReader reader = - new StudentCsvReader("C:\\Users\\Dexp\\TestData\\invalid_groupnumber.csv"); + new StudentCsvReader(ABSOLUTE_DIR + "invalid_groupnumber.csv"); Student student = reader.next(); }); } @@ -110,7 +111,7 @@ class StudentCsvReaderTest { IllegalArgumentException.class, () -> { StudentCsvReader reader = - new StudentCsvReader("C:\\Users\\Dexp\\TestData\\invalid_averagegrade.csv"); + new StudentCsvReader(ABSOLUTE_DIR + "invalid_averagegrade.csv"); Student student = reader.next(); }); } @@ -121,7 +122,7 @@ class StudentCsvReaderTest { IllegalArgumentException.class, () -> { StudentCsvReader reader = - new StudentCsvReader("C:\\Users\\Dexp\\TestData\\invalid_recordbooknumber.csv"); + new StudentCsvReader(ABSOLUTE_DIR + "invalid_recordbooknumber.csv"); Student student = reader.next(); }); } From 537c91d98138e4e5fe7d28ade80f81dc7bc59adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Tue, 1 Sep 2026 15:14:50 +0700 Subject: [PATCH 133/156] test: add test csv-files --- TestData/fewparameters.csv | 2 ++ TestData/invalid_averagegrade.csv | 2 ++ TestData/invalid_groupnumber.csv | 2 ++ TestData/invalid_header.csv | 1 + TestData/invalid_recordbooknumber.csv | 2 ++ TestData/sample.csv | 3 +++ 6 files changed, 12 insertions(+) create mode 100644 TestData/fewparameters.csv create mode 100644 TestData/invalid_averagegrade.csv create mode 100644 TestData/invalid_groupnumber.csv create mode 100644 TestData/invalid_header.csv create mode 100644 TestData/invalid_recordbooknumber.csv create mode 100644 TestData/sample.csv diff --git a/TestData/fewparameters.csv b/TestData/fewparameters.csv new file mode 100644 index 0000000..56f72ca --- /dev/null +++ b/TestData/fewparameters.csv @@ -0,0 +1,2 @@ +groupNumber,averageGrade,recordBookNumber +A21,5.0 \ No newline at end of file diff --git a/TestData/invalid_averagegrade.csv b/TestData/invalid_averagegrade.csv new file mode 100644 index 0000000..860b9e8 --- /dev/null +++ b/TestData/invalid_averagegrade.csv @@ -0,0 +1,2 @@ +groupNumber,averageGrade,recordBookNumber +A21,-1.0,111 \ No newline at end of file diff --git a/TestData/invalid_groupnumber.csv b/TestData/invalid_groupnumber.csv new file mode 100644 index 0000000..12ac7c4 --- /dev/null +++ b/TestData/invalid_groupnumber.csv @@ -0,0 +1,2 @@ +groupNumber,averageGrade,recordBookNumber +A123,4.0,111 \ No newline at end of file diff --git a/TestData/invalid_header.csv b/TestData/invalid_header.csv new file mode 100644 index 0000000..a08b1a1 --- /dev/null +++ b/TestData/invalid_header.csv @@ -0,0 +1 @@ +groupNumber;averageGrade;recordBookNomber diff --git a/TestData/invalid_recordbooknumber.csv b/TestData/invalid_recordbooknumber.csv new file mode 100644 index 0000000..5020f73 --- /dev/null +++ b/TestData/invalid_recordbooknumber.csv @@ -0,0 +1,2 @@ +groupNumber,averageGrade,recordBookNumber +A21,5.0,-1 \ No newline at end of file diff --git a/TestData/sample.csv b/TestData/sample.csv new file mode 100644 index 0000000..dc39a74 --- /dev/null +++ b/TestData/sample.csv @@ -0,0 +1,3 @@ +groupNumber,averageGrade,recordBookNumber +A21,5.0,331 +A21,4.0,332 \ No newline at end of file From 8c4f2e22c45f7433da733e7cbdc2e96aa93c24eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Tue, 1 Sep 2026 16:23:31 +0700 Subject: [PATCH 134/156] refactor: remove test csv-files --- TestData/fewparameters.csv | 2 -- TestData/invalid_averagegrade.csv | 2 -- TestData/invalid_groupnumber.csv | 2 -- TestData/invalid_header.csv | 1 - TestData/invalid_recordbooknumber.csv | 2 -- TestData/sample.csv | 3 --- 6 files changed, 12 deletions(-) delete mode 100644 TestData/fewparameters.csv delete mode 100644 TestData/invalid_averagegrade.csv delete mode 100644 TestData/invalid_groupnumber.csv delete mode 100644 TestData/invalid_header.csv delete mode 100644 TestData/invalid_recordbooknumber.csv delete mode 100644 TestData/sample.csv diff --git a/TestData/fewparameters.csv b/TestData/fewparameters.csv deleted file mode 100644 index 56f72ca..0000000 --- a/TestData/fewparameters.csv +++ /dev/null @@ -1,2 +0,0 @@ -groupNumber,averageGrade,recordBookNumber -A21,5.0 \ No newline at end of file diff --git a/TestData/invalid_averagegrade.csv b/TestData/invalid_averagegrade.csv deleted file mode 100644 index 860b9e8..0000000 --- a/TestData/invalid_averagegrade.csv +++ /dev/null @@ -1,2 +0,0 @@ -groupNumber,averageGrade,recordBookNumber -A21,-1.0,111 \ No newline at end of file diff --git a/TestData/invalid_groupnumber.csv b/TestData/invalid_groupnumber.csv deleted file mode 100644 index 12ac7c4..0000000 --- a/TestData/invalid_groupnumber.csv +++ /dev/null @@ -1,2 +0,0 @@ -groupNumber,averageGrade,recordBookNumber -A123,4.0,111 \ No newline at end of file diff --git a/TestData/invalid_header.csv b/TestData/invalid_header.csv deleted file mode 100644 index a08b1a1..0000000 --- a/TestData/invalid_header.csv +++ /dev/null @@ -1 +0,0 @@ -groupNumber;averageGrade;recordBookNomber diff --git a/TestData/invalid_recordbooknumber.csv b/TestData/invalid_recordbooknumber.csv deleted file mode 100644 index 5020f73..0000000 --- a/TestData/invalid_recordbooknumber.csv +++ /dev/null @@ -1,2 +0,0 @@ -groupNumber,averageGrade,recordBookNumber -A21,5.0,-1 \ No newline at end of file diff --git a/TestData/sample.csv b/TestData/sample.csv deleted file mode 100644 index dc39a74..0000000 --- a/TestData/sample.csv +++ /dev/null @@ -1,3 +0,0 @@ -groupNumber,averageGrade,recordBookNumber -A21,5.0,331 -A21,4.0,332 \ No newline at end of file From 2f50097de00fcf395e35135beb8d59eb66643487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D1=80=D1=88=D0=BE=D0=B2=20=D0=92=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B0=D0=B2?= Date: Tue, 1 Sep 2026 17:46:02 +0700 Subject: [PATCH 135/156] test: add temp directory for test csv's, add test for invalid header --- .../ru/project/io/StudentCsvReaderTest.java | 100 ++++++++++++++++-- 1 file changed, 90 insertions(+), 10 deletions(-) diff --git a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java index 2ba9ed1..2be00de 100644 --- a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java +++ b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java @@ -6,39 +6,59 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.charset.StandardCharsets; import java.util.NoSuchElementException; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.Test; import ru.project.student.Student; class StudentCsvReaderTest { - private final String ABSOLUTE_DIR = "C:\\Users\\Dexp\\TestData\\" - private StudentCsvReader valideReader; + @TempDir Path tempDir; + private Path studentPath; // Testing StudentCsvReader + + @BeforeAll + void generateStudentTestFile(){ + studentPath = tempDir.resolve("students.csv"); + Files.write( + studentPath, + new String[]{ + "groupNumber,averageGrade,recordBookNumber", + "A21,5.0,331", + "A21,4.0,332" + }, + StandardCharsets.UTF_8 + ); + } + @BeforeEach void setUp() { - valideReader = new StudentCsvReader(ABSOLUTE_DIR + "sample.csv"); + valideReader = new StudentCsvReader(studentPath.toAbsolutePath().toString()); } @Test void readerTestRejectRelativePath() { assertThrows( IllegalArgumentException.class, - () -> new StudentCsvReader("/Users/Dexp/TestData/sample.csv")); + () -> new StudentCsvReader(studentPath.toString())); } @Test void readerRejectPathIsDir() { assertThrows( - UncheckedIOException.class, () -> new StudentCsvReader(ABSOLUTE_DIR)); + UncheckedIOException.class, () -> new StudentCsvReader(tempDir.toAbsolutePath())); } @Test void readerTestFileNotExist() { assertThrows( UncheckedIOException.class, - () -> new StudentCsvReader(ABSOLUTE_DIR + "sss.csv")); + () -> new StudentCsvReader(tempDir.resolve("sss.csv").toAbsolutePath().toString())); } @Test @@ -88,8 +108,17 @@ class StudentCsvReaderTest { assertThrows( IllegalArgumentException.class, () -> { + Path path = tempDir.resolve("fewparameters.csv"); + Files.write( + path, + new String[]{ + "groupNumber,averageGrade,recordBookNumber", + "A21,5.0" + }, + StandardCharsets.UTF_8 + ); StudentCsvReader reader = - new StudentCsvReader(ABSOLUTE_DIR + "fewparameters.csv"); + new StudentCsvReader(path.toAbsolutePath().toString()); Student student = reader.next(); }); } @@ -99,8 +128,18 @@ class StudentCsvReaderTest { assertThrows( IllegalArgumentException.class, () -> { + Path path = tempDir.resolve("invalid_groupnumber.csv"); + Files.write( + path, + new String[]{ + "groupNumber,averageGrade,recordBookNumber", + "AAA,5.0,331", + "A21,4.0,332" + }, + StandardCharsets.UTF_8 + ); StudentCsvReader reader = - new StudentCsvReader(ABSOLUTE_DIR + "invalid_groupnumber.csv"); + new StudentCsvReader(path.toAbsolutePath().toString()); Student student = reader.next(); }); } @@ -110,8 +149,18 @@ class StudentCsvReaderTest { assertThrows( IllegalArgumentException.class, () -> { + Path path = tempDir.resolve("invalid_averagegrade.csv"); + Files.write( + path, + new String[]{ + "groupNumber,averageGrade,recordBookNumber", + "A21,-5.0,331", + "A21,4.0,332" + }, + StandardCharsets.UTF_8 + ); StudentCsvReader reader = - new StudentCsvReader(ABSOLUTE_DIR + "invalid_averagegrade.csv"); + new StudentCsvReader(path.toAbsolutePath().toString()); Student student = reader.next(); }); } @@ -121,9 +170,40 @@ class StudentCsvReaderTest { assertThrows( IllegalArgumentException.class, () -> { + Path path = tempDir.resolve("invalid_recordbooknumber.csv"); + Files.write( + path, + new String[]{ + "groupNumber,averageGrade,recordBookNumber", + "A21,5.0,-331", + "A21,4.0,332" + }, + StandardCharsets.UTF_8 + ); StudentCsvReader reader = - new StudentCsvReader(ABSOLUTE_DIR + "invalid_recordbooknumber.csv"); + new StudentCsvReader(path.toAbsolutePath().toString()); Student student = reader.next(); }); } + + @Test + void testInvalidHeader(){ + assertThrows( + IllegalArgumentException.class, + () -> { + Path path = tempDir.resolve("invalid_header.csv"); + Files.write( + path, + new String[]{ + "groupNumber,averageGrade,recordBookNomber", + "A21,5.0,-331", + "A21,4.0,332" + }, + StandardCharsets.UTF_8 + ); + StudentCsvReader reader = + new StudentCsvReader(path.toAbsolutePath().toString()); + Student student = reader.next(); + }); + } } From af233e92624f83bfb26cd031996ad27e0b4a9923 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, 1 Sep 2026 15:47:45 +0400 Subject: [PATCH 136/156] chore: remove obsolete application and collector classes --- app/src/main/java/ru/project/App.java | 45 ------------- .../main/java/ru/project/MyCollectors.java | 10 --- .../main/java/ru/project/MyListCollector.java | 41 ------------ app/src/test/java/ru/project/AppTest.java | 15 ----- .../java/ru/project/MyListCollectorTest.java | 63 ------------------- 5 files changed, 174 deletions(-) delete mode 100644 app/src/main/java/ru/project/App.java delete mode 100644 app/src/main/java/ru/project/MyCollectors.java delete mode 100644 app/src/main/java/ru/project/MyListCollector.java delete mode 100644 app/src/test/java/ru/project/AppTest.java delete mode 100644 app/src/test/java/ru/project/MyListCollectorTest.java diff --git a/app/src/main/java/ru/project/App.java b/app/src/main/java/ru/project/App.java deleted file mode 100644 index 73f5efa..0000000 --- a/app/src/main/java/ru/project/App.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.project; - -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.main.MainMenu; - -public final class App { - - private App() {} - - public static void main(String[] args) { - Screen screen = null; - - try { - DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory(); - - screen = terminalFactory.createScreen(); - screen.startScreen(); - - MultiWindowTextGUI gui = new MultiWindowTextGUI(screen); - - AppState state = new AppState(); - - MainMenu mainMenu = new MainMenu(gui, state); - - mainMenu.showModal(); - - } catch (Exception e) { - System.err.println("Произошла ошибка при выполнении программы:"); - e.printStackTrace(); - - } finally { - if (screen != null) { - try { - screen.stopScreen(); - } catch (Exception e) { - System.err.println("Не удалось корректно завершить работу терминала:"); - e.printStackTrace(); - } - } - } - } -} diff --git a/app/src/main/java/ru/project/MyCollectors.java b/app/src/main/java/ru/project/MyCollectors.java deleted file mode 100644 index 20840c3..0000000 --- a/app/src/main/java/ru/project/MyCollectors.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.test.maven.spring; -import java.util.stream.Collector; -public class MyCollectors -{ - private MyCollectors() { - } - public static Collector, MyList> toMyList() { - return new MyListCollector<>(); - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/project/MyListCollector.java b/app/src/main/java/ru/project/MyListCollector.java deleted file mode 100644 index f731379..0000000 --- a/app/src/main/java/ru/project/MyListCollector.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.test.maven.spring; -import java.util.Collections; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.BinaryOperator; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collector; -public class MyListCollector implements Collector, MyList> -{ - @Override - public Supplier> supplier() - { - return () -> new MyLinkedList<>(); - } - @Override - public BiConsumer, T> accumulator() { - - return (list, element) -> list.add(element); - } - @Override - public BinaryOperator> combiner() { - return (list1, list2) -> { - for (int i = 0; i < list2.size(); i++) { - list1.add(list2.get(i)); - } - return list1; - }; - } - @Override - public Function, MyList> finisher() - { - return list -> list; - } - @Override - public Set characteristics() { - return java.util.Set.of( - Collector.Characteristics.IDENTITY_FINISH - ); - } -} \ No newline at end of file diff --git a/app/src/test/java/ru/project/AppTest.java b/app/src/test/java/ru/project/AppTest.java deleted file mode 100644 index 44a05ef..0000000 --- a/app/src/test/java/ru/project/AppTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package ru.project; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -/** Unit test for simple App. */ -public class AppTest { - - /** Rigorous Test :-) */ - @Test - public void shouldAnswerWithTrue() { - assertTrue(true); - } -} diff --git a/app/src/test/java/ru/project/MyListCollectorTest.java b/app/src/test/java/ru/project/MyListCollectorTest.java deleted file mode 100644 index 6155d57..0000000 --- a/app/src/test/java/ru/project/MyListCollectorTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package ru.test.maven.spring; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; -import java.util.Set; -import java.util.stream.Collector; -import java.util.stream.IntStream; -import java.util.Collections; -public class MyListCollectorTest -{ - private final MyListCollector collector = new MyListCollector<>(); - - @Test - void shouldTestCollectInOrderSequential() { - MyList result = IntStream.range(0, 10) - .boxed() - .collect(MyCollectors.toMyList()); - - assertEquals(10, result.size()); - for (int i = 0; i < result.size(); i++) { - assertEquals(i, (int) result.get(i)); - } - - } - - @Test - void shouldTestCollectInOrderParallel() { - MyList result = IntStream.range(0, 100) - .parallel() - .boxed() - .collect(MyCollectors.toMyList()); - - assertEquals(100, result.size()); - for (int i = 0; i < result.size(); i++) { - assertEquals(i, (int) result.get(i)); - } - } - - @Test - void shouldTestHandleEmptyStream() { - MyList result = IntStream.empty() - .boxed() - .collect(MyCollectors.toMyList()); - - assertTrue(result.isEmpty()); - - } - @Test - void shouldTestNotUseStandardList() { - MyList result = IntStream.range(0, 5) - .mapToObj(i -> "item" + i) - .collect(MyCollectors.toMyList()); - - assertInstanceOf(MyList.class, result); - assertFalse(result instanceof java.util.List); - } - @Test - void shouldTestHaveCorrectCharacteristics() { - Set characteristics = collector.characteristics(); - assertTrue(characteristics.contains(java.util.stream.Collector.Characteristics.IDENTITY_FINISH)); - assertFalse(characteristics.contains(java.util.stream.Collector.Characteristics.UNORDERED)); - } - -} From cf6a318086deb7800e51903ddb68b18c869c0f15 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, 1 Sep 2026 14:50:16 +0300 Subject: [PATCH 137/156] refactor: fixed dependencies --- app/src/main/java/ru/project/random/RandomStudentGenerator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/ru/project/random/RandomStudentGenerator.java b/app/src/main/java/ru/project/random/RandomStudentGenerator.java index c7b1812..25e6032 100644 --- a/app/src/main/java/ru/project/random/RandomStudentGenerator.java +++ b/app/src/main/java/ru/project/random/RandomStudentGenerator.java @@ -1,3 +1,4 @@ +package ru.project.random; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import ru.project.collection.MyLinkedList; From ff83c48650f3b2feffc5deb3f874b3676b769cd3 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, 1 Sep 2026 15:55:41 +0400 Subject: [PATCH 138/156] style: reformat source and tests, fix imports and packages --- .../ru/project/collectors/MyCollectors.java | 17 +- .../project/collectors/MyListCollector.java | 58 +- .../finder/StudentOccurrenceIndexFinder.java | 211 +++--- .../java/ru/project/io/StudentCsvReader.java | 4 +- .../project/search/StudentSearchService.java | 42 +- .../project/collection/MyLinkedListTest.java | 236 +++---- .../collectors/MyListCollectorTest.java | 88 ++- .../StudentOccurrenceIndexFinderTest.java | 229 +++---- .../ru/project/io/StudentCsvReaderTest.java | 116 ++-- .../ru/project/search/StudentSearchTest.java | 601 +++++++++--------- 10 files changed, 792 insertions(+), 810 deletions(-) diff --git a/app/src/main/java/ru/project/collectors/MyCollectors.java b/app/src/main/java/ru/project/collectors/MyCollectors.java index d0d7cf5..83b7ae8 100644 --- a/app/src/main/java/ru/project/collectors/MyCollectors.java +++ b/app/src/main/java/ru/project/collectors/MyCollectors.java @@ -1,15 +1,12 @@ package ru.project.collectors; + import java.util.stream.Collector; +import ru.project.collection.MyList; -public class MyCollectors -{ - private MyCollectors() { +public class MyCollectors { + private MyCollectors() {} - } - - public static Collector, MyList> toMyList() - { - return new MyListCollector<>(); - - } + public static Collector, MyList> toMyList() { + return new MyListCollector<>(); + } } diff --git a/app/src/main/java/ru/project/collectors/MyListCollector.java b/app/src/main/java/ru/project/collectors/MyListCollector.java index bb168e4..c5958b0 100644 --- a/app/src/main/java/ru/project/collectors/MyListCollector.java +++ b/app/src/main/java/ru/project/collectors/MyListCollector.java @@ -1,43 +1,41 @@ package ru.project.collectors; -import java.util.Collections; + import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; -public class MyListCollector implements Collector, MyList> -{ - @Override - public Supplier> supplier() - { - return () -> new MyLinkedList<>(); - } +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; - @Override - public BiConsumer, T> accumulator() { +public class MyListCollector implements Collector, MyList> { + @Override + public Supplier> supplier() { + return () -> new MyLinkedList<>(); + } - return (list, element) -> list.add(element); - } + @Override + public BiConsumer, T> accumulator() { - @Override - public BinaryOperator> combiner() { - return (list1, list2) -> { - list1.addAll(list2); - return list1; - }; - } + return (list, element) -> list.add(element); + } - @Override - public Function, MyList> finisher() - { - return list -> list; - } + @Override + public BinaryOperator> combiner() { + return (list1, list2) -> { + list1.addAll(list2); + return list1; + }; + } - @Override - public Set characteristics() { - return java.util.Set.of( - Collector.Characteristics.IDENTITY_FINISH - ); - } + @Override + public Function, MyList> finisher() { + return list -> list; + } + + @Override + public Set characteristics() { + return java.util.Set.of(Collector.Characteristics.IDENTITY_FINISH); + } } diff --git a/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java b/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java index 7e7c3d2..45ac263 100644 --- a/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java +++ b/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java @@ -1,125 +1,132 @@ package ru.project.finder; + import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -public class StudentOccurrenceIndexFinder -{ - public int[] findOccurrences(MyList students, Student target) { - if (students == null) { - throw new IllegalArgumentException(); - } - if (target == null) { - throw new IllegalArgumentException(); - } - if (students.isEmpty()) { - return new int[0]; - } - int size = students.size(); - int threadCount = calculateThreadCount(size); - int chunkSize = countChunkSize(size, threadCount); +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.student.Student; - ExecutorService executor = Executors.newFixedThreadPool(threadCount); - MyList>> futures = new MyLinkedList<>(); +public class StudentOccurrenceIndexFinder { + public int[] findOccurrences(MyList students, Student target) { + if (students == null) { + throw new IllegalArgumentException(); + } + if (target == null) { + throw new IllegalArgumentException(); + } + if (students.isEmpty()) { + return new int[0]; + } + int size = students.size(); + int threadCount = calculateThreadCount(size); + int chunkSize = countChunkSize(size, threadCount); - for (int i = 0; i < threadCount; i++) { - int fromIndex = i * chunkSize; - int toIndex = Math.min(fromIndex + chunkSize, size); + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + MyList>> futures = new MyLinkedList<>(); - if (fromIndex >= toIndex) { - break; - } + for (int i = 0; i < threadCount; i++) { + int fromIndex = i * chunkSize; + int toIndex = Math.min(fromIndex + chunkSize, size); - final int start = fromIndex; - final int end = toIndex; + if (fromIndex >= toIndex) { + break; + } - Future> future = executor.submit(() -> { + final int start = fromIndex; + final int end = toIndex; + + Future> future = + executor.submit( + () -> { return findInRange(students, target, start, end); - }); - futures.add(future); + }); + futures.add(future); + } + MyList allIndices = new MyLinkedList<>(); + try { + for (int i = 0; i < futures.size(); i++) { + Future> future = futures.get(i); + MyList partialResult = future.get(); + + if (partialResult != null) { + for (int j = 0; j < partialResult.size(); j++) { + allIndices.add(partialResult.get(j)); + } } - MyList allIndices = new MyLinkedList<>(); - try { - for (int i = 0; i < futures.size(); i++) { - Future> future = futures.get(i); - MyList partialResult = future.get(); - - if (partialResult != null) { - for (int j = 0; j < partialResult.size(); j++) { - allIndices.add(partialResult.get(j)); - } - } - } - } catch (Exception e) { - throw new RuntimeException("Ошибка при поиске", e); - } finally { - executor.shutdown(); - } - - sortList(allIndices); - - int[] resultArray = new int[allIndices.size()]; - for (int i = 0; i < allIndices.size(); i++) { - resultArray[i] = allIndices.get(i); - } - - return resultArray; + } + } catch (Exception e) { + throw new RuntimeException("Ошибка при поиске", e); + } finally { + executor.shutdown(); } - private void sortList(MyList list) { - if (list.size() <= 1) return; + sortList(allIndices); - MyList sorted = new MyLinkedList<>(); - - for (int i = 0; i < list.size(); i++) { - int value = list.get(i); - int insertIndex = 0; - while (insertIndex < sorted.size()) { - if (sorted.get(insertIndex) > value) { - break; - } - insertIndex++; - } - - MyList temp = new MyLinkedList<>(); - for (int k = 0; k < insertIndex; k++) { - temp.add(sorted.get(k)); - } - temp.add(value); - for (int k = insertIndex; k < sorted.size(); k++) { - temp.add(sorted.get(k)); - } - sorted = temp; - } - - while (!list.isEmpty()) { - list.remove(0); - } - - for (int i = 0; i < sorted.size(); i++) { - list.add(sorted.get(i)); - } + int[] resultArray = new int[allIndices.size()]; + for (int i = 0; i < allIndices.size(); i++) { + resultArray[i] = allIndices.get(i); } - private int calculateThreadCount(int size) { - int processors = Runtime.getRuntime().availableProcessors(); - return Math.min(processors, size); - } + return resultArray; + } - private int countChunkSize(int size, int threadCount) { - if (threadCount == 0) return 0; - return (size + threadCount - 1) / threadCount; - } + private void sortList(MyList list) { + if (list.size() <= 1) return; - private MyList findInRange(MyList students, Student target, int fromIndex, int toIndex) { - MyList foundIndices = new MyLinkedList<>(); + MyList sorted = new MyLinkedList<>(); - for (int i = fromIndex; i < toIndex; i++) { - Student current = students.get(i); - if (current != null && current.equals(target)) { - foundIndices.add(i); - } + for (int i = 0; i < list.size(); i++) { + int value = list.get(i); + int insertIndex = 0; + while (insertIndex < sorted.size()) { + if (sorted.get(insertIndex) > value) { + break; } + insertIndex++; + } - return foundIndices; + MyList temp = new MyLinkedList<>(); + for (int k = 0; k < insertIndex; k++) { + temp.add(sorted.get(k)); + } + temp.add(value); + for (int k = insertIndex; k < sorted.size(); k++) { + temp.add(sorted.get(k)); + } + sorted = temp; } + + while (!list.isEmpty()) { + list.remove(0); + } + + for (int i = 0; i < sorted.size(); i++) { + list.add(sorted.get(i)); + } + } + + private int calculateThreadCount(int size) { + int processors = Runtime.getRuntime().availableProcessors(); + return Math.min(processors, size); + } + + private int countChunkSize(int size, int threadCount) { + if (threadCount == 0) return 0; + return (size + threadCount - 1) / threadCount; + } + + private MyList findInRange( + MyList students, Student target, int fromIndex, int toIndex) { + MyList foundIndices = new MyLinkedList<>(); + + for (int i = fromIndex; i < toIndex; i++) { + Student current = students.get(i); + if (current != null && current.equals(target)) { + foundIndices.add(i); + } + } + + return foundIndices; + } } diff --git a/app/src/main/java/ru/project/io/StudentCsvReader.java b/app/src/main/java/ru/project/io/StudentCsvReader.java index b44a738..a6b93c4 100644 --- a/app/src/main/java/ru/project/io/StudentCsvReader.java +++ b/app/src/main/java/ru/project/io/StudentCsvReader.java @@ -25,9 +25,9 @@ public class StudentCsvReader implements Iterator { private int lineOfFile; - public StudentCsvReader(String absoluteFilePath) { + public StudentCsvReader(Path path2) { lineOfFile = 0; - Path path = Paths.get(absoluteFilePath); + Path path = Paths.get(path2); if (!path.isAbsolute()) { throw new IllegalArgumentException("File path must be absolute"); } diff --git a/app/src/main/java/ru/project/search/StudentSearchService.java b/app/src/main/java/ru/project/search/StudentSearchService.java index 4ced3c9..dc3a366 100644 --- a/app/src/main/java/ru/project/search/StudentSearchService.java +++ b/app/src/main/java/ru/project/search/StudentSearchService.java @@ -1,30 +1,30 @@ package ru.project.search; -import ru.project.list.MyList; -import ru.project.list.MyLinkedList; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; import ru.project.student.Student; public class StudentSearchService { - private final StudentSearchCriteria criteria; + private final StudentSearchCriteria criteria; - public StudentSearchService(StudentSearchCriteria criteria) { - if (criteria == null) { - throw new IllegalArgumentException("Criteria must not be null"); - } - this.criteria = criteria; + public StudentSearchService(StudentSearchCriteria criteria) { + if (criteria == null) { + throw new IllegalArgumentException("Criteria must not be null"); } + this.criteria = criteria; + } - public MyList find(MyList students) { - if (students == null) { - return new MyLinkedList<>(); - } - MyList result = new MyLinkedList<>(); - for (int i = 0; i < students.size(); i++) { - Student s = students.get(i); - if (criteria.matches(s)) { - result.add(s); - } - } - return result; + public MyList find(MyList students) { + if (students == null) { + return new MyLinkedList<>(); } -} \ No newline at end of file + MyList result = new MyLinkedList<>(); + for (int i = 0; i < students.size(); i++) { + Student s = students.get(i); + if (criteria.matches(s)) { + result.add(s); + } + } + return result; + } +} diff --git a/app/src/test/java/ru/project/collection/MyLinkedListTest.java b/app/src/test/java/ru/project/collection/MyLinkedListTest.java index 6c43673..8137964 100644 --- a/app/src/test/java/ru/project/collection/MyLinkedListTest.java +++ b/app/src/test/java/ru/project/collection/MyLinkedListTest.java @@ -1,130 +1,138 @@ -package ru.test.maven.spring; -import org.junit.jupiter.api.Test; +package ru.project.collection; + import static org.junit.jupiter.api.Assertions.*; -public class MyLinkedListTest -{ - @Test - public void shouldTestAdd() { - MyLinkedList list = new MyLinkedList<>(); - list.add(10); - list.add(20); - list.add(30); - assertEquals(10, list.get(0)); - assertEquals(20, list.get(1)); - assertEquals(30, list.get(2)); - } - @Test - public void shouldTestGet() - { - MyLinkedList list = new MyLinkedList<>(); - list.add(10); - assertEquals(10, list.get(0)); - } - @Test - public void shouldTestRemove() { - MyLinkedList list = new MyLinkedList<>(); - list.add(10); - list.add(20); - list.add(30); - list.remove(0); +import org.junit.jupiter.api.Test; - assertEquals(2, list.size()); - assertEquals(20, list.get(0)); - assertEquals(30, list.get(1)); - } - @Test - void shouldTestSet() { - MyLinkedList list = new MyLinkedList<>(); - list.add("A"); - list.add("B"); - list.add("C"); +public class MyLinkedListTest { + @Test + public void shouldTestAdd() { + MyLinkedList list = new MyLinkedList<>(); + list.add(10); + list.add(20); + list.add(30); - String oldValue = list.set(1, "Z"); + assertEquals(10, list.get(0)); + assertEquals(20, list.get(1)); + assertEquals(30, list.get(2)); + } - assertEquals("B", oldValue); - assertEquals("Z", list.get(1)); - assertEquals(3, list.size()); + @Test + public void shouldTestGet() { + MyLinkedList list = new MyLinkedList<>(); + list.add(10); + assertEquals(10, list.get(0)); + } - } - @Test - void shouldTestSize() - { - MyLinkedList list = new MyLinkedList<>(); - list.add(1); - list.add(2); - list.add(3); - assertEquals(3, list.size()); - } - @Test - void shouldTestisEmpty() - { - MyLinkedList list = new MyLinkedList<>(); - assertTrue(list.isEmpty()); - } - @Test - void shouldTestGet_OutOfBounds_ThrowsException() { - MyLinkedList list = new MyLinkedList<>(); - list.add(1); + @Test + public void shouldTestRemove() { + MyLinkedList list = new MyLinkedList<>(); + list.add(10); + list.add(20); + list.add(30); + list.remove(0); - assertThrows(IllegalArgumentException.class, () -> list.get(-1)); - assertThrows(IllegalArgumentException.class, () -> list.get(1)); // index == size - assertThrows(IllegalArgumentException.class, () -> list.get(5)); // index > size - } - @Test - void shouldTestSet_OutOfBounds_ThrowsException() { - MyLinkedList list = new MyLinkedList<>(); - list.add(1); + assertEquals(2, list.size()); + assertEquals(20, list.get(0)); + assertEquals(30, list.get(1)); + } - assertThrows(IllegalArgumentException.class, () -> list.set(-1, 99)); - assertThrows(IllegalArgumentException.class, () -> list.set(1, 99)); - } - @Test - void shouldTestRemove_IndexGreaterThanSize_ThrowsException() { - MyLinkedList list = new MyLinkedList<>(); - list.add("man"); - assertThrows(IndexOutOfBoundsException.class, () -> list.remove(5)); - } - @Test - void shouldTestRemove_NegativeIndex_ThrowsException() { - MyLinkedList list = new MyLinkedList<>(); - list.add("1"); - list.add("2"); + @Test + void shouldTestSet() { + MyLinkedList list = new MyLinkedList<>(); + list.add("A"); + list.add("B"); + list.add("C"); - assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1)); - } - @Test - void shouldTestaddAll_nonEmptySource_toEmptyList_addsAllElementsInOrder() { - MyList list = new MyLinkedList<>(); - MyList source = new MyLinkedList<>(); - source.add("A"); - source.add("B"); - source.add("C"); + String oldValue = list.set(1, "Z"); - list.addAll(source); + assertEquals("B", oldValue); + assertEquals("Z", list.get(1)); + assertEquals(3, list.size()); + } - assertEquals(3, list.size()); - assertEquals("A", list.get(0)); - assertEquals("B", list.get(1)); - assertEquals("C", list.get(2)); - } + @Test + void shouldTestSize() { + MyLinkedList list = new MyLinkedList<>(); + list.add(1); + list.add(2); + list.add(3); + assertEquals(3, list.size()); + } - @Test - void shouldTestaddAll_toNonEmptyList_appendsAtEnd() { - MyList list = new MyLinkedList<>(); - list.add("X"); - list.add("Y"); + @Test + void shouldTestisEmpty() { + MyLinkedList list = new MyLinkedList<>(); + assertTrue(list.isEmpty()); + } - MyList source = new MyLinkedList<>(); - source.add("A"); - source.add("B"); + @Test + void shouldTestGet_OutOfBounds_ThrowsException() { + MyLinkedList list = new MyLinkedList<>(); + list.add(1); - list.addAll(source); + assertThrows(IllegalArgumentException.class, () -> list.get(-1)); + assertThrows(IllegalArgumentException.class, () -> list.get(1)); // index == size + assertThrows(IllegalArgumentException.class, () -> list.get(5)); // index > size + } - assertEquals(4, list.size()); - assertEquals("X", list.get(0)); - assertEquals("Y", list.get(1)); - assertEquals("A", list.get(2)); - assertEquals("B", list.get(3)); - } + @Test + void shouldTestSet_OutOfBounds_ThrowsException() { + MyLinkedList list = new MyLinkedList<>(); + list.add(1); + + assertThrows(IllegalArgumentException.class, () -> list.set(-1, 99)); + assertThrows(IllegalArgumentException.class, () -> list.set(1, 99)); + } + + @Test + void shouldTestRemove_IndexGreaterThanSize_ThrowsException() { + MyLinkedList list = new MyLinkedList<>(); + list.add("man"); + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(5)); + } + + @Test + void shouldTestRemove_NegativeIndex_ThrowsException() { + MyLinkedList list = new MyLinkedList<>(); + list.add("1"); + list.add("2"); + + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1)); + } + + @Test + void shouldTestaddAll_nonEmptySource_toEmptyList_addsAllElementsInOrder() { + MyList list = new MyLinkedList<>(); + MyList source = new MyLinkedList<>(); + source.add("A"); + source.add("B"); + source.add("C"); + + list.addAll(source); + + assertEquals(3, list.size()); + assertEquals("A", list.get(0)); + assertEquals("B", list.get(1)); + assertEquals("C", list.get(2)); + } + + @Test + void shouldTestaddAll_toNonEmptyList_appendsAtEnd() { + MyList list = new MyLinkedList<>(); + list.add("X"); + list.add("Y"); + + MyList source = new MyLinkedList<>(); + source.add("A"); + source.add("B"); + + list.addAll(source); + + assertEquals(4, list.size()); + assertEquals("X", list.get(0)); + assertEquals("Y", list.get(1)); + assertEquals("A", list.get(2)); + assertEquals("B", list.get(3)); + } } diff --git a/app/src/test/java/ru/project/collectors/MyListCollectorTest.java b/app/src/test/java/ru/project/collectors/MyListCollectorTest.java index 3ca8d21..35abcbc 100644 --- a/app/src/test/java/ru/project/collectors/MyListCollectorTest.java +++ b/app/src/test/java/ru/project/collectors/MyListCollectorTest.java @@ -1,64 +1,58 @@ -package ru.test.maven.spring; -import org.junit.jupiter.api.Test; +package ru.project.collectors; + import static org.junit.jupiter.api.Assertions.*; + import java.util.Set; import java.util.stream.Collector; import java.util.stream.IntStream; -import java.util.Collections; +import org.junit.jupiter.api.Test; +import ru.project.collection.MyList; -public class MyListCollectorTest -{ - private final MyListCollector collector = new MyListCollector<>(); +public class MyListCollectorTest { + private final MyListCollector collector = new MyListCollector<>(); - @Test - void shouldTestCollectInOrderSequential() { - MyList result = IntStream.range(0, 10) - .boxed() - .collect(MyCollectors.toMyList()); - - assertEquals(10, result.size()); - for (int i = 0; i < result.size(); i++) { - assertEquals(i, (int) result.get(i)); - } + @Test + void shouldTestCollectInOrderSequential() { + MyList result = IntStream.range(0, 10).boxed().collect(MyCollectors.toMyList()); + assertEquals(10, result.size()); + for (int i = 0; i < result.size(); i++) { + assertEquals(i, (int) result.get(i)); } + } - @Test - void shouldTestCollectInOrderParallel() { - MyList result = IntStream.range(0, 100) - .parallel() - .boxed() - .collect(MyCollectors.toMyList()); + @Test + void shouldTestCollectInOrderParallel() { + MyList result = + IntStream.range(0, 100).parallel().boxed().collect(MyCollectors.toMyList()); - assertEquals(100, result.size()); - for (int i = 0; i < result.size(); i++) { - assertEquals(i, (int) result.get(i)); - } + assertEquals(100, result.size()); + for (int i = 0; i < result.size(); i++) { + assertEquals(i, (int) result.get(i)); } + } - @Test - void shouldTestHandleEmptyStream() { - MyList result = IntStream.empty() - .boxed() - .collect(MyCollectors.toMyList()); + @Test + void shouldTestHandleEmptyStream() { + MyList result = IntStream.empty().boxed().collect(MyCollectors.toMyList()); - assertTrue(result.isEmpty()); + assertTrue(result.isEmpty()); + } - } - @Test - void shouldTestNotUseStandardList() { - MyList result = IntStream.range(0, 5) - .mapToObj(i -> "item" + i) - .collect(MyCollectors.toMyList()); + @Test + void shouldTestNotUseStandardList() { + MyList result = + IntStream.range(0, 5).mapToObj(i -> "item" + i).collect(MyCollectors.toMyList()); - assertInstanceOf(MyList.class, result); - assertFalse(result instanceof java.util.List); - } - @Test - void shouldTestHaveCorrectCharacteristics() { - Set characteristics = collector.characteristics(); - assertTrue(characteristics.contains(java.util.stream.Collector.Characteristics.IDENTITY_FINISH)); - assertFalse(characteristics.contains(java.util.stream.Collector.Characteristics.UNORDERED)); - } + assertInstanceOf(MyList.class, result); + assertFalse(result instanceof java.util.List); + } + @Test + void shouldTestHaveCorrectCharacteristics() { + Set characteristics = collector.characteristics(); + assertTrue( + characteristics.contains(java.util.stream.Collector.Characteristics.IDENTITY_FINISH)); + assertFalse(characteristics.contains(java.util.stream.Collector.Characteristics.UNORDERED)); + } } diff --git a/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java b/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java index b61ed58..3e5c710 100644 --- a/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java +++ b/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java @@ -1,134 +1,137 @@ package ru.project.finder; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.*; -public class StudentOccurrenceIndexFinderTest -{ - private StudentOccurrenceIndexFinder finder; - private MyList students; +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; - @BeforeEach - void setUp() { - finder = new StudentOccurrenceIndexFinder(); - students = new MyLinkedList<>(); - } +public class StudentOccurrenceIndexFinderTest { + private StudentOccurrenceIndexFinder finder; + private MyList students; - @Test - void shouldTesfindOccurrences_emptyList_returnsEmptyArray() { - int[] result = finder.findOccurrences(students, new Student("A12", 4.5, 1)); - assertEquals(0, result.length); - } - @Test - void shouldTesfindOccurrences_noMatches_returnsEmptyArray() { - students.add(new Student("A12", 4.0, 101)); - students.add(new Student("B23", 3.5, 102)); - Student target = new Student("C34", 4.8, 103); // такого нет + @BeforeEach + void setUp() { + finder = new StudentOccurrenceIndexFinder(); + students = new MyLinkedList<>(); + } - int[] result = finder.findOccurrences(students, target); - assertEquals(0, result.length); - } - @Test - void shouldTesfindOccurrences_singleMatch_returnsCorrectIndex() { - students.add(new Student("A12", 4.0, 101)); - students.add(new Student("B23", 3.5, 102)); // target здесь - students.add(new Student("C34", 4.8, 103)); + @Test + void shouldTesfindOccurrences_emptyList_returnsEmptyArray() { + int[] result = finder.findOccurrences(students, new Student("A12", 4.5, 1)); + assertEquals(0, result.length); + } - Student target = new Student("B23", 3.5, 102); - int[] result = finder.findOccurrences(students, target); + @Test + void shouldTesfindOccurrences_noMatches_returnsEmptyArray() { + students.add(new Student("A12", 4.0, 101)); + students.add(new Student("B23", 3.5, 102)); + Student target = new Student("C34", 4.8, 103); // такого нет - assertEquals(1, result.length); - assertEquals(1, result[0]); - } - @Test - void shouldTesfindOccurrences_multipleMatches_returnsAllIndicesSorted() { - // индексы: 0, 2, 4 — должны вернуться в порядке возрастания - students.add(new Student("A12", 4.5, 101)); // match - students.add(new Student("B23", 3.8, 102)); - students.add(new Student("A12", 4.5, 101)); // match - students.add(new Student("C34", 4.0, 103)); - students.add(new Student("A12", 4.5, 101)); // match + int[] result = finder.findOccurrences(students, target); + assertEquals(0, result.length); + } - Student target = new Student("A12", 4.5, 101); - int[] result = finder.findOccurrences(students, target); + @Test + void shouldTesfindOccurrences_singleMatch_returnsCorrectIndex() { + students.add(new Student("A12", 4.0, 101)); + students.add(new Student("B23", 3.5, 102)); // target здесь + students.add(new Student("C34", 4.8, 103)); - assertEquals(3, result.length); - assertArrayEquals(new int[]{0, 2, 4}, result); - } + Student target = new Student("B23", 3.5, 102); + int[] result = finder.findOccurrences(students, target); - @Test - void shouldTesfindOccurrences_allElementsMatch_returnsAllIndices() { - Student s = new Student("X99", 5.0, 999); - students.add(s); - students.add(s); - students.add(s); + assertEquals(1, result.length); + assertEquals(1, result[0]); + } - int[] result = finder.findOccurrences(students, s); - assertEquals(3, result.length); - assertArrayEquals(new int[]{0, 1, 2}, result); - } + @Test + void shouldTesfindOccurrences_multipleMatches_returnsAllIndicesSorted() { + // индексы: 0, 2, 4 — должны вернуться в порядке возрастания + students.add(new Student("A12", 4.5, 101)); // match + students.add(new Student("B23", 3.8, 102)); + students.add(new Student("A12", 4.5, 101)); // match + students.add(new Student("C34", 4.0, 103)); + students.add(new Student("A12", 4.5, 101)); // match - @Test - void shouldTesfindOccurrences_differentAverageGrade_noMatch() { - students.add(new Student("A12", 4.50, 101)); - Student target = new Student("A12", 4.51, 101); // отличается балл + Student target = new Student("A12", 4.5, 101); + int[] result = finder.findOccurrences(students, target); - int[] result = finder.findOccurrences(students, target); - assertEquals(0, result.length); - } + assertEquals(3, result.length); + assertArrayEquals(new int[] {0, 2, 4}, result); + } + + @Test + void shouldTesfindOccurrences_allElementsMatch_returnsAllIndices() { + Student s = new Student("X99", 5.0, 999); + students.add(s); + students.add(s); + students.add(s); + + int[] result = finder.findOccurrences(students, s); + assertEquals(3, result.length); + assertArrayEquals(new int[] {0, 1, 2}, result); + } + + @Test + void shouldTesfindOccurrences_differentAverageGrade_noMatch() { + students.add(new Student("A12", 4.50, 101)); + Student target = new Student("A12", 4.51, 101); // отличается балл + + int[] result = finder.findOccurrences(students, target); + assertEquals(0, result.length); + } + + @Test + void shouldTesfindOccurrences_differentGroupNumber_noMatch() { + students.add(new Student("A12", 4.5, 101)); + Student target = new Student("B12", 4.5, 101); // другая группа + + int[] result = finder.findOccurrences(students, target); + assertEquals(0, result.length); + } + + @Test + void shouldTesfindOccurrences_differentRecordBookNumber_noMatch() { + students.add(new Student("A12", 4.5, 101)); + Student target = new Student("A12", 4.5, 102); // другой номер зачетки + + int[] result = finder.findOccurrences(students, target); + assertEquals(0, result.length); + } + + @Test + void shouldTesfindOccurrences_nullList_throwsException() { + Student target = new Student("A12", 4.5, 101); + assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(null, target)); + } + + @Test + void shouldTesfindOccurrences_nullTarget_throwsException() { + assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(students, null)); + } + + @Test + void shouldTesfindOccurrences_resultIsSorted() { + String[] validGroups = {"A00", "A01", "B12", "C99", "X55"}; + + for (int i = 0; i < 50; i++) { + String group = validGroups[i % validGroups.length]; + double grade = (3.0 + (i % 3)) / 2.0; + if (i % 3 == 0) { - @Test - void shouldTesfindOccurrences_differentGroupNumber_noMatch() { students.add(new Student("A12", 4.5, 101)); - Student target = new Student("B12", 4.5, 101); // другая группа - - int[] result = finder.findOccurrences(students, target); - assertEquals(0, result.length); + } else { + students.add(new Student(group, grade, 200 + i)); + } } + Student target = new Student("A12", 4.5, 101); + int[] result = finder.findOccurrences(students, target); - @Test - void shouldTesfindOccurrences_differentRecordBookNumber_noMatch() { - students.add(new Student("A12", 4.5, 101)); - Student target = new Student("A12", 4.5, 102); // другой номер зачетки - - int[] result = finder.findOccurrences(students, target); - assertEquals(0, result.length); - } - - - @Test - void shouldTesfindOccurrences_nullList_throwsException() { - Student target = new Student("A12", 4.5, 101); - assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(null, target)); - } - - - @Test - void shouldTesfindOccurrences_nullTarget_throwsException() { - assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(students, null)); - } - @Test - void shouldTesfindOccurrences_resultIsSorted() { - String[] validGroups = {"A00", "A01", "B12", "C99", "X55"}; - - for (int i = 0; i < 50; i++) { - String group = validGroups[i % validGroups.length]; - double grade = (3.0 + (i % 3)) / 2.0; - if (i % 3 == 0) { - - students.add(new Student("A12", 4.5, 101)); - } else { - students.add(new Student(group, grade, 200 + i)); - } - } - Student target = new Student("A12", 4.5, 101); - int[] result = finder.findOccurrences(students, target); - - - for (int i = 1; i < result.length; i++) { - assertTrue(result[i] > result[i - 1], "Результат должен быть отсортирован"); - } + for (int i = 1; i < result.length; i++) { + assertTrue(result[i] > result[i - 1], "Результат должен быть отсортирован"); } + } } diff --git a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java index 2be00de..1ca6657 100644 --- a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java +++ b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java @@ -6,14 +6,14 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.charset.StandardCharsets; import java.util.NoSuchElementException; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import ru.project.student.Student; class StudentCsvReaderTest { @@ -23,17 +23,12 @@ class StudentCsvReaderTest { // Testing StudentCsvReader @BeforeAll - void generateStudentTestFile(){ + void generateStudentTestFile() { studentPath = tempDir.resolve("students.csv"); Files.write( - studentPath, - new String[]{ - "groupNumber,averageGrade,recordBookNumber", - "A21,5.0,331", - "A21,4.0,332" - }, - StandardCharsets.UTF_8 - ); + studentPath, + new String[] {"groupNumber,averageGrade,recordBookNumber", "A21,5.0,331", "A21,4.0,332"}, + StandardCharsets.UTF_8); } @BeforeEach @@ -44,14 +39,12 @@ class StudentCsvReaderTest { @Test void readerTestRejectRelativePath() { assertThrows( - IllegalArgumentException.class, - () -> new StudentCsvReader(studentPath.toString())); + IllegalArgumentException.class, () -> new StudentCsvReader(studentPath.toString())); } @Test void readerRejectPathIsDir() { - assertThrows( - UncheckedIOException.class, () -> new StudentCsvReader(tempDir.toAbsolutePath())); + assertThrows(UncheckedIOException.class, () -> new StudentCsvReader(tempDir.toAbsolutePath())); } @Test @@ -110,15 +103,10 @@ class StudentCsvReaderTest { () -> { Path path = tempDir.resolve("fewparameters.csv"); Files.write( - path, - new String[]{ - "groupNumber,averageGrade,recordBookNumber", - "A21,5.0" - }, - StandardCharsets.UTF_8 - ); - StudentCsvReader reader = - new StudentCsvReader(path.toAbsolutePath().toString()); + path, + new String[] {"groupNumber,averageGrade,recordBookNumber", "A21,5.0"}, + StandardCharsets.UTF_8); + StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); Student student = reader.next(); }); } @@ -130,16 +118,12 @@ class StudentCsvReaderTest { () -> { Path path = tempDir.resolve("invalid_groupnumber.csv"); Files.write( - path, - new String[]{ - "groupNumber,averageGrade,recordBookNumber", - "AAA,5.0,331", - "A21,4.0,332" - }, - StandardCharsets.UTF_8 - ); - StudentCsvReader reader = - new StudentCsvReader(path.toAbsolutePath().toString()); + path, + new String[] { + "groupNumber,averageGrade,recordBookNumber", "AAA,5.0,331", "A21,4.0,332" + }, + StandardCharsets.UTF_8); + StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); Student student = reader.next(); }); } @@ -151,16 +135,12 @@ class StudentCsvReaderTest { () -> { Path path = tempDir.resolve("invalid_averagegrade.csv"); Files.write( - path, - new String[]{ - "groupNumber,averageGrade,recordBookNumber", - "A21,-5.0,331", - "A21,4.0,332" - }, - StandardCharsets.UTF_8 - ); - StudentCsvReader reader = - new StudentCsvReader(path.toAbsolutePath().toString()); + path, + new String[] { + "groupNumber,averageGrade,recordBookNumber", "A21,-5.0,331", "A21,4.0,332" + }, + StandardCharsets.UTF_8); + StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); Student student = reader.next(); }); } @@ -172,38 +152,30 @@ class StudentCsvReaderTest { () -> { Path path = tempDir.resolve("invalid_recordbooknumber.csv"); Files.write( - path, - new String[]{ - "groupNumber,averageGrade,recordBookNumber", - "A21,5.0,-331", - "A21,4.0,332" - }, - StandardCharsets.UTF_8 - ); - StudentCsvReader reader = - new StudentCsvReader(path.toAbsolutePath().toString()); + path, + new String[] { + "groupNumber,averageGrade,recordBookNumber", "A21,5.0,-331", "A21,4.0,332" + }, + StandardCharsets.UTF_8); + StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); Student student = reader.next(); }); } @Test - void testInvalidHeader(){ + void testInvalidHeader() { assertThrows( - IllegalArgumentException.class, - () -> { - Path path = tempDir.resolve("invalid_header.csv"); - Files.write( - path, - new String[]{ - "groupNumber,averageGrade,recordBookNomber", - "A21,5.0,-331", - "A21,4.0,332" - }, - StandardCharsets.UTF_8 - ); - StudentCsvReader reader = - new StudentCsvReader(path.toAbsolutePath().toString()); - Student student = reader.next(); - }); + IllegalArgumentException.class, + () -> { + Path path = tempDir.resolve("invalid_header.csv"); + Files.write( + path, + new String[] { + "groupNumber,averageGrade,recordBookNomber", "A21,5.0,-331", "A21,4.0,332" + }, + StandardCharsets.UTF_8); + StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); + Student student = reader.next(); + }); } } diff --git a/app/src/test/java/ru/project/search/StudentSearchTest.java b/app/src/test/java/ru/project/search/StudentSearchTest.java index d12bd9c..db43fa1 100644 --- a/app/src/test/java/ru/project/search/StudentSearchTest.java +++ b/app/src/test/java/ru/project/search/StudentSearchTest.java @@ -1,314 +1,317 @@ package ru.project.search; -import org.junit.jupiter.api.Test; -import ru.project.list.MyList; -import ru.project.list.MyLinkedList; -import ru.project.student.Student; - import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.student.Student; + class StudentSearchTest { - @Test - void testGroupNumberRangeMatchesInside() { - StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); - Student student = new Student("B20", 4.5, 123); - assertTrue(criteria.matches(student)); + @Test + void testGroupNumberRangeMatchesInside() { + StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); + Student student = new Student("B20", 4.5, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testGroupNumberRangeMatchesLowerBoundary() { + StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); + Student student = new Student("A10", 4.5, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testGroupNumberRangeMatchesUpperBoundary() { + StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); + Student student = new Student("C30", 4.5, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testGroupNumberRangeDoesNotMatchOutside() { + StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); + Student student = new Student("D00", 4.5, 123); + assertFalse(criteria.matches(student)); + } + + @Test + void testGroupNumberRangeInvalidMinMaxThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new GroupNumberRangeCriteria("C30", "A10")); + } + + @Test + void testGroupNumberRangeNullValuesThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new GroupNumberRangeCriteria(null, "A10")); + assertThrows(IllegalArgumentException.class, () -> new GroupNumberRangeCriteria("A10", null)); + } + + @Test + void testAverageGradeRangeMatchesInside() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 4.5, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeMatchesLowerBoundary() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 4.0, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeMatchesUpperBoundary() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 5.0, 123); + assertTrue(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeDoesNotMatchBelow() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 3.9, 123); + assertFalse(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeDoesNotMatchAbove() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + Student student = new Student("A12", 5.1, 123); + assertFalse(criteria.matches(student)); + } + + @Test + void testAverageGradeRangeInvalidMinMaxThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new AverageGradeRangeCriteria(5.0, 4.0)); + } + + @Test + void testRecordBookNumberRangeMatchesInside() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 150); + assertTrue(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeMatchesLowerBoundary() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 100); + assertTrue(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeMatchesUpperBoundary() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 200); + assertTrue(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeDoesNotMatchBelow() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 99); + assertFalse(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeDoesNotMatchAbove() { + StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); + Student student = new Student("A12", 4.5, 201); + assertFalse(criteria.matches(student)); + } + + @Test + void testRecordBookNumberRangeInvalidMinMaxThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new RecordBookNumberRangeCriteria(200, 100)); + } + + @Test + void testCompositeIntersectionEmptyReturnsTrue() { + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + Student student = new Student("A12", 4.5, 123); + assertTrue(composite.matches(student)); + } + + @Test + void testCompositeUnionEmptyReturnsFalse() { + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.UNION); + Student student = new Student("A12", 4.5, 123); + assertFalse(composite.matches(student)); + } + + @Test + void testCompositeIntersectionAllTrue() { + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + composite.add(new GroupNumberRangeCriteria("A10", "C30")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + Student student = new Student("B20", 4.5, 123); + assertTrue(composite.matches(student)); + } + + @Test + void testCompositeIntersectionOneFalse() { + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + composite.add(new GroupNumberRangeCriteria("A10", "C30")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + Student student = new Student("B20", 3.5, 123); + assertFalse(composite.matches(student)); + } + + @Test + void testCompositeUnionOneTrue() { + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.UNION); + composite.add(new GroupNumberRangeCriteria("A10", "C30")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + Student student = new Student("D00", 4.5, 123); + assertTrue(composite.matches(student)); + } + + @Test + void testCompositeUnionAllFalse() { + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.UNION); + composite.add(new GroupNumberRangeCriteria("A10", "C30")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); + Student student = new Student("D00", 3.5, 123); + assertFalse(composite.matches(student)); + } + + @Test + void testCompositeWithNestedComposite() { + CompositeStudentSearchCriteria outer = + new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + outer.add(new GroupNumberRangeCriteria("A10", "C30")); + + CompositeStudentSearchCriteria inner = + new CompositeStudentSearchCriteria(SearchOperation.UNION); + inner.add(new AverageGradeRangeCriteria(4.0, 5.0)); + inner.add(new RecordBookNumberRangeCriteria(100, 200)); + + outer.add(inner); + + Student student = new Student("B20", 4.5, 150); + assertTrue(outer.matches(student)); + student = new Student("B20", 3.5, 150); + assertFalse(outer.matches(student)); + student = new Student("D00", 4.5, 150); + assertFalse(outer.matches(student)); + } + + @Test + void testSearchServiceWithSingleCriteria() { + MyList students = new MyLinkedList<>(); + students.add(new Student("A10", 4.5, 1)); + students.add(new Student("B20", 3.8, 2)); + students.add(new Student("C30", 4.9, 3)); + + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + + assertEquals(2, result.size()); + assertEquals("A10", result.get(0).getGroupNumber()); + assertEquals("C30", result.get(1).getGroupNumber()); + } + + @Test + void testSearchServiceEmptyResult() { + MyList students = new MyLinkedList<>(); + students.add(new Student("A10", 3.5, 1)); + students.add(new Student("B20", 3.8, 2)); + + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + + assertEquals(0, result.size()); + } + + @Test + void testSearchServiceEmptyCollection() { + MyList students = new MyLinkedList<>(); + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + assertEquals(0, result.size()); + } + + @Test + void testSearchServicePreservesOrder() { + MyList students = new MyLinkedList<>(); + students.add(new Student("Z00", 4.5, 1)); + students.add(new Student("A10", 4.5, 2)); + students.add(new Student("M30", 4.5, 3)); + + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + + assertEquals(3, result.size()); + assertEquals("Z00", result.get(0).getGroupNumber()); + assertEquals("A10", result.get(1).getGroupNumber()); + assertEquals("M30", result.get(2).getGroupNumber()); + } + + @Test + void testSearchServiceDoesNotModifyOriginal() { + MyList original = new MyLinkedList<>(); + original.add(new Student("A10", 4.5, 1)); + original.add(new Student("B20", 3.8, 2)); + + MyList copy = new MyLinkedList<>(); + for (int i = 0; i < original.size(); i++) { + copy.add(original.get(i)); } - @Test - void testGroupNumberRangeMatchesLowerBoundary() { - StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); - Student student = new Student("A10", 4.5, 123); - assertTrue(criteria.matches(student)); + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + service.find(original); + + assertEquals(copy.size(), original.size()); + for (int i = 0; i < copy.size(); i++) { + assertEquals(copy.get(i).getGroupNumber(), original.get(i).getGroupNumber()); } + } - @Test - void testGroupNumberRangeMatchesUpperBoundary() { - StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); - Student student = new Student("C30", 4.5, 123); - assertTrue(criteria.matches(student)); - } + @Test + void testSearchServiceWithNullCriteriaThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new StudentSearchService(null)); + } - @Test - void testGroupNumberRangeDoesNotMatchOutside() { - StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30"); - Student student = new Student("D00", 4.5, 123); - assertFalse(criteria.matches(student)); - } + @Test + void testSearchServiceWithCompositeCriteria() { + MyList students = new MyLinkedList<>(); + students.add(new Student("A10", 4.5, 1)); + students.add(new Student("B20", 4.5, 2)); + students.add(new Student("C30", 3.5, 3)); - @Test - void testGroupNumberRangeInvalidMinMaxThrowsException() { - assertThrows(IllegalArgumentException.class, - () -> new GroupNumberRangeCriteria("C30", "A10")); - } + CompositeStudentSearchCriteria composite = + new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); + composite.add(new GroupNumberRangeCriteria("A10", "B20")); + composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); - @Test - void testGroupNumberRangeNullValuesThrowsException() { - assertThrows(IllegalArgumentException.class, - () -> new GroupNumberRangeCriteria(null, "A10")); - assertThrows(IllegalArgumentException.class, - () -> new GroupNumberRangeCriteria("A10", null)); - } + StudentSearchService service = new StudentSearchService(composite); + MyList result = service.find(students); - @Test - void testAverageGradeRangeMatchesInside() { - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - Student student = new Student("A12", 4.5, 123); - assertTrue(criteria.matches(student)); - } + assertEquals(2, result.size()); + assertEquals("A10", result.get(0).getGroupNumber()); + assertEquals("B20", result.get(1).getGroupNumber()); + } - @Test - void testAverageGradeRangeMatchesLowerBoundary() { - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - Student student = new Student("A12", 4.0, 123); - assertTrue(criteria.matches(student)); - } - - @Test - void testAverageGradeRangeMatchesUpperBoundary() { - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - Student student = new Student("A12", 5.0, 123); - assertTrue(criteria.matches(student)); - } - - @Test - void testAverageGradeRangeDoesNotMatchBelow() { - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - Student student = new Student("A12", 3.9, 123); - assertFalse(criteria.matches(student)); - } - - @Test - void testAverageGradeRangeDoesNotMatchAbove() { - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - Student student = new Student("A12", 5.1, 123); - assertFalse(criteria.matches(student)); - } - - @Test - void testAverageGradeRangeInvalidMinMaxThrowsException() { - assertThrows(IllegalArgumentException.class, - () -> new AverageGradeRangeCriteria(5.0, 4.0)); - } - - @Test - void testRecordBookNumberRangeMatchesInside() { - StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); - Student student = new Student("A12", 4.5, 150); - assertTrue(criteria.matches(student)); - } - - @Test - void testRecordBookNumberRangeMatchesLowerBoundary() { - StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); - Student student = new Student("A12", 4.5, 100); - assertTrue(criteria.matches(student)); - } - - @Test - void testRecordBookNumberRangeMatchesUpperBoundary() { - StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); - Student student = new Student("A12", 4.5, 200); - assertTrue(criteria.matches(student)); - } - - @Test - void testRecordBookNumberRangeDoesNotMatchBelow() { - StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); - Student student = new Student("A12", 4.5, 99); - assertFalse(criteria.matches(student)); - } - - @Test - void testRecordBookNumberRangeDoesNotMatchAbove() { - StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200); - Student student = new Student("A12", 4.5, 201); - assertFalse(criteria.matches(student)); - } - - @Test - void testRecordBookNumberRangeInvalidMinMaxThrowsException() { - assertThrows(IllegalArgumentException.class, - () -> new RecordBookNumberRangeCriteria(200, 100)); - } - - @Test - void testCompositeIntersectionEmptyReturnsTrue() { - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); - Student student = new Student("A12", 4.5, 123); - assertTrue(composite.matches(student)); - } - - @Test - void testCompositeUnionEmptyReturnsFalse() { - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.UNION); - Student student = new Student("A12", 4.5, 123); - assertFalse(composite.matches(student)); - } - - @Test - void testCompositeIntersectionAllTrue() { - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); - composite.add(new GroupNumberRangeCriteria("A10", "C30")); - composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); - Student student = new Student("B20", 4.5, 123); - assertTrue(composite.matches(student)); - } - - @Test - void testCompositeIntersectionOneFalse() { - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); - composite.add(new GroupNumberRangeCriteria("A10", "C30")); - composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); - Student student = new Student("B20", 3.5, 123); - assertFalse(composite.matches(student)); - } - - @Test - void testCompositeUnionOneTrue() { - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.UNION); - composite.add(new GroupNumberRangeCriteria("A10", "C30")); - composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); - Student student = new Student("D00", 4.5, 123); - assertTrue(composite.matches(student)); - } - - @Test - void testCompositeUnionAllFalse() { - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.UNION); - composite.add(new GroupNumberRangeCriteria("A10", "C30")); - composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); - Student student = new Student("D00", 3.5, 123); - assertFalse(composite.matches(student)); - } - - @Test - void testCompositeWithNestedComposite() { - CompositeStudentSearchCriteria outer = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); - outer.add(new GroupNumberRangeCriteria("A10", "C30")); - - CompositeStudentSearchCriteria inner = new CompositeStudentSearchCriteria(SearchOperation.UNION); - inner.add(new AverageGradeRangeCriteria(4.0, 5.0)); - inner.add(new RecordBookNumberRangeCriteria(100, 200)); - - outer.add(inner); - - Student student = new Student("B20", 4.5, 150); - assertTrue(outer.matches(student)); - student = new Student("B20", 3.5, 150); - assertFalse(outer.matches(student)); - student = new Student("D00", 4.5, 150); - assertFalse(outer.matches(student)); - } - - @Test - void testSearchServiceWithSingleCriteria() { - MyList students = new MyLinkedList<>(); - students.add(new Student("A10", 4.5, 1)); - students.add(new Student("B20", 3.8, 2)); - students.add(new Student("C30", 4.9, 3)); - - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - StudentSearchService service = new StudentSearchService(criteria); - MyList result = service.find(students); - - assertEquals(2, result.size()); - assertEquals("A10", result.get(0).getGroupNumber()); - assertEquals("C30", result.get(1).getGroupNumber()); - } - - @Test - void testSearchServiceEmptyResult() { - MyList students = new MyLinkedList<>(); - students.add(new Student("A10", 3.5, 1)); - students.add(new Student("B20", 3.8, 2)); - - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - StudentSearchService service = new StudentSearchService(criteria); - MyList result = service.find(students); - - assertEquals(0, result.size()); - } - - @Test - void testSearchServiceEmptyCollection() { - MyList students = new MyLinkedList<>(); - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - StudentSearchService service = new StudentSearchService(criteria); - MyList result = service.find(students); - assertEquals(0, result.size()); - } - - @Test - void testSearchServicePreservesOrder() { - MyList students = new MyLinkedList<>(); - students.add(new Student("Z00", 4.5, 1)); - students.add(new Student("A10", 4.5, 2)); - students.add(new Student("M30", 4.5, 3)); - - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - StudentSearchService service = new StudentSearchService(criteria); - MyList result = service.find(students); - - assertEquals(3, result.size()); - assertEquals("Z00", result.get(0).getGroupNumber()); - assertEquals("A10", result.get(1).getGroupNumber()); - assertEquals("M30", result.get(2).getGroupNumber()); - } - - @Test - void testSearchServiceDoesNotModifyOriginal() { - MyList original = new MyLinkedList<>(); - original.add(new Student("A10", 4.5, 1)); - original.add(new Student("B20", 3.8, 2)); - - MyList copy = new MyLinkedList<>(); - for (int i = 0; i < original.size(); i++) { - copy.add(original.get(i)); - } - - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - StudentSearchService service = new StudentSearchService(criteria); - service.find(original); - - assertEquals(copy.size(), original.size()); - for (int i = 0; i < copy.size(); i++) { - assertEquals(copy.get(i).getGroupNumber(), original.get(i).getGroupNumber()); - } - } - - @Test - void testSearchServiceWithNullCriteriaThrowsException() { - assertThrows(IllegalArgumentException.class, - () -> new StudentSearchService(null)); - } - - @Test - void testSearchServiceWithCompositeCriteria() { - MyList students = new MyLinkedList<>(); - students.add(new Student("A10", 4.5, 1)); - students.add(new Student("B20", 4.5, 2)); - students.add(new Student("C30", 3.5, 3)); - - CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION); - composite.add(new GroupNumberRangeCriteria("A10", "B20")); - composite.add(new AverageGradeRangeCriteria(4.0, 5.0)); - - StudentSearchService service = new StudentSearchService(composite); - MyList result = service.find(students); - - assertEquals(2, result.size()); - assertEquals("A10", result.get(0).getGroupNumber()); - assertEquals("B20", result.get(1).getGroupNumber()); - } - - @Test - void testSearchServiceWithNullStudentListReturnsEmpty() { - StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - StudentSearchService service = new StudentSearchService(criteria); - MyList result = service.find(null); - assertNotNull(result); - assertEquals(0, result.size()); - } -} \ No newline at end of file + @Test + void testSearchServiceWithNullStudentListReturnsEmpty() { + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(null); + assertNotNull(result); + assertEquals(0, result.size()); + } +} From 259e0bc8bddd9ad4a0c801fead91a5c4404e2e57 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, 1 Sep 2026 17:13:48 +0400 Subject: [PATCH 139/156] feat(search): add occurrence counting menu and student settings - add occurrence counting menu using StudentOccurrenceIndexFinder - add student settings window for configuring search target - display occurrence count and matching indices - update current collection menu to delegate occurrence counting --- .../ui/collection/CurrentCollectionMenu.java | 14 +- .../counting/CountOccurrencesMenu.java | 109 +++++++++++ .../OccurrenceStudentSettingsWindow.java | 172 ++++++++++++++++++ 3 files changed, 285 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/ru/project/ui/collection/counting/CountOccurrencesMenu.java create mode 100644 app/src/main/java/ru/project/ui/collection/counting/OccurrenceStudentSettingsWindow.java diff --git a/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java b/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java index 516c020..af294af 100644 --- a/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java +++ b/app/src/main/java/ru/project/ui/collection/CurrentCollectionMenu.java @@ -4,6 +4,7 @@ import com.googlecode.lanterna.gui2.ActionListBox; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import ru.project.model.AppState; import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.collection.counting.CountOccurrencesMenu; import ru.project.ui.collection.search.SearchCollectionMenu; import ru.project.ui.collection.sorting.SortCollectionMenu; import ru.project.ui.common.CollectionDisplayWindow; @@ -15,8 +16,7 @@ public class CurrentCollectionMenu extends BaseModalWindow { private final SortCollectionMenu sortCollectionMenu; private final SearchCollectionMenu searchCollectionMenu; - - // private final CountOccurrencesMenu countOccurrencesMenu; + private final CountOccurrencesMenu countOccurrencesMenu; public CurrentCollectionMenu(WindowBasedTextGUI gui, AppState state) { @@ -28,7 +28,7 @@ public class CurrentCollectionMenu extends BaseModalWindow { this.searchCollectionMenu = new SearchCollectionMenu(gui, state); - // this.countCollectionMenu = new CountOccurrencesMenu(gui, state); + this.countOccurrencesMenu = new CountOccurrencesMenu(gui, state); ActionListBox menu = new ActionListBox(); menu.addItem("Показать коллекцию...", this::showCurrentCollection); @@ -39,7 +39,7 @@ public class CurrentCollectionMenu extends BaseModalWindow { menu.addItem("Меню поиска элемента в коллекции", searchCollectionMenu::showModal); - menu.addItem("Меню подсчёта количества вхождений элемента", this::countOccurrences); + menu.addItem("Меню подсчёта количества вхождений элемента", countOccurrencesMenu::showModal); menu.addItem("Выйти", this::close); @@ -57,10 +57,4 @@ public class CurrentCollectionMenu extends BaseModalWindow { window.showModal(); } - - private void countOccurrences() { - // CountOccurrencesMenu countOccurrencesMenu = new CountOccurrencesMenu(gui, state); - // - // countOccurrencesMenu.showModal(); - } } diff --git a/app/src/main/java/ru/project/ui/collection/counting/CountOccurrencesMenu.java b/app/src/main/java/ru/project/ui/collection/counting/CountOccurrencesMenu.java new file mode 100644 index 0000000..26cd626 --- /dev/null +++ b/app/src/main/java/ru/project/ui/collection/counting/CountOccurrencesMenu.java @@ -0,0 +1,109 @@ +package ru.project.ui.collection.counting; + +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.finder.StudentOccurrenceIndexFinder; +import ru.project.model.AppState; +import ru.project.student.Student; +import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.common.MessageWindow; + +public class CountOccurrencesMenu extends BaseModalWindow { + + private final AppState state; + + private final OccurrenceStudentSettingsWindow occurrenceStudentSettingsWindow; + + private Student selectedStudent; + private int occurrences; + + private final Label studentLabel; + private final Label occurrencesLabel; + + public CountOccurrencesMenu(WindowBasedTextGUI gui, AppState state) { + + super("Меню подсчёта количества вхождений элемента", gui); + + this.state = state; + this.selectedStudent = null; + this.occurrences = 0; + + this.studentLabel = new Label("Элемент для поиска: не задан"); + + this.occurrencesLabel = new Label("Количество вхождений: не подсчитано"); + + this.occurrenceStudentSettingsWindow = + new OccurrenceStudentSettingsWindow( + gui, + student -> { + selectedStudent = student; + + studentLabel.setText("Элемент для поиска: задан"); + }); + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + panel.addComponent(studentLabel); + panel.addComponent(occurrencesLabel); + + panel.addComponent(new Button("Настроить элемент для поиска...", this::setupStudent)); + + panel.addComponent(new Button("Выполнить подсчёт", this::countOccurrences)); + + panel.addComponent(new Button("Выйти", this::close)); + + setComponent(panel); + } + + private void setupStudent() { + occurrenceStudentSettingsWindow.showModal(); + } + + private void countOccurrences() { + MyList collection = state.getMainCollection(); + + if (collection == null || collection.isEmpty()) { + MessageWindow.showModal(gui, "Текущая коллекция пуста."); + + return; + } + + if (selectedStudent == null) { + MessageWindow.showModal(gui, "Сначала настройте элемент для поиска."); + + return; + } + + StudentOccurrenceIndexFinder finder = new StudentOccurrenceIndexFinder(); + + int[] occurrenceIndices = finder.findOccurrences(collection, selectedStudent); + + occurrences = occurrenceIndices.length; + + StringBuilder result = new StringBuilder(); + + result.append("Количество вхождений: ").append(occurrences); + + result.append("\nИндексы: "); + + if (occurrenceIndices.length == 0) { + result.append("нет"); + } else { + for (int i = 0; i < occurrenceIndices.length; i++) { + + if (i > 0) { + result.append(", "); + } + + result.append(occurrenceIndices[i]); + } + } + + occurrencesLabel.setText(result.toString()); + } +} diff --git a/app/src/main/java/ru/project/ui/collection/counting/OccurrenceStudentSettingsWindow.java b/app/src/main/java/ru/project/ui/collection/counting/OccurrenceStudentSettingsWindow.java new file mode 100644 index 0000000..06cd33d --- /dev/null +++ b/app/src/main/java/ru/project/ui/collection/counting/OccurrenceStudentSettingsWindow.java @@ -0,0 +1,172 @@ +package ru.project.ui.collection.counting; + +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 java.util.function.Consumer; +import ru.project.student.Student; +import ru.project.student.StudentBuilder; +import ru.project.ui.base.BaseModalWindow; +import ru.project.ui.common.InputValueWindow; +import ru.project.ui.common.MessageWindow; + +public class OccurrenceStudentSettingsWindow extends BaseModalWindow { + + private final StudentBuilder studentBuilder; + private final Consumer onApply; + + private String groupNumber; + private Double averageGrade; + private Integer recordBookNumber; + + private final Label groupNumberLabel; + private final Label averageGradeLabel; + private final Label recordBookNumberLabel; + + public OccurrenceStudentSettingsWindow(WindowBasedTextGUI gui, Consumer onApply) { + + super("Настройка элемента для поиска", gui); + + this.studentBuilder = new StudentBuilder(); + this.onApply = onApply; + + Panel panel = new Panel(); + panel.setLayoutManager(new LinearLayout()); + + groupNumberLabel = new Label("Номер группы: не задан"); + + averageGradeLabel = new Label("Средний балл: не задан"); + + recordBookNumberLabel = new Label("Номер зачётной книжки: не задан"); + + panel.addComponent(groupNumberLabel); + panel.addComponent(averageGradeLabel); + panel.addComponent(recordBookNumberLabel); + + panel.addComponent(new Button("Задать номер группы...", this::setGroupNumber)); + + panel.addComponent(new Button("Задать средний балл...", this::setAverageGrade)); + + panel.addComponent(new Button("Задать номер зачётной книжки...", this::setRecordBookNumber)); + + 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.showModal(gui, "Номер группы должен соответствовать формату A12."); + + return false; + } + + groupNumber = value; + studentBuilder.setGroupNumber(value); + + groupNumberLabel.setText("Номер группы: " + groupNumber); + + return true; + }); + + window.showModal(); + } + + 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.showModal(gui, "Средний балл должен быть от 0.0 до 5.0."); + + return false; + } + + averageGrade = parsedValue; + studentBuilder.setAverageGrade(parsedValue); + + averageGradeLabel.setText("Средний балл: " + averageGrade); + + return true; + + } catch (NumberFormatException e) { + MessageWindow.showModal(gui, "Введите корректное число."); + + return false; + } + }); + + window.showModal(); + } + + 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.showModal(gui, "Номер зачётной книжки должен быть положительным."); + + return false; + } + + recordBookNumber = parsedValue; + studentBuilder.setRecordBookNumber(parsedValue); + + recordBookNumberLabel.setText("Номер зачётной книжки: " + recordBookNumber); + + return true; + + } catch (NumberFormatException e) { + MessageWindow.showModal(gui, "Введите целое число."); + + return false; + } + }); + + window.showModal(); + } + + private void apply() { + if (groupNumber == null || averageGrade == null || recordBookNumber == null) { + + MessageWindow.showModal(gui, "Необходимо задать все поля элемента."); + + return; + } + + Student student = studentBuilder.build(); + + onApply.accept(student); + + MessageWindow.showModal(gui, "Элемент для поиска успешно настроен."); + } +} From 34d08eb39753673a8450805d1ce54ef001d022ee 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, 1 Sep 2026 16:23:57 +0300 Subject: [PATCH 140/156] fix: corrected errors in exceptions and tests --- .../java/ru/project/io/StudentCsvReader.java | 120 ++++---- .../ru/project/io/StudentCsvReaderTest.java | 261 +++++++----------- 2 files changed, 161 insertions(+), 220 deletions(-) diff --git a/app/src/main/java/ru/project/io/StudentCsvReader.java b/app/src/main/java/ru/project/io/StudentCsvReader.java index a6b93c4..8dc2bb5 100644 --- a/app/src/main/java/ru/project/io/StudentCsvReader.java +++ b/app/src/main/java/ru/project/io/StudentCsvReader.java @@ -14,70 +14,76 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import ru.project.student.Student; import ru.project.student.StudentBuilder; - -public class StudentCsvReader implements Iterator { + public class StudentCsvReader implements Iterator { private static final String GROUP_NUMBER = "groupNumber"; private static final String AVERAGE_GRADE = "averageGrade"; private static final String RECORD_BOOK_NUMBER = "recordBookNumber"; - private final CSVParser parser; private final Iterator records; - - private int lineOfFile; - - public StudentCsvReader(Path path2) { - lineOfFile = 0; - Path path = Paths.get(path2); - if (!path.isAbsolute()) { - throw new IllegalArgumentException("File path must be absolute"); + private int lineOfFile=0; + public StudentCsvReader(Path path) { + if (!path.isAbsolute()) { + throw new IllegalArgumentException("File path must be absolute"); + } + try { + CSVFormat format = CSVFormat.DEFAULT.builder() + .setHeader() + .setSkipHeaderRecord(true) + .build(); + this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); + validateHeader(parser.getHeaderMap()); + this.records = parser.iterator(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } - - try { - CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).get(); - this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); - validateHeader(parser.getHeaderMap()); - this.records = parser.iterator(); - } catch (IOException e) { - throw new UncheckedIOException(e); + @Override + public boolean hasNext() { + return records.hasNext(); } - } - - @Override - public boolean hasNext() { - return records.hasNext(); - } - - @Override - public Student next() { - lineOfFile++; - if (!records.hasNext()) { - throw new NoSuchElementException("Problem at line " + lineOfFile + "."); - } - - CSVRecord record = records.next(); - if (record.size() != 3) { - throw new IllegalArgumentException( - "Problem at line " + lineOfFile + ".Expected 3 columns, but got " + record.size()); - } - try { - double averageGrade = Double.parseDouble(record.get(AVERAGE_GRADE)); - int recordBookNumber = Integer.parseInt(record.get(RECORD_BOOK_NUMBER)); + @Override + public Student next() { + if (!records.hasNext()) { + throw new NoSuchElementException("No more records at line " + (lineOfFile + 1)); + } + lineOfFile++; + CSVRecord record = records.next(); + if (record.size() != 3) { + throw new IllegalArgumentException( + "Problem at line " + lineOfFile + ". Expected 3 columns, but got " + record.size()); + } + String groupNumber = record.get(GROUP_NUMBER); + String averageGradeStr = record.get(AVERAGE_GRADE); + String recordBookNumberStr = record.get(RECORD_BOOK_NUMBER); + double averageGrade; + int recordBookNumber; + try { + averageGrade = Double.parseDouble(averageGradeStr); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Problem at line " + lineOfFile + ": cannot parse averageGrade '" + averageGradeStr + "' as double", e); + } + try { + recordBookNumber = Integer.parseInt(recordBookNumberStr); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Problem at line " + lineOfFile + ": cannot parse recordBookNumber '" + recordBookNumberStr + "' as int", e); + } return new StudentBuilder() - .setGroupNumber(record.get(GROUP_NUMBER)) - .setAverageGrade(averageGrade) - .setRecordBookNumber(recordBookNumber) - .build(); - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "Problem at line " + lineOfFile + ". Unacceptable type convertation"); + .setGroupNumber(groupNumber) + .setAverageGrade(averageGrade) + .setRecordBookNumber(recordBookNumber) + .build(); + } + @Override + public void close() throws IOException { + parser.close(); + } + private static void validateHeader(Map headerMap) { + String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER}; + if (headerMap.size() != expected.length + || !headerMap.keySet().containsAll(Arrays.asList(expected))) { + throw new IllegalArgumentException("Invalid CSV header"); + } } } - - private static void validateHeader(Map headerMap) { - String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER}; - if (headerMap.size() != expected.length - || !headerMap.keySet().containsAll(Arrays.asList(expected))) { - throw new IllegalArgumentException("Invalid CSV header"); - } - } -} diff --git a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java index 1ca6657..ed1bb02 100644 --- a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java +++ b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java @@ -1,10 +1,8 @@ package ru.project.io; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -15,167 +13,104 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import ru.project.student.Student; - +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.collectors.MyCollectors; +import ru.project.collectors.MyListCollector; class StudentCsvReaderTest { - @TempDir Path tempDir; - private Path studentPath; + @TempDir + Path tempDir; + @Test + void shouldTestreadValidCsvIntoMyList() throws IOException { + Path csv = tempDir.resolve("students.csv"); + String content = String.join("\n", + "groupNumber,averageGrade,recordBookNumber", + "M10,4.5,12345", // валидно: 1 буква + 2 цифры + "B05,3.8,12346" // валидно + ); + Files.writeString(csv, content, StandardCharsets.UTF_8); + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + var studentStream = StreamSupport.stream( + Spliterators.spliteratorUnknownSize(reader, 0), + false + ); + MyList students = studentStream + .collect(MyCollectors.toMyList()); + assertEquals(2, students.size()); + assertEquals("M10", students.get(0).getGroupNumber()); + assertEquals(4.5, students.get(0).getAverageGrade(), 0.0); + assertEquals(12345, students.get(0).getRecordBookNumber()); + assertEquals("B05", students.get(1).getGroupNumber()); + assertEquals(3.8, students.get(1).getAverageGrade(), 0.0); + assertEquals(12346, students.get(1).getRecordBookNumber()); + } + } + @Test + void shouldTestinvalidHeaderThrowsException() throws IOException { + Path csv = tempDir.resolve("bad-header.csv"); + String content = """ + badGroup,badGrade,badBook + M101,4.5,12345 + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + () -> new StudentCsvReader(csv.toAbsolutePath()) + ); + assertTrue(ex.getMessage().contains("Invalid CSV header")); + } + @Test + void shouldTestwrongColumnCountThrowsException() throws IOException { + Path csv = tempDir.resolve("wrong-columns.csv"); + String content = """ + groupNumber,averageGrade,recordBookNumber + M101,4.5 + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + var reader = new StudentCsvReader(csv.toAbsolutePath()); + assertTrue(reader.hasNext()); + // следующая строка имеет только 2 колонки + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + reader::next + ); + assertTrue(ex.getMessage().contains("Expected 3 columns")); + } + @Test + void shouldTestparseErrorThrowsExceptionWithDetails() throws IOException { + Path csv = tempDir.resolve("parse-error.csv"); + String content = """ + groupNumber,averageGrade,recordBookNumber + M101,not-a-number,12345 + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); - // Testing StudentCsvReader - - @BeforeAll - void generateStudentTestFile() { - studentPath = tempDir.resolve("students.csv"); - Files.write( - studentPath, - new String[] {"groupNumber,averageGrade,recordBookNumber", "A21,5.0,331", "A21,4.0,332"}, - StandardCharsets.UTF_8); - } - - @BeforeEach - void setUp() { - valideReader = new StudentCsvReader(studentPath.toAbsolutePath().toString()); - } - - @Test - void readerTestRejectRelativePath() { - assertThrows( - IllegalArgumentException.class, () -> new StudentCsvReader(studentPath.toString())); - } - - @Test - void readerRejectPathIsDir() { - assertThrows(UncheckedIOException.class, () -> new StudentCsvReader(tempDir.toAbsolutePath())); - } - - @Test - void readerTestFileNotExist() { - assertThrows( - UncheckedIOException.class, - () -> new StudentCsvReader(tempDir.resolve("sss.csv").toAbsolutePath().toString())); - } - - @Test - void testSkipsHeader() { - Student student = valideReader.next(); - assertNotEquals(student.getGroupNumber(), "groupNumber"); - } - - @Test - void testCreationStudentOnValidData() { - Student student = valideReader.next(); - assertEquals(student.getGroupNumber(), "A21"); - assertEquals(student.getAverageGrade(), 5.0); - assertEquals(student.getRecordBookNumber(), 331); - } - - @Test - void testNotSkippingFirstStudent() { - valideReader.hasNext(); - valideReader.hasNext(); - Student student = valideReader.next(); - assertEquals(student.getGroupNumber(), "A21"); - assertEquals(student.getAverageGrade(), 5.0); - assertEquals(student.getRecordBookNumber(), 331); - } - - @Test - void testValidEndsOfFile() { - valideReader.next(); - valideReader.next(); - assertTrue(!valideReader.hasNext()); - } - - @Test - void testOutOfIndex() { - assertThrows( - NoSuchElementException.class, - () -> { - Student student = valideReader.next(); - student = valideReader.next(); - student = valideReader.next(); - }); - } - - @Test - void readerTestRejectFewParametres() { - assertThrows( - IllegalArgumentException.class, - () -> { - Path path = tempDir.resolve("fewparameters.csv"); - Files.write( - path, - new String[] {"groupNumber,averageGrade,recordBookNumber", "A21,5.0"}, - StandardCharsets.UTF_8); - StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); - Student student = reader.next(); - }); - } - - @Test - void readerTestRejectInvalidGroupNumber() { - assertThrows( - IllegalArgumentException.class, - () -> { - Path path = tempDir.resolve("invalid_groupnumber.csv"); - Files.write( - path, - new String[] { - "groupNumber,averageGrade,recordBookNumber", "AAA,5.0,331", "A21,4.0,332" - }, - StandardCharsets.UTF_8); - StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); - Student student = reader.next(); - }); - } - - @Test - void readerTestRejectInvalidAverageGrade() { - assertThrows( - IllegalArgumentException.class, - () -> { - Path path = tempDir.resolve("invalid_averagegrade.csv"); - Files.write( - path, - new String[] { - "groupNumber,averageGrade,recordBookNumber", "A21,-5.0,331", "A21,4.0,332" - }, - StandardCharsets.UTF_8); - StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); - Student student = reader.next(); - }); - } - - @Test - void readerTestRejectInvalidRecordBookNumber() { - assertThrows( - IllegalArgumentException.class, - () -> { - Path path = tempDir.resolve("invalid_recordbooknumber.csv"); - Files.write( - path, - new String[] { - "groupNumber,averageGrade,recordBookNumber", "A21,5.0,-331", "A21,4.0,332" - }, - StandardCharsets.UTF_8); - StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); - Student student = reader.next(); - }); - } - - @Test - void testInvalidHeader() { - assertThrows( - IllegalArgumentException.class, - () -> { - Path path = tempDir.resolve("invalid_header.csv"); - Files.write( - path, - new String[] { - "groupNumber,averageGrade,recordBookNomber", "A21,5.0,-331", "A21,4.0,332" - }, - StandardCharsets.UTF_8); - StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); - Student student = reader.next(); - }); - } + var reader = new StudentCsvReader(csv.toAbsolutePath()); + assertTrue(reader.hasNext()); + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + reader::next + ); + assertTrue(ex.getMessage().contains("cannot parse averageGrade")); + } + @Test + void shouldTestemptyDataAfterHeaderThrowsNoSuchElement() throws IOException { + Path csv = tempDir.resolve("empty-data.csv"); + String content = """ + groupNumber,averageGrade,recordBookNumber + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + var reader = new StudentCsvReader(csv.toAbsolutePath()); + assertFalse(reader.hasNext()); + assertThrows(NoSuchElementException.class, reader::next); + } + @Test + void shouldTestrelativePathThrowsException() { + Path relative = Path.of("relative-path.csv"); + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + () -> new StudentCsvReader(relative) + ); + assertTrue(ex.getMessage().contains("absolute")); + } } From 712fe6c112bb4c444975438508b12c0ce91f6d9d 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, 1 Sep 2026 17:11:08 +0300 Subject: [PATCH 141/156] fix: fixed errors and tests --- .../java/ru/project/io/StudentCsvReader.java | 4 +- .../ru/project/io/StudentCsvReaderTest.java | 62 ++++++++++--------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/ru/project/io/StudentCsvReader.java b/app/src/main/java/ru/project/io/StudentCsvReader.java index 8dc2bb5..5974c04 100644 --- a/app/src/main/java/ru/project/io/StudentCsvReader.java +++ b/app/src/main/java/ru/project/io/StudentCsvReader.java @@ -14,7 +14,7 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import ru.project.student.Student; import ru.project.student.StudentBuilder; - public class StudentCsvReader implements Iterator { + public class StudentCsvReader implements Iterator, AutoCloseable { private static final String GROUP_NUMBER = "groupNumber"; private static final String AVERAGE_GRADE = "averageGrade"; private static final String RECORD_BOOK_NUMBER = "recordBookNumber"; @@ -29,7 +29,7 @@ import ru.project.student.StudentBuilder; CSVFormat format = CSVFormat.DEFAULT.builder() .setHeader() .setSkipHeaderRecord(true) - .build(); + .get(); this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); validateHeader(parser.getHeaderMap()); this.records = parser.iterator(); diff --git a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java index ed1bb02..3541f35 100644 --- a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java +++ b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java @@ -1,22 +1,17 @@ package ru.project.io; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.UncheckedIOException; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.NoSuchElementException; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; +import java.util.Spliterators; +import java.util.stream.StreamSupport; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import ru.project.student.Student; -import ru.project.collection.MyLinkedList; import ru.project.collection.MyList; import ru.project.collectors.MyCollectors; -import ru.project.collectors.MyListCollector; +import static org.junit.jupiter.api.Assertions.*; class StudentCsvReaderTest { @TempDir Path tempDir; @@ -25,8 +20,8 @@ class StudentCsvReaderTest { Path csv = tempDir.resolve("students.csv"); String content = String.join("\n", "groupNumber,averageGrade,recordBookNumber", - "M10,4.5,12345", // валидно: 1 буква + 2 цифры - "B05,3.8,12346" // валидно + "M10,4.5,12345", + "B05,3.8,12346" ); Files.writeString(csv, content, StandardCharsets.UTF_8); try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { @@ -67,14 +62,15 @@ class StudentCsvReaderTest { M101,4.5 """; Files.writeString(csv, content, StandardCharsets.UTF_8); - var reader = new StudentCsvReader(csv.toAbsolutePath()); - assertTrue(reader.hasNext()); - // следующая строка имеет только 2 колонки - IllegalArgumentException ex = assertThrows( - IllegalArgumentException.class, - reader::next - ); - assertTrue(ex.getMessage().contains("Expected 3 columns")); + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + assertTrue(reader.hasNext()); + + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + reader::next + ); + assertTrue(ex.getMessage().contains("Expected 3 columns")); + } } @Test void shouldTestparseErrorThrowsExceptionWithDetails() throws IOException { @@ -84,14 +80,15 @@ class StudentCsvReaderTest { M101,not-a-number,12345 """; Files.writeString(csv, content, StandardCharsets.UTF_8); + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + assertTrue(reader.hasNext()); - var reader = new StudentCsvReader(csv.toAbsolutePath()); - assertTrue(reader.hasNext()); - IllegalArgumentException ex = assertThrows( - IllegalArgumentException.class, - reader::next - ); - assertTrue(ex.getMessage().contains("cannot parse averageGrade")); + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + reader::next + ); + assertTrue(ex.getMessage().contains("cannot parse averageGrade")); + } } @Test void shouldTestemptyDataAfterHeaderThrowsNoSuchElement() throws IOException { @@ -100,17 +97,22 @@ class StudentCsvReaderTest { groupNumber,averageGrade,recordBookNumber """; Files.writeString(csv, content, StandardCharsets.UTF_8); - var reader = new StudentCsvReader(csv.toAbsolutePath()); - assertFalse(reader.hasNext()); - assertThrows(NoSuchElementException.class, reader::next); + + // ИСПРАВЛЕНО: добавлен try-with-resources + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + assertFalse(reader.hasNext()); + + assertThrows(NoSuchElementException.class, reader::next); + } } @Test void shouldTestrelativePathThrowsException() { Path relative = Path.of("relative-path.csv"); + IllegalArgumentException ex = assertThrows( IllegalArgumentException.class, () -> new StudentCsvReader(relative) ); assertTrue(ex.getMessage().contains("absolute")); } -} + } \ No newline at end of file From 2971548e5280d4125b05b6b8922ff2de2204371b 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, 1 Sep 2026 17:24:27 +0300 Subject: [PATCH 142/156] chore: remove unused Paths --- app/src/main/java/ru/project/io/StudentCsvReader.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/ru/project/io/StudentCsvReader.java b/app/src/main/java/ru/project/io/StudentCsvReader.java index 5974c04..db2a4a9 100644 --- a/app/src/main/java/ru/project/io/StudentCsvReader.java +++ b/app/src/main/java/ru/project/io/StudentCsvReader.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; import java.util.Iterator; import java.util.Map; From 0144d42d0071bb2d3be0669d0acff5894dac381d 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, 1 Sep 2026 17:54:21 +0300 Subject: [PATCH 143/156] fix : Student and collection import --- app/src/main/java/ru/project/search/StudentSearchService.java | 4 ++-- app/src/test/java/ru/project/search/StudentSearchTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/ru/project/search/StudentSearchService.java b/app/src/main/java/ru/project/search/StudentSearchService.java index 4ced3c9..4d92689 100644 --- a/app/src/main/java/ru/project/search/StudentSearchService.java +++ b/app/src/main/java/ru/project/search/StudentSearchService.java @@ -1,7 +1,7 @@ package ru.project.search; -import ru.project.list.MyList; -import ru.project.list.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.collection.MyLinkedList; import ru.project.student.Student; public class StudentSearchService { diff --git a/app/src/test/java/ru/project/search/StudentSearchTest.java b/app/src/test/java/ru/project/search/StudentSearchTest.java index d12bd9c..e76e122 100644 --- a/app/src/test/java/ru/project/search/StudentSearchTest.java +++ b/app/src/test/java/ru/project/search/StudentSearchTest.java @@ -1,8 +1,8 @@ package ru.project.search; import org.junit.jupiter.api.Test; -import ru.project.list.MyList; -import ru.project.list.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.collection.MyLinkedList; import ru.project.student.Student; import static org.junit.jupiter.api.Assertions.*; From c046265d11a3effc7d956bc1851ad32f5c0c276a 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, 1 Sep 2026 20:02:32 +0400 Subject: [PATCH 144/156] test: correct student search criteria test cases --- app/src/test/java/ru/project/search/StudentSearchTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/ru/project/search/StudentSearchTest.java b/app/src/test/java/ru/project/search/StudentSearchTest.java index db43fa1..2b9b756 100644 --- a/app/src/test/java/ru/project/search/StudentSearchTest.java +++ b/app/src/test/java/ru/project/search/StudentSearchTest.java @@ -79,7 +79,7 @@ class StudentSearchTest { @Test void testAverageGradeRangeDoesNotMatchAbove() { StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); - Student student = new Student("A12", 5.1, 123); + Student student = new Student("A12", 2.0, 123); assertFalse(criteria.matches(student)); } @@ -199,8 +199,10 @@ class StudentSearchTest { Student student = new Student("B20", 4.5, 150); assertTrue(outer.matches(student)); + student = new Student("B20", 3.5, 150); - assertFalse(outer.matches(student)); + assertTrue(outer.matches(student)); + student = new Student("D00", 4.5, 150); assertFalse(outer.matches(student)); } From 62fabdd97c54756ae00f387fb3766e5d06e83dd6 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, 1 Sep 2026 21:55:11 +0400 Subject: [PATCH 145/156] feat(creation): complete random fill settings --- .../random/RandomDataSettingsWindow.java | 48 +++++++++++++++++++ .../creation/random/RandomFillSettings.java | 12 +++++ .../creation/random/RandomFillStrategy.java | 33 +++++++++++-- 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/ru/project/ui/creation/random/RandomDataSettingsWindow.java b/app/src/main/java/ru/project/ui/creation/random/RandomDataSettingsWindow.java index fcba4e1..5cc80c7 100644 --- a/app/src/main/java/ru/project/ui/creation/random/RandomDataSettingsWindow.java +++ b/app/src/main/java/ru/project/ui/creation/random/RandomDataSettingsWindow.java @@ -15,6 +15,7 @@ public class RandomDataSettingsWindow extends BaseModalWindow { private final RandomFillSettings settings; private final Consumer onApply; + private final Label collectionSizeLabel; private final Label minGroupNumberLabel; private final Label maxGroupNumberLabel; private final Label minAverageGradeLabel; @@ -32,6 +33,10 @@ public class RandomDataSettingsWindow extends BaseModalWindow { Panel panel = new Panel(); panel.setLayoutManager(new LinearLayout()); + collectionSizeLabel = + new Label("Размер коллекции: " + getValueOrDefault(settings.getCollectionSize())); + + panel.addComponent(collectionSizeLabel); minGroupNumberLabel = new Label("Минимальный номер группы: " + getValueOrDefault(settings.getMinGroupNumber())); @@ -61,6 +66,8 @@ public class RandomDataSettingsWindow extends BaseModalWindow { panel.addComponent(minRecordBookNumberLabel); panel.addComponent(maxRecordBookNumberLabel); + panel.addComponent(new Button("Задать размер коллекции...", this::setCollectionSize)); + panel.addComponent(new Button("Задать минимальный номер группы", this::setMinGroupNumber)); panel.addComponent(new Button("Задать максимальный номер группы", this::setMaxGroupNumber)); @@ -82,6 +89,41 @@ public class RandomDataSettingsWindow extends BaseModalWindow { setComponent(panel); } + private void setCollectionSize() { + InputValueWindow window = + new InputValueWindow( + gui, + "Размер коллекции", + "Введите размер коллекции:", + settings.getCollectionSize() == null + ? null + : String.valueOf(settings.getCollectionSize()), + value -> { + try { + int collectionSize = Integer.parseInt(value); + + if (collectionSize <= 0) { + MessageWindow.showModal(gui, "Размер коллекции должен быть больше 0."); + + return false; + } + + settings.setCollectionSize(collectionSize); + + collectionSizeLabel.setText("Размер коллекции: " + collectionSize); + + return true; + + } catch (NumberFormatException e) { + MessageWindow.showModal(gui, "Введите целое число."); + + return false; + } + }); + + window.showModal(); + } + private void setMinGroupNumber() { showGroupNumberInput( "Минимальный номер группы", @@ -302,6 +344,12 @@ public class RandomDataSettingsWindow extends BaseModalWindow { } private void apply() { + if (settings.getCollectionSize() == null) { + MessageWindow.showModal(gui, "Сначала задайте размер коллекции."); + + return; + } + onApply.accept(settings); MessageWindow.showModal(gui, "Настройки создания коллекции случайно успешно применены."); diff --git a/app/src/main/java/ru/project/ui/creation/random/RandomFillSettings.java b/app/src/main/java/ru/project/ui/creation/random/RandomFillSettings.java index eab4334..22befaf 100644 --- a/app/src/main/java/ru/project/ui/creation/random/RandomFillSettings.java +++ b/app/src/main/java/ru/project/ui/creation/random/RandomFillSettings.java @@ -2,6 +2,8 @@ package ru.project.ui.creation.random; public class RandomFillSettings { + private Integer collectionSize; + private String minGroupNumber; private String maxGroupNumber; @@ -12,6 +14,8 @@ public class RandomFillSettings { private Integer maxRecordBookNumber; public RandomFillSettings() { + this.collectionSize = null; + this.minGroupNumber = null; this.maxGroupNumber = null; @@ -22,6 +26,14 @@ public class RandomFillSettings { this.maxRecordBookNumber = null; } + public Integer getCollectionSize() { + return collectionSize; + } + + public void setCollectionSize(Integer collectionSize) { + this.collectionSize = collectionSize; + } + public String getMinGroupNumber() { return minGroupNumber; } diff --git a/app/src/main/java/ru/project/ui/creation/random/RandomFillStrategy.java b/app/src/main/java/ru/project/ui/creation/random/RandomFillStrategy.java index bbebc93..77dd072 100644 --- a/app/src/main/java/ru/project/ui/creation/random/RandomFillStrategy.java +++ b/app/src/main/java/ru/project/ui/creation/random/RandomFillStrategy.java @@ -1,19 +1,46 @@ package ru.project.ui.creation.random; import ru.project.collection.MyList; +import ru.project.random.RandomStudentGenerator; import ru.project.student.Student; import ru.project.ui.creation.FillStrategy; public class RandomFillStrategy implements FillStrategy { - // private final RandomFillSettings settings; + private final RandomFillSettings settings; public RandomFillStrategy(RandomFillSettings settings) { - // this.settings = settings; + this.settings = settings; } @Override public MyList create() { - return null; + RandomStudentGenerator generator = new RandomStudentGenerator(); + + if (settings.getMinGroupNumber() != null) { + generator.setMinGroupNumber(settings.getMinGroupNumber()); + } + + if (settings.getMaxGroupNumber() != null) { + generator.setMaxGroupNumber(settings.getMaxGroupNumber()); + } + + if (settings.getMinAverageGrade() != null) { + generator.setMinAverageGrade(settings.getMinAverageGrade()); + } + + if (settings.getMaxAverageGrade() != null) { + generator.setMaxAverageGrade(settings.getMaxAverageGrade()); + } + + if (settings.getMinRecordBookNumber() != null) { + generator.setMinRecordBookNumber(settings.getMinRecordBookNumber()); + } + + if (settings.getMaxRecordBookNumber() != null) { + generator.setMaxRecordBookNumber(settings.getMaxRecordBookNumber()); + } + + return generator.generate(settings.getCollectionSize()); } } From bd5aef0c6566c7f441ae7b1268278f628d95e8ec 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, 1 Sep 2026 22:16:52 +0400 Subject: [PATCH 146/156] refactor(io): use StudentCsvWriter for collection saving --- .../project/ui/common/CollectionFileSaveWindow.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/ru/project/ui/common/CollectionFileSaveWindow.java b/app/src/main/java/ru/project/ui/common/CollectionFileSaveWindow.java index 9388da9..bcd04de 100644 --- a/app/src/main/java/ru/project/ui/common/CollectionFileSaveWindow.java +++ b/app/src/main/java/ru/project/ui/common/CollectionFileSaveWindow.java @@ -4,11 +4,10 @@ import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import com.googlecode.lanterna.gui2.dialogs.FileDialogBuilder; import com.googlecode.lanterna.gui2.dialogs.MessageDialog; import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton; -import java.io.BufferedWriter; import java.io.File; -import java.io.FileWriter; import java.io.IOException; import ru.project.collection.MyList; +import ru.project.io.StudentCsvWriter; import ru.project.student.Student; public class CollectionFileSaveWindow { @@ -57,14 +56,11 @@ public class CollectionFileSaveWindow { } private void saveToFile(File file) { - try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + try { + StudentCsvWriter writer = new StudentCsvWriter(file.getAbsolutePath()); for (int i = 0; i < collection.size(); i++) { - Student student = collection.get(i); - - writer.write(student == null ? "null" : student.toString()); - - writer.newLine(); + writer.write(collection.get(i)); } MessageWindow.showModal(gui, "Коллекция успешно сохранена."); From a98265b44ef31cd1d9d2bf3e3539a0c36aff8b34 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, 1 Sep 2026 22:24:06 +0400 Subject: [PATCH 147/156] feat(creation): implement file-based collection strategy - add file settings to FileFillStrategy - read students from CSV using StudentCsvReader - create collection from file data - handle file reading errors --- .../ui/creation/file/FileFillStrategy.java | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/ru/project/ui/creation/file/FileFillStrategy.java b/app/src/main/java/ru/project/ui/creation/file/FileFillStrategy.java index 51f14f8..85cca03 100644 --- a/app/src/main/java/ru/project/ui/creation/file/FileFillStrategy.java +++ b/app/src/main/java/ru/project/ui/creation/file/FileFillStrategy.java @@ -1,19 +1,37 @@ package ru.project.ui.creation.file; +import java.io.IOException; +import java.nio.file.Path; +import ru.project.collection.MyLinkedList; import ru.project.collection.MyList; +import ru.project.io.StudentCsvReader; import ru.project.student.Student; import ru.project.ui.creation.FillStrategy; public class FileFillStrategy implements FillStrategy { - // private final FileFillSettings settings; + private final FileFillSettings settings; public FileFillStrategy(FileFillSettings settings) { - // this.settings = settings; + this.settings = settings; } @Override public MyList create() { - return null; + MyList collection = new MyLinkedList<>(); + + Path path = Path.of(settings.getAbsoluteFilePath()); + + try (StudentCsvReader reader = new StudentCsvReader(path)) { + + while (reader.hasNext()) { + collection.add(reader.next()); + } + + } catch (IOException e) { + throw new RuntimeException("Не удалось прочитать коллекцию из файла.", e); + } + + return collection; } } From 039b08c56560956784ea1499e26678cc91887c64 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, 1 Sep 2026 22:52:15 +0400 Subject: [PATCH 148/156] fix(ui): create file before saving collection --- .../ui/common/CollectionFileSaveWindow.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/ru/project/ui/common/CollectionFileSaveWindow.java b/app/src/main/java/ru/project/ui/common/CollectionFileSaveWindow.java index bcd04de..a8ce5eb 100644 --- a/app/src/main/java/ru/project/ui/common/CollectionFileSaveWindow.java +++ b/app/src/main/java/ru/project/ui/common/CollectionFileSaveWindow.java @@ -42,7 +42,7 @@ public class CollectionFileSaveWindow { MessageDialogButton.No); if (result == MessageDialogButton.Yes) { - saveToFile(selectedFile); + createAndSaveToFile(selectedFile); } } @@ -55,6 +55,20 @@ public class CollectionFileSaveWindow { .showDialog(gui); } + private void createAndSaveToFile(File file) { + try { + if (!file.createNewFile()) { + MessageWindow.showModal(gui, "Не удалось создать файл."); + return; + } + + saveToFile(file); + + } catch (IOException e) { + MessageWindow.showModal(gui, "Не удалось создать файл."); + } + } + private void saveToFile(File file) { try { StudentCsvWriter writer = new StudentCsvWriter(file.getAbsolutePath()); From 0c2c71878184f672f3ee6749991ec420a2a2b35f 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, 1 Sep 2026 21:57:28 +0300 Subject: [PATCH 149/156] fix: round averageGrade to two decimal places in RandomStudentGenerator --- .../java/ru/project/random/RandomStudentGenerator.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/ru/project/random/RandomStudentGenerator.java b/app/src/main/java/ru/project/random/RandomStudentGenerator.java index 25e6032..bad7de2 100644 --- a/app/src/main/java/ru/project/random/RandomStudentGenerator.java +++ b/app/src/main/java/ru/project/random/RandomStudentGenerator.java @@ -1,12 +1,10 @@ package ru.project.random; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import ru.project.collection.MyLinkedList; import ru.project.collection.MyList; import ru.project.student.Student; import java.util.Random; import java.util.regex.Pattern; - +import ru.project.student.StudentBuilder; public class RandomStudentGenerator { private static final Random RANDOM = new Random(); @@ -141,9 +139,9 @@ public class RandomStudentGenerator private double generateAverageGrade() { double min = (minAverageGrade != null) ? minAverageGrade : DEFAULT_MIN_GRADE; double max = (maxAverageGrade != null) ? maxAverageGrade : DEFAULT_MAX_GRADE; - double range = max - min; - return min + (range * RANDOM.nextDouble()); + double grade = min + (range * RANDOM.nextDouble()); + return Math.round(grade * 100.0) / 100.0; } private int generateRecordBookNumber() { int min = (minRecordBookNumber != null) ? minRecordBookNumber : DEFAULT_MIN_RECORD_BOOK; From 6e7040b2456c27427758d3ae06d51b71c626cce2 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, 1 Sep 2026 23:35:52 +0400 Subject: [PATCH 150/156] feat(random): generate students using streams - generate students with IntStream - collect generated students into MyList using MyListCollector - round average grade to two decimal places - extract student generation into a separate method --- .../random/RandomStudentGenerator.java | 189 ++++++++++++------ 1 file changed, 126 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/ru/project/random/RandomStudentGenerator.java b/app/src/main/java/ru/project/random/RandomStudentGenerator.java index 5ed279e..3e3c705 100644 --- a/app/src/main/java/ru/project/random/RandomStudentGenerator.java +++ b/app/src/main/java/ru/project/random/RandomStudentGenerator.java @@ -1,53 +1,63 @@ package ru.project.random; -import ru.project.collection.MyLinkedList; -import ru.project.collection.MyList; -import ru.project.student.Student; + import java.util.Random; import java.util.regex.Pattern; +import java.util.stream.IntStream; +import ru.project.collection.MyList; +import ru.project.collectors.MyCollectors; +import ru.project.student.Student; import ru.project.student.StudentBuilder; -public class RandomStudentGenerator -{ - private static final Random RANDOM = new Random(); - private String minGroupNumber; - private String maxGroupNumber; - private Double minAverageGrade; - private Double maxAverageGrade; - private Integer minRecordBookNumber; - private Integer maxRecordBookNumber; - private static final double DEFAULT_MIN_GRADE = 0.0; - private static final double DEFAULT_MAX_GRADE = 5.0; - private static final int DEFAULT_MIN_RECORD_BOOK = 1; - private static final String GROUP_PATTERN_REGEX = "^[A-Z][0-9]{2}$"; - private static final Pattern GROUP_PATTERN = Pattern.compile(GROUP_PATTERN_REGEX); - public RandomStudentGenerator setMinGroupNumber(String minGroupNumber) { - if (minGroupNumber != null && !GROUP_PATTERN.matcher(minGroupNumber).matches()) { - throw new IllegalArgumentException("minGroupNumber does not match Student group pattern: " + GROUP_PATTERN_REGEX); - } - this.minGroupNumber = minGroupNumber; - return this; - } - public RandomStudentGenerator setMaxGroupNumber(String maxGroupNumber) { - if (maxGroupNumber != null && !GROUP_PATTERN.matcher(maxGroupNumber).matches()) { - throw new IllegalArgumentException("maxGroupNumber does not match Student group pattern: " + GROUP_PATTERN_REGEX); - } - this.maxGroupNumber = maxGroupNumber; - return this; + +public class RandomStudentGenerator { + + private static final Random RANDOM = new Random(); + + private String minGroupNumber; + private String maxGroupNumber; + + private Double minAverageGrade; + private Double maxAverageGrade; + + private Integer minRecordBookNumber; + private Integer maxRecordBookNumber; + + private static final double DEFAULT_MIN_GRADE = 0.0; + private static final double DEFAULT_MAX_GRADE = 5.0; + private static final int DEFAULT_MIN_RECORD_BOOK = 1; + + private static final String GROUP_PATTERN_REGEX = "^[A-Z][0-9]{2}$"; + private static final Pattern GROUP_PATTERN = Pattern.compile(GROUP_PATTERN_REGEX); + + public RandomStudentGenerator setMinGroupNumber(String minGroupNumber) { + + if (minGroupNumber != null && !GROUP_PATTERN.matcher(minGroupNumber).matches()) { + + throw new IllegalArgumentException( + "minGroupNumber does not match Student group pattern: " + GROUP_PATTERN_REGEX); } + this.minGroupNumber = minGroupNumber; + return this; } public RandomStudentGenerator setMaxGroupNumber(String maxGroupNumber) { + if (maxGroupNumber != null && !GROUP_PATTERN.matcher(maxGroupNumber).matches()) { + throw new IllegalArgumentException( "maxGroupNumber does not match Student group pattern: " + GROUP_PATTERN_REGEX); } + this.maxGroupNumber = maxGroupNumber; + return this; } public RandomStudentGenerator setMinAverageGrade(double minAverageGrade) { + if (minAverageGrade < DEFAULT_MIN_GRADE || minAverageGrade > DEFAULT_MAX_GRADE) { + throw new IllegalArgumentException( "minAverageGrade out of Student allowed range [" + DEFAULT_MIN_GRADE @@ -55,12 +65,16 @@ public class RandomStudentGenerator + DEFAULT_MAX_GRADE + "]"); } + this.minAverageGrade = minAverageGrade; + return this; } public RandomStudentGenerator setMaxAverageGrade(double maxAverageGrade) { + if (maxAverageGrade < DEFAULT_MIN_GRADE || maxAverageGrade > DEFAULT_MAX_GRADE) { + throw new IllegalArgumentException( "maxAverageGrade out of Student allowed range [" + DEFAULT_MIN_GRADE @@ -68,25 +82,33 @@ public class RandomStudentGenerator + DEFAULT_MAX_GRADE + "]"); } + this.maxAverageGrade = maxAverageGrade; + return this; } public RandomStudentGenerator setMinRecordBookNumber(int minRecordBookNumber) { + if (minRecordBookNumber < DEFAULT_MIN_RECORD_BOOK) { throw new IllegalArgumentException( "minRecordBookNumber must be >= " + DEFAULT_MIN_RECORD_BOOK); } + this.minRecordBookNumber = minRecordBookNumber; + return this; } public RandomStudentGenerator setMaxRecordBookNumber(int maxRecordBookNumber) { + if (maxRecordBookNumber < DEFAULT_MIN_RECORD_BOOK) { throw new IllegalArgumentException( "maxRecordBookNumber must be >= " + DEFAULT_MIN_RECORD_BOOK); } + this.maxRecordBookNumber = maxRecordBookNumber; + return this; } @@ -94,75 +116,116 @@ public class RandomStudentGenerator if (size < 0) { throw new IllegalArgumentException("size must be non-negative"); } + validateRanges(); - MyList result = new MyLinkedList<>(); - for (int i = 0; i < size; i++) { - String group = generateGroupNumber(); - double grade = generateAverageGrade(); - int recordBook = generateRecordBookNumber(); - Student student = - new StudentBuilder() - .setGroupNumber(group) - .setAverageGrade(grade) - .setRecordBookNumber(recordBook) - .build(); - - result.add(student); - } - return result; + return IntStream.range(0, size) + .mapToObj(i -> generateStudent()) + .collect(MyCollectors.toMyList()); } - private double generateAverageGrade() { - double min = (minAverageGrade != null) ? minAverageGrade : DEFAULT_MIN_GRADE; - double max = (maxAverageGrade != null) ? maxAverageGrade : DEFAULT_MAX_GRADE; - double range = max - min; - double grade = min + (range * RANDOM.nextDouble()); - return Math.round(grade * 100.0) / 100.0; + private Student generateStudent() { + String groupNumber = generateGroupNumber(); + + double averageGrade = generateAverageGrade(); + + int recordBookNumber = generateRecordBookNumber(); + + return new StudentBuilder() + .setGroupNumber(groupNumber) + .setAverageGrade(averageGrade) + .setRecordBookNumber(recordBookNumber) + .build(); + } + + private void validateRanges() { + double effectiveMinGrade = minAverageGrade != null ? minAverageGrade : DEFAULT_MIN_GRADE; + + double effectiveMaxGrade = maxAverageGrade != null ? maxAverageGrade : DEFAULT_MAX_GRADE; + + if (effectiveMinGrade > effectiveMaxGrade) { + throw new IllegalArgumentException("minAverageGrade cannot be greater than maxAverageGrade"); } - if (minGroupNumber != null && maxGroupNumber != null) { - if (minGroupNumber.compareTo(maxGroupNumber) > 0) { - throw new IllegalArgumentException("minGroupNumber cannot be greater than maxGroupNumber"); - } + + int effectiveMinRecord = + minRecordBookNumber != null ? minRecordBookNumber : DEFAULT_MIN_RECORD_BOOK; + + int effectiveMaxRecord = maxRecordBookNumber != null ? maxRecordBookNumber : Integer.MAX_VALUE; + + if (effectiveMaxRecord == Integer.MAX_VALUE) { + effectiveMaxRecord = effectiveMinRecord + 999_999; + } + + if (effectiveMinRecord > effectiveMaxRecord) { + throw new IllegalArgumentException( + "minRecordBookNumber cannot be greater than maxRecordBookNumber"); + } + + if (minGroupNumber != null + && maxGroupNumber != null + && minGroupNumber.compareTo(maxGroupNumber) > 0) { + + throw new IllegalArgumentException("minGroupNumber cannot be greater than maxGroupNumber"); } } private String generateGroupNumber() { - String min = (minGroupNumber != null) ? minGroupNumber : "A00"; - String max = (maxGroupNumber != null) ? maxGroupNumber : "Z99"; + String min = minGroupNumber != null ? minGroupNumber : "A00"; + + String max = maxGroupNumber != null ? maxGroupNumber : "Z99"; + char minLetter = min.charAt(0); char maxLetter = max.charAt(0); + int minDigit = Integer.parseInt(min.substring(1)); + int maxDigit = Integer.parseInt(max.substring(1)); + char letter = (char) (minLetter + RANDOM.nextInt((maxLetter - minLetter) + 1)); + int digit; + if (letter == minLetter && letter == maxLetter) { + digit = minDigit + RANDOM.nextInt((maxDigit - minDigit) + 1); + } else if (letter == minLetter) { + digit = minDigit + RANDOM.nextInt(100 - minDigit); + } else if (letter == maxLetter) { + digit = RANDOM.nextInt(maxDigit + 1); + } else { + digit = RANDOM.nextInt(100); } + return letter + String.format("%02d", digit); } private double generateAverageGrade() { - double min = (minAverageGrade != null) ? minAverageGrade : DEFAULT_MIN_GRADE; - double max = (maxAverageGrade != null) ? maxAverageGrade : DEFAULT_MAX_GRADE; + double min = minAverageGrade != null ? minAverageGrade : DEFAULT_MIN_GRADE; + + double max = maxAverageGrade != null ? maxAverageGrade : DEFAULT_MAX_GRADE; double range = max - min; - return min + (range * RANDOM.nextDouble()); + + double grade = min + (range * RANDOM.nextDouble()); + + return Math.round(grade * 100.0) / 100.0; } private int generateRecordBookNumber() { - int min = (minRecordBookNumber != null) ? minRecordBookNumber : DEFAULT_MIN_RECORD_BOOK; - int max = - (maxRecordBookNumber != null) ? maxRecordBookNumber : min + 999_999; // разумный максимум + int min = minRecordBookNumber != null ? minRecordBookNumber : DEFAULT_MIN_RECORD_BOOK; + + int max = maxRecordBookNumber != null ? maxRecordBookNumber : min + 999_999; + if (max < min) { max = min; } + return min + RANDOM.nextInt((max - min) + 1); } } From f7ee9e2da6da242ffef2bce9763575f45239da69 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, 2 Sep 2026 13:01:57 +0400 Subject: [PATCH 151/156] feat(collection): add stream support to MyList --- .../java/ru/project/collection/MyLinkedList.java | 13 +++++++++++++ app/src/main/java/ru/project/collection/MyList.java | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/app/src/main/java/ru/project/collection/MyLinkedList.java b/app/src/main/java/ru/project/collection/MyLinkedList.java index a919309..abbd9c5 100644 --- a/app/src/main/java/ru/project/collection/MyLinkedList.java +++ b/app/src/main/java/ru/project/collection/MyLinkedList.java @@ -1,11 +1,14 @@ package ru.project.collection; import java.util.Arrays; +import java.util.stream.IntStream; +import java.util.stream.Stream; public class MyLinkedList implements MyList { private Node head; private int size; + @Override public void add(T value) { if (head == null) { this.head = new Node<>(value); @@ -19,6 +22,7 @@ public class MyLinkedList implements MyList { size++; } + @Override public T get(int index) { int currentIndex = 0; Node temp = head; @@ -34,6 +38,7 @@ public class MyLinkedList implements MyList { throw new IllegalArgumentException(); } + @Override public void remove(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(); @@ -57,6 +62,7 @@ public class MyLinkedList implements MyList { } } + @Override public T set(int index, T value) { if (index < 0 || index >= size) { throw new IllegalArgumentException(); @@ -79,6 +85,8 @@ public class MyLinkedList implements MyList { public boolean isEmpty() { return size == 0; } + + @Override public void addAll(MyList collection) { if (collection == null || collection.isEmpty()) { return; @@ -101,6 +109,11 @@ public class MyLinkedList implements MyList { return Arrays.toString(res); } + @Override + public Stream stream() { + return IntStream.range(0, size()).mapToObj(this::get); + } + private static class Node { private T value; private Node next; diff --git a/app/src/main/java/ru/project/collection/MyList.java b/app/src/main/java/ru/project/collection/MyList.java index 27af25d..70411d1 100644 --- a/app/src/main/java/ru/project/collection/MyList.java +++ b/app/src/main/java/ru/project/collection/MyList.java @@ -1,5 +1,7 @@ package ru.project.collection; +import java.util.stream.Stream; + public interface MyList { void add(T element); @@ -13,5 +15,8 @@ public interface MyList { int size(); boolean isEmpty(); + void addAll(MyList collection); + + Stream stream(); } From 5e9eb6b46a9c45502968ebba6bc382a4960653ab 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, 2 Sep 2026 13:05:56 +0400 Subject: [PATCH 152/156] refactor(creation): use stream for manual collection creation --- .../ui/creation/manual/ManualFillStrategy.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/ru/project/ui/creation/manual/ManualFillStrategy.java b/app/src/main/java/ru/project/ui/creation/manual/ManualFillStrategy.java index 1a5c54e..69d47aa 100644 --- a/app/src/main/java/ru/project/ui/creation/manual/ManualFillStrategy.java +++ b/app/src/main/java/ru/project/ui/creation/manual/ManualFillStrategy.java @@ -1,7 +1,7 @@ package ru.project.ui.creation.manual; -import ru.project.collection.MyLinkedList; import ru.project.collection.MyList; +import ru.project.collectors.MyCollectors; import ru.project.student.Student; import ru.project.ui.creation.FillStrategy; @@ -15,14 +15,6 @@ public class ManualFillStrategy implements FillStrategy { @Override public MyList create() { - MyList mainCollection = new MyLinkedList<>(); - - MyList manualDataCollection = settings.getCollection(); - - for (int i = 0; i < manualDataCollection.size(); i++) { - mainCollection.add(manualDataCollection.get(i)); - } - - return mainCollection; + return settings.getCollection().stream().collect(MyCollectors.toMyList()); } } From d7f4d30cc3199fe396e9942fe5beb8be8cb3953d 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, 2 Sep 2026 13:08:32 +0400 Subject: [PATCH 153/156] refactor(creation): use stream for file collection creation --- .../ui/creation/file/FileFillStrategy.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/ru/project/ui/creation/file/FileFillStrategy.java b/app/src/main/java/ru/project/ui/creation/file/FileFillStrategy.java index 85cca03..aa5feb5 100644 --- a/app/src/main/java/ru/project/ui/creation/file/FileFillStrategy.java +++ b/app/src/main/java/ru/project/ui/creation/file/FileFillStrategy.java @@ -2,8 +2,11 @@ package ru.project.ui.creation.file; import java.io.IOException; import java.nio.file.Path; -import ru.project.collection.MyLinkedList; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.StreamSupport; import ru.project.collection.MyList; +import ru.project.collectors.MyCollectors; import ru.project.io.StudentCsvReader; import ru.project.student.Student; import ru.project.ui.creation.FillStrategy; @@ -18,20 +21,16 @@ public class FileFillStrategy implements FillStrategy { @Override public MyList create() { - MyList collection = new MyLinkedList<>(); - Path path = Path.of(settings.getAbsoluteFilePath()); try (StudentCsvReader reader = new StudentCsvReader(path)) { - while (reader.hasNext()) { - collection.add(reader.next()); - } + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize(reader, Spliterator.ORDERED), false) + .collect(MyCollectors.toMyList()); } catch (IOException e) { throw new RuntimeException("Не удалось прочитать коллекцию из файла.", e); } - - return collection; } } From 1ec1d68a02ae7acfbe14292ba3bec45470f04537 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, 2 Sep 2026 13:14:28 +0400 Subject: [PATCH 154/156] style(code): unify formatting across io and search components --- .../java/ru/project/io/StudentCsvReader.java | 143 +++++++------ .../search/AverageGradeRangeCriteria.java | 28 +-- .../CompositeStudentSearchCriteria.java | 78 +++---- .../search/GroupNumberRangeCriteria.java | 34 ++-- .../search/RecordBookNumberRangeCriteria.java | 28 +-- .../ru/project/search/SearchOperation.java | 6 +- .../project/search/StudentSearchCriteria.java | 4 +- .../ru/project/io/StudentCsvReaderTest.java | 190 +++++++++--------- 8 files changed, 261 insertions(+), 250 deletions(-) diff --git a/app/src/main/java/ru/project/io/StudentCsvReader.java b/app/src/main/java/ru/project/io/StudentCsvReader.java index db2a4a9..7cc6deb 100644 --- a/app/src/main/java/ru/project/io/StudentCsvReader.java +++ b/app/src/main/java/ru/project/io/StudentCsvReader.java @@ -13,76 +13,89 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import ru.project.student.Student; import ru.project.student.StudentBuilder; - public class StudentCsvReader implements Iterator, AutoCloseable { + +public class StudentCsvReader implements Iterator, AutoCloseable { private static final String GROUP_NUMBER = "groupNumber"; private static final String AVERAGE_GRADE = "averageGrade"; private static final String RECORD_BOOK_NUMBER = "recordBookNumber"; private final CSVParser parser; private final Iterator records; - private int lineOfFile=0; - public StudentCsvReader(Path path) { - if (!path.isAbsolute()) { - throw new IllegalArgumentException("File path must be absolute"); - } - try { - CSVFormat format = CSVFormat.DEFAULT.builder() - .setHeader() - .setSkipHeaderRecord(true) - .get(); - this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); - validateHeader(parser.getHeaderMap()); - this.records = parser.iterator(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + private int lineOfFile = 0; + + public StudentCsvReader(Path path) { + if (!path.isAbsolute()) { + throw new IllegalArgumentException("File path must be absolute"); } - @Override - public boolean hasNext() { - return records.hasNext(); - } - @Override - public Student next() { - if (!records.hasNext()) { - throw new NoSuchElementException("No more records at line " + (lineOfFile + 1)); - } - lineOfFile++; - CSVRecord record = records.next(); - if (record.size() != 3) { - throw new IllegalArgumentException( - "Problem at line " + lineOfFile + ". Expected 3 columns, but got " + record.size()); - } - String groupNumber = record.get(GROUP_NUMBER); - String averageGradeStr = record.get(AVERAGE_GRADE); - String recordBookNumberStr = record.get(RECORD_BOOK_NUMBER); - double averageGrade; - int recordBookNumber; - try { - averageGrade = Double.parseDouble(averageGradeStr); - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "Problem at line " + lineOfFile + ": cannot parse averageGrade '" + averageGradeStr + "' as double", e); - } - try { - recordBookNumber = Integer.parseInt(recordBookNumberStr); - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "Problem at line " + lineOfFile + ": cannot parse recordBookNumber '" + recordBookNumberStr + "' as int", e); - } - return new StudentBuilder() - .setGroupNumber(groupNumber) - .setAverageGrade(averageGrade) - .setRecordBookNumber(recordBookNumber) - .build(); - } - @Override - public void close() throws IOException { - parser.close(); - } - private static void validateHeader(Map headerMap) { - String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER}; - if (headerMap.size() != expected.length - || !headerMap.keySet().containsAll(Arrays.asList(expected))) { - throw new IllegalArgumentException("Invalid CSV header"); - } + try { + CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).get(); + this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); + validateHeader(parser.getHeaderMap()); + this.records = parser.iterator(); + } catch (IOException e) { + throw new UncheckedIOException(e); } } + + @Override + public boolean hasNext() { + return records.hasNext(); + } + + @Override + public Student next() { + if (!records.hasNext()) { + throw new NoSuchElementException("No more records at line " + (lineOfFile + 1)); + } + lineOfFile++; + CSVRecord record = records.next(); + if (record.size() != 3) { + throw new IllegalArgumentException( + "Problem at line " + lineOfFile + ". Expected 3 columns, but got " + record.size()); + } + String groupNumber = record.get(GROUP_NUMBER); + String averageGradeStr = record.get(AVERAGE_GRADE); + String recordBookNumberStr = record.get(RECORD_BOOK_NUMBER); + double averageGrade; + int recordBookNumber; + try { + averageGrade = Double.parseDouble(averageGradeStr); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Problem at line " + + lineOfFile + + ": cannot parse averageGrade '" + + averageGradeStr + + "' as double", + e); + } + try { + recordBookNumber = Integer.parseInt(recordBookNumberStr); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Problem at line " + + lineOfFile + + ": cannot parse recordBookNumber '" + + recordBookNumberStr + + "' as int", + e); + } + return new StudentBuilder() + .setGroupNumber(groupNumber) + .setAverageGrade(averageGrade) + .setRecordBookNumber(recordBookNumber) + .build(); + } + + @Override + public void close() throws IOException { + parser.close(); + } + + private static void validateHeader(Map headerMap) { + String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER}; + if (headerMap.size() != expected.length + || !headerMap.keySet().containsAll(Arrays.asList(expected))) { + throw new IllegalArgumentException("Invalid CSV header"); + } + } +} diff --git a/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java b/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java index a53a7c5..dd7ea4b 100644 --- a/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java +++ b/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java @@ -3,20 +3,20 @@ package ru.project.search; import ru.project.student.Student; public class AverageGradeRangeCriteria implements StudentSearchCriteria { - private final double min; - private final double max; + private final double min; + private final double max; - public AverageGradeRangeCriteria(double min, double max) { - if (min > max) { - throw new IllegalArgumentException("Min grade must be <= max grade"); - } - this.min = min; - this.max = max; + public AverageGradeRangeCriteria(double min, double max) { + if (min > max) { + throw new IllegalArgumentException("Min grade must be <= max grade"); } + this.min = min; + this.max = max; + } - @Override - public boolean matches(Student student) { - double grade = student.getAverageGrade(); - return grade >= min && grade <= max; - } -} \ No newline at end of file + @Override + public boolean matches(Student student) { + double grade = student.getAverageGrade(); + return grade >= min && grade <= max; + } +} diff --git a/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java b/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java index 063d087..3b87850 100644 --- a/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java +++ b/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java @@ -1,51 +1,51 @@ package ru.project.search; -import ru.project.student.Student; import java.util.ArrayList; import java.util.List; +import ru.project.student.Student; public class CompositeStudentSearchCriteria implements StudentSearchCriteria { - private final SearchOperation operation; - private final List criteriaList = new ArrayList<>(); + private final SearchOperation operation; + private final List criteriaList = new ArrayList<>(); - public CompositeStudentSearchCriteria(SearchOperation operation) { - if (operation == null) { - throw new IllegalArgumentException("Operation must not be null"); - } - this.operation = operation; + public CompositeStudentSearchCriteria(SearchOperation operation) { + if (operation == null) { + throw new IllegalArgumentException("Operation must not be null"); + } + this.operation = operation; + } + + public void add(StudentSearchCriteria criteria) { + if (criteria == null) { + throw new IllegalArgumentException("Criteria must not be null"); + } + criteriaList.add(criteria); + } + + public void remove(StudentSearchCriteria criteria) { + criteriaList.remove(criteria); + } + + @Override + public boolean matches(Student student) { + if (criteriaList.isEmpty()) { + return operation == SearchOperation.INTERSECTION; } - public void add(StudentSearchCriteria criteria) { - if (criteria == null) { - throw new IllegalArgumentException("Criteria must not be null"); + if (operation == SearchOperation.UNION) { + for (StudentSearchCriteria c : criteriaList) { + if (c.matches(student)) { + return true; } - criteriaList.add(criteria); - } - - public void remove(StudentSearchCriteria criteria) { - criteriaList.remove(criteria); - } - - @Override - public boolean matches(Student student) { - if (criteriaList.isEmpty()) { - return operation == SearchOperation.INTERSECTION; - } - - if (operation == SearchOperation.UNION) { - for (StudentSearchCriteria c : criteriaList) { - if (c.matches(student)) { - return true; - } - } - return false; - } else { - for (StudentSearchCriteria c : criteriaList) { - if (!c.matches(student)) { - return false; - } - } - return true; + } + return false; + } else { + for (StudentSearchCriteria c : criteriaList) { + if (!c.matches(student)) { + return false; } + } + return true; } -} \ No newline at end of file + } +} diff --git a/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java b/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java index 61ea406..636b64d 100644 --- a/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java +++ b/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java @@ -3,23 +3,23 @@ package ru.project.search; import ru.project.student.Student; public class GroupNumberRangeCriteria implements StudentSearchCriteria { - private final String min; - private final String max; + private final String min; + private final String max; - public GroupNumberRangeCriteria(String min, String max) { - if (min == null || max == null) { - throw new IllegalArgumentException("Min and max must not be null"); - } - if (min.compareTo(max) > 0) { - throw new IllegalArgumentException("Min group must be <= max group"); - } - this.min = min; - this.max = max; + public GroupNumberRangeCriteria(String min, String max) { + if (min == null || max == null) { + throw new IllegalArgumentException("Min and max must not be null"); } + if (min.compareTo(max) > 0) { + throw new IllegalArgumentException("Min group must be <= max group"); + } + this.min = min; + this.max = max; + } - @Override - public boolean matches(Student student) { - String group = student.getGroupNumber(); - return group.compareTo(min) >= 0 && group.compareTo(max) <= 0; - } -} \ No newline at end of file + @Override + public boolean matches(Student student) { + String group = student.getGroupNumber(); + return group.compareTo(min) >= 0 && group.compareTo(max) <= 0; + } +} diff --git a/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java b/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java index 41131b4..39998f7 100644 --- a/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java +++ b/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java @@ -3,20 +3,20 @@ package ru.project.search; import ru.project.student.Student; public class RecordBookNumberRangeCriteria implements StudentSearchCriteria { - private final int min; - private final int max; + private final int min; + private final int max; - public RecordBookNumberRangeCriteria(int min, int max) { - if (min > max) { - throw new IllegalArgumentException("Min record number must be <= max"); - } - this.min = min; - this.max = max; + public RecordBookNumberRangeCriteria(int min, int max) { + if (min > max) { + throw new IllegalArgumentException("Min record number must be <= max"); } + this.min = min; + this.max = max; + } - @Override - public boolean matches(Student student) { - int number = student.getRecordBookNumber(); - return number >= min && number <= max; - } -} \ No newline at end of file + @Override + public boolean matches(Student student) { + int number = student.getRecordBookNumber(); + return number >= min && number <= max; + } +} diff --git a/app/src/main/java/ru/project/search/SearchOperation.java b/app/src/main/java/ru/project/search/SearchOperation.java index d616f97..badfc12 100644 --- a/app/src/main/java/ru/project/search/SearchOperation.java +++ b/app/src/main/java/ru/project/search/SearchOperation.java @@ -1,6 +1,6 @@ package ru.project.search; public enum SearchOperation { - UNION, - INTERSECTION -} \ No newline at end of file + UNION, + INTERSECTION +} diff --git a/app/src/main/java/ru/project/search/StudentSearchCriteria.java b/app/src/main/java/ru/project/search/StudentSearchCriteria.java index f157982..283dc65 100644 --- a/app/src/main/java/ru/project/search/StudentSearchCriteria.java +++ b/app/src/main/java/ru/project/search/StudentSearchCriteria.java @@ -3,5 +3,5 @@ package ru.project.search; import ru.project.student.Student; public interface StudentSearchCriteria { - boolean matches(Student student); -} \ No newline at end of file + boolean matches(Student student); +} diff --git a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java index 3541f35..2cc4c35 100644 --- a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java +++ b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java @@ -1,4 +1,7 @@ package ru.project.io; + +import static org.junit.jupiter.api.Assertions.*; + import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -8,111 +11,106 @@ import java.util.Spliterators; import java.util.stream.StreamSupport; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import ru.project.student.Student; import ru.project.collection.MyList; import ru.project.collectors.MyCollectors; -import static org.junit.jupiter.api.Assertions.*; +import ru.project.student.Student; + class StudentCsvReaderTest { - @TempDir - Path tempDir; - @Test - void shouldTestreadValidCsvIntoMyList() throws IOException { - Path csv = tempDir.resolve("students.csv"); - String content = String.join("\n", - "groupNumber,averageGrade,recordBookNumber", - "M10,4.5,12345", - "B05,3.8,12346" - ); - Files.writeString(csv, content, StandardCharsets.UTF_8); - try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { - var studentStream = StreamSupport.stream( - Spliterators.spliteratorUnknownSize(reader, 0), - false - ); - MyList students = studentStream - .collect(MyCollectors.toMyList()); - assertEquals(2, students.size()); - assertEquals("M10", students.get(0).getGroupNumber()); - assertEquals(4.5, students.get(0).getAverageGrade(), 0.0); - assertEquals(12345, students.get(0).getRecordBookNumber()); - assertEquals("B05", students.get(1).getGroupNumber()); - assertEquals(3.8, students.get(1).getAverageGrade(), 0.0); - assertEquals(12346, students.get(1).getRecordBookNumber()); - } - } - @Test - void shouldTestinvalidHeaderThrowsException() throws IOException { - Path csv = tempDir.resolve("bad-header.csv"); - String content = """ - badGroup,badGrade,badBook - M101,4.5,12345 - """; - Files.writeString(csv, content, StandardCharsets.UTF_8); - IllegalArgumentException ex = assertThrows( - IllegalArgumentException.class, - () -> new StudentCsvReader(csv.toAbsolutePath()) - ); - assertTrue(ex.getMessage().contains("Invalid CSV header")); - } - @Test - void shouldTestwrongColumnCountThrowsException() throws IOException { - Path csv = tempDir.resolve("wrong-columns.csv"); - String content = """ - groupNumber,averageGrade,recordBookNumber - M101,4.5 - """; - Files.writeString(csv, content, StandardCharsets.UTF_8); - try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { - assertTrue(reader.hasNext()); + @TempDir Path tempDir; - IllegalArgumentException ex = assertThrows( - IllegalArgumentException.class, - reader::next - ); - assertTrue(ex.getMessage().contains("Expected 3 columns")); - } + @Test + void shouldTestreadValidCsvIntoMyList() throws IOException { + Path csv = tempDir.resolve("students.csv"); + String content = + String.join( + "\n", "groupNumber,averageGrade,recordBookNumber", "M10,4.5,12345", "B05,3.8,12346"); + Files.writeString(csv, content, StandardCharsets.UTF_8); + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + var studentStream = + StreamSupport.stream(Spliterators.spliteratorUnknownSize(reader, 0), false); + MyList students = studentStream.collect(MyCollectors.toMyList()); + assertEquals(2, students.size()); + assertEquals("M10", students.get(0).getGroupNumber()); + assertEquals(4.5, students.get(0).getAverageGrade(), 0.0); + assertEquals(12345, students.get(0).getRecordBookNumber()); + assertEquals("B05", students.get(1).getGroupNumber()); + assertEquals(3.8, students.get(1).getAverageGrade(), 0.0); + assertEquals(12346, students.get(1).getRecordBookNumber()); } - @Test - void shouldTestparseErrorThrowsExceptionWithDetails() throws IOException { - Path csv = tempDir.resolve("parse-error.csv"); - String content = """ - groupNumber,averageGrade,recordBookNumber - M101,not-a-number,12345 - """; - Files.writeString(csv, content, StandardCharsets.UTF_8); - try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { - assertTrue(reader.hasNext()); + } - IllegalArgumentException ex = assertThrows( - IllegalArgumentException.class, - reader::next - ); - assertTrue(ex.getMessage().contains("cannot parse averageGrade")); - } + @Test + void shouldTestinvalidHeaderThrowsException() throws IOException { + Path csv = tempDir.resolve("bad-header.csv"); + String content = + """ + badGroup,badGrade,badBook + M101,4.5,12345 + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + IllegalArgumentException ex = + assertThrows( + IllegalArgumentException.class, () -> new StudentCsvReader(csv.toAbsolutePath())); + assertTrue(ex.getMessage().contains("Invalid CSV header")); + } + + @Test + void shouldTestwrongColumnCountThrowsException() throws IOException { + Path csv = tempDir.resolve("wrong-columns.csv"); + String content = + """ + groupNumber,averageGrade,recordBookNumber + M101,4.5 + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + assertTrue(reader.hasNext()); + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, reader::next); + assertTrue(ex.getMessage().contains("Expected 3 columns")); } - @Test - void shouldTestemptyDataAfterHeaderThrowsNoSuchElement() throws IOException { - Path csv = tempDir.resolve("empty-data.csv"); - String content = """ - groupNumber,averageGrade,recordBookNumber - """; - Files.writeString(csv, content, StandardCharsets.UTF_8); + } - // ИСПРАВЛЕНО: добавлен try-with-resources - try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { - assertFalse(reader.hasNext()); + @Test + void shouldTestparseErrorThrowsExceptionWithDetails() throws IOException { + Path csv = tempDir.resolve("parse-error.csv"); + String content = + """ + groupNumber,averageGrade,recordBookNumber + M101,not-a-number,12345 + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + assertTrue(reader.hasNext()); - assertThrows(NoSuchElementException.class, reader::next); - } + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, reader::next); + assertTrue(ex.getMessage().contains("cannot parse averageGrade")); } - @Test - void shouldTestrelativePathThrowsException() { - Path relative = Path.of("relative-path.csv"); + } - IllegalArgumentException ex = assertThrows( - IllegalArgumentException.class, - () -> new StudentCsvReader(relative) - ); - assertTrue(ex.getMessage().contains("absolute")); + @Test + void shouldTestemptyDataAfterHeaderThrowsNoSuchElement() throws IOException { + Path csv = tempDir.resolve("empty-data.csv"); + String content = + """ + groupNumber,averageGrade,recordBookNumber + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + + // ИСПРАВЛЕНО: добавлен try-with-resources + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + assertFalse(reader.hasNext()); + + assertThrows(NoSuchElementException.class, reader::next); } - } \ No newline at end of file + } + + @Test + void shouldTestrelativePathThrowsException() { + Path relative = Path.of("relative-path.csv"); + + IllegalArgumentException ex = + assertThrows(IllegalArgumentException.class, () -> new StudentCsvReader(relative)); + assertTrue(ex.getMessage().contains("absolute")); + } +} From 39c17d37f52099e0d7f94e8a0effc8724fa1d3c9 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, 2 Sep 2026 14:26:54 +0400 Subject: [PATCH 155/156] build(maven): configure fat jar with bundled dependencies --- app/pom.xml | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/app/pom.xml b/app/pom.xml index d5108b2..1ce9aa3 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -112,15 +112,38 @@ + org.codehaus.mojo exec-maven-plugin 3.6.3 - ru.project.App + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + package + + shade + + + + + ru.project.App + + + + + + + From e96d7563c7010126f08a5173f52d645435468422 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, 2 Sep 2026 19:04:42 +0400 Subject: [PATCH 156/156] docs: add project README --- README | 256 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 README diff --git a/README b/README new file mode 100644 index 0000000..d587acd --- /dev/null +++ b/README @@ -0,0 +1,256 @@ +# Student Management Console Application + +Консольное Java-приложение для создания, хранения, сортировки, поиска и сохранения коллекций студентов. + +Проект разработан командой в рамках учебного задания и демонстрирует применение ООП, собственных коллекций, алгоритмов сортировки, Stream API, паттернов проектирования, CSV-ввода/вывода и unit-тестирования. + +--- + +## Возможности + +Приложение позволяет: + +- создавать коллекции студентов вручную; +- генерировать случайные данные; +- загружать студентов из CSV-файла; +- просматривать текущую коллекцию; +- сохранять коллекцию в CSV; +- сортировать студентов по номеру группы, среднему баллу и номеру зачётной книжки; +- выбирать алгоритм сортировки; +- выполнять специальную even-only сортировку; +- искать студентов по нескольким критериям; +- объединять критерии поиска через `И` / `ИЛИ`; +- подсчитывать количество вхождений студента; +- получать индексы найденных элементов. + +--- + +## Архитектура + +Основной поток данных: + +```text +Manual / CSV / Random + ↓ + Student + ↓ + MyList + ↓ + ┌─────┴─────┐ + ↓ ↓ + Sorting Search + │ │ + └─────┬─────┘ + ↓ + Console / CSV +``` + +Логика приложения разделена на отдельные функциональные области: модель данных, создание коллекции, хранение, сортировка, поиск, CSV-ввод/вывод и пользовательский интерфейс. + +--- + +## Основные компоненты + +### Student + +`Student` — основная модель данных приложения. + +Модель содержит данные студента, выполняет валидацию входных значений и определяет правила сравнения объектов через `equals()` и `hashCode()`. + +Для пошагового создания объектов используется `StudentBuilder`. + +### Custom Collection + +Для хранения студентов используются собственные: + +- `MyList`; +- `MyLinkedList`. + +Коллекция поддерживает основные операции работы с данными и интеграцию со Stream API. + +Для сбора результатов Stream обратно в `MyList` реализован собственный `MyListCollector`. + +### Sorting + +Система сортировки построена на паттерне **Strategy**. + +Доступны: + +- Merge Sort; +- Bubble Sort; +- even-only sorting. + +`SortStrategy` определяет алгоритм сортировки, а `Comparator` — поле, по которому сравниваются студенты. + +```text +SortStrategy + ↓ +КАК сортировать + +Comparator + ↓ +ПО КАКОМУ ПОЛЮ сортировать +``` + +### Search + +Поиск поддерживает несколько критериев: + +- номер группы; +- средний балл; +- номер зачётной книжки. + +Несколько условий могут объединяться через `И` / `ИЛИ`. + +Для составных условий используется паттерн **Composite**. + +Также реализован поиск количества вхождений студента и индексов найденных элементов. + +### CSV + +За работу с CSV отвечают: + +- `StudentCsvReader`; +- `StudentCsvWriter`. + +Загрузка данных: + +```text +CSV + ↓ +StudentCsvReader + ↓ +Student + ↓ +MyList +``` + +Сохранение: + +```text +MyList + ↓ +StudentCsvWriter + ↓ + CSV +``` + +### Создание коллекции + +Поддерживаются разные способы заполнения коллекции: + +- ручной ввод; +- случайная генерация; +- загрузка из файла. + +Для этого используются отдельные стратегии заполнения. + +--- + +## Использованные паттерны и подходы + +| Паттерн / подход | Использование | +|---|---| +| Builder | создание объектов `Student` | +| Strategy | выбор алгоритма сортировки и способа заполнения коллекции | +| Composite | объединение критериев поиска | +| Custom Collector | преобразование Stream в `MyList` | + +--- + +## Технологии + +- Java 21 +- Maven +- JUnit 5 +- Apache Commons CSV +- Java Stream API +- Git +- GitHub + +--- + +## Команда + +| Участник | GitHub | Основная зона ответственности | +|---|---|---| +| **Володин Герман** | — | архитектура приложения, Console UI, интеграция компонентов | +| **Артющик Анастасия** | `AnastasiArchi` | `Student`, `StudentBuilder`, even-only sorting | +| **Дюрягин Данил** | — | сортировка, `Comparator`, Composite Search | +| **Гаджикурбанов Темирлан** | `t1-f` | собственная коллекция, Collector, Random Generator, поиск вхождений | +| **Ершов Владислав** | `Vladislav-E-N` | CSV Reader/Writer, DataSource, обработка файлов | + +> Компоненты проекта разрабатывались параллельно и в ходе интеграции дорабатывались несколькими участниками. В таблице указаны основные зоны ответственности. + +--- + +## Тестирование + +Основные компоненты проекта покрыты unit-тестами с использованием **JUnit 5**. + +Проверяются: + +- модель `Student` и валидация; +- `equals()` / `hashCode()`; +- алгоритмы сортировки; +- even-only sorting; +- собственная коллекция; +- Custom Collector; +- поиск; +- генерация данных; +- CSV Reader/Writer. + +--- + +## Документация + +### Задания и компоненты + +- [Общее задание](TASK.ru.md) +- [Student](docs/tasks/Student.md) +- [Обновление Student](docs/tasks/StudentUpdate.md) +- [Собственная коллекция](docs/tasks/CustomList.md) +- [Сортировка](docs/tasks/Sorting.md) +- [Even-only sorting](docs/tasks/EvenOnlySortStrategy.md) +- [Composite Search](docs/tasks/CompositeStudentSearch.md) +- [Поиск вхождений](docs/tasks/StudentOccurrenceIndexFinder.md) +- [CSV Reader / Writer](docs/tasks/CsvStudentIO.md) +- [Stream и собственная коллекция](docs/tasks/StreamCollection.md) +- [Тестирование](docs/tasks/Testing.md) + +### Git и командная разработка + +- [Правила создания коммитов](CONTRIBUTING.ru.md) +- [Работа с зависимостями между ветками](GIT_BRANCH_DEPENDENCIES.ru.md) +- [Настройка Git](GIT_CONFIG.ru.md) +- [Восстановление после force-with-lease](GIT_FORCE_WITH_LEASE_RECOVERY.ru.md) + +--- + +## Запуск + +`pom.xml` находится в директории `app`. + +Перейдите в директорию приложения: + +```bash +cd app +``` + +Соберите проект: + +```bash +mvn clean package +``` + +Запустите собранное приложение: + +```bash +java -jar target/app-1.0-SNAPSHOT.jar +``` + +Основной класс приложения: + +```text +ru.project.App +```