Merge branch 'release/1.0.0'
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
# Student Management Console Application
|
||||
|
||||
Консольное Java-приложение для создания, хранения, сортировки, поиска и сохранения коллекций студентов.
|
||||
|
||||
Проект разработан командой в рамках учебного задания и демонстрирует применение ООП, собственных коллекций, алгоритмов сортировки, Stream API, паттернов проектирования, CSV-ввода/вывода и unit-тестирования.
|
||||
|
||||
---
|
||||
|
||||
## Возможности
|
||||
|
||||
Приложение позволяет:
|
||||
|
||||
- создавать коллекции студентов вручную;
|
||||
- генерировать случайные данные;
|
||||
- загружать студентов из CSV-файла;
|
||||
- просматривать текущую коллекцию;
|
||||
- сохранять коллекцию в CSV;
|
||||
- сортировать студентов по номеру группы, среднему баллу и номеру зачётной книжки;
|
||||
- выбирать алгоритм сортировки;
|
||||
- выполнять специальную even-only сортировку;
|
||||
- искать студентов по нескольким критериям;
|
||||
- объединять критерии поиска через `И` / `ИЛИ`;
|
||||
- подсчитывать количество вхождений студента;
|
||||
- получать индексы найденных элементов.
|
||||
|
||||
---
|
||||
|
||||
## Архитектура
|
||||
|
||||
Основной поток данных:
|
||||
|
||||
```text
|
||||
Manual / CSV / Random
|
||||
↓
|
||||
Student
|
||||
↓
|
||||
MyList<Student>
|
||||
↓
|
||||
┌─────┴─────┐
|
||||
↓ ↓
|
||||
Sorting Search
|
||||
│ │
|
||||
└─────┬─────┘
|
||||
↓
|
||||
Console / CSV
|
||||
```
|
||||
|
||||
Логика приложения разделена на отдельные функциональные области: модель данных, создание коллекции, хранение, сортировка, поиск, CSV-ввод/вывод и пользовательский интерфейс.
|
||||
|
||||
---
|
||||
|
||||
## Основные компоненты
|
||||
|
||||
### Student
|
||||
|
||||
`Student` — основная модель данных приложения.
|
||||
|
||||
Модель содержит данные студента, выполняет валидацию входных значений и определяет правила сравнения объектов через `equals()` и `hashCode()`.
|
||||
|
||||
Для пошагового создания объектов используется `StudentBuilder`.
|
||||
|
||||
### Custom Collection
|
||||
|
||||
Для хранения студентов используются собственные:
|
||||
|
||||
- `MyList<T>`;
|
||||
- `MyLinkedList<T>`.
|
||||
|
||||
Коллекция поддерживает основные операции работы с данными и интеграцию со Stream API.
|
||||
|
||||
Для сбора результатов Stream обратно в `MyList` реализован собственный `MyListCollector`.
|
||||
|
||||
### Sorting
|
||||
|
||||
Система сортировки построена на паттерне **Strategy**.
|
||||
|
||||
Доступны:
|
||||
|
||||
- Merge Sort;
|
||||
- Bubble Sort;
|
||||
- even-only sorting.
|
||||
|
||||
`SortStrategy` определяет алгоритм сортировки, а `Comparator<Student>` — поле, по которому сравниваются студенты.
|
||||
|
||||
```text
|
||||
SortStrategy
|
||||
↓
|
||||
КАК сортировать
|
||||
|
||||
Comparator
|
||||
↓
|
||||
ПО КАКОМУ ПОЛЮ сортировать
|
||||
```
|
||||
|
||||
### Search
|
||||
|
||||
Поиск поддерживает несколько критериев:
|
||||
|
||||
- номер группы;
|
||||
- средний балл;
|
||||
- номер зачётной книжки.
|
||||
|
||||
Несколько условий могут объединяться через `И` / `ИЛИ`.
|
||||
|
||||
Для составных условий используется паттерн **Composite**.
|
||||
|
||||
Также реализован поиск количества вхождений студента и индексов найденных элементов.
|
||||
|
||||
### CSV
|
||||
|
||||
За работу с CSV отвечают:
|
||||
|
||||
- `StudentCsvReader`;
|
||||
- `StudentCsvWriter`.
|
||||
|
||||
Загрузка данных:
|
||||
|
||||
```text
|
||||
CSV
|
||||
↓
|
||||
StudentCsvReader
|
||||
↓
|
||||
Student
|
||||
↓
|
||||
MyList<Student>
|
||||
```
|
||||
|
||||
Сохранение:
|
||||
|
||||
```text
|
||||
MyList<Student>
|
||||
↓
|
||||
StudentCsvWriter
|
||||
↓
|
||||
CSV
|
||||
```
|
||||
|
||||
### Создание коллекции
|
||||
|
||||
Поддерживаются разные способы заполнения коллекции:
|
||||
|
||||
- ручной ввод;
|
||||
- случайная генерация;
|
||||
- загрузка из файла.
|
||||
|
||||
Для этого используются отдельные стратегии заполнения.
|
||||
|
||||
---
|
||||
|
||||
## Использованные паттерны и подходы
|
||||
|
||||
| Паттерн / подход | Использование |
|
||||
|---|---|
|
||||
| Builder | создание объектов `Student` |
|
||||
| Strategy | выбор алгоритма сортировки и способа заполнения коллекции |
|
||||
| Composite | объединение критериев поиска |
|
||||
| Custom Collector | преобразование Stream в `MyList` |
|
||||
|
||||
---
|
||||
|
||||
## Технологии
|
||||
|
||||
- Java 21
|
||||
- Maven
|
||||
- JUnit 5
|
||||
- Apache Commons CSV
|
||||
- Java Stream API
|
||||
- Git
|
||||
- GitHub
|
||||
|
||||
---
|
||||
|
||||
## Команда
|
||||
|
||||
| Участник | GitHub | Основная зона ответственности |
|
||||
|---|---|---|
|
||||
| **Володин Герман** | — | архитектура приложения, Console UI, интеграция компонентов |
|
||||
| **Артющик Анастасия** | `AnastasiArchi` | `Student`, `StudentBuilder`, even-only sorting |
|
||||
| **Дюрягин Данил** | — | сортировка, `Comparator`, Composite Search |
|
||||
| **Гаджикурбанов Темирлан** | `t1-f` | собственная коллекция, Collector, Random Generator, поиск вхождений |
|
||||
| **Ершов Владислав** | `Vladislav-E-N` | CSV Reader/Writer, DataSource, обработка файлов |
|
||||
|
||||
> Компоненты проекта разрабатывались параллельно и в ходе интеграции дорабатывались несколькими участниками. В таблице указаны основные зоны ответственности.
|
||||
|
||||
---
|
||||
|
||||
## Тестирование
|
||||
|
||||
Основные компоненты проекта покрыты unit-тестами с использованием **JUnit 5**.
|
||||
|
||||
Проверяются:
|
||||
|
||||
- модель `Student` и валидация;
|
||||
- `equals()` / `hashCode()`;
|
||||
- алгоритмы сортировки;
|
||||
- even-only sorting;
|
||||
- собственная коллекция;
|
||||
- Custom Collector;
|
||||
- поиск;
|
||||
- генерация данных;
|
||||
- CSV Reader/Writer.
|
||||
|
||||
---
|
||||
|
||||
## Документация
|
||||
|
||||
### Задания и компоненты
|
||||
|
||||
- [Общее задание](TASK.ru.md)
|
||||
- [Student](docs/tasks/Student.md)
|
||||
- [Обновление Student](docs/tasks/StudentUpdate.md)
|
||||
- [Собственная коллекция](docs/tasks/CustomList.md)
|
||||
- [Сортировка](docs/tasks/Sorting.md)
|
||||
- [Even-only sorting](docs/tasks/EvenOnlySortStrategy.md)
|
||||
- [Composite Search](docs/tasks/CompositeStudentSearch.md)
|
||||
- [Поиск вхождений](docs/tasks/StudentOccurrenceIndexFinder.md)
|
||||
- [CSV Reader / Writer](docs/tasks/CsvStudentIO.md)
|
||||
- [Stream и собственная коллекция](docs/tasks/StreamCollection.md)
|
||||
- [Тестирование](docs/tasks/Testing.md)
|
||||
|
||||
### Git и командная разработка
|
||||
|
||||
- [Правила создания коммитов](CONTRIBUTING.ru.md)
|
||||
- [Работа с зависимостями между ветками](GIT_BRANCH_DEPENDENCIES.ru.md)
|
||||
- [Настройка Git](GIT_CONFIG.ru.md)
|
||||
- [Восстановление после force-with-lease](GIT_FORCE_WITH_LEASE_RECOVERY.ru.md)
|
||||
|
||||
---
|
||||
|
||||
## Запуск
|
||||
|
||||
`pom.xml` находится в директории `app`.
|
||||
|
||||
Перейдите в директорию приложения:
|
||||
|
||||
```bash
|
||||
cd app
|
||||
```
|
||||
|
||||
Соберите проект:
|
||||
|
||||
```bash
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
Запустите собранное приложение:
|
||||
|
||||
```bash
|
||||
java -jar target/app-1.0-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
Основной класс приложения:
|
||||
|
||||
```text
|
||||
ru.project.App
|
||||
```
|
||||
+55
@@ -41,6 +41,25 @@
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.14.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.googlecode.lanterna</groupId>
|
||||
<artifactId>lanterna</artifactId>
|
||||
<version>3.1.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.14.1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -91,5 +110,41 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.6.3</version>
|
||||
<configuration>
|
||||
<mainClass>ru.project.App</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>ru.project.App</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -1,10 +1,45 @@
|
||||
package ru.project;
|
||||
|
||||
/**
|
||||
* Hello world!
|
||||
*/
|
||||
public class App {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello World!");
|
||||
import com.googlecode.lanterna.gui2.MultiWindowTextGUI;
|
||||
import com.googlecode.lanterna.screen.Screen;
|
||||
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
|
||||
import ru.project.model.AppState;
|
||||
import ru.project.ui.MainMenu;
|
||||
|
||||
public final class App {
|
||||
|
||||
private App() {}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Screen screen = null;
|
||||
|
||||
try {
|
||||
DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory();
|
||||
|
||||
screen = terminalFactory.createScreen();
|
||||
screen.startScreen();
|
||||
|
||||
MultiWindowTextGUI gui = new MultiWindowTextGUI(screen);
|
||||
|
||||
AppState state = new AppState();
|
||||
|
||||
MainMenu mainMenu = new MainMenu(gui, state);
|
||||
|
||||
mainMenu.showModal();
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Произошла ошибка при выполнении программы:");
|
||||
e.printStackTrace();
|
||||
|
||||
} finally {
|
||||
if (screen != null) {
|
||||
try {
|
||||
screen.stopScreen();
|
||||
} catch (Exception e) {
|
||||
System.err.println("Не удалось корректно завершить работу терминала:");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package ru.project.collection;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MyLinkedList<T> implements MyList<T> {
|
||||
private Node<T> head;
|
||||
private int size;
|
||||
|
||||
@Override
|
||||
public void add(T value) {
|
||||
if (head == null) {
|
||||
this.head = new Node<>(value);
|
||||
} else {
|
||||
Node<T> temp = head;
|
||||
while (temp.getNext() != null) {
|
||||
temp = temp.getNext();
|
||||
}
|
||||
temp.setNext(new Node<>(value));
|
||||
}
|
||||
size++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int index) {
|
||||
int currentIndex = 0;
|
||||
Node<T> temp = head;
|
||||
while (temp != null) {
|
||||
if (currentIndex == index) {
|
||||
return temp.getValue();
|
||||
|
||||
} else {
|
||||
temp = temp.getNext();
|
||||
currentIndex++;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(int index) {
|
||||
if (index < 0 || index >= size) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
if (index == 0) {
|
||||
head = head.getNext();
|
||||
size--;
|
||||
return;
|
||||
}
|
||||
int currentIndex = 0;
|
||||
Node<T> temp = head;
|
||||
while (temp != null) {
|
||||
if (currentIndex == index - 1) {
|
||||
temp.setNext(temp.getNext().getNext());
|
||||
size--;
|
||||
return;
|
||||
} else {
|
||||
temp = temp.getNext();
|
||||
currentIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T set(int index, T value) {
|
||||
if (index < 0 || index >= size) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
Node<T> temp = head;
|
||||
for (int i = 0; i < index; i++) {
|
||||
temp = temp.getNext();
|
||||
}
|
||||
|
||||
T oldValue = temp.getValue();
|
||||
temp.setValue(value);
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(MyList<? extends T> collection) {
|
||||
if (collection == null || collection.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < collection.size(); i++) {
|
||||
add(collection.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Object[] res = new Object[size];
|
||||
int index = 0;
|
||||
Node<T> temp = head;
|
||||
while (temp != null) {
|
||||
res[index] = temp.getValue();
|
||||
index++;
|
||||
temp = temp.getNext();
|
||||
}
|
||||
return Arrays.toString(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> stream() {
|
||||
return IntStream.range(0, size()).mapToObj(this::get);
|
||||
}
|
||||
|
||||
private static class Node<T> {
|
||||
private T value;
|
||||
private Node<T> next;
|
||||
|
||||
public Node(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void setValue(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void setNext(Node<T> next) {
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public Node<T> getNext() {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package ru.project.collection;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface MyList<T> {
|
||||
|
||||
void add(T element);
|
||||
|
||||
T get(int index);
|
||||
|
||||
T set(int index, T element);
|
||||
|
||||
void remove(int index);
|
||||
|
||||
int size();
|
||||
|
||||
boolean isEmpty();
|
||||
|
||||
void addAll(MyList<? extends T> collection);
|
||||
|
||||
Stream<T> stream();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package ru.project.collectors;
|
||||
|
||||
import java.util.stream.Collector;
|
||||
import ru.project.collection.MyList;
|
||||
|
||||
public class MyCollectors {
|
||||
private MyCollectors() {}
|
||||
|
||||
public static <T> Collector<T, MyList<T>, MyList<T>> toMyList() {
|
||||
return new MyListCollector<>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package ru.project.collectors;
|
||||
|
||||
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;
|
||||
import ru.project.collection.MyLinkedList;
|
||||
import ru.project.collection.MyList;
|
||||
|
||||
public class MyListCollector<T> implements Collector<T, MyList<T>, MyList<T>> {
|
||||
@Override
|
||||
public Supplier<MyList<T>> supplier() {
|
||||
return () -> new MyLinkedList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiConsumer<MyList<T>, T> accumulator() {
|
||||
|
||||
return (list, element) -> list.add(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryOperator<MyList<T>> combiner() {
|
||||
return (list1, list2) -> {
|
||||
list1.addAll(list2);
|
||||
return list1;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<MyList<T>, MyList<T>> finisher() {
|
||||
return list -> list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Collector.Characteristics> characteristics() {
|
||||
return java.util.Set.of(Collector.Characteristics.IDENTITY_FINISH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package ru.project.finder;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import ru.project.collection.MyLinkedList;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class StudentOccurrenceIndexFinder {
|
||||
public int[] findOccurrences(MyList<Student> students, Student target) {
|
||||
if (students == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (target == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (students.isEmpty()) {
|
||||
return new int[0];
|
||||
}
|
||||
int size = students.size();
|
||||
int threadCount = calculateThreadCount(size);
|
||||
int chunkSize = countChunkSize(size, threadCount);
|
||||
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
|
||||
MyList<Future<MyList<Integer>>> futures = new MyLinkedList<>();
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
int fromIndex = i * chunkSize;
|
||||
int toIndex = Math.min(fromIndex + chunkSize, size);
|
||||
|
||||
if (fromIndex >= toIndex) {
|
||||
break;
|
||||
}
|
||||
|
||||
final int start = fromIndex;
|
||||
final int end = toIndex;
|
||||
|
||||
Future<MyList<Integer>> future =
|
||||
executor.submit(
|
||||
() -> {
|
||||
return findInRange(students, target, start, end);
|
||||
});
|
||||
futures.add(future);
|
||||
}
|
||||
MyList<Integer> allIndices = new MyLinkedList<>();
|
||||
try {
|
||||
for (int i = 0; i < futures.size(); i++) {
|
||||
Future<MyList<Integer>> future = futures.get(i);
|
||||
MyList<Integer> partialResult = future.get();
|
||||
|
||||
if (partialResult != null) {
|
||||
for (int j = 0; j < partialResult.size(); j++) {
|
||||
allIndices.add(partialResult.get(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Ошибка при поиске", e);
|
||||
} finally {
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
sortList(allIndices);
|
||||
|
||||
int[] resultArray = new int[allIndices.size()];
|
||||
for (int i = 0; i < allIndices.size(); i++) {
|
||||
resultArray[i] = allIndices.get(i);
|
||||
}
|
||||
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
private void sortList(MyList<Integer> list) {
|
||||
if (list.size() <= 1) return;
|
||||
|
||||
MyList<Integer> 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<Integer> 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<Integer> findInRange(
|
||||
MyList<Student> students, Student target, int fromIndex, int toIndex) {
|
||||
MyList<Integer> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package ru.project.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
import ru.project.student.Student;
|
||||
import ru.project.student.StudentBuilder;
|
||||
|
||||
public class StudentCsvReader implements Iterator<Student>, AutoCloseable {
|
||||
private static final String GROUP_NUMBER = "groupNumber";
|
||||
private static final String AVERAGE_GRADE = "averageGrade";
|
||||
private static final String RECORD_BOOK_NUMBER = "recordBookNumber";
|
||||
private final CSVParser parser;
|
||||
private final Iterator<CSVRecord> records;
|
||||
private int lineOfFile = 0;
|
||||
|
||||
public StudentCsvReader(Path path) {
|
||||
if (!path.isAbsolute()) {
|
||||
throw new IllegalArgumentException("File path must be absolute");
|
||||
}
|
||||
try {
|
||||
CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).get();
|
||||
this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format);
|
||||
validateHeader(parser.getHeaderMap());
|
||||
this.records = parser.iterator();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return records.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Student next() {
|
||||
if (!records.hasNext()) {
|
||||
throw new NoSuchElementException("No more records at line " + (lineOfFile + 1));
|
||||
}
|
||||
lineOfFile++;
|
||||
CSVRecord record = records.next();
|
||||
if (record.size() != 3) {
|
||||
throw new IllegalArgumentException(
|
||||
"Problem at line " + lineOfFile + ". Expected 3 columns, but got " + record.size());
|
||||
}
|
||||
String groupNumber = record.get(GROUP_NUMBER);
|
||||
String averageGradeStr = record.get(AVERAGE_GRADE);
|
||||
String recordBookNumberStr = record.get(RECORD_BOOK_NUMBER);
|
||||
double averageGrade;
|
||||
int recordBookNumber;
|
||||
try {
|
||||
averageGrade = Double.parseDouble(averageGradeStr);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Problem at line "
|
||||
+ lineOfFile
|
||||
+ ": cannot parse averageGrade '"
|
||||
+ averageGradeStr
|
||||
+ "' as double",
|
||||
e);
|
||||
}
|
||||
try {
|
||||
recordBookNumber = Integer.parseInt(recordBookNumberStr);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Problem at line "
|
||||
+ lineOfFile
|
||||
+ ": cannot parse recordBookNumber '"
|
||||
+ recordBookNumberStr
|
||||
+ "' as int",
|
||||
e);
|
||||
}
|
||||
return new StudentBuilder()
|
||||
.setGroupNumber(groupNumber)
|
||||
.setAverageGrade(averageGrade)
|
||||
.setRecordBookNumber(recordBookNumber)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
parser.close();
|
||||
}
|
||||
|
||||
private static void validateHeader(Map<String, Integer> headerMap) {
|
||||
String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER};
|
||||
if (headerMap.size() != expected.length
|
||||
|| !headerMap.keySet().containsAll(Arrays.asList(expected))) {
|
||||
throw new IllegalArgumentException("Invalid CSV header");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package ru.project.io;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVPrinter;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class StudentCsvWriter {
|
||||
|
||||
private static final String GROUP_NUMBER_HEADER = "groupNumber";
|
||||
private static final String AVERAGE_GRADE_HEADER = "averageGrade";
|
||||
private static final String RECORD_BOOK_NUMBER_HEADER = "recordBookNumber";
|
||||
|
||||
private final String absoluteFilePath;
|
||||
|
||||
public StudentCsvWriter(String absoluteFilePath) {
|
||||
this.absoluteFilePath = absoluteFilePath;
|
||||
}
|
||||
|
||||
public void write(Student student) throws IOException {
|
||||
ensureFileExists();
|
||||
|
||||
boolean fileIsEmpty = isFileEmpty();
|
||||
|
||||
try (CSVPrinter printer = createPrinter()) {
|
||||
if (fileIsEmpty) {
|
||||
writeHeader(printer);
|
||||
}
|
||||
|
||||
writeStudent(printer, student);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(MyList<Student>[] students) throws IOException {
|
||||
ensureFileExists();
|
||||
|
||||
boolean fileIsEmpty = isFileEmpty();
|
||||
|
||||
try (CSVPrinter printer = createPrinter()) {
|
||||
if (fileIsEmpty) {
|
||||
writeHeader(printer);
|
||||
}
|
||||
|
||||
for (MyList<Student> studentList : students) {
|
||||
for (int i = 0; i < studentList.size(); i++) {
|
||||
writeStudent(printer, studentList.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CSVPrinter createPrinter() throws IOException {
|
||||
BufferedWriter writer =
|
||||
new BufferedWriter(
|
||||
new OutputStreamWriter(
|
||||
new FileOutputStream(absoluteFilePath, true), StandardCharsets.UTF_8));
|
||||
|
||||
return new CSVPrinter(writer, CSVFormat.DEFAULT);
|
||||
}
|
||||
|
||||
private void writeHeader(CSVPrinter printer) throws IOException {
|
||||
printer.printRecord(GROUP_NUMBER_HEADER, AVERAGE_GRADE_HEADER, RECORD_BOOK_NUMBER_HEADER);
|
||||
}
|
||||
|
||||
private void writeStudent(CSVPrinter printer, Student student) throws IOException {
|
||||
|
||||
printer.printRecord(
|
||||
student.getGroupNumber(), student.getAverageGrade(), student.getRecordBookNumber());
|
||||
}
|
||||
|
||||
private void ensureFileExists() throws IOException {
|
||||
File file = new File(absoluteFilePath);
|
||||
|
||||
if (!file.isFile()) {
|
||||
throw new IOException("CSV file does not exist: " + absoluteFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFileEmpty() {
|
||||
return new File(absoluteFilePath).length() == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package ru.project.model;
|
||||
|
||||
import ru.project.collection.MyLinkedList;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class AppState {
|
||||
|
||||
private MyList<Student> mainCollection;
|
||||
|
||||
public AppState() {
|
||||
this.mainCollection = new MyLinkedList<>();
|
||||
}
|
||||
|
||||
public MyList<Student> getMainCollection() {
|
||||
return mainCollection;
|
||||
}
|
||||
|
||||
public void setMainCollection(MyList<Student> currentCollection) {
|
||||
this.mainCollection = currentCollection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package ru.project.random;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.IntStream;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.collectors.MyCollectors;
|
||||
import ru.project.student.Student;
|
||||
import ru.project.student.StudentBuilder;
|
||||
|
||||
public class RandomStudentGenerator {
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
private String minGroupNumber;
|
||||
private String maxGroupNumber;
|
||||
|
||||
private Double minAverageGrade;
|
||||
private Double maxAverageGrade;
|
||||
|
||||
private Integer minRecordBookNumber;
|
||||
private Integer maxRecordBookNumber;
|
||||
|
||||
private static final double DEFAULT_MIN_GRADE = 0.0;
|
||||
private static final double DEFAULT_MAX_GRADE = 5.0;
|
||||
private static final int DEFAULT_MIN_RECORD_BOOK = 1;
|
||||
|
||||
private static final String GROUP_PATTERN_REGEX = "^[A-Z][0-9]{2}$";
|
||||
private static final Pattern GROUP_PATTERN = Pattern.compile(GROUP_PATTERN_REGEX);
|
||||
|
||||
public RandomStudentGenerator setMinGroupNumber(String minGroupNumber) {
|
||||
|
||||
if (minGroupNumber != null && !GROUP_PATTERN.matcher(minGroupNumber).matches()) {
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"minGroupNumber does not match Student group pattern: " + GROUP_PATTERN_REGEX);
|
||||
}
|
||||
|
||||
this.minGroupNumber = minGroupNumber;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public RandomStudentGenerator setMaxGroupNumber(String maxGroupNumber) {
|
||||
|
||||
if (maxGroupNumber != null && !GROUP_PATTERN.matcher(maxGroupNumber).matches()) {
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"maxGroupNumber does not match Student group pattern: " + GROUP_PATTERN_REGEX);
|
||||
}
|
||||
|
||||
this.maxGroupNumber = maxGroupNumber;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public RandomStudentGenerator setMinAverageGrade(double minAverageGrade) {
|
||||
|
||||
if (minAverageGrade < DEFAULT_MIN_GRADE || minAverageGrade > DEFAULT_MAX_GRADE) {
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"minAverageGrade out of Student allowed range ["
|
||||
+ DEFAULT_MIN_GRADE
|
||||
+ ", "
|
||||
+ DEFAULT_MAX_GRADE
|
||||
+ "]");
|
||||
}
|
||||
|
||||
this.minAverageGrade = minAverageGrade;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public RandomStudentGenerator setMaxAverageGrade(double maxAverageGrade) {
|
||||
|
||||
if (maxAverageGrade < DEFAULT_MIN_GRADE || maxAverageGrade > DEFAULT_MAX_GRADE) {
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"maxAverageGrade out of Student allowed range ["
|
||||
+ DEFAULT_MIN_GRADE
|
||||
+ ", "
|
||||
+ DEFAULT_MAX_GRADE
|
||||
+ "]");
|
||||
}
|
||||
|
||||
this.maxAverageGrade = maxAverageGrade;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public RandomStudentGenerator setMinRecordBookNumber(int minRecordBookNumber) {
|
||||
|
||||
if (minRecordBookNumber < DEFAULT_MIN_RECORD_BOOK) {
|
||||
throw new IllegalArgumentException(
|
||||
"minRecordBookNumber must be >= " + DEFAULT_MIN_RECORD_BOOK);
|
||||
}
|
||||
|
||||
this.minRecordBookNumber = minRecordBookNumber;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public RandomStudentGenerator setMaxRecordBookNumber(int maxRecordBookNumber) {
|
||||
|
||||
if (maxRecordBookNumber < DEFAULT_MIN_RECORD_BOOK) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxRecordBookNumber must be >= " + DEFAULT_MIN_RECORD_BOOK);
|
||||
}
|
||||
|
||||
this.maxRecordBookNumber = maxRecordBookNumber;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public MyList<Student> generate(int size) {
|
||||
if (size < 0) {
|
||||
throw new IllegalArgumentException("size must be non-negative");
|
||||
}
|
||||
|
||||
validateRanges();
|
||||
|
||||
return IntStream.range(0, size)
|
||||
.mapToObj(i -> generateStudent())
|
||||
.collect(MyCollectors.toMyList());
|
||||
}
|
||||
|
||||
private Student generateStudent() {
|
||||
String groupNumber = generateGroupNumber();
|
||||
|
||||
double averageGrade = generateAverageGrade();
|
||||
|
||||
int recordBookNumber = generateRecordBookNumber();
|
||||
|
||||
return new StudentBuilder()
|
||||
.setGroupNumber(groupNumber)
|
||||
.setAverageGrade(averageGrade)
|
||||
.setRecordBookNumber(recordBookNumber)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void validateRanges() {
|
||||
double effectiveMinGrade = minAverageGrade != null ? minAverageGrade : DEFAULT_MIN_GRADE;
|
||||
|
||||
double effectiveMaxGrade = maxAverageGrade != null ? maxAverageGrade : DEFAULT_MAX_GRADE;
|
||||
|
||||
if (effectiveMinGrade > effectiveMaxGrade) {
|
||||
throw new IllegalArgumentException("minAverageGrade cannot be greater than maxAverageGrade");
|
||||
}
|
||||
|
||||
int effectiveMinRecord =
|
||||
minRecordBookNumber != null ? minRecordBookNumber : DEFAULT_MIN_RECORD_BOOK;
|
||||
|
||||
int effectiveMaxRecord = maxRecordBookNumber != null ? maxRecordBookNumber : Integer.MAX_VALUE;
|
||||
|
||||
if (effectiveMaxRecord == Integer.MAX_VALUE) {
|
||||
effectiveMaxRecord = effectiveMinRecord + 999_999;
|
||||
}
|
||||
|
||||
if (effectiveMinRecord > effectiveMaxRecord) {
|
||||
throw new IllegalArgumentException(
|
||||
"minRecordBookNumber cannot be greater than maxRecordBookNumber");
|
||||
}
|
||||
|
||||
if (minGroupNumber != null
|
||||
&& maxGroupNumber != null
|
||||
&& minGroupNumber.compareTo(maxGroupNumber) > 0) {
|
||||
|
||||
throw new IllegalArgumentException("minGroupNumber cannot be greater than maxGroupNumber");
|
||||
}
|
||||
}
|
||||
|
||||
private String generateGroupNumber() {
|
||||
String min = minGroupNumber != null ? minGroupNumber : "A00";
|
||||
|
||||
String max = maxGroupNumber != null ? maxGroupNumber : "Z99";
|
||||
|
||||
char minLetter = min.charAt(0);
|
||||
char maxLetter = max.charAt(0);
|
||||
|
||||
int minDigit = Integer.parseInt(min.substring(1));
|
||||
|
||||
int maxDigit = Integer.parseInt(max.substring(1));
|
||||
|
||||
char letter = (char) (minLetter + RANDOM.nextInt((maxLetter - minLetter) + 1));
|
||||
|
||||
int digit;
|
||||
|
||||
if (letter == minLetter && letter == maxLetter) {
|
||||
|
||||
digit = minDigit + RANDOM.nextInt((maxDigit - minDigit) + 1);
|
||||
|
||||
} else if (letter == minLetter) {
|
||||
|
||||
digit = minDigit + RANDOM.nextInt(100 - minDigit);
|
||||
|
||||
} else if (letter == maxLetter) {
|
||||
|
||||
digit = RANDOM.nextInt(maxDigit + 1);
|
||||
|
||||
} else {
|
||||
|
||||
digit = RANDOM.nextInt(100);
|
||||
}
|
||||
|
||||
return letter + String.format("%02d", digit);
|
||||
}
|
||||
|
||||
private double generateAverageGrade() {
|
||||
double min = minAverageGrade != null ? minAverageGrade : DEFAULT_MIN_GRADE;
|
||||
|
||||
double max = maxAverageGrade != null ? maxAverageGrade : DEFAULT_MAX_GRADE;
|
||||
|
||||
double range = max - min;
|
||||
|
||||
double grade = min + (range * RANDOM.nextDouble());
|
||||
|
||||
return Math.round(grade * 100.0) / 100.0;
|
||||
}
|
||||
|
||||
private int generateRecordBookNumber() {
|
||||
int min = minRecordBookNumber != null ? minRecordBookNumber : DEFAULT_MIN_RECORD_BOOK;
|
||||
|
||||
int max = maxRecordBookNumber != null ? maxRecordBookNumber : min + 999_999;
|
||||
|
||||
if (max < min) {
|
||||
max = min;
|
||||
}
|
||||
|
||||
return min + RANDOM.nextInt((max - min) + 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package ru.project.search;
|
||||
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class AverageGradeRangeCriteria implements StudentSearchCriteria {
|
||||
private final double min;
|
||||
private final double max;
|
||||
|
||||
public AverageGradeRangeCriteria(double min, double max) {
|
||||
if (min > max) {
|
||||
throw new IllegalArgumentException("Min grade must be <= max grade");
|
||||
}
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Student student) {
|
||||
double grade = student.getAverageGrade();
|
||||
return grade >= min && grade <= max;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package ru.project.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class CompositeStudentSearchCriteria implements StudentSearchCriteria {
|
||||
private final SearchOperation operation;
|
||||
private final List<StudentSearchCriteria> criteriaList = new ArrayList<>();
|
||||
|
||||
public CompositeStudentSearchCriteria(SearchOperation operation) {
|
||||
if (operation == null) {
|
||||
throw new IllegalArgumentException("Operation must not be null");
|
||||
}
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
public void add(StudentSearchCriteria criteria) {
|
||||
if (criteria == null) {
|
||||
throw new IllegalArgumentException("Criteria must not be null");
|
||||
}
|
||||
criteriaList.add(criteria);
|
||||
}
|
||||
|
||||
public void remove(StudentSearchCriteria criteria) {
|
||||
criteriaList.remove(criteria);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Student student) {
|
||||
if (criteriaList.isEmpty()) {
|
||||
return operation == SearchOperation.INTERSECTION;
|
||||
}
|
||||
|
||||
if (operation == SearchOperation.UNION) {
|
||||
for (StudentSearchCriteria c : criteriaList) {
|
||||
if (c.matches(student)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
for (StudentSearchCriteria c : criteriaList) {
|
||||
if (!c.matches(student)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package ru.project.search;
|
||||
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class GroupNumberRangeCriteria implements StudentSearchCriteria {
|
||||
private final String min;
|
||||
private final String max;
|
||||
|
||||
public GroupNumberRangeCriteria(String min, String max) {
|
||||
if (min == null || max == null) {
|
||||
throw new IllegalArgumentException("Min and max must not be null");
|
||||
}
|
||||
if (min.compareTo(max) > 0) {
|
||||
throw new IllegalArgumentException("Min group must be <= max group");
|
||||
}
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Student student) {
|
||||
String group = student.getGroupNumber();
|
||||
return group.compareTo(min) >= 0 && group.compareTo(max) <= 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package ru.project.search;
|
||||
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class RecordBookNumberRangeCriteria implements StudentSearchCriteria {
|
||||
private final int min;
|
||||
private final int max;
|
||||
|
||||
public RecordBookNumberRangeCriteria(int min, int max) {
|
||||
if (min > max) {
|
||||
throw new IllegalArgumentException("Min record number must be <= max");
|
||||
}
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Student student) {
|
||||
int number = student.getRecordBookNumber();
|
||||
return number >= min && number <= max;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.project.search;
|
||||
|
||||
public enum SearchOperation {
|
||||
UNION,
|
||||
INTERSECTION
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.project.search;
|
||||
|
||||
import ru.project.student.Student;
|
||||
|
||||
public interface StudentSearchCriteria {
|
||||
boolean matches(Student student);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package ru.project.search;
|
||||
|
||||
import ru.project.collection.MyLinkedList;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class StudentSearchService {
|
||||
private final StudentSearchCriteria criteria;
|
||||
|
||||
public StudentSearchService(StudentSearchCriteria criteria) {
|
||||
if (criteria == null) {
|
||||
throw new IllegalArgumentException("Criteria must not be null");
|
||||
}
|
||||
this.criteria = criteria;
|
||||
}
|
||||
|
||||
public MyList<Student> find(MyList<Student> students) {
|
||||
if (students == null) {
|
||||
return new MyLinkedList<>();
|
||||
}
|
||||
MyList<Student> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package ru.project.sorting;
|
||||
|
||||
import java.util.Comparator;
|
||||
import ru.project.collection.MyList;
|
||||
|
||||
public class BubbleSortStrategy<T> implements SortStrategy<T> {
|
||||
|
||||
@Override
|
||||
public void sort(MyList<T> list, Comparator<T> comparator) {
|
||||
if (list == null || list.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
int n = list.size();
|
||||
for (int i = 0; i < n - 1; i++) {
|
||||
for (int j = 0; j < n - i - 1; j++) {
|
||||
if (comparator.compare(list.get(j), list.get(j + 1)) > 0) {
|
||||
T temp = list.get(j);
|
||||
list.set(j, list.get(j + 1));
|
||||
list.set(j + 1, temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package ru.project.sorting;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.function.ToIntFunction;
|
||||
import ru.project.collection.MyLinkedList;
|
||||
import ru.project.collection.MyList;
|
||||
|
||||
public class EvenOnlySortStrategy<T> implements SortStrategy<T> {
|
||||
|
||||
private SortStrategy<T> sortStrategy;
|
||||
private ToIntFunction<T> valueExtractor;
|
||||
|
||||
public EvenOnlySortStrategy() {}
|
||||
|
||||
public EvenOnlySortStrategy(SortStrategy<T> sortStrategy) {
|
||||
setSortStrategy(sortStrategy);
|
||||
}
|
||||
|
||||
public EvenOnlySortStrategy(SortStrategy<T> sortStrategy, ToIntFunction<T> valueExtractor) {
|
||||
setSortStrategy(sortStrategy);
|
||||
setValueExtractor(valueExtractor);
|
||||
}
|
||||
|
||||
public void setSortStrategy(SortStrategy<T> sortStrategy) {
|
||||
if (sortStrategy == null) {
|
||||
throw new IllegalArgumentException("Sort strategy must not be null");
|
||||
}
|
||||
|
||||
this.sortStrategy = sortStrategy;
|
||||
}
|
||||
|
||||
public void setValueExtractor(ToIntFunction<T> valueExtractor) {
|
||||
if (valueExtractor == null) {
|
||||
throw new IllegalArgumentException("Value extractor must not be null");
|
||||
}
|
||||
|
||||
this.valueExtractor = valueExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sort(MyList<T> list, Comparator<T> comparator) {
|
||||
if (sortStrategy == null) {
|
||||
throw new IllegalStateException("Sort strategy is not set");
|
||||
}
|
||||
|
||||
if (valueExtractor == null) {
|
||||
throw new IllegalStateException("Value extractor is not set");
|
||||
}
|
||||
|
||||
if (list == null || list.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int evenCount = 0;
|
||||
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
T element = list.get(i);
|
||||
|
||||
if (valueExtractor.applyAsInt(element) % 2 == 0) {
|
||||
evenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (evenCount <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
MyList<T> evenElements = new MyLinkedList<>();
|
||||
int[] evenIndexes = new int[evenCount];
|
||||
|
||||
int evenIndex = 0;
|
||||
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
T element = list.get(i);
|
||||
|
||||
if (valueExtractor.applyAsInt(element) % 2 == 0) {
|
||||
evenElements.add(element);
|
||||
evenIndexes[evenIndex] = i;
|
||||
evenIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
sortStrategy.sort(evenElements, comparator);
|
||||
|
||||
for (int i = 0; i < evenIndexes.length; i++) {
|
||||
list.set(evenIndexes[i], evenElements.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package ru.project.sorting;
|
||||
|
||||
import java.util.Comparator;
|
||||
import ru.project.collection.MyList;
|
||||
|
||||
public class MergeSortStrategy<T> implements SortStrategy<T> {
|
||||
|
||||
@Override
|
||||
public void sort(MyList<T> list, Comparator<T> comparator) {
|
||||
if (list == null || list.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
mergeSort(list, 0, list.size() - 1, comparator);
|
||||
}
|
||||
|
||||
private void mergeSort(MyList<T> list, int left, int right, Comparator<T> comparator) {
|
||||
if (left >= right) {
|
||||
return;
|
||||
}
|
||||
int mid = (left + right) / 2;
|
||||
mergeSort(list, left, mid, comparator);
|
||||
mergeSort(list, mid + 1, right, comparator);
|
||||
merge(list, left, mid, right, comparator);
|
||||
}
|
||||
|
||||
private void merge(MyList<T> list, int left, int mid, int right, Comparator<T> comparator) {
|
||||
int leftSize = mid - left + 1;
|
||||
int rightSize = right - mid;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] leftArray = (T[]) new Object[leftSize];
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] rightArray = (T[]) new Object[rightSize];
|
||||
|
||||
for (int i = 0; i < leftSize; i++) {
|
||||
leftArray[i] = list.get(left + i);
|
||||
}
|
||||
for (int j = 0; j < rightSize; j++) {
|
||||
rightArray[j] = list.get(mid + 1 + j);
|
||||
}
|
||||
|
||||
int i = 0, j = 0, k = left;
|
||||
|
||||
while (i < leftSize && j < rightSize) {
|
||||
if (comparator.compare(leftArray[i], rightArray[j]) <= 0) {
|
||||
list.set(k, leftArray[i]);
|
||||
i++;
|
||||
} else {
|
||||
list.set(k, rightArray[j]);
|
||||
j++;
|
||||
}
|
||||
k++;
|
||||
}
|
||||
|
||||
while (i < leftSize) {
|
||||
list.set(k, leftArray[i]);
|
||||
i++;
|
||||
k++;
|
||||
}
|
||||
while (j < rightSize) {
|
||||
list.set(k, rightArray[j]);
|
||||
j++;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package ru.project.sorting;
|
||||
|
||||
import java.util.Comparator;
|
||||
import ru.project.collection.MyList;
|
||||
|
||||
public interface SortStrategy<T> {
|
||||
void sort(MyList<T> list, Comparator<T> comparator);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package ru.project.sorting;
|
||||
|
||||
import java.util.Comparator;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class StudentAverageGradeComparator implements Comparator<Student> {
|
||||
|
||||
@Override
|
||||
public int compare(Student s1, Student s2) {
|
||||
return Double.compare(s1.getAverageGrade(), s2.getAverageGrade());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package ru.project.sorting;
|
||||
|
||||
import java.util.Comparator;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class StudentGroupComparator implements Comparator<Student> {
|
||||
|
||||
@Override
|
||||
public int compare(Student s1, Student s2) {
|
||||
return s1.getGroupNumber().compareTo(s2.getGroupNumber());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package ru.project.sorting;
|
||||
|
||||
import java.util.Comparator;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class StudentRecordBookComparator implements Comparator<Student> {
|
||||
|
||||
@Override
|
||||
public int compare(Student s1, Student s2) {
|
||||
return Integer.compare(s1.getRecordBookNumber(), s2.getRecordBookNumber());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package ru.project.student;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Student {
|
||||
|
||||
public static final double MIN_AVERAGE_GRADE = 0.0;
|
||||
public static final double MAX_AVERAGE_GRADE = 5.0;
|
||||
public static final int MIN_RECORD_BOOK_NUMBER = 1;
|
||||
public static final String GROUP_NUMBER_PATTERN = "^[A-Z][0-9]{2}$";
|
||||
|
||||
private final String groupNumber;
|
||||
private final double averageGrade;
|
||||
private final int recordBookNumber;
|
||||
|
||||
public Student(String groupNumber, double averageGrade, int recordBookNumber) {
|
||||
if (groupNumber == null || !groupNumber.matches(GROUP_NUMBER_PATTERN)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Group number must match pattern: " + GROUP_NUMBER_PATTERN);
|
||||
}
|
||||
if (averageGrade < MIN_AVERAGE_GRADE || averageGrade > MAX_AVERAGE_GRADE) {
|
||||
throw new IllegalArgumentException(
|
||||
"Average grade must be between " + MIN_AVERAGE_GRADE + " and " + MAX_AVERAGE_GRADE);
|
||||
}
|
||||
if (recordBookNumber <= 0) {
|
||||
throw new IllegalArgumentException("Record book number must be positive");
|
||||
}
|
||||
if (recordBookNumber < MIN_RECORD_BOOK_NUMBER) {
|
||||
throw new IllegalArgumentException(
|
||||
"Record book number must be at least " + MIN_RECORD_BOOK_NUMBER);
|
||||
}
|
||||
this.groupNumber = groupNumber;
|
||||
this.averageGrade = averageGrade;
|
||||
this.recordBookNumber = recordBookNumber;
|
||||
}
|
||||
|
||||
public String getGroupNumber() {
|
||||
return groupNumber;
|
||||
}
|
||||
|
||||
public double getAverageGrade() {
|
||||
return averageGrade;
|
||||
}
|
||||
|
||||
public int getRecordBookNumber() {
|
||||
return recordBookNumber;
|
||||
}
|
||||
|
||||
public Student withGroupNumber(String groupNumber) {
|
||||
return new Student(groupNumber, this.averageGrade, this.recordBookNumber);
|
||||
}
|
||||
|
||||
public Student withAverageGrade(double averageGrade) {
|
||||
return new Student(this.groupNumber, averageGrade, this.recordBookNumber);
|
||||
}
|
||||
|
||||
public Student withRecordBookNumber(int recordBookNumber) {
|
||||
return new Student(this.groupNumber, this.averageGrade, recordBookNumber);
|
||||
}
|
||||
|
||||
public static double getMinAverageGrade() {
|
||||
return MIN_AVERAGE_GRADE;
|
||||
}
|
||||
|
||||
public static double getMaxAverageGrade() {
|
||||
return MAX_AVERAGE_GRADE;
|
||||
}
|
||||
|
||||
public static int getMinRecordBookNumber() {
|
||||
return MIN_RECORD_BOOK_NUMBER;
|
||||
}
|
||||
|
||||
public static String getGroupNumberPattern() {
|
||||
return GROUP_NUMBER_PATTERN;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Student{"
|
||||
+ "groupNumber='"
|
||||
+ groupNumber
|
||||
+ '\''
|
||||
+ ", averageGrade="
|
||||
+ averageGrade
|
||||
+ ", recordBookNumber="
|
||||
+ recordBookNumber
|
||||
+ '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(groupNumber, averageGrade, recordBookNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (this.hashCode() != obj.hashCode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Student student = (Student) obj;
|
||||
return Double.compare(averageGrade, student.averageGrade) == 0
|
||||
&& recordBookNumber == student.recordBookNumber
|
||||
&& Objects.equals(groupNumber, student.groupNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package ru.project.student;
|
||||
|
||||
public class StudentBuilder {
|
||||
private String groupNumber;
|
||||
private double averageGrade;
|
||||
private int recordBookNumber;
|
||||
|
||||
public StudentBuilder setGroupNumber(String groupNumber) {
|
||||
this.groupNumber = groupNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StudentBuilder setAverageGrade(double averageGrade) {
|
||||
this.averageGrade = averageGrade;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StudentBuilder setRecordBookNumber(int recordBookNumber) {
|
||||
this.recordBookNumber = recordBookNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Student build() {
|
||||
return new Student(groupNumber, averageGrade, recordBookNumber);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package ru.project.ui.base;
|
||||
|
||||
import com.googlecode.lanterna.gui2.BasicWindow;
|
||||
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||
|
||||
public abstract class BaseModalWindow extends BasicWindow {
|
||||
|
||||
protected final WindowBasedTextGUI gui;
|
||||
|
||||
protected BaseModalWindow(String title, WindowBasedTextGUI gui) {
|
||||
super(title);
|
||||
|
||||
this.gui = gui;
|
||||
}
|
||||
|
||||
public void showModal() {
|
||||
gui.addWindowAndWait(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
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.counting.CountOccurrencesMenu;
|
||||
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;
|
||||
|
||||
public class CurrentCollectionMenu extends BaseModalWindow {
|
||||
|
||||
private final AppState state;
|
||||
|
||||
private final SortCollectionMenu sortCollectionMenu;
|
||||
private final SearchCollectionMenu searchCollectionMenu;
|
||||
private final CountOccurrencesMenu countOccurrencesMenu;
|
||||
|
||||
public CurrentCollectionMenu(WindowBasedTextGUI gui, AppState state) {
|
||||
|
||||
super("Меню текущей коллекции", gui);
|
||||
|
||||
this.state = state;
|
||||
|
||||
this.sortCollectionMenu = new SortCollectionMenu(gui, state);
|
||||
|
||||
this.searchCollectionMenu = new SearchCollectionMenu(gui, state);
|
||||
|
||||
this.countOccurrencesMenu = new CountOccurrencesMenu(gui, state);
|
||||
|
||||
ActionListBox menu = new ActionListBox();
|
||||
menu.addItem("Показать коллекцию...", this::showCurrentCollection);
|
||||
|
||||
menu.addItem("Сохранить коллекцию в файл...", this::saveCurrentCollection);
|
||||
|
||||
menu.addItem("Меню сортировки коллекции", sortCollectionMenu::showModal);
|
||||
|
||||
menu.addItem("Меню поиска элемента в коллекции", searchCollectionMenu::showModal);
|
||||
|
||||
menu.addItem("Меню подсчёта количества вхождений элемента", countOccurrencesMenu::showModal);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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<Student> 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());
|
||||
}
|
||||
}
|
||||
+172
@@ -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<Student> 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<Student> 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, "Элемент для поиска успешно настроен.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.project.ui.collection.search;
|
||||
|
||||
public enum FilterRelation {
|
||||
AND,
|
||||
OR
|
||||
}
|
||||
@@ -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<Student> 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<Student> 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<SearchSettings> 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<String> relationList;
|
||||
|
||||
public SearchSettingsWindow(WindowBasedTextGUI gui, Consumer<SearchSettings> 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<String> 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<Double> 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<Integer> 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package ru.project.ui.collection.sorting;
|
||||
|
||||
import com.googlecode.lanterna.gui2.ActionListBox;
|
||||
import com.googlecode.lanterna.gui2.Button;
|
||||
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||
import com.googlecode.lanterna.gui2.Panel;
|
||||
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||
import java.util.function.Consumer;
|
||||
import ru.project.ui.base.BaseModalWindow;
|
||||
|
||||
public class NormalSortSettingsWindow extends BaseModalWindow {
|
||||
|
||||
private final Consumer<SortField> onSortFieldSelected;
|
||||
|
||||
public NormalSortSettingsWindow(WindowBasedTextGUI gui, Consumer<SortField> onSortFieldSelected) {
|
||||
|
||||
super("Обычная сортировка", gui);
|
||||
|
||||
this.onSortFieldSelected = onSortFieldSelected;
|
||||
|
||||
Panel panel = new Panel();
|
||||
panel.setLayoutManager(new LinearLayout());
|
||||
|
||||
ActionListBox menu = new ActionListBox();
|
||||
|
||||
menu.addItem("Сортировать по номеру группы", () -> selectSortField(SortField.GROUP_NUMBER));
|
||||
|
||||
menu.addItem("Сортировать по среднему баллу", () -> selectSortField(SortField.AVERAGE_GRADE));
|
||||
|
||||
menu.addItem(
|
||||
"Сортировать по номеру зачётной книжки",
|
||||
() -> selectSortField(SortField.RECORD_BOOK_NUMBER));
|
||||
|
||||
panel.addComponent(menu);
|
||||
|
||||
panel.addComponent(new Button("Выйти", this::close));
|
||||
|
||||
setComponent(panel);
|
||||
}
|
||||
|
||||
private void selectSortField(SortField sortField) {
|
||||
onSortFieldSelected.accept(sortField);
|
||||
close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package ru.project.ui.collection.sorting;
|
||||
|
||||
import com.googlecode.lanterna.gui2.Button;
|
||||
import com.googlecode.lanterna.gui2.Label;
|
||||
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||
import com.googlecode.lanterna.gui2.Panel;
|
||||
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||
import ru.project.ui.base.BaseModalWindow;
|
||||
|
||||
public class ParitySortSettingsWindow extends BaseModalWindow {
|
||||
|
||||
public ParitySortSettingsWindow(WindowBasedTextGUI gui) {
|
||||
super("Настройка сортировки по чётности", gui);
|
||||
|
||||
Panel panel = new Panel();
|
||||
panel.setLayoutManager(new LinearLayout());
|
||||
|
||||
panel.addComponent(new Label("Сортировка выполняется по номеру зачётной книжки."));
|
||||
|
||||
panel.addComponent(new Button("Назад", this::close));
|
||||
|
||||
setComponent(panel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.project.ui.collection.sorting;
|
||||
|
||||
public enum SortAlgorithm {
|
||||
BUBBLE,
|
||||
MERGE
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package ru.project.ui.collection.sorting;
|
||||
|
||||
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.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;
|
||||
|
||||
public class SortCollectionMenu extends BaseModalWindow {
|
||||
|
||||
private final AppState state;
|
||||
|
||||
private final Label sortAlgorithmLabel;
|
||||
private final Label sortFieldLabel;
|
||||
private final Label sortMethodLabel;
|
||||
|
||||
private final NormalSortSettingsWindow normalSortSettingsWindow;
|
||||
private final ParitySortSettingsWindow paritySortSettingsWindow;
|
||||
private final SortMethodSelectionWindow sortMethodSelectionWindow;
|
||||
private final SortStrategySelectionWindow sortStrategySelectionWindow;
|
||||
|
||||
private SortAlgorithm selectedSortAlgorithm;
|
||||
private SortField selectedSortField;
|
||||
private SortMethod selectedSortMethod;
|
||||
|
||||
public SortCollectionMenu(WindowBasedTextGUI gui, AppState state) {
|
||||
|
||||
super("Меню сортировки текущей коллекции", gui);
|
||||
|
||||
this.state = state;
|
||||
|
||||
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));
|
||||
|
||||
this.sortMethodSelectionWindow =
|
||||
new SortMethodSelectionWindow(
|
||||
gui,
|
||||
sortMethod -> {
|
||||
selectedSortMethod = sortMethod;
|
||||
|
||||
sortMethodLabel.setText(
|
||||
"Способ сортировки: " + getSortMethodName(selectedSortMethod));
|
||||
|
||||
sortFieldLabel.setText(
|
||||
"Сортировать по: " + getSortFieldName(selectedSortMethod, selectedSortField));
|
||||
});
|
||||
|
||||
this.sortStrategySelectionWindow =
|
||||
new SortStrategySelectionWindow(
|
||||
gui,
|
||||
sortAlgorithm -> {
|
||||
selectedSortAlgorithm = sortAlgorithm;
|
||||
|
||||
sortAlgorithmLabel.setText(
|
||||
"Алгоритм сортировки: " + getSortAlgorithmName(selectedSortAlgorithm));
|
||||
});
|
||||
|
||||
this.normalSortSettingsWindow =
|
||||
new NormalSortSettingsWindow(
|
||||
gui,
|
||||
sortField -> {
|
||||
selectedSortField = sortField;
|
||||
|
||||
sortFieldLabel.setText(
|
||||
"Сортировать по: " + getSortFieldName(selectedSortMethod, selectedSortField));
|
||||
});
|
||||
|
||||
this.paritySortSettingsWindow = new ParitySortSettingsWindow(gui);
|
||||
|
||||
panel.addComponent(sortMethodLabel);
|
||||
panel.addComponent(sortFieldLabel);
|
||||
panel.addComponent(sortAlgorithmLabel);
|
||||
|
||||
panel.addComponent(new Button("Выбрать способ сортировки...", this::selectSortMethod));
|
||||
|
||||
panel.addComponent(new Button("Выбрать алгоритм сортировки...", this::selectSortAlgorithm));
|
||||
|
||||
panel.addComponent(
|
||||
new Button("Настроить текущий способ сортировки...", this::setupCurrentSortMethod));
|
||||
|
||||
panel.addComponent(new Button("Сортировать текущую коллекцию", this::sortCurrentCollection));
|
||||
|
||||
panel.addComponent(new Button("Выйти", this::close));
|
||||
|
||||
setComponent(panel);
|
||||
}
|
||||
|
||||
private void setupCurrentSortMethod() {
|
||||
switch (selectedSortMethod) {
|
||||
case NORMAL -> normalSortSettingsWindow.showModal();
|
||||
|
||||
case PARITY -> paritySortSettingsWindow.showModal();
|
||||
}
|
||||
}
|
||||
|
||||
private void selectSortMethod() {
|
||||
sortMethodSelectionWindow.showModal();
|
||||
}
|
||||
|
||||
private void selectSortAlgorithm() {
|
||||
sortStrategySelectionWindow.showModal();
|
||||
}
|
||||
|
||||
private void sortCurrentCollection() {
|
||||
if (state.getMainCollection() == null || state.getMainCollection().isEmpty()) {
|
||||
|
||||
MessageWindow.showModal(gui, "Текущая коллекция пуста.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (selectedSortMethod) {
|
||||
case NORMAL -> sortNormally();
|
||||
|
||||
case PARITY -> sortByParity();
|
||||
}
|
||||
}
|
||||
|
||||
private void sortNormally() {
|
||||
SortStrategy<Student> sortStrategy = getSortStrategy();
|
||||
|
||||
Comparator<Student> comparator = getSortComparator();
|
||||
|
||||
sortStrategy.sort(state.getMainCollection(), comparator);
|
||||
|
||||
MessageWindow.showModal(gui, "Обычная сортировка успешно выполнена.");
|
||||
}
|
||||
|
||||
private void sortByParity() {
|
||||
SortStrategy<Student> sortStrategy =
|
||||
new EvenOnlySortStrategy<>(getSortStrategy(), Student::getRecordBookNumber);
|
||||
|
||||
Comparator<Student> comparator = new StudentRecordBookComparator();
|
||||
|
||||
sortStrategy.sort(state.getMainCollection(), comparator);
|
||||
|
||||
MessageWindow.showModal(gui, "Сортировка по чётности успешно выполнена.");
|
||||
}
|
||||
|
||||
private SortStrategy<Student> getSortStrategy() {
|
||||
return switch (selectedSortAlgorithm) {
|
||||
case BUBBLE -> new BubbleSortStrategy<>();
|
||||
|
||||
case MERGE -> new MergeSortStrategy<>();
|
||||
};
|
||||
}
|
||||
|
||||
private Comparator<Student> 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 -> "номеру зачётной книжки";
|
||||
};
|
||||
}
|
||||
|
||||
private String getNormalSortFieldName(SortField sortField) {
|
||||
return switch (sortField) {
|
||||
case GROUP_NUMBER -> "номеру группы";
|
||||
|
||||
case AVERAGE_GRADE -> "среднему баллу";
|
||||
|
||||
case RECORD_BOOK_NUMBER -> "номеру зачётной книжки";
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.project.ui.collection.sorting;
|
||||
|
||||
public enum SortField {
|
||||
GROUP_NUMBER,
|
||||
AVERAGE_GRADE,
|
||||
RECORD_BOOK_NUMBER
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.project.ui.collection.sorting;
|
||||
|
||||
public enum SortMethod {
|
||||
NORMAL,
|
||||
PARITY
|
||||
}
|
||||
@@ -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 SortMethodSelectionWindow extends BaseModalWindow {
|
||||
|
||||
private final Consumer<SortMethod> onSortMethodSelected;
|
||||
|
||||
public SortMethodSelectionWindow(
|
||||
WindowBasedTextGUI gui, Consumer<SortMethod> onSortMethodSelected) {
|
||||
|
||||
super("Выбрать способ сортировки", gui);
|
||||
|
||||
this.onSortMethodSelected = onSortMethodSelected;
|
||||
|
||||
ActionListBox menu = new ActionListBox();
|
||||
|
||||
menu.addItem("Обычная сортировка", () -> selectSortMethod(SortMethod.NORMAL));
|
||||
|
||||
menu.addItem("Сортировка по чётности", () -> selectSortMethod(SortMethod.PARITY));
|
||||
|
||||
menu.addItem("Выйти", this::close);
|
||||
|
||||
setComponent(menu);
|
||||
}
|
||||
|
||||
private void selectSortMethod(SortMethod sortMethod) {
|
||||
onSortMethodSelected.accept(sortMethod);
|
||||
close();
|
||||
}
|
||||
}
|
||||
@@ -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<SortAlgorithm> onSortAlgorithmSelected;
|
||||
|
||||
public SortStrategySelectionWindow(
|
||||
WindowBasedTextGUI gui, Consumer<SortAlgorithm> 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package ru.project.ui.common;
|
||||
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import com.googlecode.lanterna.gui2.Button;
|
||||
import com.googlecode.lanterna.gui2.Direction;
|
||||
import com.googlecode.lanterna.gui2.EmptySpace;
|
||||
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||
import com.googlecode.lanterna.gui2.Panel;
|
||||
import com.googlecode.lanterna.gui2.TextBox;
|
||||
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||
import com.googlecode.lanterna.input.KeyStroke;
|
||||
import com.googlecode.lanterna.input.KeyType;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
import ru.project.ui.base.BaseModalWindow;
|
||||
|
||||
public class CollectionDisplayWindow extends BaseModalWindow {
|
||||
|
||||
private final MyList<Student> collection;
|
||||
|
||||
private TextBox collectionTextBox;
|
||||
private Button exitButton;
|
||||
|
||||
public CollectionDisplayWindow(WindowBasedTextGUI gui, MyList<Student> collection) {
|
||||
|
||||
super("Просмотр коллекции", gui);
|
||||
|
||||
this.collection = collection;
|
||||
|
||||
Panel panel = createPanel();
|
||||
|
||||
setComponent(panel);
|
||||
|
||||
exitButton.takeFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleInput(KeyStroke key) {
|
||||
if (key.getKeyType() == KeyType.Escape && collectionTextBox.isFocused()) {
|
||||
|
||||
exitButton.takeFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.handleInput(key);
|
||||
}
|
||||
|
||||
private Panel createPanel() {
|
||||
Panel panel = new Panel();
|
||||
|
||||
panel.setLayoutManager(new LinearLayout(Direction.VERTICAL));
|
||||
|
||||
addCollectionDisplay(panel);
|
||||
addExitButton(panel);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void addCollectionDisplay(Panel panel) {
|
||||
collectionTextBox = createCollectionScrollBox();
|
||||
|
||||
panel.addComponent(collectionTextBox);
|
||||
}
|
||||
|
||||
private TextBox createCollectionScrollBox() {
|
||||
TextBox textBox = new TextBox(new TerminalSize(80, 20), TextBox.Style.MULTI_LINE);
|
||||
|
||||
textBox.setReadOnly(true);
|
||||
|
||||
if (collection == null || collection.isEmpty()) {
|
||||
textBox.setText("Коллекция пуста.");
|
||||
} else {
|
||||
textBox.setText(buildCollectionText());
|
||||
}
|
||||
|
||||
return textBox;
|
||||
}
|
||||
|
||||
private void addExitButton(Panel panel) {
|
||||
panel.addComponent(new EmptySpace(new TerminalSize(0, 1)));
|
||||
|
||||
exitButton = new Button("Выйти", this::close);
|
||||
|
||||
panel.addComponent(exitButton);
|
||||
}
|
||||
|
||||
private String buildCollectionText() {
|
||||
StringBuilder text = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < collection.size(); i++) {
|
||||
Student student = collection.get(i);
|
||||
|
||||
text.append("[")
|
||||
.append(i)
|
||||
.append("] ")
|
||||
.append(student == null ? "null" : student)
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
return text.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
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.File;
|
||||
import java.io.IOException;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.io.StudentCsvWriter;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class CollectionFileSaveWindow {
|
||||
|
||||
private final WindowBasedTextGUI gui;
|
||||
private final MyList<Student> collection;
|
||||
|
||||
public CollectionFileSaveWindow(WindowBasedTextGUI gui, MyList<Student> 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) {
|
||||
createAndSaveToFile(selectedFile);
|
||||
}
|
||||
}
|
||||
|
||||
private File selectFile() {
|
||||
return new FileDialogBuilder()
|
||||
.setTitle("Сохранение коллекции")
|
||||
.setDescription("Выберите файл:")
|
||||
.setActionLabel("Сохранить")
|
||||
.build()
|
||||
.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());
|
||||
|
||||
for (int i = 0; i < collection.size(); i++) {
|
||||
writer.write(collection.get(i));
|
||||
}
|
||||
|
||||
MessageWindow.showModal(gui, "Коллекция успешно сохранена.");
|
||||
|
||||
} catch (IOException e) {
|
||||
MessageWindow.showModal(gui, "Не удалось сохранить коллекцию.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Integer> onSizeEntered;
|
||||
|
||||
public CollectionSizeInputWindow(
|
||||
WindowBasedTextGUI gui, Integer currentSize, Consumer<Integer> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package ru.project.ui.common;
|
||||
|
||||
import com.googlecode.lanterna.gui2.Button;
|
||||
import com.googlecode.lanterna.gui2.Label;
|
||||
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||
import com.googlecode.lanterna.gui2.Panel;
|
||||
import com.googlecode.lanterna.gui2.TextBox;
|
||||
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||
import java.util.function.Predicate;
|
||||
import ru.project.ui.base.BaseModalWindow;
|
||||
|
||||
public class InputValueWindow extends BaseModalWindow {
|
||||
|
||||
private final TextBox textBox;
|
||||
private final Predicate<String> onSave;
|
||||
|
||||
public InputValueWindow(
|
||||
WindowBasedTextGUI gui,
|
||||
String title,
|
||||
String prompt,
|
||||
String initialValue,
|
||||
Predicate<String> onSave) {
|
||||
|
||||
super(title, gui);
|
||||
|
||||
this.onSave = onSave;
|
||||
|
||||
Panel panel = new Panel();
|
||||
panel.setLayoutManager(new LinearLayout());
|
||||
|
||||
panel.addComponent(new Label(prompt));
|
||||
|
||||
textBox = new TextBox(initialValue == null ? "" : initialValue);
|
||||
|
||||
panel.addComponent(textBox);
|
||||
|
||||
panel.addComponent(new Button("Сохранить", this::save));
|
||||
|
||||
panel.addComponent(new Button("Отмена", this::close));
|
||||
|
||||
setComponent(panel);
|
||||
}
|
||||
|
||||
private void save() {
|
||||
String value = textBox.getText().trim();
|
||||
|
||||
if (value.isEmpty()) {
|
||||
MessageWindow.showModal(gui, "Значение не может быть пустым.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSave.test(value)) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package ru.project.ui.common;
|
||||
|
||||
import com.googlecode.lanterna.gui2.Button;
|
||||
import com.googlecode.lanterna.gui2.Label;
|
||||
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||
import com.googlecode.lanterna.gui2.Panel;
|
||||
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||
import ru.project.ui.base.BaseModalWindow;
|
||||
|
||||
public class MessageWindow extends BaseModalWindow {
|
||||
|
||||
public MessageWindow(WindowBasedTextGUI gui, String message) {
|
||||
super("Информация", gui);
|
||||
|
||||
Panel panel = new Panel();
|
||||
panel.setLayoutManager(new LinearLayout());
|
||||
|
||||
panel.addComponent(new Label(message));
|
||||
|
||||
panel.addComponent(new Button("OK", this::close));
|
||||
|
||||
setComponent(panel);
|
||||
}
|
||||
|
||||
public static void showModal(WindowBasedTextGUI gui, String message) {
|
||||
new MessageWindow(gui, message).showModal();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package ru.project.ui.creation;
|
||||
|
||||
import com.googlecode.lanterna.gui2.Button;
|
||||
import com.googlecode.lanterna.gui2.Label;
|
||||
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||
import com.googlecode.lanterna.gui2.Panel;
|
||||
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||
import ru.project.model.AppState;
|
||||
import ru.project.ui.base.BaseModalWindow;
|
||||
import ru.project.ui.common.MessageWindow;
|
||||
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;
|
||||
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 {
|
||||
|
||||
private final AppState state;
|
||||
|
||||
private FillMethod selectedFillMethod;
|
||||
private FillStrategy fillStrategy;
|
||||
|
||||
private final Label fillMethodLabel;
|
||||
|
||||
private final ManualDataSettingsWindow manualDataSettingsWindow;
|
||||
private final FileDataSettingsWindow fileDataSettingsWindow;
|
||||
private final RandomDataSettingsWindow randomDataSettingsWindow;
|
||||
|
||||
public CreateCollectionMenu(WindowBasedTextGUI gui, AppState state) {
|
||||
|
||||
super("Создание новой коллекции", gui);
|
||||
|
||||
this.state = state;
|
||||
this.selectedFillMethod = FillMethod.CUSTOM;
|
||||
this.fillStrategy = null;
|
||||
|
||||
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());
|
||||
|
||||
fillMethodLabel = new Label("Способ заполнения: " + getFillMethodName(selectedFillMethod));
|
||||
|
||||
panel.addComponent(fillMethodLabel);
|
||||
|
||||
panel.addComponent(new Button("Выбрать способ заполнения...", this::selectFillMethod));
|
||||
|
||||
panel.addComponent(
|
||||
new Button("Настроить текущий способ заполнения...", this::setupCurrentFillMethod));
|
||||
|
||||
panel.addComponent(new Button("Создать новую коллекцию", this::updateMainCollection));
|
||||
|
||||
panel.addComponent(new Button("Выйти", this::close));
|
||||
|
||||
setComponent(panel);
|
||||
}
|
||||
|
||||
private void setManualFillStrategy(ManualFillSettings settings) {
|
||||
fillStrategy = new ManualFillStrategy(settings);
|
||||
}
|
||||
|
||||
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 -> manualDataSettingsWindow.showModal();
|
||||
|
||||
case FILE -> fileDataSettingsWindow.showModal();
|
||||
|
||||
case RANDOM -> randomDataSettingsWindow.showModal();
|
||||
}
|
||||
}
|
||||
|
||||
private void selectFillMethod() {
|
||||
BaseModalWindow menu =
|
||||
new FillMethodSelectionWindow(
|
||||
gui,
|
||||
fillMethod -> {
|
||||
selectedFillMethod = fillMethod;
|
||||
|
||||
fillMethodLabel.setText(
|
||||
"Способ заполнения: " + getFillMethodName(selectedFillMethod));
|
||||
});
|
||||
|
||||
menu.showModal();
|
||||
}
|
||||
|
||||
private void updateMainCollection() {
|
||||
if (fillStrategy == null) {
|
||||
MessageWindow.showModal(gui, "Сначала настройте способ заполнения.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
state.setMainCollection(fillStrategy.create());
|
||||
|
||||
MessageWindow.showModal(gui, "Коллекция успешно создана.");
|
||||
}
|
||||
|
||||
private String getFillMethodName(FillMethod fillMethod) {
|
||||
return switch (fillMethod) {
|
||||
case CUSTOM -> "Пользовательские данные";
|
||||
case RANDOM -> "Случайные данные";
|
||||
case FILE -> "Данные из файла";
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.project.ui.creation;
|
||||
|
||||
public enum FillMethod {
|
||||
CUSTOM,
|
||||
RANDOM,
|
||||
FILE
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package ru.project.ui.creation;
|
||||
|
||||
import com.googlecode.lanterna.gui2.ActionListBox;
|
||||
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||
import java.util.function.Consumer;
|
||||
import ru.project.ui.base.BaseModalWindow;
|
||||
|
||||
public class FillMethodSelectionWindow extends BaseModalWindow {
|
||||
|
||||
private final Consumer<FillMethod> onFillMethodSelected;
|
||||
|
||||
public FillMethodSelectionWindow(
|
||||
WindowBasedTextGUI gui, Consumer<FillMethod> onFillMethodSelected) {
|
||||
|
||||
super("Выбор способа заполнения", gui);
|
||||
|
||||
this.onFillMethodSelected = onFillMethodSelected;
|
||||
|
||||
ActionListBox menu = new ActionListBox();
|
||||
|
||||
menu.addItem("Пользовательские данные", () -> selectFillMethod(FillMethod.CUSTOM));
|
||||
|
||||
menu.addItem("Случайные данные", () -> selectFillMethod(FillMethod.RANDOM));
|
||||
|
||||
menu.addItem("Данные из файла", () -> selectFillMethod(FillMethod.FILE));
|
||||
|
||||
menu.addItem("Выйти", this::close);
|
||||
|
||||
setComponent(menu);
|
||||
}
|
||||
|
||||
private void selectFillMethod(FillMethod fillMethod) {
|
||||
onFillMethodSelected.accept(fillMethod);
|
||||
close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.project.ui.creation;
|
||||
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public interface FillStrategy {
|
||||
|
||||
MyList<Student> create();
|
||||
}
|
||||
@@ -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<FileFillSettings> onApply;
|
||||
|
||||
private final Label filePathLabel;
|
||||
|
||||
public FileDataSettingsWindow(WindowBasedTextGUI gui, Consumer<FileFillSettings> 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package ru.project.ui.creation.file;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.stream.StreamSupport;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.collectors.MyCollectors;
|
||||
import ru.project.io.StudentCsvReader;
|
||||
import ru.project.student.Student;
|
||||
import ru.project.ui.creation.FillStrategy;
|
||||
|
||||
public class FileFillStrategy implements FillStrategy {
|
||||
|
||||
private final FileFillSettings settings;
|
||||
|
||||
public FileFillStrategy(FileFillSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MyList<Student> create() {
|
||||
Path path = Path.of(settings.getAbsoluteFilePath());
|
||||
|
||||
try (StudentCsvReader reader = new StudentCsvReader(path)) {
|
||||
|
||||
return StreamSupport.stream(
|
||||
Spliterators.spliteratorUnknownSize(reader, Spliterator.ORDERED), false)
|
||||
.collect(MyCollectors.toMyList());
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Не удалось прочитать коллекцию из файла.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
package ru.project.ui.creation.manual;
|
||||
|
||||
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.collection.MyLinkedList;
|
||||
import ru.project.collection.MyList;
|
||||
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 ManualDataSettingsWindow extends BaseModalWindow {
|
||||
|
||||
private final StudentBuilder studentBuilder;
|
||||
|
||||
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;
|
||||
private final Label indexLabel;
|
||||
|
||||
private MyList<Student> preparedCustomDataCollection;
|
||||
|
||||
private final ManualFillSettings settings;
|
||||
private final Consumer<ManualFillSettings> onApply;
|
||||
|
||||
public ManualDataSettingsWindow(WindowBasedTextGUI gui, Consumer<ManualFillSettings> onApply) {
|
||||
|
||||
super("Настройка пользовательских данных", gui);
|
||||
|
||||
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("Средний балл: не задан");
|
||||
|
||||
recordBookNumberLabel = new Label("Номер зачётной книжки: не задан");
|
||||
|
||||
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));
|
||||
|
||||
panel.addComponent(new Button("Задать номер зачётной книжки", this::setRecordBookNumber));
|
||||
|
||||
panel.addComponent(new Button("Задать индекс объекта", this::setObjectIndex));
|
||||
|
||||
panel.addComponent(new Button("Задать данные объекта", this::editObject));
|
||||
|
||||
panel.addComponent(new Button("Предпросмотр", this::preview));
|
||||
|
||||
panel.addComponent(new Button("Применить", this::apply));
|
||||
|
||||
panel.addComponent(new Button("Назад", this::close));
|
||||
|
||||
setComponent(panel);
|
||||
}
|
||||
|
||||
private void showCollectionSizeInputWindow() {
|
||||
CollectionSizeInputWindow window =
|
||||
new CollectionSizeInputWindow(
|
||||
gui, preparedCustomDataCollection.size(), this::handleCollectionSizeEntered);
|
||||
|
||||
window.showModal();
|
||||
}
|
||||
|
||||
private void handleCollectionSizeEntered(int size) {
|
||||
MyList<Student> 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<Student> currentCustomDataCollection = settings.getCollection();
|
||||
|
||||
preparedCustomDataCollection = new MyLinkedList<>();
|
||||
|
||||
int currentCollectionSize = currentCustomDataCollection.size();
|
||||
|
||||
int targetSize =
|
||||
lastRequestedCollectionSize == null ? currentCollectionSize : lastRequestedCollectionSize;
|
||||
|
||||
int elementsToCopy = Math.min(currentCollectionSize, targetSize);
|
||||
|
||||
for (int i = 0; i < elementsToCopy; i++) {
|
||||
preparedCustomDataCollection.add(currentCustomDataCollection.get(i));
|
||||
}
|
||||
|
||||
for (int i = elementsToCopy; i < targetSize; i++) {
|
||||
preparedCustomDataCollection.add(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void setGroupNumber() {
|
||||
InputValueWindow window =
|
||||
new InputValueWindow(
|
||||
gui,
|
||||
"Номер группы",
|
||||
"Введите номер группы (например, A12):",
|
||||
lastRequestedGroupNumber,
|
||||
value -> {
|
||||
if (!value.matches(Student.getGroupNumberPattern())) {
|
||||
MessageWindow.showModal(gui, "Номер группы должен соответствовать формату A12.");
|
||||
return false;
|
||||
}
|
||||
|
||||
lastRequestedGroupNumber = value;
|
||||
studentBuilder.setGroupNumber(value);
|
||||
|
||||
groupNumberLabel.setText("Номер группы: " + lastRequestedGroupNumber);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
window.showModal();
|
||||
}
|
||||
|
||||
private void setAverageGrade() {
|
||||
InputValueWindow window =
|
||||
new InputValueWindow(
|
||||
gui,
|
||||
"Средний балл",
|
||||
"Введите средний балл от 0.0 до 5.0:",
|
||||
lastRequestedAverageGrade == null ? null : String.valueOf(lastRequestedAverageGrade),
|
||||
value -> {
|
||||
try {
|
||||
double parsedValue = Double.parseDouble(value);
|
||||
|
||||
if (parsedValue < Student.getMinAverageGrade()
|
||||
|| parsedValue > Student.getMaxAverageGrade()) {
|
||||
|
||||
MessageWindow.showModal(gui, "Средний балл должен быть от 0.0 до 5.0.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
lastRequestedAverageGrade = parsedValue;
|
||||
|
||||
studentBuilder.setAverageGrade(parsedValue);
|
||||
|
||||
averageGradeLabel.setText("Средний балл: " + lastRequestedAverageGrade);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
MessageWindow.showModal(gui, "Введите корректное число.");
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
window.showModal();
|
||||
}
|
||||
|
||||
private void setRecordBookNumber() {
|
||||
InputValueWindow window =
|
||||
new InputValueWindow(
|
||||
gui,
|
||||
"Номер зачётной книжки",
|
||||
"Введите номер зачётной книжки:",
|
||||
lastRequestedRecordBookNumber == null
|
||||
? null
|
||||
: String.valueOf(lastRequestedRecordBookNumber),
|
||||
value -> {
|
||||
try {
|
||||
int parsedValue = Integer.parseInt(value);
|
||||
|
||||
if (parsedValue < Student.getMinRecordBookNumber()) {
|
||||
|
||||
MessageWindow.showModal(gui, "Номер зачётной книжки должен быть положительным.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
lastRequestedRecordBookNumber = parsedValue;
|
||||
|
||||
studentBuilder.setRecordBookNumber(parsedValue);
|
||||
|
||||
recordBookNumberLabel.setText(
|
||||
"Номер зачётной книжки: " + lastRequestedRecordBookNumber);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
MessageWindow.showModal(gui, "Введите целое число.");
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
window.showModal();
|
||||
}
|
||||
|
||||
private void setObjectIndex() {
|
||||
InputValueWindow window =
|
||||
new InputValueWindow(
|
||||
gui,
|
||||
"Индекс объекта",
|
||||
"Введите индекс объекта:",
|
||||
lastRequestedSelectedObjectIndex == null
|
||||
? null
|
||||
: String.valueOf(lastRequestedSelectedObjectIndex),
|
||||
value -> {
|
||||
try {
|
||||
int index = Integer.parseInt(value);
|
||||
|
||||
if (index < 0) {
|
||||
MessageWindow.showModal(gui, "Индекс объекта не может быть отрицательным.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index >= preparedCustomDataCollection.size()) {
|
||||
MessageWindow.showModal(gui, "Индекс должен быть меньше размера коллекции.");
|
||||
return false;
|
||||
}
|
||||
|
||||
lastRequestedSelectedObjectIndex = index;
|
||||
|
||||
indexLabel.setText("Индекс объекта: " + lastRequestedSelectedObjectIndex);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
MessageWindow.showModal(gui, "Введите целое число.");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
window.showModal();
|
||||
}
|
||||
|
||||
private void editObject() {
|
||||
if (!hasStudentData()) {
|
||||
MessageWindow.showModal(gui, "Сначала задайте все поля студента.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastRequestedSelectedObjectIndex == null) {
|
||||
MessageWindow.showModal(gui, "Сначала задайте индекс объекта.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastRequestedSelectedObjectIndex >= preparedCustomDataCollection.size()) {
|
||||
MessageWindow.showModal(gui, "Индекс объекта выходит за границы подготовленной коллекции.");
|
||||
return;
|
||||
}
|
||||
|
||||
Student student = studentBuilder.build();
|
||||
|
||||
preparedCustomDataCollection.set(lastRequestedSelectedObjectIndex, student);
|
||||
|
||||
MessageWindow.showModal(gui, "Данные объекта успешно заданы.");
|
||||
}
|
||||
|
||||
private void apply() {
|
||||
settings.setCollection(preparedCustomDataCollection);
|
||||
onApply.accept(settings);
|
||||
|
||||
MessageWindow.showModal(gui, "Настройки создания коллекции вручную успешно применены.");
|
||||
}
|
||||
|
||||
private void preview() {
|
||||
if (preparedCustomDataCollection == null || preparedCustomDataCollection.isEmpty()) {
|
||||
|
||||
MessageWindow.showModal(gui, "Подготовленная коллекция пуста.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CollectionDisplayWindow window = new CollectionDisplayWindow(gui, preparedCustomDataCollection);
|
||||
|
||||
window.showModal();
|
||||
}
|
||||
|
||||
private boolean hasStudentData() {
|
||||
return lastRequestedGroupNumber != null
|
||||
&& lastRequestedAverageGrade != null
|
||||
&& lastRequestedRecordBookNumber != null;
|
||||
}
|
||||
}
|
||||
@@ -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<Student> manualDataCollection;
|
||||
|
||||
public ManualFillSettings() {
|
||||
this.manualDataCollection = new MyLinkedList<>();
|
||||
}
|
||||
|
||||
public MyList<Student> getCollection() {
|
||||
return manualDataCollection;
|
||||
}
|
||||
|
||||
public void setCollection(MyList<Student> collection) {
|
||||
this.manualDataCollection = collection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package ru.project.ui.creation.manual;
|
||||
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.collectors.MyCollectors;
|
||||
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<Student> create() {
|
||||
return settings.getCollection().stream().collect(MyCollectors.toMyList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
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<RandomFillSettings> onApply;
|
||||
|
||||
private final Label collectionSizeLabel;
|
||||
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<RandomFillSettings> onApply) {
|
||||
|
||||
super("Настройка случайного заполнения", gui);
|
||||
|
||||
this.settings = new RandomFillSettings();
|
||||
this.onApply = onApply;
|
||||
|
||||
Panel panel = new Panel();
|
||||
panel.setLayoutManager(new LinearLayout());
|
||||
|
||||
collectionSizeLabel =
|
||||
new Label("Размер коллекции: " + getValueOrDefault(settings.getCollectionSize()));
|
||||
|
||||
panel.addComponent(collectionSizeLabel);
|
||||
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::setCollectionSize));
|
||||
|
||||
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 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(
|
||||
"Минимальный номер группы",
|
||||
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<String> 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<Double> 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<Integer> 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() {
|
||||
if (settings.getCollectionSize() == null) {
|
||||
MessageWindow.showModal(gui, "Сначала задайте размер коллекции.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
onApply.accept(settings);
|
||||
|
||||
MessageWindow.showModal(gui, "Настройки создания коллекции случайно успешно применены.");
|
||||
}
|
||||
|
||||
private String getValueOrDefault(Object value) {
|
||||
return value == null ? "не задан" : String.valueOf(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package ru.project.ui.creation.random;
|
||||
|
||||
public class RandomFillSettings {
|
||||
|
||||
private Integer collectionSize;
|
||||
|
||||
private String minGroupNumber;
|
||||
private String maxGroupNumber;
|
||||
|
||||
private Double minAverageGrade;
|
||||
private Double maxAverageGrade;
|
||||
|
||||
private Integer minRecordBookNumber;
|
||||
private Integer maxRecordBookNumber;
|
||||
|
||||
public RandomFillSettings() {
|
||||
this.collectionSize = null;
|
||||
|
||||
this.minGroupNumber = null;
|
||||
this.maxGroupNumber = null;
|
||||
|
||||
this.minAverageGrade = null;
|
||||
this.maxAverageGrade = null;
|
||||
|
||||
this.minRecordBookNumber = null;
|
||||
this.maxRecordBookNumber = null;
|
||||
}
|
||||
|
||||
public Integer getCollectionSize() {
|
||||
return collectionSize;
|
||||
}
|
||||
|
||||
public void setCollectionSize(Integer collectionSize) {
|
||||
this.collectionSize = collectionSize;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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;
|
||||
|
||||
public RandomFillStrategy(RandomFillSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MyList<Student> create() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,19 +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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package ru.project.collection;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class MyLinkedListTest {
|
||||
@Test
|
||||
public void shouldTestAdd() {
|
||||
MyLinkedList<Integer> 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<Integer> list = new MyLinkedList<>();
|
||||
list.add(10);
|
||||
assertEquals(10, list.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldTestRemove() {
|
||||
MyLinkedList<Integer> list = new MyLinkedList<>();
|
||||
list.add(10);
|
||||
list.add(20);
|
||||
list.add(30);
|
||||
list.remove(0);
|
||||
|
||||
assertEquals(2, list.size());
|
||||
assertEquals(20, list.get(0));
|
||||
assertEquals(30, list.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestSet() {
|
||||
MyLinkedList<String> list = new MyLinkedList<>();
|
||||
list.add("A");
|
||||
list.add("B");
|
||||
list.add("C");
|
||||
|
||||
String oldValue = list.set(1, "Z");
|
||||
|
||||
assertEquals("B", oldValue);
|
||||
assertEquals("Z", list.get(1));
|
||||
assertEquals(3, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestSize() {
|
||||
MyLinkedList<Integer> list = new MyLinkedList<>();
|
||||
list.add(1);
|
||||
list.add(2);
|
||||
list.add(3);
|
||||
assertEquals(3, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestisEmpty() {
|
||||
MyLinkedList<Integer> list = new MyLinkedList<>();
|
||||
assertTrue(list.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestGet_OutOfBounds_ThrowsException() {
|
||||
MyLinkedList<Integer> list = new MyLinkedList<>();
|
||||
list.add(1);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> list.get(-1));
|
||||
assertThrows(IllegalArgumentException.class, () -> list.get(1)); // index == size
|
||||
assertThrows(IllegalArgumentException.class, () -> list.get(5)); // index > size
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestSet_OutOfBounds_ThrowsException() {
|
||||
MyLinkedList<Integer> 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<String> list = new MyLinkedList<>();
|
||||
list.add("man");
|
||||
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestRemove_NegativeIndex_ThrowsException() {
|
||||
MyLinkedList<String> list = new MyLinkedList<>();
|
||||
list.add("1");
|
||||
list.add("2");
|
||||
|
||||
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestaddAll_nonEmptySource_toEmptyList_addsAllElementsInOrder() {
|
||||
MyList<String> list = new MyLinkedList<>();
|
||||
MyList<String> 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<String> list = new MyLinkedList<>();
|
||||
list.add("X");
|
||||
list.add("Y");
|
||||
|
||||
MyList<String> 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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
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 org.junit.jupiter.api.Test;
|
||||
import ru.project.collection.MyList;
|
||||
|
||||
public class MyListCollectorTest {
|
||||
private final MyListCollector<Integer> collector = new MyListCollector<>();
|
||||
|
||||
@Test
|
||||
void shouldTestCollectInOrderSequential() {
|
||||
MyList<Integer> 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<Integer> 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<Integer> result = IntStream.empty().boxed().collect(MyCollectors.toMyList());
|
||||
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestNotUseStandardList() {
|
||||
MyList<String> 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<Collector.Characteristics> characteristics = collector.characteristics();
|
||||
assertTrue(
|
||||
characteristics.contains(java.util.stream.Collector.Characteristics.IDENTITY_FINISH));
|
||||
assertFalse(characteristics.contains(java.util.stream.Collector.Characteristics.UNORDERED));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package ru.project.finder;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
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;
|
||||
|
||||
public class StudentOccurrenceIndexFinderTest {
|
||||
private StudentOccurrenceIndexFinder finder;
|
||||
private MyList<Student> students;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
finder = new StudentOccurrenceIndexFinder();
|
||||
students = new MyLinkedList<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_emptyList_returnsEmptyArray() {
|
||||
int[] result = finder.findOccurrences(students, new Student("A12", 4.5, 1));
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_noMatches_returnsEmptyArray() {
|
||||
students.add(new Student("A12", 4.0, 101));
|
||||
students.add(new Student("B23", 3.5, 102));
|
||||
Student target = new Student("C34", 4.8, 103); // такого нет
|
||||
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_singleMatch_returnsCorrectIndex() {
|
||||
students.add(new Student("A12", 4.0, 101));
|
||||
students.add(new Student("B23", 3.5, 102)); // target здесь
|
||||
students.add(new Student("C34", 4.8, 103));
|
||||
|
||||
Student target = new Student("B23", 3.5, 102);
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
|
||||
assertEquals(1, result.length);
|
||||
assertEquals(1, result[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_multipleMatches_returnsAllIndicesSorted() {
|
||||
// индексы: 0, 2, 4 — должны вернуться в порядке возрастания
|
||||
students.add(new Student("A12", 4.5, 101)); // match
|
||||
students.add(new Student("B23", 3.8, 102));
|
||||
students.add(new Student("A12", 4.5, 101)); // match
|
||||
students.add(new Student("C34", 4.0, 103));
|
||||
students.add(new Student("A12", 4.5, 101)); // match
|
||||
|
||||
Student target = new Student("A12", 4.5, 101);
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
|
||||
assertEquals(3, result.length);
|
||||
assertArrayEquals(new int[] {0, 2, 4}, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_allElementsMatch_returnsAllIndices() {
|
||||
Student s = new Student("X99", 5.0, 999);
|
||||
students.add(s);
|
||||
students.add(s);
|
||||
students.add(s);
|
||||
|
||||
int[] result = finder.findOccurrences(students, s);
|
||||
assertEquals(3, result.length);
|
||||
assertArrayEquals(new int[] {0, 1, 2}, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_differentAverageGrade_noMatch() {
|
||||
students.add(new Student("A12", 4.50, 101));
|
||||
Student target = new Student("A12", 4.51, 101); // отличается балл
|
||||
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_differentGroupNumber_noMatch() {
|
||||
students.add(new Student("A12", 4.5, 101));
|
||||
Student target = new Student("B12", 4.5, 101); // другая группа
|
||||
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_differentRecordBookNumber_noMatch() {
|
||||
students.add(new Student("A12", 4.5, 101));
|
||||
Student target = new Student("A12", 4.5, 102); // другой номер зачетки
|
||||
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_nullList_throwsException() {
|
||||
Student target = new Student("A12", 4.5, 101);
|
||||
assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(null, target));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_nullTarget_throwsException() {
|
||||
assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(students, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTesfindOccurrences_resultIsSorted() {
|
||||
String[] validGroups = {"A00", "A01", "B12", "C99", "X55"};
|
||||
|
||||
for (int i = 0; i < 50; i++) {
|
||||
String group = validGroups[i % validGroups.length];
|
||||
double grade = (3.0 + (i % 3)) / 2.0;
|
||||
if (i % 3 == 0) {
|
||||
|
||||
students.add(new Student("A12", 4.5, 101));
|
||||
} else {
|
||||
students.add(new Student(group, grade, 200 + i));
|
||||
}
|
||||
}
|
||||
Student target = new Student("A12", 4.5, 101);
|
||||
int[] result = finder.findOccurrences(students, target);
|
||||
|
||||
for (int i = 1; i < result.length; i++) {
|
||||
assertTrue(result[i] > result[i - 1], "Результат должен быть отсортирован");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package ru.project.io;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Spliterators;
|
||||
import java.util.stream.StreamSupport;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.collectors.MyCollectors;
|
||||
import ru.project.student.Student;
|
||||
|
||||
class StudentCsvReaderTest {
|
||||
@TempDir Path tempDir;
|
||||
|
||||
@Test
|
||||
void shouldTestreadValidCsvIntoMyList() throws IOException {
|
||||
Path csv = tempDir.resolve("students.csv");
|
||||
String content =
|
||||
String.join(
|
||||
"\n", "groupNumber,averageGrade,recordBookNumber", "M10,4.5,12345", "B05,3.8,12346");
|
||||
Files.writeString(csv, content, StandardCharsets.UTF_8);
|
||||
try (var reader = new StudentCsvReader(csv.toAbsolutePath())) {
|
||||
var studentStream =
|
||||
StreamSupport.stream(Spliterators.spliteratorUnknownSize(reader, 0), false);
|
||||
MyList<Student> students = studentStream.collect(MyCollectors.toMyList());
|
||||
assertEquals(2, students.size());
|
||||
assertEquals("M10", students.get(0).getGroupNumber());
|
||||
assertEquals(4.5, students.get(0).getAverageGrade(), 0.0);
|
||||
assertEquals(12345, students.get(0).getRecordBookNumber());
|
||||
assertEquals("B05", students.get(1).getGroupNumber());
|
||||
assertEquals(3.8, students.get(1).getAverageGrade(), 0.0);
|
||||
assertEquals(12346, students.get(1).getRecordBookNumber());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestinvalidHeaderThrowsException() throws IOException {
|
||||
Path csv = tempDir.resolve("bad-header.csv");
|
||||
String content =
|
||||
"""
|
||||
badGroup,badGrade,badBook
|
||||
M101,4.5,12345
|
||||
""";
|
||||
Files.writeString(csv, content, StandardCharsets.UTF_8);
|
||||
IllegalArgumentException ex =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> new StudentCsvReader(csv.toAbsolutePath()));
|
||||
assertTrue(ex.getMessage().contains("Invalid CSV header"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestwrongColumnCountThrowsException() throws IOException {
|
||||
Path csv = tempDir.resolve("wrong-columns.csv");
|
||||
String content =
|
||||
"""
|
||||
groupNumber,averageGrade,recordBookNumber
|
||||
M101,4.5
|
||||
""";
|
||||
Files.writeString(csv, content, StandardCharsets.UTF_8);
|
||||
try (var reader = new StudentCsvReader(csv.toAbsolutePath())) {
|
||||
assertTrue(reader.hasNext());
|
||||
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, reader::next);
|
||||
assertTrue(ex.getMessage().contains("Expected 3 columns"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestparseErrorThrowsExceptionWithDetails() throws IOException {
|
||||
Path csv = tempDir.resolve("parse-error.csv");
|
||||
String content =
|
||||
"""
|
||||
groupNumber,averageGrade,recordBookNumber
|
||||
M101,not-a-number,12345
|
||||
""";
|
||||
Files.writeString(csv, content, StandardCharsets.UTF_8);
|
||||
try (var reader = new StudentCsvReader(csv.toAbsolutePath())) {
|
||||
assertTrue(reader.hasNext());
|
||||
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, reader::next);
|
||||
assertTrue(ex.getMessage().contains("cannot parse averageGrade"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestemptyDataAfterHeaderThrowsNoSuchElement() throws IOException {
|
||||
Path csv = tempDir.resolve("empty-data.csv");
|
||||
String content =
|
||||
"""
|
||||
groupNumber,averageGrade,recordBookNumber
|
||||
""";
|
||||
Files.writeString(csv, content, StandardCharsets.UTF_8);
|
||||
|
||||
// ИСПРАВЛЕНО: добавлен try-with-resources
|
||||
try (var reader = new StudentCsvReader(csv.toAbsolutePath())) {
|
||||
assertFalse(reader.hasNext());
|
||||
|
||||
assertThrows(NoSuchElementException.class, reader::next);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestrelativePathThrowsException() {
|
||||
Path relative = Path.of("relative-path.csv");
|
||||
|
||||
IllegalArgumentException ex =
|
||||
assertThrows(IllegalArgumentException.class, () -> new StudentCsvReader(relative));
|
||||
assertTrue(ex.getMessage().contains("absolute"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package ru.project.io;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import ru.project.collection.MyLinkedList;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
class StudentCsvWriterTest {
|
||||
|
||||
@TempDir Path tempDir;
|
||||
|
||||
@Test
|
||||
void writeShouldWriteHeaderAndStudentToEmptyFile() throws Exception {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
Files.createFile(file);
|
||||
|
||||
Student student = new Student("A12", 4.5, 10001);
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
writer.write(student);
|
||||
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
|
||||
assertEquals(List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001"), lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeShouldAppendStudentToExistingFile() throws Exception {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
|
||||
Files.write(file, List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001"));
|
||||
|
||||
Student student = new Student("B07", 3.8, 10002);
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
writer.write(student);
|
||||
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
|
||||
assertEquals(
|
||||
List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001", "B07,3.8,10002"),
|
||||
lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeShouldNotCreateMissingFile() {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
|
||||
Student student = new Student("A12", 4.5, 10001);
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
assertThrows(IOException.class, () -> writer.write(student));
|
||||
|
||||
assertFalse(Files.exists(file));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeShouldAppendSeveralStudentsInOrder() throws Exception {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
Files.createFile(file);
|
||||
|
||||
Student student1 = new Student("A12", 4.5, 10001);
|
||||
Student student2 = new Student("B07", 3.8, 10002);
|
||||
Student student3 = new Student("C25", 4.9, 10003);
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
writer.write(student1);
|
||||
writer.write(student2);
|
||||
writer.write(student3);
|
||||
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
|
||||
assertEquals(
|
||||
List.of(
|
||||
"groupNumber,averageGrade,recordBookNumber",
|
||||
"A12,4.5,10001",
|
||||
"B07,3.8,10002",
|
||||
"C25,4.9,10003"),
|
||||
lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeArrayShouldAppendStudentsFromAllListsInOrder() throws Exception {
|
||||
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
Files.createFile(file);
|
||||
|
||||
Student student1 = new Student("A12", 4.5, 10001);
|
||||
Student student2 = new Student("B07", 3.8, 10002);
|
||||
Student student3 = new Student("C25", 4.9, 10003);
|
||||
Student student4 = new Student("D15", 4.2, 10004);
|
||||
Student student5 = new Student("E20", 3.6, 10005);
|
||||
|
||||
MyList<Student> firstList = new MyLinkedList<>();
|
||||
firstList.add(student1);
|
||||
firstList.add(student2);
|
||||
|
||||
MyList<Student> secondList = new MyLinkedList<>();
|
||||
secondList.add(student3);
|
||||
|
||||
MyList<Student> thirdList = new MyLinkedList<>();
|
||||
thirdList.add(student4);
|
||||
thirdList.add(student5);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MyList<Student>[] students = new MyList[] {firstList, secondList, thirdList};
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
writer.write(students);
|
||||
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
|
||||
assertEquals(
|
||||
List.of(
|
||||
"groupNumber,averageGrade,recordBookNumber",
|
||||
"A12,4.5,10001",
|
||||
"B07,3.8,10002",
|
||||
"C25,4.9,10003",
|
||||
"D15,4.2,10004",
|
||||
"E20,3.6,10005"),
|
||||
lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeArrayShouldSkipEmptyLists() throws Exception {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
Files.createFile(file);
|
||||
|
||||
Student student = new Student("A12", 4.5, 10001);
|
||||
|
||||
MyList<Student> emptyList = new MyLinkedList<>();
|
||||
|
||||
MyList<Student> nonEmptyList = new MyLinkedList<>();
|
||||
nonEmptyList.add(student);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MyList<Student>[] students = new MyList[] {emptyList, nonEmptyList, new MyLinkedList<>()};
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
writer.write(students);
|
||||
|
||||
List<String> lines = Files.readAllLines(file);
|
||||
|
||||
assertEquals(List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001"), lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeEmptyArrayShouldNotModifyFile() throws Exception {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
|
||||
List<String> initialContent =
|
||||
List.of("groupNumber,averageGrade,recordBookNumber", "A12,4.5,10001");
|
||||
|
||||
Files.write(file, initialContent);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MyList<Student>[] students = new MyList[0];
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
writer.write(students);
|
||||
|
||||
assertEquals(initialContent, Files.readAllLines(file));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeEmptyArrayShouldNotCreateMissingFile() {
|
||||
Path file = tempDir.resolve("students.csv");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MyList<Student>[] students = new MyList[0];
|
||||
|
||||
StudentCsvWriter writer = new StudentCsvWriter(file.toAbsolutePath().toString());
|
||||
|
||||
assertThrows(IOException.class, () -> writer.write(students));
|
||||
|
||||
assertFalse(Files.exists(file));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package ru.project.random;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
public class RandomStudentGeneratorTest {
|
||||
private static final Pattern GROUP_PATTERN = Pattern.compile("^[A-Z][0-9]{2}$");
|
||||
private static final double MIN_GRADE = 0.0;
|
||||
private static final double MAX_GRADE = 5.0;
|
||||
|
||||
@Test
|
||||
void shouldTestgenerate_withoutConstraints_returnsCorrectSizeAndValidStudents() {
|
||||
int size = 100;
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
MyList<Student> students = generator.generate(size);
|
||||
assertEquals(size, students.size(), "Размер списка должен совпадать с запрошенным");
|
||||
for (int i = 0; i < students.size(); i++) {
|
||||
Student s = students.get(i);
|
||||
assertTrue(
|
||||
GROUP_PATTERN.matcher(s.getGroupNumber()).matches(),
|
||||
"groupNumber должен соответствовать шаблону A00–Z99");
|
||||
assertTrue(
|
||||
s.getAverageGrade() >= MIN_GRADE && s.getAverageGrade() <= MAX_GRADE,
|
||||
"averageGrade должен быть в диапазоне [0.0, 5.0]");
|
||||
assertTrue(s.getRecordBookNumber() >= 1, "recordBookNumber должен быть >= 1");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestgenerate_withGroupRange_respectsLexicographicalBounds() {
|
||||
RandomStudentGenerator generator =
|
||||
new RandomStudentGenerator().setMinGroupNumber("A00").setMaxGroupNumber("B99");
|
||||
MyList<Student> students = generator.generate(200);
|
||||
assertEquals(200, students.size());
|
||||
for (int i = 0; i < students.size(); i++) {
|
||||
Student s = students.get(i);
|
||||
String g = s.getGroupNumber();
|
||||
assertTrue(g.compareTo("A00") >= 0, "Группа не должна быть меньше A00");
|
||||
assertTrue(g.compareTo("B99") <= 0, "Группа не должна быть больше B99");
|
||||
assertTrue(GROUP_PATTERN.matcher(g).matches(), "Группа должна соответствовать шаблону");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestgenerate_withGradeRange_respectsBounds() {
|
||||
double minGrade = 3.0;
|
||||
double maxGrade = 4.0;
|
||||
|
||||
RandomStudentGenerator generator =
|
||||
new RandomStudentGenerator().setMinAverageGrade(minGrade).setMaxAverageGrade(maxGrade);
|
||||
MyList<Student> students = generator.generate(100);
|
||||
|
||||
for (int i = 0; i < students.size(); i++) {
|
||||
Student s = students.get(i);
|
||||
double g = s.getAverageGrade();
|
||||
assertTrue(g >= minGrade && g <= maxGrade, "averageGrade должен быть в заданном диапазоне");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestgenerate_withRecordBookRange_respectsBounds() {
|
||||
int minRec = 10_000;
|
||||
int maxRec = 20_000;
|
||||
|
||||
RandomStudentGenerator generator =
|
||||
new RandomStudentGenerator().setMinRecordBookNumber(minRec).setMaxRecordBookNumber(maxRec);
|
||||
MyList<Student> students = generator.generate(100);
|
||||
|
||||
for (int i = 0; i < students.size(); i++) {
|
||||
Student s = students.get(i);
|
||||
int r = s.getRecordBookNumber();
|
||||
assertTrue(r >= minRec && r <= maxRec, "recordBookNumber должен быть в заданном диапазоне");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestgenerate_negativeSize_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.generate(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMinGroupNumber_invalidPattern_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.setMinGroupNumber("invalid"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMaxGroupNumber_invalidPattern_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> generator.setMaxGroupNumber("ZZ")); // не 2 цифры
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMinMaxGroupNumber_minGreaterThanMax_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> generator.setMinGroupNumber("C00").setMaxGroupNumber("A99").generate(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMinAverageGrade_outOfRange_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.setMinAverageGrade(-0.1));
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.setMinAverageGrade(5.1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMaxAverageGrade_outOfRange_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.setMaxAverageGrade(-0.1));
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.setMaxAverageGrade(5.1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMinMaxAverageGrade_minGreaterThanMax_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> generator.setMinAverageGrade(4.0).setMaxAverageGrade(3.0).generate(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMinRecordBookNumber_lessThanAllowed_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.setMinRecordBookNumber(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestsetMinMaxRecordBookNumber_minGreaterThanMax_throwsException() {
|
||||
RandomStudentGenerator generator = new RandomStudentGenerator();
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> generator.setMinRecordBookNumber(20_000).setMaxRecordBookNumber(10_000).generate(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTestchainedBuilderStyle_worksAsExpected() {
|
||||
RandomStudentGenerator generator =
|
||||
new RandomStudentGenerator()
|
||||
.setMinGroupNumber("A00")
|
||||
.setMaxGroupNumber("Z99")
|
||||
.setMinAverageGrade(2.0)
|
||||
.setMaxAverageGrade(4.0)
|
||||
.setMinRecordBookNumber(1_000)
|
||||
.setMaxRecordBookNumber(9_999);
|
||||
MyList<Student> students = generator.generate(50);
|
||||
assertEquals(50, students.size());
|
||||
for (int i = 0; i < students.size(); i++) {
|
||||
Student s = students.get(i);
|
||||
assertTrue(
|
||||
s.getGroupNumber().compareTo("A00") >= 0 && s.getGroupNumber().compareTo("Z99") <= 0);
|
||||
assertTrue(s.getAverageGrade() >= 2.0 && s.getAverageGrade() <= 4.0);
|
||||
assertTrue(s.getRecordBookNumber() >= 1_000 && s.getRecordBookNumber() <= 9_999);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
package ru.project.search;
|
||||
|
||||
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 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", 2.0, 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);
|
||||
assertTrue(outer.matches(student));
|
||||
|
||||
student = new Student("D00", 4.5, 150);
|
||||
assertFalse(outer.matches(student));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSearchServiceWithSingleCriteria() {
|
||||
MyList<Student> 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<Student> result = service.find(students);
|
||||
|
||||
assertEquals(2, result.size());
|
||||
assertEquals("A10", result.get(0).getGroupNumber());
|
||||
assertEquals("C30", result.get(1).getGroupNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSearchServiceEmptyResult() {
|
||||
MyList<Student> 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<Student> result = service.find(students);
|
||||
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSearchServiceEmptyCollection() {
|
||||
MyList<Student> students = new MyLinkedList<>();
|
||||
StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0);
|
||||
StudentSearchService service = new StudentSearchService(criteria);
|
||||
MyList<Student> result = service.find(students);
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSearchServicePreservesOrder() {
|
||||
MyList<Student> 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<Student> 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<Student> original = new MyLinkedList<>();
|
||||
original.add(new Student("A10", 4.5, 1));
|
||||
original.add(new Student("B20", 3.8, 2));
|
||||
|
||||
MyList<Student> 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<Student> 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<Student> 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<Student> result = service.find(null);
|
||||
assertNotNull(result);
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package ru.project.sorting;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Comparator;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.project.collection.MyLinkedList;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
class EvenOnlySortStrategyTest {
|
||||
|
||||
@Test
|
||||
void shouldHandleEmptyList() {
|
||||
MyList<Integer> list = new MyLinkedList<>();
|
||||
|
||||
EvenOnlySortStrategy<Integer> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value);
|
||||
|
||||
strategy.sort(list, Integer::compare);
|
||||
|
||||
assertEquals(0, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepSingleEvenElementUnchanged() {
|
||||
MyList<Integer> list = new MyLinkedList<>();
|
||||
list.add(4);
|
||||
|
||||
EvenOnlySortStrategy<Integer> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value);
|
||||
|
||||
strategy.sort(list, Integer::compare);
|
||||
|
||||
assertEquals(4, list.get(0));
|
||||
assertEquals(1, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepSingleOddElementUnchanged() {
|
||||
MyList<Integer> list = new MyLinkedList<>();
|
||||
list.add(7);
|
||||
|
||||
EvenOnlySortStrategy<Integer> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value);
|
||||
|
||||
strategy.sort(list, Integer::compare);
|
||||
|
||||
assertEquals(7, list.get(0));
|
||||
assertEquals(1, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleDuplicateEvenValues() {
|
||||
MyList<Integer> list = new MyLinkedList<>();
|
||||
|
||||
list.add(8);
|
||||
list.add(4);
|
||||
list.add(8);
|
||||
list.add(2);
|
||||
list.add(4);
|
||||
|
||||
EvenOnlySortStrategy<Integer> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value);
|
||||
|
||||
strategy.sort(list, Integer::compare);
|
||||
|
||||
assertEquals(2, list.get(0));
|
||||
assertEquals(4, list.get(1));
|
||||
assertEquals(4, list.get(2));
|
||||
assertEquals(8, list.get(3));
|
||||
assertEquals(8, list.get(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepOddStudentObjectsAtSameIndexes() {
|
||||
Student oddFirst = new Student("A01", 4.0, 7);
|
||||
Student evenFirst = new Student("A02", 4.0, 8);
|
||||
Student oddSecond = new Student("A03", 4.0, 3);
|
||||
Student evenSecond = new Student("A04", 4.0, 2);
|
||||
Student oddThird = new Student("A05", 4.0, 5);
|
||||
Student evenThird = new Student("A06", 4.0, 4);
|
||||
|
||||
MyList<Student> students = new MyLinkedList<>();
|
||||
|
||||
students.add(oddFirst);
|
||||
students.add(evenFirst);
|
||||
students.add(oddSecond);
|
||||
students.add(evenSecond);
|
||||
students.add(oddThird);
|
||||
students.add(evenThird);
|
||||
|
||||
EvenOnlySortStrategy<Student> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), Student::getRecordBookNumber);
|
||||
|
||||
strategy.sort(students, new StudentRecordBookComparator());
|
||||
|
||||
assertSame(oddFirst, students.get(0));
|
||||
assertSame(oddSecond, students.get(2));
|
||||
assertSame(oddThird, students.get(4));
|
||||
|
||||
assertEquals(2, students.get(1).getRecordBookNumber());
|
||||
assertEquals(4, students.get(3).getRecordBookNumber());
|
||||
assertEquals(8, students.get(5).getRecordBookNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSortWhenStrategyPassedThroughConstructor() {
|
||||
MyList<Integer> list = new MyLinkedList<>();
|
||||
|
||||
list.add(8);
|
||||
list.add(2);
|
||||
list.add(4);
|
||||
|
||||
EvenOnlySortStrategy<Integer> strategy = new EvenOnlySortStrategy<>(new MergeSortStrategy<>());
|
||||
|
||||
strategy.setValueExtractor(value -> value);
|
||||
|
||||
strategy.sort(list, Integer::compare);
|
||||
|
||||
assertEquals(2, list.get(0));
|
||||
assertEquals(4, list.get(1));
|
||||
assertEquals(8, list.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSortOnlyEvenElements() {
|
||||
MyList<Student> students = new MyLinkedList<>();
|
||||
|
||||
students.add(new Student("A01", 4.0, 7));
|
||||
students.add(new Student("A02", 4.0, 8));
|
||||
students.add(new Student("A03", 4.0, 3));
|
||||
students.add(new Student("A04", 4.0, 2));
|
||||
students.add(new Student("A05", 4.0, 5));
|
||||
students.add(new Student("A06", 4.0, 4));
|
||||
|
||||
EvenOnlySortStrategy<Student> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), Student::getRecordBookNumber);
|
||||
|
||||
strategy.sort(students, new StudentRecordBookComparator());
|
||||
|
||||
assertEquals(7, students.get(0).getRecordBookNumber());
|
||||
assertEquals(2, students.get(1).getRecordBookNumber());
|
||||
assertEquals(3, students.get(2).getRecordBookNumber());
|
||||
assertEquals(4, students.get(3).getRecordBookNumber());
|
||||
assertEquals(5, students.get(4).getRecordBookNumber());
|
||||
assertEquals(8, students.get(5).getRecordBookNumber());
|
||||
|
||||
assertEquals(6, students.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSortAllEvenElements() {
|
||||
MyList<Integer> list = new MyLinkedList<>();
|
||||
|
||||
list.add(8);
|
||||
list.add(2);
|
||||
list.add(6);
|
||||
list.add(4);
|
||||
|
||||
EvenOnlySortStrategy<Integer> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value);
|
||||
|
||||
strategy.sort(list, Integer::compare);
|
||||
|
||||
assertEquals(2, list.get(0));
|
||||
assertEquals(4, list.get(1));
|
||||
assertEquals(6, list.get(2));
|
||||
assertEquals(8, list.get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepOddElementsUnchanged() {
|
||||
MyList<Integer> list = new MyLinkedList<>();
|
||||
|
||||
list.add(7);
|
||||
list.add(3);
|
||||
list.add(5);
|
||||
list.add(1);
|
||||
|
||||
EvenOnlySortStrategy<Integer> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value);
|
||||
|
||||
strategy.sort(list, Integer::compare);
|
||||
|
||||
assertEquals(7, list.get(0));
|
||||
assertEquals(3, list.get(1));
|
||||
assertEquals(5, list.get(2));
|
||||
assertEquals(1, list.get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowWhenSortStrategyIsNotSet() {
|
||||
EvenOnlySortStrategy<Integer> strategy = new EvenOnlySortStrategy<>();
|
||||
|
||||
strategy.setValueExtractor(value -> value);
|
||||
|
||||
MyList<Integer> list = new MyLinkedList<>();
|
||||
list.add(2);
|
||||
list.add(4);
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> strategy.sort(list, Integer::compare));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectNullSortStrategy() {
|
||||
EvenOnlySortStrategy<Integer> strategy = new EvenOnlySortStrategy<>();
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> strategy.setSortStrategy(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDelegateSortingToProvidedStrategy() {
|
||||
MyList<Integer> list = new MyLinkedList<>();
|
||||
|
||||
list.add(7);
|
||||
list.add(8);
|
||||
list.add(3);
|
||||
list.add(2);
|
||||
|
||||
RecordingSortStrategy recordingStrategy = new RecordingSortStrategy();
|
||||
|
||||
EvenOnlySortStrategy<Integer> strategy =
|
||||
new EvenOnlySortStrategy<>(recordingStrategy, value -> value);
|
||||
|
||||
strategy.sort(list, Integer::compare);
|
||||
|
||||
assertTrue(recordingStrategy.wasCalled);
|
||||
assertEquals(2, recordingStrategy.receivedSize);
|
||||
assertEquals(8, recordingStrategy.firstReceivedElement);
|
||||
assertEquals(2, recordingStrategy.secondReceivedElement);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseNewStrategyAfterReplacement() {
|
||||
RecordingSortStrategy firstStrategy = new RecordingSortStrategy();
|
||||
|
||||
RecordingSortStrategy secondStrategy = new RecordingSortStrategy();
|
||||
|
||||
EvenOnlySortStrategy<Integer> strategy =
|
||||
new EvenOnlySortStrategy<>(firstStrategy, value -> value);
|
||||
|
||||
MyList<Integer> firstList = new MyLinkedList<>();
|
||||
firstList.add(4);
|
||||
firstList.add(2);
|
||||
|
||||
strategy.sort(firstList, Integer::compare);
|
||||
|
||||
assertTrue(firstStrategy.wasCalled);
|
||||
|
||||
strategy.setSortStrategy(secondStrategy);
|
||||
|
||||
MyList<Integer> secondList = new MyLinkedList<>();
|
||||
secondList.add(8);
|
||||
secondList.add(6);
|
||||
|
||||
strategy.sort(secondList, Integer::compare);
|
||||
|
||||
assertTrue(secondStrategy.wasCalled);
|
||||
}
|
||||
|
||||
private static class RecordingSortStrategy implements SortStrategy<Integer> {
|
||||
|
||||
private boolean wasCalled;
|
||||
private int receivedSize;
|
||||
private Integer firstReceivedElement;
|
||||
private Integer secondReceivedElement;
|
||||
|
||||
@Override
|
||||
public void sort(MyList<Integer> list, Comparator<Integer> comparator) {
|
||||
wasCalled = true;
|
||||
receivedSize = list.size();
|
||||
|
||||
if (list.size() > 0) {
|
||||
firstReceivedElement = list.get(0);
|
||||
}
|
||||
|
||||
if (list.size() > 1) {
|
||||
secondReceivedElement = list.get(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package ru.project.sorting;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.Comparator;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.project.collection.MyLinkedList;
|
||||
import ru.project.collection.MyList;
|
||||
import ru.project.student.Student;
|
||||
|
||||
class SortStrategyTest {
|
||||
|
||||
private MyList<Student> students;
|
||||
private final Comparator<Student> groupComparator = new StudentGroupComparator();
|
||||
private final Comparator<Student> gradeComparator = new StudentAverageGradeComparator();
|
||||
private final Comparator<Student> recordComparator = new StudentRecordBookComparator();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
students = new MyLinkedList<>();
|
||||
students.add(new Student("A12", 4.5, 12345));
|
||||
students.add(new Student("B32", 3.8, 12346));
|
||||
students.add(new Student("Z00", 4.9, 12347));
|
||||
students.add(new Student("A12", 4.2, 12348));
|
||||
students.add(new Student("B32", 5.0, 12349));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMergeSortByGroupNumber() {
|
||||
SortStrategy<Student> strategy = new MergeSortStrategy<>();
|
||||
strategy.sort(students, groupComparator);
|
||||
assertOrder(students, "A12", "A12", "B32", "B32", "Z00");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMergeSortByAverageGrade() {
|
||||
SortStrategy<Student> strategy = new MergeSortStrategy<>();
|
||||
strategy.sort(students, gradeComparator);
|
||||
assertOrder(students, 3.8, 4.2, 4.5, 4.9, 5.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMergeSortByRecordBookNumber() {
|
||||
SortStrategy<Student> strategy = new MergeSortStrategy<>();
|
||||
strategy.sort(students, recordComparator);
|
||||
assertOrder(students, 12345, 12346, 12347, 12348, 12349);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBubbleSortByGroupNumber() {
|
||||
SortStrategy<Student> strategy = new BubbleSortStrategy<>();
|
||||
strategy.sort(students, groupComparator);
|
||||
assertOrder(students, "A12", "A12", "B32", "B32", "Z00");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBubbleSortByAverageGrade() {
|
||||
SortStrategy<Student> strategy = new BubbleSortStrategy<>();
|
||||
strategy.sort(students, gradeComparator);
|
||||
assertOrder(students, 3.8, 4.2, 4.5, 4.9, 5.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBubbleSortByRecordBookNumber() {
|
||||
SortStrategy<Student> strategy = new BubbleSortStrategy<>();
|
||||
strategy.sort(students, recordComparator);
|
||||
assertOrder(students, 12345, 12346, 12347, 12348, 12349);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEvenOnlySortByRecordBookNumber() {
|
||||
MyList<Student> list = new MyLinkedList<>();
|
||||
list.add(new Student("A01", 4.5, 7));
|
||||
list.add(new Student("A02", 4.5, 8));
|
||||
list.add(new Student("A03", 4.5, 3));
|
||||
list.add(new Student("A04", 4.5, 2));
|
||||
list.add(new Student("A05", 4.5, 5));
|
||||
list.add(new Student("A06", 4.5, 4));
|
||||
|
||||
EvenOnlySortStrategy<Student> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<Student>(), Student::getRecordBookNumber);
|
||||
|
||||
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
||||
|
||||
int[] expected = {7, 2, 3, 4, 5, 8};
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
assertEquals(expected[i], list.get(i).getRecordBookNumber());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEvenOnlyWithNoEvenElements() {
|
||||
MyList<Student> list = new MyLinkedList<>();
|
||||
list.add(new Student("A01", 4.5, 1));
|
||||
list.add(new Student("A02", 4.5, 3));
|
||||
list.add(new Student("A03", 4.5, 5));
|
||||
|
||||
EvenOnlySortStrategy<Student> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<Student>(), Student::getRecordBookNumber);
|
||||
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
||||
|
||||
int[] expected = {1, 3, 5};
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
assertEquals(expected[i], list.get(i).getRecordBookNumber());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEvenOnlyWithAllEvenElements() {
|
||||
MyList<Student> list = new MyLinkedList<>();
|
||||
list.add(new Student("A01", 4.5, 4));
|
||||
list.add(new Student("A02", 4.5, 2));
|
||||
list.add(new Student("A03", 4.5, 6));
|
||||
|
||||
EvenOnlySortStrategy<Student> strategy =
|
||||
new EvenOnlySortStrategy<>(new MergeSortStrategy<Student>(), Student::getRecordBookNumber);
|
||||
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
||||
|
||||
int[] expected = {2, 4, 6};
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
assertEquals(expected[i], list.get(i).getRecordBookNumber());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSortEmptyList() {
|
||||
MyList<Student> empty = new MyLinkedList<>();
|
||||
SortStrategy<Student> strategy = new MergeSortStrategy<>();
|
||||
strategy.sort(empty, groupComparator);
|
||||
assertEquals(0, empty.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSortSingleElement() {
|
||||
MyList<Student> single = new MyLinkedList<>();
|
||||
single.add(new Student("A12", 4.5, 12345));
|
||||
SortStrategy<Student> strategy = new MergeSortStrategy<>();
|
||||
strategy.sort(single, groupComparator);
|
||||
assertEquals(1, single.size());
|
||||
}
|
||||
|
||||
private void assertOrder(MyList<Student> list, String... groups) {
|
||||
assertEquals(groups.length, list.size());
|
||||
for (int i = 0; i < groups.length; i++) {
|
||||
assertEquals(groups[i], list.get(i).getGroupNumber());
|
||||
}
|
||||
}
|
||||
|
||||
private void assertOrder(MyList<Student> list, double... grades) {
|
||||
assertEquals(grades.length, list.size());
|
||||
for (int i = 0; i < grades.length; i++) {
|
||||
assertEquals(grades[i], list.get(i).getAverageGrade(), 0.001);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertOrder(MyList<Student> list, int... records) {
|
||||
assertEquals(records.length, list.size());
|
||||
for (int i = 0; i < records.length; i++) {
|
||||
assertEquals(records[i], list.get(i).getRecordBookNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package ru.project.student;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class StudentTest {
|
||||
|
||||
@Test
|
||||
void testValidStudentCreation() {
|
||||
Student student = new Student("A12", 4.5, 12345);
|
||||
assertEquals("A12", student.getGroupNumber());
|
||||
assertEquals(4.5, student.getAverageGrade());
|
||||
assertEquals(12345, student.getRecordBookNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidGroupNumbers() {
|
||||
assertDoesNotThrow(() -> new Student("A12", 4.5, 1));
|
||||
assertDoesNotThrow(() -> new Student("B32", 4.5, 1));
|
||||
assertDoesNotThrow(() -> new Student("Z00", 4.5, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidGroupNumbers() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new Student("a12", 4.5, 1));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Student("A1", 4.5, 1));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Student("A123", 4.5, 1));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Student("AA1", 4.5, 1));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Student("а1А", 4.5, 1));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Student("аа!!а123", 4.5, 1));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Student("23фи!12", 4.5, 1));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Student("№№###*()", 4.5, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidAverageGrades() {
|
||||
assertDoesNotThrow(() -> new Student("A12", 0.0, 1));
|
||||
assertDoesNotThrow(() -> new Student("A12", 5.0, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidAverageGrades() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new Student("A12", -1.0, 1));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Student("A12", 6.0, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidRecordBookNumbers() {
|
||||
assertDoesNotThrow(() -> new Student("A12", 4.5, 1));
|
||||
assertDoesNotThrow(() -> new Student("A12", 4.5, 12345));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidRecordBookNumbers() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, 0));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithGroupNumber() {
|
||||
Student original = new Student("A12", 4.5, 12345);
|
||||
Student updated = original.withGroupNumber("B32");
|
||||
|
||||
assertNotSame(original, updated);
|
||||
assertEquals("B32", updated.getGroupNumber());
|
||||
assertEquals(4.5, updated.getAverageGrade());
|
||||
assertEquals(12345, updated.getRecordBookNumber());
|
||||
|
||||
assertEquals("A12", original.getGroupNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithAverageGrade() {
|
||||
Student original = new Student("A12", 4.5, 12345);
|
||||
Student updated = original.withAverageGrade(4.8);
|
||||
|
||||
assertNotSame(original, updated);
|
||||
assertEquals(4.8, updated.getAverageGrade());
|
||||
assertEquals("A12", updated.getGroupNumber());
|
||||
assertEquals(12345, updated.getRecordBookNumber());
|
||||
|
||||
assertEquals(4.5, original.getAverageGrade());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithRecordBookNumber() {
|
||||
Student original = new Student("A12", 4.5, 12345);
|
||||
Student updated = original.withRecordBookNumber(99999);
|
||||
|
||||
assertNotSame(original, updated);
|
||||
assertEquals(99999, updated.getRecordBookNumber());
|
||||
assertEquals("A12", updated.getGroupNumber());
|
||||
assertEquals(4.5, updated.getAverageGrade());
|
||||
|
||||
assertEquals(12345, original.getRecordBookNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithInvalidGroupNumber() {
|
||||
Student original = new Student("A12", 4.5, 12345);
|
||||
assertThrows(IllegalArgumentException.class, () -> original.withGroupNumber("a12"));
|
||||
assertEquals("A12", original.getGroupNumber()); // не изменился
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithInvalidAverageGrade() {
|
||||
Student original = new Student("A12", 4.5, 12345);
|
||||
assertThrows(IllegalArgumentException.class, () -> original.withAverageGrade(6.0));
|
||||
assertEquals(4.5, original.getAverageGrade());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithInvalidRecordBookNumber() {
|
||||
Student original = new Student("A12", 4.5, 12345);
|
||||
assertThrows(IllegalArgumentException.class, () -> original.withRecordBookNumber(0));
|
||||
assertEquals(12345, original.getRecordBookNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEqualsAndHashCode() {
|
||||
Student s1 = new Student("A12", 4.5, 12345);
|
||||
Student s2 = new Student("A12", 4.5, 12345);
|
||||
Student s3 = new Student("B32", 4.5, 12345);
|
||||
Student s4 = new Student("A12", 4.8, 12345);
|
||||
Student s5 = new Student("A12", 4.5, 99999);
|
||||
|
||||
assertEquals(s1, s2);
|
||||
assertEquals(s1.hashCode(), s2.hashCode());
|
||||
|
||||
assertNotEquals(s1, s3);
|
||||
assertNotEquals(s1, s4);
|
||||
assertNotEquals(s1, s5);
|
||||
assertNotEquals(s1, null);
|
||||
assertNotEquals(s1, "some string");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
Главное меню
|
||||
{
|
||||
Показать текущую коллекцию
|
||||
|
||||
Меню создания новой коллекции
|
||||
{
|
||||
Вернуться
|
||||
|
||||
Задать размер
|
||||
|
||||
Настроить способ заполнения
|
||||
{
|
||||
Пользовательские данные
|
||||
{
|
||||
Задать поле 'Название A'
|
||||
Задать поле 'Название B'
|
||||
Задать поле 'Название C'
|
||||
Задать индекс изменяемого объекта
|
||||
Изменить данные объекта
|
||||
Предпросмотр
|
||||
}
|
||||
|
||||
Случайные данные
|
||||
{
|
||||
Настройка диапазона генерации
|
||||
{
|
||||
Поле 'Название A'
|
||||
{
|
||||
Задать минимальное значение
|
||||
Задать максимальное значение
|
||||
}
|
||||
|
||||
Поле 'Название B'
|
||||
{
|
||||
Задать минимальное значение
|
||||
Задать максимальное значение
|
||||
}
|
||||
|
||||
Поле 'Название C'
|
||||
{
|
||||
Задать минимальное значение
|
||||
Задать максимальное значение
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Данные из файла
|
||||
{
|
||||
Задать путь до файла
|
||||
{
|
||||
Абсолютный
|
||||
Относительный
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Выбрать способ заполнения
|
||||
{
|
||||
Пользовательские данные *
|
||||
Случайные данные
|
||||
Данные из файла
|
||||
}
|
||||
|
||||
Создать новую коллекцию
|
||||
}
|
||||
|
||||
Меню сохранения текущей коллекции в файл
|
||||
{
|
||||
Вывести текущий абсолютный путь до файла
|
||||
|
||||
Задать путь до файла
|
||||
{
|
||||
Абсолютный
|
||||
Относительный
|
||||
}
|
||||
|
||||
Дописать коллекцию в текущий файл
|
||||
}
|
||||
|
||||
Меню сортировки текущей коллекции
|
||||
{
|
||||
Вернуться
|
||||
|
||||
Настроить способ сортировки
|
||||
{
|
||||
Обычная сортировка
|
||||
{
|
||||
Сортировать по полю 'Название A'
|
||||
Сортировать по полю 'Название B'
|
||||
Сортировать по полю 'Название C'
|
||||
}
|
||||
}
|
||||
|
||||
Настроить способ сортировки
|
||||
{
|
||||
Сортировка по чётности
|
||||
{
|
||||
Выбрать числовое поле 'Название A'
|
||||
Выбрать числовое поле 'Название B'
|
||||
}
|
||||
}
|
||||
|
||||
Выбрать способ сортировки
|
||||
{
|
||||
Обычная сортировка *
|
||||
Сортировка по чётности
|
||||
}
|
||||
|
||||
Сортировать текущую коллекцию
|
||||
}
|
||||
|
||||
Меню поиска элемента в текущей коллекции
|
||||
{
|
||||
Вернуться
|
||||
Задать значение поля 'Название A'
|
||||
Задать значение поля 'Название B'
|
||||
Задать значение поля 'Название C'
|
||||
Найти первое вхождение в текущей коллекции
|
||||
}
|
||||
|
||||
Меню подсчёта количества вхождений элемента
|
||||
{
|
||||
Задать значение поля 'Название A'
|
||||
Задать значение поля 'Название B'
|
||||
Задать значение поля 'Название C'
|
||||
Выполнить многопоточный подсчёт
|
||||
}
|
||||
|
||||
Завершить выполнение программы
|
||||
}
|
||||
+143
-26
@@ -42,13 +42,13 @@ C25,4.9,10003
|
||||
|
||||
---
|
||||
|
||||
# `StudentCsvWriter`
|
||||
## `StudentCsvWriter`
|
||||
|
||||
## Назначение
|
||||
### Назначение
|
||||
|
||||
Дозаписывать одного студента в указанный CSV-файл.
|
||||
Дозаписывать одного студента в **существующий** CSV-файл.
|
||||
|
||||
## Контракт
|
||||
### Контракт
|
||||
|
||||
Конструктор:
|
||||
|
||||
@@ -65,41 +65,158 @@ public void write(Student student) throws IOException
|
||||
### Требования
|
||||
|
||||
1. Метод принимает одного `Student`.
|
||||
2. Запись выполняется в режиме добавления.
|
||||
3. Существующие записи не изменяются.
|
||||
4. Если файл существует, заголовок повторно не записывается.
|
||||
5. Если файл не существует, необходимо создать файл.
|
||||
6. При создании нового файла имя должно соответствовать формату:
|
||||
2. Указанный файл должен уже существовать.
|
||||
3. Запись выполняется в режиме добавления.
|
||||
4. Существующие записи не должны изменяться.
|
||||
5. Заголовок CSV не должен записываться повторно.
|
||||
6. Если файл не существует, новый файл **не создавать**.
|
||||
7. Если файл не существует, метод должен выбросить `IOException`.
|
||||
8. При успешной записи метод завершается без исключения.
|
||||
9. Метод не возвращает значение.
|
||||
10. Исключения, связанные с записью файла, не обрабатывать внутри метода.
|
||||
11. Исключения `Student` и `StudentBuilder` также не обрабатывать.
|
||||
|
||||
## Невозможность записи
|
||||
|
||||
Если указанный файл не существует, метод должен выбросить `IOException`.
|
||||
|
||||
Также `IOException` должен возникать и передаваться вызывающему коду, если:
|
||||
|
||||
- файл недоступен для записи;
|
||||
- отсутствуют права на запись;
|
||||
- произошла ошибка файловой системы;
|
||||
- запись в файл невозможна по другой причине.
|
||||
|
||||
Создание файла с именем вида:
|
||||
|
||||
```text
|
||||
students-yyyy-MM-dd-HH-mm.csv
|
||||
```
|
||||
|
||||
Например:
|
||||
`StudentCsvWriter` **не выполняет**.
|
||||
|
||||
```text
|
||||
students-2026-08-28-20-45.csv
|
||||
## Пример теста: несуществующий файл
|
||||
|
||||
Передать путь к несуществующему файлу:
|
||||
|
||||
```java
|
||||
writer.write(student);
|
||||
```
|
||||
|
||||
1. Новый файл создаётся в директории, указанной абсолютным путём.
|
||||
2. При создании нового файла сначала записывается заголовок.
|
||||
3. При успешной записи метод завершается без исключения.
|
||||
4. Метод не возвращает значение.
|
||||
5. Исключения, связанные с записью файла, не обрабатывать внутри метода — они должны передаваться вызывающему коду.
|
||||
6. Исключения `Student` и `StudentBuilder` также не обрабатывать.
|
||||
Проверить:
|
||||
|
||||
## Невозможность записи
|
||||
```text
|
||||
IOException
|
||||
```
|
||||
|
||||
Если студент не может быть записан, например:
|
||||
Проверить также, что новый файл не был создан.
|
||||
|
||||
- отсутствуют права на запись;
|
||||
- невозможно создать файл;
|
||||
- файл недоступен;
|
||||
- произошла ошибка файловой системы;
|
||||
## Изменение теста существующего файла
|
||||
|
||||
метод должен выбросить соответствующее исключение, например `IOException`.
|
||||
Для существующего файла:
|
||||
|
||||
Запись не должна возвращать специальное значение вроде `0` для обозначения ошибки.
|
||||
```text
|
||||
groupNumber,averageGrade,recordBookNumber
|
||||
A12,4.5,10001
|
||||
```
|
||||
|
||||
выполнить:
|
||||
|
||||
```java
|
||||
writer.write(student2);
|
||||
```
|
||||
|
||||
Проверить, что результат:
|
||||
|
||||
```text
|
||||
groupNumber,averageGrade,recordBookNumber
|
||||
A12,4.5,10001
|
||||
B07,3.8,10002
|
||||
```
|
||||
|
||||
Заголовок присутствует только один раз.
|
||||
|
||||
## Дополнительный метод `StudentCsvWriter`
|
||||
|
||||
Кроме метода записи одного студента:
|
||||
|
||||
```java
|
||||
public void write(Student student) throws IOException;
|
||||
```
|
||||
|
||||
необходимо реализовать метод:
|
||||
|
||||
```java
|
||||
public void write(MyList<Student>[] students) throws IOException;
|
||||
```
|
||||
|
||||
### Контракт
|
||||
|
||||
Метод должен:
|
||||
|
||||
1. Принимать массив `MyList<Student>`.
|
||||
2. Последовательно обрабатывать все переданные коллекции.
|
||||
3. Записывать каждого студента в указанный CSV-файл.
|
||||
4. Сохранять порядок:
|
||||
- коллекций в массиве;
|
||||
- студентов внутри каждой `MyList`.
|
||||
5. Использовать тот же формат CSV и те же правила записи, что и `write(Student)`.
|
||||
6. Работать только с уже существующим файлом.
|
||||
7. Не создавать файл, если он отсутствует.
|
||||
8. При отсутствии файла выбрасывать `IOException`.
|
||||
9. Не изменять переданные `MyList<Student>`.
|
||||
10. Не обрабатывать исключения, возникающие при создании или валидации `Student`.
|
||||
11. При ошибке записи прекращать операцию и передавать исключение вызывающему коду.
|
||||
|
||||
### Пример
|
||||
|
||||
```java
|
||||
MyList<Student>[] studentLists = ...;
|
||||
|
||||
writer.write(studentLists);
|
||||
```
|
||||
|
||||
Если:
|
||||
|
||||
```text
|
||||
studentLists[0] = [Student A, Student B]
|
||||
studentLists[1] = [Student C]
|
||||
studentLists[2] = [Student D, Student E]
|
||||
```
|
||||
|
||||
в файл должны последовательно добавиться:
|
||||
|
||||
```text
|
||||
Student A
|
||||
Student B
|
||||
Student C
|
||||
Student D
|
||||
Student E
|
||||
```
|
||||
|
||||
### Проверка пустых коллекций
|
||||
|
||||
Пустые `MyList<Student>` внутри массива не должны приводить к ошибке и не должны добавлять записей в файл.
|
||||
|
||||
### Проверка пустого массива
|
||||
|
||||
Для:
|
||||
|
||||
```java
|
||||
MyList<Student>[] studentLists = ...; // размер 0
|
||||
```
|
||||
|
||||
метод не должен добавлять никаких записей и не должен изменять файл.
|
||||
|
||||
### Проверка отсутствующего файла
|
||||
|
||||
Для несуществующего файла:
|
||||
|
||||
```java
|
||||
writer.write(studentLists);
|
||||
```
|
||||
|
||||
должен выбрасываться `IOException`. Файл создавать запрещено.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
# Техническое задание: генератор случайных студентов
|
||||
|
||||
## Задача
|
||||
|
||||
Реализовать класс для формирования `MyList<Student>` со случайными значениями.
|
||||
|
||||
По умолчанию ограничения диапазона для генерации не задаются. В таком случае значения должны генерироваться во всём допустимом диапазоне, установленном правилами класса `Student`.
|
||||
|
||||
Генерация объектов должна выполняться через существующий `StudentBuilder`.
|
||||
|
||||
## Название класса
|
||||
|
||||
```text id="m9s0q1"
|
||||
RandomStudentGenerator
|
||||
```
|
||||
|
||||
## Контракт класса
|
||||
|
||||
Класс должен хранить текущие ограничения генерации для каждого поля `Student`.
|
||||
|
||||
Для каждого поля необходимо предоставить по два метода: левое и правое ограничение.
|
||||
|
||||
### `groupNumber`
|
||||
|
||||
Тип поля: `String`.
|
||||
|
||||
```java id="t2z8k4"
|
||||
RandomStudentGenerator setMinGroupNumber(String minGroupNumber);
|
||||
```
|
||||
|
||||
```java id="q7x5n1"
|
||||
RandomStudentGenerator setMaxGroupNumber(String maxGroupNumber);
|
||||
```
|
||||
|
||||
Ограничения применяются при генерации `groupNumber`.
|
||||
|
||||
Сравнение границ выполнять в соответствии с правилами, используемыми для `groupNumber` в `Student`.
|
||||
|
||||
### `averageGrade`
|
||||
|
||||
Тип поля: `double`.
|
||||
|
||||
```java id="v4n2j8"
|
||||
RandomStudentGenerator setMinAverageGrade(double minAverageGrade);
|
||||
```
|
||||
|
||||
```java id="r5c1p9"
|
||||
RandomStudentGenerator setMaxAverageGrade(double maxAverageGrade);
|
||||
```
|
||||
|
||||
Диапазон должен быть включительным:
|
||||
|
||||
```text id="u3k7w2"
|
||||
minAverageGrade <= averageGrade <= maxAverageGrade
|
||||
```
|
||||
|
||||
### `recordBookNumber`
|
||||
|
||||
Тип поля: `int`.
|
||||
|
||||
```java id="d8m4x6"
|
||||
RandomStudentGenerator setMinRecordBookNumber(int minRecordBookNumber);
|
||||
```
|
||||
|
||||
```java id="e1q9s5"
|
||||
RandomStudentGenerator setMaxRecordBookNumber(int maxRecordBookNumber);
|
||||
```
|
||||
|
||||
Диапазон должен быть включительным:
|
||||
|
||||
```text id="y6p2v8"
|
||||
minRecordBookNumber <= recordBookNumber <= maxRecordBookNumber
|
||||
```
|
||||
|
||||
## Метод генерации
|
||||
|
||||
Основной метод:
|
||||
|
||||
```java id="k3w7m1"
|
||||
MyList<Student> generate(int size);
|
||||
```
|
||||
|
||||
Контракт:
|
||||
|
||||
- создаёт ровно `size` объектов `Student`;
|
||||
- все объекты должны быть валидными;
|
||||
- каждый объект создаётся через `StudentBuilder`;
|
||||
- результат возвращается как `MyList<Student>`;
|
||||
- стандартные `List`, `ArrayList`, `LinkedList` и другие контейнеры использовать нельзя.
|
||||
|
||||
## Поведение диапазонов по умолчанию
|
||||
|
||||
Если для поля ограничение не задано, используется весь допустимый диапазон этого поля.
|
||||
|
||||
Необходимо учитывать существующие ограничения `Student`, в частности:
|
||||
|
||||
```text id="z4r8t2"
|
||||
averageGrade:
|
||||
getMinAverageGrade()
|
||||
getMaxAverageGrade()
|
||||
|
||||
recordBookNumber:
|
||||
getMinRecordBookNumber()
|
||||
```
|
||||
|
||||
Для `groupNumber` использовать существующий шаблон:
|
||||
|
||||
```text id="n5c7y3"
|
||||
getGroupNumberPattern()
|
||||
```
|
||||
|
||||
Генерируемое значение `groupNumber` должно соответствовать требованиям `Student`.
|
||||
|
||||
## Проверка ограничений
|
||||
|
||||
При установке границ необходимо проверять их корректность.
|
||||
|
||||
Недопустимые случаи:
|
||||
|
||||
```text id="h2k6p4"
|
||||
min > max
|
||||
```
|
||||
|
||||
Для них выбрасывать:
|
||||
|
||||
```java id="c7v3n9"
|
||||
IllegalArgumentException
|
||||
```
|
||||
|
||||
Ограничение также не должно выходить за допустимые значения `Student`.
|
||||
|
||||
## Рекомендуемая реализация
|
||||
|
||||
Класс должен хранить шесть ограничений:
|
||||
|
||||
```text id="b5q9x2"
|
||||
minGroupNumber
|
||||
maxGroupNumber
|
||||
|
||||
minAverageGrade
|
||||
maxAverageGrade
|
||||
|
||||
minRecordBookNumber
|
||||
maxRecordBookNumber
|
||||
```
|
||||
|
||||
Для каждого поля значения генерируются независимо.
|
||||
|
||||
Рекомендуется использовать стандартный генератор случайных чисел Java, например:
|
||||
|
||||
```java id="a8f4k1"
|
||||
Random
|
||||
```
|
||||
|
||||
или:
|
||||
|
||||
```java id="p3m7d5"
|
||||
ThreadLocalRandom
|
||||
```
|
||||
|
||||
При создании `Student`:
|
||||
|
||||
```text id="w6j2r9"
|
||||
сгенерировать поля
|
||||
↓
|
||||
StudentBuilder
|
||||
↓
|
||||
build()
|
||||
↓
|
||||
Student
|
||||
↓
|
||||
MyList<Student>
|
||||
```
|
||||
|
||||
Дополнительную валидацию, дублирующую правила `Student`, в генераторе не реализовывать.
|
||||
|
||||
## Примеры использования
|
||||
|
||||
Без настройки диапазонов:
|
||||
|
||||
```java id="s2k8m4"
|
||||
RandomStudentGenerator generator =
|
||||
new RandomStudentGenerator();
|
||||
|
||||
MyList<Student> students =
|
||||
generator.generate(100);
|
||||
```
|
||||
|
||||
С настройкой диапазонов:
|
||||
|
||||
```java id="f7q3n1"
|
||||
RandomStudentGenerator generator =
|
||||
new RandomStudentGenerator()
|
||||
.setMinGroupNumber("A00")
|
||||
.setMaxGroupNumber("Z99")
|
||||
.setMinAverageGrade(3.0)
|
||||
.setMaxAverageGrade(5.0)
|
||||
.setMinRecordBookNumber(10000)
|
||||
.setMaxRecordBookNumber(99999);
|
||||
|
||||
MyList<Student> students =
|
||||
generator.generate(100);
|
||||
```
|
||||
|
||||
## Тесты
|
||||
|
||||
### Генерация без ограничений
|
||||
|
||||
```java id="r1w5p8"
|
||||
MyList<Student> result = generator.generate(100);
|
||||
```
|
||||
|
||||
Проверить:
|
||||
|
||||
- размер равен `100`;
|
||||
- все элементы не равны `null`;
|
||||
- каждый `Student` валиден.
|
||||
|
||||
### Ограничение `averageGrade`
|
||||
|
||||
Задать:
|
||||
|
||||
```text id="x4n7k2"
|
||||
3.0 — 4.0
|
||||
```
|
||||
|
||||
Проверить, что все значения находятся в указанном диапазоне.
|
||||
|
||||
### Ограничение `recordBookNumber`
|
||||
|
||||
Задать:
|
||||
|
||||
```text id="q9m3v6"
|
||||
10000 — 20000
|
||||
```
|
||||
|
||||
Проверить диапазон и включение границ.
|
||||
|
||||
### Ограничение `groupNumber`
|
||||
|
||||
Задать левую и правую границы.
|
||||
|
||||
Проверить, что все сгенерированные значения находятся между ними согласно правилам сравнения `groupNumber`.
|
||||
|
||||
### Граничные значения
|
||||
|
||||
Установить:
|
||||
|
||||
```text id="p8c4s1"
|
||||
min == max
|
||||
```
|
||||
|
||||
Проверить, что все сгенерированные значения равны указанной границе.
|
||||
|
||||
### Некорректный диапазон
|
||||
|
||||
Проверить выброс `IllegalArgumentException`, если:
|
||||
|
||||
```text id="z6t2h5"
|
||||
min > max
|
||||
```
|
||||
|
||||
### Некорректное ограничение
|
||||
|
||||
Передать диапазон, выходящий за допустимые значения `Student`.
|
||||
|
||||
Проверить выброс исключения.
|
||||
|
||||
### Генерация пустой коллекции
|
||||
|
||||
Для:
|
||||
|
||||
```java id="j5r8w3"
|
||||
generate(0)
|
||||
```
|
||||
|
||||
ожидается пустой `MyList<Student>`.
|
||||
|
||||
### Некорректный размер
|
||||
|
||||
Для отрицательного размера:
|
||||
|
||||
```java id="n2k7p4"
|
||||
generate(-1)
|
||||
```
|
||||
|
||||
ожидается `IllegalArgumentException`.
|
||||
|
||||
## Результат
|
||||
|
||||
Необходимо реализовать:
|
||||
|
||||
```text id="v8m3q6"
|
||||
RandomStudentGenerator.java
|
||||
```
|
||||
|
||||
Класс должен использовать существующие:
|
||||
|
||||
```text id="f1j9x4"
|
||||
Student.java
|
||||
StudentBuilder.java
|
||||
MyList.java
|
||||
```
|
||||
|
||||
Изменять интерфейсы `Student`, `StudentBuilder` и `MyList` для выполнения данного задания не требуется.
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user