Merge branch 'release/1.0.0'

This commit is contained in:
2026-09-02 19:05:47 +04:00
81 changed files with 8094 additions and 51 deletions
+256
View File
@@ -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
View File
@@ -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>
+41 -6
View File
@@ -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());
}
}
@@ -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());
}
}
-19
View File
@@ -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");
}
}
+130
View File
@@ -0,0 +1,130 @@
Главное меню
{
Показать текущую коллекцию
Меню создания новой коллекции
{
Вернуться
Задать размер
Настроить способ заполнения
{
Пользовательские данные
{
Задать поле 'Название A'
Задать поле 'Название B'
Задать поле 'Название C'
Задать индекс изменяемого объекта
Изменить данные объекта
Предпросмотр
}
Случайные данные
{
Настройка диапазона генерации
{
Поле 'Название A'
{
Задать минимальное значение
Задать максимальное значение
}
Поле 'Название B'
{
Задать минимальное значение
Задать максимальное значение
}
Поле 'Название C'
{
Задать минимальное значение
Задать максимальное значение
}
}
}
Данные из файла
{
Задать путь до файла
{
Абсолютный
Относительный
}
}
}
Выбрать способ заполнения
{
Пользовательские данные *
Случайные данные
Данные из файла
}
Создать новую коллекцию
}
Меню сохранения текущей коллекции в файл
{
Вывести текущий абсолютный путь до файла
Задать путь до файла
{
Абсолютный
Относительный
}
Дописать коллекцию в текущий файл
}
Меню сортировки текущей коллекции
{
Вернуться
Настроить способ сортировки
{
Обычная сортировка
{
Сортировать по полю 'Название A'
Сортировать по полю 'Название B'
Сортировать по полю 'Название C'
}
}
Настроить способ сортировки
{
Сортировка по чётности
{
Выбрать числовое поле 'Название A'
Выбрать числовое поле 'Название B'
}
}
Выбрать способ сортировки
{
Обычная сортировка *
Сортировка по чётности
}
Сортировать текущую коллекцию
}
Меню поиска элемента в текущей коллекции
{
Вернуться
Задать значение поля 'Название A'
Задать значение поля 'Название B'
Задать значение поля 'Название C'
Найти первое вхождение в текущей коллекции
}
Меню подсчёта количества вхождений элемента
{
Задать значение поля 'Название A'
Задать значение поля 'Название B'
Задать значение поля 'Название C'
Выполнить многопоточный подсчёт
}
Завершить выполнение программы
}
+143 -26
View File
@@ -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`. Файл создавать запрещено.
---
+305
View File
@@ -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