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 01/18] 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 02/18] 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 03/18] 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 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 04/18] 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 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 05/18] 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 06/18] 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 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 07/18] 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 08/18] 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 09/18] 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 10/18] 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 11/18] 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 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 12/18] 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 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 13/18] 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 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 14/18] 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 15/18] 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 16/18] 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 17/18] 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 18/18] 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());