diff --git a/.gitignore b/.gitignore index 1fac4d5..805b0bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .gradle +.idea/ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ @@ -10,6 +11,7 @@ build/ .idea/jarRepositories.xml .idea/compiler.xml .idea/libraries/ +.idea/misc.xml *.iws *.iml *.ipr @@ -40,4 +42,4 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 2a65317..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 3b60104..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index d5d5cd9..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/FedorPetrovReadMe b/FedorPetrovReadMe new file mode 100644 index 0000000..16841f1 --- /dev/null +++ b/FedorPetrovReadMe @@ -0,0 +1,10 @@ +### Работа с пользовательским вводом (Фёдор Петров) + +Задачи: + +- Реализовать меню для пользователя: + - Выбор способа заполнения данных (рандом, из файла, ручной ввод). + - Ввод длины массива/списка. + - Возможность выхода из цикла только по соответствующему выбору. +- Обеспечить корректный цикл программы, чтобы интерфейс оставался интерактивным. +- Реализовать вывод данных через интерфейс Printable. diff --git a/NikitaUdinReadMe b/NikitaUdinReadMe new file mode 100644 index 0000000..23e7989 --- /dev/null +++ b/NikitaUdinReadMe @@ -0,0 +1,13 @@ +### Генерация и валидация данных (Никита Юдин) + +Задачи: + +- Создать классы и методы, которые заполняют коллекцию выбранного класса: + - Генерация случайных объектов. + - Чтение объектов из файла. + - Ручной ввод объектов пользователем. +- Сделать валидацию данных . +- Работать с Builder, чтобы объекты создавались корректно. +- Дополнительное задание 3: заполнение коллекций должно осуществляться посредством стримов. (Генерация данных тоже через стримы) +- 3* Коллекции для заполнения должны быть кастомными. +- (Использовать стрим для генерации данных, а результат перекладывать в кастомную коллекцию) diff --git a/PavelTrofimovReadMe b/PavelTrofimovReadMe new file mode 100644 index 0000000..532e143 --- /dev/null +++ b/PavelTrofimovReadMe @@ -0,0 +1,12 @@ +Задача: +### Реализация паттерна «Стратегия» для сортировки (Павел Трофимов) + +Задачи: + +- Реализовать конкретные стратегии сортировки по каждому полю. +- Обеспечить возможность динамического выбора стратегии во время выполнения программы. +- Убедиться, что сортировка корректно работает с любым количеством объектов. + +Дополнительное задание 1: дополнительно к основным сортировкам реализовать эти же алгоритмы сортировки таким образом, +что объекты классов будут сортироваться по какому-либо числовому полю: объекты с четными значениями этого поля должны быть отсортированы в натуральном порядке, +а с нечетными – оставаться на исходных позициях. diff --git a/Test.class b/Test.class new file mode 100644 index 0000000..9a44dd4 Binary files /dev/null and b/Test.class differ diff --git a/Test.java b/Test.java new file mode 100644 index 0000000..f048d54 --- /dev/null +++ b/Test.java @@ -0,0 +1 @@ +public class Test { public static void main(String[] a) {} } diff --git a/build.gradle b/build.gradle index 973cc48..328b3db 100644 --- a/build.gradle +++ b/build.gradle @@ -13,8 +13,24 @@ dependencies { testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation 'org.junit.jupiter:junit-jupiter' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'com.github.javafaker:javafaker:1.0.2' } test { useJUnitPlatform() -} \ No newline at end of file +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +task run(type: JavaExec) { + classpath = sourceSets.main.runtimeClasspath + mainClass = 'App' + standardInput = System.in +} diff --git a/savedData.txt b/savedData.txt new file mode 100644 index 0000000..67aad48 --- /dev/null +++ b/savedData.txt @@ -0,0 +1,32 @@ + +Client{name='Андреева Дарья', phoneNumber='+79384195474', idNumber=999} +Client{name='Анжела Викторовна Блохина', phoneNumber='+79296568824', idNumber=661} +Client{name='Анжелика Кошелева', phoneNumber='+79804707713', idNumber=78} +Client{name='Антон Воронов', phoneNumber='+79938685129', idNumber=262} +Client{name='Аркадий Игнатьевич Тихонов', phoneNumber='+79337132205', idNumber=23} +Client{name='Бобылев Вячеслав', phoneNumber='+79549089324', idNumber=573} +Client{name='Большаков Матвей Эдуардович', phoneNumber='+79051246042', idNumber=591} +Client{name='Василиса Антоновна Лапина', phoneNumber='+79971387677', idNumber=853} +Client{name='Вера Артёмовна Капустина', phoneNumber='+79591168118', idNumber=461} +Client{name='Вероника Моисеева', phoneNumber='+79058575995', idNumber=889} +Client{name='Виктор Леонидович Зиновьев', phoneNumber='+79272614762', idNumber=627} +Client{name='Гаврилова Алла Владимировна', phoneNumber='+79303034501', idNumber=387} +Client{name='Екатерина Филатова', phoneNumber='+79524244998', idNumber=669} +Client{name='Ефремова Ольга Вячеславовна', phoneNumber='+79201039302', idNumber=778} +Client{name='Игнатий Ильич Антонов', phoneNumber='+79946035137', idNumber=829} +Client{name='Игнатьева Зинаида Вячеславовна', phoneNumber='+79468715374', idNumber=813} +Client{name='Кулагин Дмитрий', phoneNumber='+79019463584', idNumber=93} +Client{name='Лаврентьев Вячеслав Сергеевич', phoneNumber='+79639495445', idNumber=459} +Client{name='Маргарита Викторовна Кузьмина', phoneNumber='+79571424972', idNumber=549} +Client{name='Медведева Валентина', phoneNumber='+79282932301', idNumber=594} +Client{name='Мухина Алина Сергеевна', phoneNumber='+79841015291', idNumber=296} +Client{name='Мухина Раиса Валентиновна', phoneNumber='+79204742853', idNumber=512} +Client{name='Оксана Кулагина', phoneNumber='+79538510155', idNumber=860} +Client{name='Панфилова Арина', phoneNumber='+79678575110', idNumber=927} +Client{name='Попов Роман Евгеньевич', phoneNumber='+79392077705', idNumber=996} +Client{name='Русакова Валентина', phoneNumber='+79499915311', idNumber=3} +Client{name='Субботина Евгения', phoneNumber='+79883134731', idNumber=275} +Client{name='Фадеев Игорь Даниилович', phoneNumber='+79907800005', idNumber=194} +Client{name='Фокин Матвей Витальевич', phoneNumber='+79274394409', idNumber=933} +Client{name='Шаров Юрий', phoneNumber='+79408809462', idNumber=699} + diff --git a/src/main/java/App.java b/src/main/java/App.java index 022ce0f..2354c74 100644 --- a/src/main/java/App.java +++ b/src/main/java/App.java @@ -1,12 +1,12 @@ import userInterface.MenuManager; -import java.util.stream.IntStream; +import java.io.IOException; public class App { - public static void main(String[] args) { + public static void main(String[] args) throws IOException { MenuManager menuManager = new MenuManager(); menuManager.run(); - } } + diff --git a/src/main/java/ClientSortingSystem.java b/src/main/java/ClientSortingSystem.java deleted file mode 100644 index 723f9d8..0000000 --- a/src/main/java/ClientSortingSystem.java +++ /dev/null @@ -1,2 +0,0 @@ -public class ClientSortingSystem { -} diff --git a/src/main/java/dto/Client.java b/src/main/java/dto/Client.java index 3fa19b2..d717db2 100644 --- a/src/main/java/dto/Client.java +++ b/src/main/java/dto/Client.java @@ -35,6 +35,17 @@ public Client build(){ return new Client(this); } + public String getName() { + return name; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public int getIdNumber() { + return idNumber; + } } public String getName() { @@ -48,4 +59,13 @@ public String getPhoneNumber() { public int getIdNumber() { return idNumber; } + + @Override + public String toString() { + return "Client{" + + "name='" + name + '\'' + + ", phoneNumber='" + phoneNumber + '\'' + + ", idNumber=" + idNumber + + '}'; + } } diff --git a/src/main/java/enums/Field.java b/src/main/java/enums/Field.java new file mode 100644 index 0000000..cd119a3 --- /dev/null +++ b/src/main/java/enums/Field.java @@ -0,0 +1,7 @@ +package enums; + +public enum Field { + NAME, + ID_NUMBER, + PHONE_NUMBER +} diff --git a/src/main/java/input/CollectionInterface.java b/src/main/java/input/CollectionInterface.java index 1e45a92..ff28ccd 100644 --- a/src/main/java/input/CollectionInterface.java +++ b/src/main/java/input/CollectionInterface.java @@ -1,12 +1,14 @@ package input; -public interface CollectionInterface { - boolean add(T car); // important - boolean remove(T car); // important - boolean removeAt(int index); +import java.util.stream.Stream; + +public interface CollectionInterface extends Iterable{ + boolean add(T element); // important + boolean remove(T element);// important + void removeByIndex(int index); void clear(); // important T get(int index); - boolean add(T car, int index); int size(); // important - boolean contains(T car); + boolean isEmpty(); + Stream stream(); } diff --git a/src/main/java/input/CustomCollection.java b/src/main/java/input/CustomCollection.java index b9fdbe3..bdab2b8 100644 --- a/src/main/java/input/CustomCollection.java +++ b/src/main/java/input/CustomCollection.java @@ -1,44 +1,250 @@ package input; -public class CustomCollection implements CollectionInterface{ +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * CustomCollection - кастомная реализация динамического массива с автоматическим расширением. + * Поддерживает хранение элементов любого типа, итерацию и базовые операции коллекции. + * + *

Коллекция использует внутренний массив для хранения элементов и автоматически + * расширяется при заполнении. Коэффициент расширения: {@value #GROWTH_FACTOR}. + * + *

Реализует интерфейс {@link Iterable} для поддержки for-each циклов и стримов. + * + *

Основные возможности:

+ *
    + *
  • Динамическое расширение при добавлении элементов
  • + *
  • Поддержка операций добавления, удаления, получения и замены элементов
  • + *
  • Итерация через for-each циклы и итераторы
  • + *
  • Поддержка Stream API
  • + *
  • Добавление всех элементов из другой коллекции
  • + *
+ * + * @param тип элементов, хранимых в коллекции + */ +public class CustomCollection implements CollectionInterface { + /** Коэффициент увеличения емкости при расширении массива. */ + private static final float GROWTH_FACTOR = 1.5f; + + /** Начальная емкость по умолчанию при создании коллекции без указания размера. */ + private static final int DEFAULT_CAPACITY = 10; + + /** Внутренний массив для хранения элементов коллекции. */ + private Object[] elements; + + /** Количество фактически хранящихся элементов в коллекции. */ + private int size; + + public Object[] getElements() { + return elements; + } + + public int getSize() { + return size; + } + + public CustomCollection() { + this.elements = new Object[DEFAULT_CAPACITY]; + } + + public CustomCollection(int initialCapacity) { + if (initialCapacity <= 0) { + throw new IllegalArgumentException("Capacity cannot be less then 0"); + } + this.elements = new Object[initialCapacity]; + } @Override - public boolean add(Object car) { - return false; + public boolean add(T element) { + // Проверяем достаточно ли места для добавления + if (size == elements.length) { + // Если недостаточно, то увеличиваем размер списка + increaseCapacity(size + 1); + } + // Добавляем новый элемент на последнюю позицию + elements[size] = element; + // Увеличиваем число элементов в массиве + size++; + return true; } @Override - public boolean remove(Object car) { - return false; + public void removeByIndex(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Index cannot be less then 0 or more then " + size); + } + // Вычисляем количество элементов, которые нужно сместить влево после удаления + int numToMove = size - index - 1; + + if (numToMove > 0) { + // Используем System.arraycopy для эффективного копирования части массива: + // 1. elements - исходный массив + // 2. index+1 - начальная позиция в исходном массиве (элемент после удаляемого) + // 3. elements - целевой массив (тот же массив, копируем в себя) + // 4. index - начальная позиция в целевом массиве (на место удаляемого элемента) + // 5. numToMove - количество элементов для копирования + System.arraycopy(elements, index + 1, elements, index, numToMove); + } + // Уменьшаем size на 1 после копирования в конце массива остался "лишний" элемент и удаляем его + elements[--size] = null; } @Override - public boolean removeAt(int index) { + public boolean remove(T element) { + for (int i = 0; i < size; i++) { + if(Objects.equals(element, elements[i])) { + removeByIndex(i); + return true; + } + } return false; } @Override public void clear() { - + for (int i = 0; i < size; i++) { + elements[i] = null; + } + size = 0; } @Override - public Object get(int index) { - return null; + public T get(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Индекс не может быть меньше 0 или больше " + size); + } + return (T) elements[index]; + } + + + public T set(int index, T element) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Индекс не может быть меньше 0 или больше " + size); + } + + T old = (T) elements[index]; + elements[index] = element; + return old; } + /** + * Возвращает последовательный {@code Stream} с элементами этой коллекции в качестве источника. + * + *

Этот метод позволяет использовать функциональные операции над элементами коллекции. + * + * @return последовательный {@code Stream} элементов этой коллекции + */ @Override - public boolean add(Object car, int index) { - return false; + public Stream stream() { + return (Stream) Arrays.stream(elements, 0, size); + } + + /** + * Добавляет все элементы из указанной коллекции в конец этой коллекции. + * + *

Порядок элементов в целевой коллекции соответствует порядку, + * в котором они возвращаются итератором указанной коллекции. + * + *

Поведение этого метода не определено, если указанная коллекция изменяется + * в процессе выполнения операции. + * + * @param collection коллекция, содержащая элементы для добавления + */ + public void addAll(CustomCollection collection) { + if (collection == null || collection.isEmpty()) { + return; + } + + for (T element : collection) { + add(element); + } } @Override public int size() { - return 0; + return size; } @Override - public boolean contains(Object car) { - return false; + public boolean isEmpty() { + return size == 0; + } + + @Override + public Iterator iterator() { + return new CustomArrayIterator(); + } + + /** + * Внутренний класс итератора для CustomCollection. + * Реализует fail-fast поведение: выбрасывает {@link IllegalStateException} + * при попытке удаления без предварительного вызова {@link #next()}. + */ + class CustomArrayIterator implements Iterator { + /** Текущая позиция итератора в массиве элементов. */ + private int cursor = 0; + + /** Индекс последнего возвращенного элемента; -1 если элемент не был возвращен или был удален. */ + private int lastRet = -1; + + public CustomArrayIterator() { + } + + /** + * Проверяет, существует ли следующий элемент для итерации. + * + * @return {@code true} если итерация содержит больше элементов, + * {@code false} в противном случае + */ + public boolean hasNext() { + return cursor < size; + } + + /** + * Возвращает следующий элемент в итерации. + * + * @return следующий элемент в итерации + * @throws NoSuchElementException если итерация не содержит больше элементов + */ + public T next() { + if (!hasNext()) { + throw new NoSuchElementException("Элемент отсутствует"); + } + lastRet = cursor; + T element = (T) elements[cursor]; + cursor++; + + return element; + } + + /** + * Удаляет из коллекции последний элемент, возвращенный этим итератором. + * + * @throws IllegalStateException если метод {@link #next} еще не был вызван, + * или метод {@link #remove} уже был вызван после последнего вызова {@link #next} + */ + public void remove() { + if (lastRet == -1) { + throw new IllegalStateException(); + } + removeByIndex(lastRet); + cursor = lastRet; + lastRet = -1; + } + } + + /** + * Увеличивает емкость внутреннего массива, чтобы обеспечить минимальную указанную емкость. + * @param minCapacity минимальная требуемая емкость + */ + private void increaseCapacity(int minCapacity) { + int newCapacity = (int) Math.max(elements.length * GROWTH_FACTOR + 1, minCapacity); + Object[] newElements = new Object[newCapacity]; + System.arraycopy(elements, 0, newElements, 0, size); + elements = newElements; } } diff --git a/src/main/java/input/FileReader.java b/src/main/java/input/FileReader.java deleted file mode 100644 index 70d3383..0000000 --- a/src/main/java/input/FileReader.java +++ /dev/null @@ -1,4 +0,0 @@ -package input; - -public class FileReader { -} diff --git a/src/main/java/input/InputManager.java b/src/main/java/input/InputManager.java index b904efb..7cb8b2e 100644 --- a/src/main/java/input/InputManager.java +++ b/src/main/java/input/InputManager.java @@ -1,5 +1,110 @@ package input; +import input.strategy.*; +import dto.Client; + +import java.io.IOException; + +/** + * Менеджер ввода данных, реализующий паттерн Strategy. + * Выступает в роли Context для управления различными стратегиями ввода данных о клиентах. + * + *

Основные функции:

+ *
    + *
  • Управление текущей стратегией ввода
  • + *
  • Загрузка данных с использованием выбранной стратегии
  • + *
  • Создание стратегий различных типов
  • + *
+ * + * @see ClientInputStrategy + * @see FileReaderStrategy + * @see ManualInputReaderStrategy + * @see RandomDataGeneratorStrategy + * @see CustomCollection + * @see Client + */ + public class InputManager { - CustomCollection customCollection = new CustomCollection(); -} + private ClientInputStrategy currentStrategy; + + + public InputManager() { + // Инициализация без стратегии + } + + public InputManager(ClientInputStrategy initialStrategy) { + if (initialStrategy == null) { + throw new IllegalArgumentException("Стратегия не может быть null"); + } + this.currentStrategy = initialStrategy; + } + + public void setStrategy(ClientInputStrategy strategy) { + if (strategy == null) { + throw new IllegalArgumentException("Стратегия не может быть null"); + } + this.currentStrategy = strategy; + } + + public ClientInputStrategy getCurrentStrategy() { + return currentStrategy; + } + + /** + * Загружает данные о клиентах с использованием текущей стратегии. + * + * @return коллекция клиентов, загруженных с помощью текущей стратегии + * @throws IllegalStateException если стратегия не установлена + * @throws IOException если возникает ошибка ввода-вывода при загрузке данных + */ + public CustomCollection loadData() throws IOException { + if (currentStrategy == null) { + throw new IllegalStateException("Стратегия ввода не установлена. " + + "Используйте setStrategy() перед вызовом loadData()"); + } + return currentStrategy.getData(); + } + + /** + * Создает стратегию для чтения данных из файла. + * + *

Файл должен содержать данные в формате:

+ *
+     * Имя|Телефон|ID
+     * Иван Иванов|+79991234567|1
+     * 
+ * + * @param filePath путь к файлу с данными о клиентах + * @return стратегия чтения из файла + * @throws IllegalArgumentException если {@code filePath} равен {@code null} или пуст + */ + public FileReaderStrategy createFileStrategy(String filePath) { + if (filePath == null || filePath.trim().isEmpty()) { + throw new IllegalArgumentException("Путь к файлу не может быть null или пустым"); + } + return new FileReaderStrategy(filePath); + } + + /** + * Создает стратегию для ручного ввода данных через консоль. + * + *

Стратегия предоставляет интерактивный интерфейс для ввода данных + * о клиентах с валидацией и подтверждением.

+ * + * @return стратегия ручного ввода + */ + public ManualInputReaderStrategy createManualStrategy() { + return new ManualInputReaderStrategy(); + } + + /** + * Создает стратегию для генерации случайных данных о клиентах. + * + * @param count количество клиентов для генерации + * @return стратегия генерации случайных данных + * @throws IllegalArgumentException если {@code count} меньше или равен 0 + */ + public RandomDataGeneratorStrategy createRandomStrategy(int count) { + return new RandomDataGeneratorStrategy(count); + } +} \ No newline at end of file diff --git a/src/main/java/input/ManualInputReader.java b/src/main/java/input/ManualInputReader.java deleted file mode 100644 index 00b0632..0000000 --- a/src/main/java/input/ManualInputReader.java +++ /dev/null @@ -1,4 +0,0 @@ -package input; - -public class ManualInputReader { -} diff --git a/src/main/java/input/RandomDataGenerator.java b/src/main/java/input/RandomDataGenerator.java deleted file mode 100644 index 804d222..0000000 --- a/src/main/java/input/RandomDataGenerator.java +++ /dev/null @@ -1,4 +0,0 @@ -package input; - -public class RandomDataGenerator { -} diff --git a/src/main/java/input/strategy/ClientInputStrategy.java b/src/main/java/input/strategy/ClientInputStrategy.java new file mode 100644 index 0000000..b58aa7f --- /dev/null +++ b/src/main/java/input/strategy/ClientInputStrategy.java @@ -0,0 +1,17 @@ +package input.strategy; + +import dto.Client; +import input.CustomCollection; + +import java.io.IOException; + +/** + * Интерфейс стратегии для ввода данных клиентов. + * Реализует паттерн Стратегия для различных способов получения объектов {@link Client}. + * Каждая реализация представляет конкретный способ получения данных + * Реализации должны гарантировать, что возвращаемые объекты {@link Client} + * являются валидными + */ +public interface ClientInputStrategy { + CustomCollection getData() throws IOException; +} diff --git a/src/main/java/input/strategy/FileReaderStrategy.java b/src/main/java/input/strategy/FileReaderStrategy.java new file mode 100644 index 0000000..f0981d1 --- /dev/null +++ b/src/main/java/input/strategy/FileReaderStrategy.java @@ -0,0 +1,140 @@ +package input.strategy; + +import dto.Client; +import input.CustomCollection; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * Стратегия для чтения данных о клиентах из текстового файла. + * Реализует интерфейс {@link ClientInputStrategy} для получения + * коллекции клиентов из внешнего файла. + * + *

Формат файла:

+ *
    + *
  • Каждая строка представляет одного клиента
  • + *
  • Формат строки: {@code Имя|Телефон|ID}
  • + *
  • Разделитель полей: вертикальная черта ({@code |})
  • + *
  • Кодировка файла: UTF-8
  • + *
+ * + *

Требования к данным:

+ *
    + *
  • Имя: непустая строка
  • + *
  • Телефон: формат {@code +7XXXXXXXXXX} (11 цифр после {@code +7})
  • + *
  • ID: целое число
  • + *
+ * + *

Обработка ошибок:

+ *
    + *
  • Пропускает некорректные строки с выводом сообщения об ошибке
  • + *
  • Игнорирует пустые строки
  • + *
  • Генерирует исключение при проблемах с файлом
  • + *
+ * + *

Пример содержимого файла:

+ *
+ * Иван Иванов|+79991234567|1
+ * Мария Петрова|+79997654321|2
+ * 
+ * + * @see ClientInputStrategy + * @see Client + * @see CustomCollection + */ +public class FileReaderStrategy implements ClientInputStrategy { + private final String filePath; + + public FileReaderStrategy(String filePath) { + this.filePath = filePath; + } + + /** + * Читает данные о клиентах из файла и возвращает их в виде коллекции. + * Файл должен содержать строки в формате: Имя|Телефон|ID + * Телефон должен соответствовать формату: +7XXXXXXXXXX (11 цифр после +7) + * + * @return коллекция клиентов, прочитанных из файла + * @throws RuntimeException если файл не найден или произошла ошибка ввода-вывода + */ + @Override + public CustomCollection getData() { + CustomCollection clients = new CustomCollection<>(); + Path path = Paths.get(filePath); + + if (!Files.exists(path)) { + throw new RuntimeException("Файл не найден: " + filePath); + } + + try (Stream lines = Files.lines(path, StandardCharsets.UTF_8)) { + lines + .filter(line -> !line.trim().isEmpty()) + .map(this::parseToClient) + .filter(Optional::isPresent) + .map(Optional::get) + .map(Client.ClientBuilder::build) + .forEach(clients::add); + + } catch (IOException e) { + throw new RuntimeException(e); + } + return clients; + } + + /** + * Парсит строку из файла в объект ClientBuilder. + * Строка должна быть в формате: Имя|Телефон|ID + * + * @param line строка из файла для парсинга + * @return Optional содержащий ClientBuilder если строка валидна, + * или пустой Optional если строка содержит ошибки формата + */ + public Optional parseToClient(String line) { + try { + String[] parts = line.split("\\|"); + if (parts.length != 3) { + throw new IllegalArgumentException("неверный формат данных"); + } + String name = parts[0].trim(); + String phoneNumber = parts[1].trim(); + int idNumber; + + try { + idNumber = Integer.parseInt(parts[2].trim()); + } catch (NumberFormatException e) { + System.out.println("ID не число: " + line); + return Optional.empty(); + } + + if (name.isEmpty()) { + System.out.println("Пустое имя в строке: " + line); + return Optional.empty(); + } + + if (!phoneNumber.matches("^\\+7\\d{10}$")) { + System.out.println("Неверный формат номера телефона в строке: " + line); + return Optional.empty(); + } + + Client.ClientBuilder builder = new Client.ClientBuilder() + .name(name) + .phoneNumber(phoneNumber) + .idNumber(idNumber); + return Optional.of(builder); + + } catch (Exception e) { + System.out.println("Ошибка в строке: " + line); + return Optional.empty(); + } + } + + public String getFilePath() { + return filePath; + } +} diff --git a/src/main/java/input/strategy/ManualInputReaderStrategy.java b/src/main/java/input/strategy/ManualInputReaderStrategy.java new file mode 100644 index 0000000..0db8327 --- /dev/null +++ b/src/main/java/input/strategy/ManualInputReaderStrategy.java @@ -0,0 +1,261 @@ +package input.strategy; + +import dto.Client; +import input.CustomCollection; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.Scanner; +import java.util.Set; + +/** + * Стратегия для ручного ввода данных клиентов через консоль. + * Реализует интерфейс {@link ClientInputStrategy} для интерактивного + * ввода данных о клиентах с валидацией и подтверждением. + * + *

Обеспечивает:

+ *
    + *
  • Пошаговый ввод данных о клиентах
  • + *
  • Валидацию введенных данных
  • + *
  • Подтверждение корректности ввода
  • + *
  • Ограничение на уникальность ID клиентов
  • + *
  • Лимит на максимальное количество клиентов (1000)
  • + *
+ * + * @see ClientInputStrategy + * @see Client + * @see CustomCollection + */ +public class ManualInputReaderStrategy implements ClientInputStrategy { + private Scanner scanner; + private Set userIds; + + public ManualInputReaderStrategy() { + this.scanner = new Scanner(System.in); + this.userIds = new HashSet<>(); + } + + + public ManualInputReaderStrategy(InputStream in) { + this.scanner = new Scanner(in); // создаём Scanner на переданном потоке + this.userIds = new HashSet<>(); + } + + /** + * Получает коллекцию клиентов через интерактивный ручной ввод. + * Метод последовательно запрашивает данные о каждом клиенте, + * выполняет валидацию и подтверждение, после чего добавляет + * клиента в результирующую коллекцию. + * + *

Процесс ввода продолжается до тех пор, пока:

+ *
    + *
  • Пользователь не откажется от добавления нового клиента
  • + *
  • Не будет достигнут лимит в 1000 клиентов
  • + *
+ * + * @return коллекция введенных клиентов + * @throws IOException если возникает ошибка ввода-вывода + */ + @Override + public CustomCollection getData() throws IOException { + CustomCollection clients = new CustomCollection<>(); + + System.out.println("=== Ручной ввод клиентов ==="); + System.out.println("Вводите данные клиентов. Для завершения введите 'стоп'.\n"); + + int clientNumber = 1; + + while (true) { + if (userIds.size() == 1000) { + System.out.println("Достигнут лимит клиентов"); + break; + } + + System.out.println("Ввод клиент №" + clientNumber); + + try { + if (!promptForConfirmation("Добавляем клиента?")) { + break; + } + + Client client = inputClient(clientNumber); + if (client != null) { + clients.add(client); + clientNumber++; + } + } catch (StopInputException e) { + System.out.println("\nВвод прерван пользователем. Текущий клиент не добавлен."); + break; + } + } + System.out.println("\n Ввод завершен. Добавлено клиентов: " + clients.size()); + return clients; + } + + /** + * Выполняет ввод данных для одного клиента. + * Запрашивает имя, телефонный номер и идентификатор, + * затем отображает введенные данные для подтверждения. + * + * @param clientNumber порядковый номер клиента (для отображения в интерфейсе) + * @return объект {@link Client} с введенными данными или {@code null}, + * если пользователь отменил ввод + */ + private Client inputClient(int clientNumber) { + try { + String name = promptForName(); + String phone = promptForNumber(); + int id = promptForId(); + + Client.ClientBuilder builder = new Client.ClientBuilder().name(name) + .phoneNumber(phone) + .idNumber(id); + + System.out.println("\nВы ввели:"); + System.out.println(" Имя: " + name); + System.out.println(" Телефон: " + phone); + System.out.println(" ID: " + id); + + if (promptForConfirmation("Все верно?")) { + return builder.build(); + } else { + System.out.println("Отмена ввода этого клиента"); + return null; + } + } catch (StopInputException e) { + throw e; + } + } + + private String promptForName() { + try { + return promptForString("Введите имя", true); + } catch (StopInputException e) { + throw e; + } + } + + private String promptForNumber() { + while (true) { + try { + String phone = promptForString("Введите номер телефона в формате +7XXXXXXXXXX", true); + + if (phone.matches("^\\+7\\d{10}$")) { + return phone; + } else { + System.out.println("⚠️ Неверный формат телефона! Пример: +79991234567"); + System.out.println(" Должно начинаться с +7 и содержать 11 цифр"); + } + } catch (StopInputException e) { + throw new RuntimeException(e); + } + } + } + + private int promptForId() { + while (true) { + String input = promptForString("Введите ID в диапазоне от 0 до 1000", true); + + try { + int id = Integer.parseInt(input); + + if (id < 0 || id > 1000) { + System.out.println("ID должен быть в диапазоне от 0 до 1000"); + continue; + } + + if (userIds.contains(id)) { + System.out.println("Этот ID уже занят"); + continue; + } + + userIds.add(id); + return id; + } catch (StopInputException e) { + throw e; + } catch (NumberFormatException e) { + System.out.println("ID должен быть целым числом"); + } + } + } + + /** + * Запрашивает у пользователя подтверждение действия. + * Поддерживает ввод на русском и английском языках. + * + *

Принимаемые варианты подтверждения:

+ *
    + *
  • Положительные: "да", "д", "y", "yes"
  • + *
  • Отрицательные: "нет", "н", "n", "no"
  • + *
+ * + *

Метод продолжает запрашивать ввод до получения корректного ответа.

+ * + * @param message сообщение с вопросом для подтверждения + * @return {@code true} если пользователь подтвердил действие, + * {@code false} если отказался + */ + private boolean promptForConfirmation(String message) { + while (true) { + System.out.print(message + " (да/д/yes/y или нет/н/no/n): "); + String input = scanner.nextLine().trim().toLowerCase(); + + if (input.equalsIgnoreCase("стоп")) { + throw new StopInputException(); + } + + if (input.equals("да") || input.equals("д") || input.equals("y") || input.equals("yes")) { + return true; + } + if (input.equals("нет") || input.equals("н") || input.equals("n") || input.equals("no")) { + return false; + } + System.out.println("⚠️ Пожалуйста, введите 'да/д/yes/y' или 'нет/н/no/n'"); + } + } + + /** + * Универсальный метод для запроса строкового ввода от пользователя. + * Обрабатывает обязательность поля и базовые ошибки ввода. + * + *

Если поле является обязательным ({@code required = true}), + * метод будет продолжать запрашивать ввод до получения непустой строки.

+ * + * @param message сообщение с описанием запрашиваемых данных + * @param required флаг, указывающий является ли поле обязательным для заполнения + * @return введенная пользователем строка (без начальных и конечных пробелов) + */ + private String promptForString(String message, boolean required) { + while (true) { + System.out.print(message + ": "); + + try { + String input = scanner.nextLine().trim(); + + if (input.equalsIgnoreCase("стоп")) { + throw new StopInputException(); + } + + if (required && input.isEmpty()) { + System.out.println("⚠️ Это поле обязательно для заполнения!"); + continue; + } + + return input; + } catch (Exception e) { + if (e instanceof StopInputException) { + throw e; // Пробрасываем дальше + } + System.out.println("Ошибка ввода: " + e.getMessage()); + scanner.nextLine(); // Очистка буфера + } + } + } + + static class StopInputException extends RuntimeException { + public StopInputException() { + super("Ввод прерван пользователем"); + } + } +} \ No newline at end of file diff --git a/src/main/java/input/strategy/RandomDataGeneratorStrategy.java b/src/main/java/input/strategy/RandomDataGeneratorStrategy.java new file mode 100644 index 0000000..c4b82bf --- /dev/null +++ b/src/main/java/input/strategy/RandomDataGeneratorStrategy.java @@ -0,0 +1,130 @@ +package input.strategy; + +import com.github.javafaker.Faker; +import dto.Client; +import input.CustomCollection; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Stream; + +/** + * Стратегия для генерации случайных данных о клиентах. + * Реализует интерфейс {@link ClientInputStrategy} для создания + * коллекции клиентов со случайными данными. + * + *

Использует библиотеку JavaFaker для генерации реалистичных данных:

+ *
    + *
  • Имена клиентов на русском языке
  • + *
  • Телефонные номера в российском формате
  • + *
  • Уникальные идентификаторы в заданном диапазоне
  • + *
+ * + *

Обеспечивает уникальность ID клиентов и соблюдение ограничений + * на диапазон значений (0-1000).

+ * + * @see ClientInputStrategy + * @see Client + * @see CustomCollection + * @see Faker + */ +public class RandomDataGeneratorStrategy implements ClientInputStrategy { + private int count; + private Faker faker; + private Set userIds; + + public RandomDataGeneratorStrategy(int count) { + if (count <= 0) { + throw new IllegalArgumentException("Количество случайных записей должно быть больше нуля"); + } + this.count = count; + this.faker = new Faker(new Locale("ru", "Ru")); + this.userIds = new HashSet<>(); + } + + /** + * Генерирует коллекцию клиентов со случайными данными. + * Создает указанное количество клиентов, используя случайные имена, + * телефонные номера и уникальные идентификаторы. + * + *

Метод использует Stream API для эффективной генерации данных:

+ *
    + *
  • Генерирует поток объектов-строителей клиентов
  • + *
  • Ограничивает поток заданным количеством элементов
  • + *
  • Преобразует строителей в готовые объекты Client
  • + *
  • Добавляет клиентов в результирующую коллекцию
  • + *
+ * + * @return коллекция клиентов со случайными данными + * @throws IOException если возникает ошибка ввода-вывода (в данной реализации + * маловероятно, но требуется по контракту интерфейса) + * @throws IllegalStateException если невозможно сгенерировать уникальный ID + * (все возможные ID в диапазоне 0-1000 уже использованы) + */ + @Override + public CustomCollection getData() throws IOException { + CustomCollection clients = new CustomCollection<>(); + + Stream.generate(this::randomClientBuilder) + .limit(count) + .map(Client.ClientBuilder::build) + .forEach(clients::add); + + return clients; + } + + /** + * Создает строитель клиента со случайными данными. + * Генерирует: + *
    + *
  • Случайное полное имя на русском языке
  • + *
  • Случайный телефонный номер в формате +79XXXXXXXXX
  • + *
  • Уникальный идентификатор в диапазоне 0-1000
  • + *
+ * + * @return {@link Client.ClientBuilder} с заполненными случайными данными + */ + private Client.ClientBuilder randomClientBuilder() { + return new Client.ClientBuilder() + .name(faker.name().fullName()) + .phoneNumber("+7" + faker.numerify("9#########")) + .idNumber(generateUniqueId()); + } + + /** + * Генерирует уникальный идентификатор в диапазоне от 0 до 1000. + * Гарантирует, что каждый сгенерированный ID будет уникальным + * в рамках данного экземпляра стратегии. + * + *

Алгоритм генерации:

+ *
    + *
  • Проверяет, не достигнут ли лимит уникальных ID
  • + *
  • Генерирует случайное число в диапазоне 0-1000
  • + *
  • Проверяет уникальность сгенерированного числа
  • + *
  • Повторяет генерацию, если число уже использовано
  • + *
+ * + * @return уникальный идентификатор клиента + * @throws IllegalStateException если все возможные ID в диапазоне 0-1000 + * уже были сгенерированы + */ + private int generateUniqueId() { + if (userIds.size() >= 1000) { + throw new IllegalStateException("Не осталось свободных ID"); + } + + int id; + do { + id = faker.random().nextInt(0, 1000); + } while (userIds.contains(id)); + + userIds.add(id); + return id; + } + + public int getCount() { + return count; + } +} diff --git a/src/main/java/output/FileDataWriter.java b/src/main/java/output/FileDataWriter.java new file mode 100644 index 0000000..c9a3e9c --- /dev/null +++ b/src/main/java/output/FileDataWriter.java @@ -0,0 +1,30 @@ +package output; + +import java.io.*; + +public class FileDataWriter { + + private final File file = new File("savedData.txt"); + + public void writeDataToFile(String data){ + if(file.exists() && file.isDirectory()){ + throw new RuntimeException("По пути сохранения находится директория"); + } + + if (!file.exists()) { + try { + if(!file.createNewFile()){ + throw new RuntimeException("Не удалось создать файл"); + } + } catch (IOException e) { + throw new RuntimeException("Ошибка создания файла", e); + } + } + + try(Writer writer = new BufferedWriter(new FileWriter(file, true))){ + writer.append(data); + } catch (IOException e){ + throw new RuntimeException("Ошибка записи в файл", e); + } + } +} diff --git a/src/main/java/sorting/AbstractMergeSortStrategy.java b/src/main/java/sorting/AbstractMergeSortStrategy.java new file mode 100644 index 0000000..d6df0d4 --- /dev/null +++ b/src/main/java/sorting/AbstractMergeSortStrategy.java @@ -0,0 +1,120 @@ +package sorting; + +import dto.Client; +import input.CustomCollection; + +import java.util.Comparator; + +public abstract class AbstractMergeSortStrategy implements SortingStrategy { + + public void sortWithComparator(CustomCollection clients, Comparator comparator) { + if (clients == null || clients.size() <= 1) { + return; + } + + mergeSort(clients, 0, clients.size() - 1, comparator); + } + + protected abstract Comparator getComparator(); + + @Override + public String getStrategyName() { + return "Abstract Merge Sort (сортировка слиянием)"; + } + + private void mergeSort(CustomCollection clients, int left, int right, + Comparator comparator) { + + if (left < right) { + int mid = left + (right - left) / 2; + + mergeSort(clients, left, mid, comparator); + + mergeSort(clients, mid + 1, right, comparator); + + merge(clients, left, mid, right, comparator); + } + } + + + private void merge(CustomCollection clients, int left, int mid, int right, + Comparator comparator) { + + CustomCollection temp = new CustomCollection<>(); + + int i = left; + int j = mid + 1; + + + while (i <= mid && j <= right) { + Client leftClient = clients.get(i); + Client rightClient = clients.get(j); + + + int comparison = comparator.compare(leftClient, rightClient); + + + if (comparison <= 0) { + temp.add(leftClient); + i++; + } else { + temp.add(rightClient); + j++; + } + } + + while (i <= mid) { + temp.add(clients.get(i)); + i++; + } + + while (j <= right) { + temp.add(clients.get(j)); + j++; + } + + for (int k = 0; k < temp.size(); k++) { + clients.set(left + k, temp.get(k)); + } + } + + @Override + public void sortEvenValuesOnly(CustomCollection clients) { + // Компаратор для сортировки по idNumber в натуральном порядке (по возрастанию) + Comparator idComparator = Comparator.comparing(Client::getIdNumber); + sortEvenValuesOnly(clients, idComparator); + } + + public void sortEvenValuesOnly(CustomCollection clients, Comparator comparator) { + if (clients == null || clients.isEmpty()) { + return; + } + + // Создаем список для хранения элементов с четными idNumber и их индексов + CustomCollection evenClients = new CustomCollection<>(); + CustomCollection evenIndices = new CustomCollection<>(); + + // Собираем все элементы с четными idNumber и запоминаем их индексы + for (int i = 0; i < clients.size(); i++) { + Client client = clients.get(i); + if (client.getIdNumber() % 2 == 0) { + evenClients.add(client); + evenIndices.add(i); + } + } + + // Если нет элементов с четными значениями, ничего не делаем + if (evenClients.isEmpty()) { + return; + } + + // Сортируем только элементы с четными значениями по idNumber в натуральном порядке + sortWithComparator(evenClients, comparator); + + // Возвращаем отсортированные элементы на исходные позиции + for (int i = 0; i < evenClients.size(); i++) { + int originalIndex = evenIndices.get(i); + clients.set(originalIndex, evenClients.get(i)); + } + } +} \ No newline at end of file diff --git a/src/main/java/sorting/BubbleSortingStrategy.java b/src/main/java/sorting/BubbleSortingStrategy.java deleted file mode 100644 index 3b50a81..0000000 --- a/src/main/java/sorting/BubbleSortingStrategy.java +++ /dev/null @@ -1,37 +0,0 @@ -package sorting; - -import dto.Client; - -import java.util.Comparator; -import java.util.List; - -public class BubbleSortingStrategy implements Sortable { - - @Override - public void sort(List clients, Comparator comparator) { - } - - - - // @Override -// public int[] bubbleIntegerSorting(int[] numbers) { -// boolean isSwaped = true; -// int lastIndex = numbers.length - 1; -// -// while (isSwaped) { -// isSwaped = false; -// int range = lastIndex; -// -// for (int i = 0; i < range; i++) { -// if (numbers[i] > numbers[i + 1]) { -// int number1 = numbers[i]; -// numbers[i] = numbers[i + 1]; -// numbers[i + 1] = number1; -// isSwaped = true; -// lastIndex = i + 1; -// } -// } -// } -// return numbers; -// } -} diff --git a/src/main/java/sorting/MergeSortDefaultStrategy.java b/src/main/java/sorting/MergeSortDefaultStrategy.java new file mode 100644 index 0000000..a1cde05 --- /dev/null +++ b/src/main/java/sorting/MergeSortDefaultStrategy.java @@ -0,0 +1,32 @@ +package sorting; + +import dto.Client; +import input.CustomCollection; + +import java.util.Comparator; + +public class MergeSortDefaultStrategy extends AbstractMergeSortStrategy { + + @Override + public void sort(CustomCollection clients) { + sortWithComparator(clients, getComparator()); + } + + protected Comparator getComparator() { + return (c1, c2) -> { + int nameComparison = c1.getName().compareTo(c2.getName()); + if (nameComparison != 0) return nameComparison; + + int idComparison = Integer.compare(c1.getIdNumber(), c2.getIdNumber()); + if (idComparison != 0) return idComparison; + + return c1.getPhoneNumber().compareTo(c2.getPhoneNumber()); + }; + + } + + @Override + public String getStrategyName() { + return "Merge Sort Default Strategy (сортировка по имени -> ID -> телефону)"; + } +} diff --git a/src/main/java/sorting/MergeSortDynamicStrategy.java b/src/main/java/sorting/MergeSortDynamicStrategy.java new file mode 100644 index 0000000..44eecd9 --- /dev/null +++ b/src/main/java/sorting/MergeSortDynamicStrategy.java @@ -0,0 +1,42 @@ +package sorting; + +import dto.Client; +import enums.Field; +import input.CustomCollection; + +import java.util.Comparator; + +public class MergeSortDynamicStrategy extends AbstractMergeSortStrategy{ + private final Field field; + private final boolean ascending; + + public MergeSortDynamicStrategy(Field field, boolean ascending) { + this.field = field; + this.ascending = ascending; + } + + @Override + public void sort(CustomCollection clients) { + sortWithComparator(clients, getComparator()); + } + + public Comparator getComparator(){ + Comparator comparator = switch (field){ + case NAME -> Comparator.comparing(Client :: getName); + case ID_NUMBER -> Comparator.comparing(Client :: getIdNumber); + case PHONE_NUMBER -> Comparator.comparing(Client :: getPhoneNumber); + default -> throw new IllegalArgumentException("Неизвестное поле: " + field); + }; + + if (ascending) { + return comparator; + } else { + return comparator.reversed(); + } + } + + @Override + public String getStrategyName() { + return "Dynamic Merge Sort (Динамическая сортировка по: " + field + ")"; + } +} diff --git a/src/main/java/sorting/Sortable.java b/src/main/java/sorting/Sortable.java deleted file mode 100644 index 9009742..0000000 --- a/src/main/java/sorting/Sortable.java +++ /dev/null @@ -1,12 +0,0 @@ -package sorting; - -import dto.Client; - -import java.util.Comparator; -import java.util.List; - -public interface Sortable { - - void sort(List clients, Comparator comparator); - -} diff --git a/src/main/java/sorting/SortingManager.java b/src/main/java/sorting/SortingManager.java new file mode 100644 index 0000000..7b3b080 --- /dev/null +++ b/src/main/java/sorting/SortingManager.java @@ -0,0 +1,25 @@ +package sorting; + +public class SortingManager { + private SortingStrategy currentStrategy; + + public SortingManager(){} + + public SortingManager(SortingStrategy initialStrategy) { + if (initialStrategy == null) { + throw new IllegalArgumentException("Стратегия не может быть null"); + } + currentStrategy = initialStrategy; + } + + public void setStrategy(SortingStrategy strategy) { + if (strategy == null) { + throw new IllegalArgumentException("Стратегия не может быть null"); + } + this.currentStrategy = strategy; + } + + public SortingStrategy getCurrentStrategy() { + return currentStrategy; + } +} diff --git a/src/main/java/sorting/SortingStrategy.java b/src/main/java/sorting/SortingStrategy.java new file mode 100644 index 0000000..f163805 --- /dev/null +++ b/src/main/java/sorting/SortingStrategy.java @@ -0,0 +1,11 @@ +package sorting; + +import dto.Client; +import input.CustomCollection; + +public interface SortingStrategy { + + void sort(CustomCollection clients); + String getStrategyName(); + void sortEvenValuesOnly(CustomCollection clients); +} \ No newline at end of file diff --git a/src/main/java/userInterface/AppController.java b/src/main/java/userInterface/AppController.java new file mode 100644 index 0000000..b16cdd0 --- /dev/null +++ b/src/main/java/userInterface/AppController.java @@ -0,0 +1,120 @@ +package userInterface; + +import dto.Client; +import enums.Field; +import input.CustomCollection; +import input.InputManager; +import input.strategy.ManualInputReaderStrategy; +import output.FileDataWriter; +import sorting.MergeSortDefaultStrategy; +import sorting.MergeSortDynamicStrategy; +import sorting.SortingManager; + +import java.io.File; +import java.io.IOException; + +public class AppController { + + private final InputManager inputManager = new InputManager(); + private final CustomCollection fullList = new CustomCollection<>(); + private final SortingManager sortingManager = new SortingManager(); + private final FileDataWriter fileDataWriter = new FileDataWriter(); + ConcurrentCounter concurrentCounter = new ConcurrentCounter(); + + public void startDefaultSorting(){ + sortingManager.setStrategy(new MergeSortDefaultStrategy()); + sortingManager.getCurrentStrategy().sort(fullList); + showAndWriteAllClients(); + } + + public void startEvenIdsSorting(){ + sortingManager.setStrategy(new MergeSortDefaultStrategy()); + sortingManager.getCurrentStrategy().sortEvenValuesOnly(fullList); + showAndWriteAllClients(); + } + + public void startDynamicSorting(Field field){ + sortingManager.setStrategy(new MergeSortDynamicStrategy(field, true)); + sortingManager.getCurrentStrategy().sort(fullList); + showAndWriteAllClients(); + } + + public CustomCollection getFullList() { + return fullList; + } + + public void showAndWriteAllClients(){ + for(Client client : fullList){ + System.out.println(client); + fileDataWriter.writeDataToFile(client + "\n"); + } + fileDataWriter.writeDataToFile("\n"); + } + + public void startFileReaderStrategy(String path){ + inputManager.setStrategy(inputManager.createFileStrategy(path)); + try { + CustomCollection fromFileList = inputManager.loadData(); + int countOfAlexes = concurrentCounter.countAlexes(fromFileList); + fullList.addAll(fromFileList); + for(Client client : fromFileList){ + System.out.println(client); + } + System.out.println("Добавлено Алексеев: " + countOfAlexes); + } catch (IOException e) { + throw new RuntimeException(e); + } + System.out.println("File path: " + path); + System.out.println("File exists: " + new File(path).exists()); + } + + public void startManualInputStrategy(){ + inputManager.setStrategy(inputManager.createManualStrategy()); + try { + CustomCollection manualList = inputManager.loadData(); + int countOfAlexes = concurrentCounter.countAlexes(manualList); + fullList.addAll(manualList); + for(Client client : manualList){ + System.out.println(client); + } + System.out.println("Добавлено Алексеев: " + countOfAlexes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void startRandomDataStrategy(int count){ + inputManager.setStrategy(inputManager.createRandomStrategy(count)); + try { + CustomCollection randomList = inputManager.loadData(); + int countOfAlexes = concurrentCounter.countAlexes(randomList); + fullList.addAll(randomList); + for(Client client : randomList){ + System.out.println(client); + } + System.out.println("Добавлено Алексеев: " + countOfAlexes); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + public void startManualInputStrategy(ManualInputReaderStrategy strategy){ + inputManager.setStrategy(strategy); + try { + CustomCollection manualList = inputManager.loadData(); + int countOfAlexes = concurrentCounter.countAlexes(manualList); + fullList.addAll(manualList); + for(Client client : manualList){ + System.out.println(client); + } + System.out.println("Добавлено Алексеев: " + countOfAlexes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public InputManager getInputManager() { + return inputManager; + } +} diff --git a/src/main/java/userInterface/ConcurrentCounter.java b/src/main/java/userInterface/ConcurrentCounter.java new file mode 100644 index 0000000..d34ee09 --- /dev/null +++ b/src/main/java/userInterface/ConcurrentCounter.java @@ -0,0 +1,50 @@ +package userInterface; + +import dto.Client; +import input.CustomCollection; + +import java.util.concurrent.*; + +public class ConcurrentCounter { + + public int countAlexes(CustomCollection clients){ + CustomCollection clients1 = new CustomCollection<>(); + CustomCollection clients2 = new CustomCollection<>(); + + for (int i = 0; i < clients.size()/2 ; i++){ + clients1.add(clients.get(i)); + } + + for (int i = clients.size()/2; i < clients.size(); i++){ + clients2.add(clients.get(i)); + } + + ExecutorService executorService = Executors.newFixedThreadPool(2); + + Future count1 = executorService.submit(new Callable() { + @Override + public Long call() throws Exception { + return clients1.stream().filter(c-> c.getName().contains("Алексей")).count(); + } + }); + + Future count2 = executorService.submit(new Callable() { + @Override + public Long call() throws Exception { + return clients2.stream().filter(c-> c.getName().contains("Алексей")).count(); + } + }); + + executorService.shutdown(); + + try { + long result = count1.get() + count2.get(); + return Math.toIntExact(result); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/src/main/java/userInterface/MenuManager.java b/src/main/java/userInterface/MenuManager.java index 1f36c4b..2e3a59d 100644 --- a/src/main/java/userInterface/MenuManager.java +++ b/src/main/java/userInterface/MenuManager.java @@ -1,30 +1,226 @@ package userInterface; +import enums.Field; + import java.util.Scanner; public class MenuManager implements Printable { - + AppController appController = new AppController(); private final Scanner scanner = new Scanner(System.in); + private boolean running = true; + + public void run() { + while (running) { + try { + printMainMenu(); + String input = scanner.nextLine(); + if (input.isEmpty()) { + System.out.println("Пожалуйста, выберите номер опции"); + continue; + } - public void run(){ + int mainOption = Integer.parseInt(input); + + switch (mainOption) { + case 1 -> printFillingDatabaseMenu(); + case 2 -> printSortingMenu(); + case 0 -> exitByChoice(); + default -> System.out.println("Неверный выбор. Пожалуйста, выберите 1, 2 или 0"); + } + } + catch (Exception e) { + System.out.println("Ошибка: " + e.getMessage()); + System.out.println("Пожалуйста, введите корректный номер опции."); + } + } + } + + @Override + public void printFillingDatabaseMenu() { + boolean backToMain = false; + while (!backToMain && running) { + Printable.super.printFillingDatabaseMenu(); + String input = scanner.nextLine().trim(); + if (input.isEmpty()) { + System.out.println("Пожалуйста, введите номер опции."); + continue; + } + try { + int fillOption = Integer.parseInt(input); + + switch (fillOption) { + case 1 -> fillManually(); + case 2 -> fillFromFile(); + case 3 -> fillRandom(); + case 0 -> { + System.out.println("Возврат в главное меню..."); + backToMain = true; + } + default -> System.out.println("Неверный выбор. Пожалуйста, выберите от 0 до 3"); + } + } + catch (NumberFormatException e) { + System.out.println("Пожалуйста, введите цифру для выбора действия"); + } + } + + } - printMainMenu(); - String input = scanner.nextLine(); - int mainOption = Integer.parseInt(input); + @Override + public void printSortingMenu() { + boolean backToMain = false; + while (!backToMain && running) { + Printable.super.printSortingMenu(); + String input = scanner.nextLine().trim(); + + if (input.isEmpty()) { + System.out.println("Пожалуйста, введите номер опции."); + continue; + } + try { + int sortOption = Integer.parseInt(input); + + switch (sortOption) { + case 1 -> printDefaultOrder(); + case 2 -> printNameSortingOptions(); + case 3 -> printIdSortingOptions(); + case 4 -> printPhoneSortingOptions(); + case 0 -> { + System.out.println("Возврат в главное меню..."); + backToMain = true; + } + default -> System.out.println("Неверный выбор. Пожалуйста, выберите от 0 до 4"); + } + } + catch (NumberFormatException e) { + System.out.println("Пожалуйста, введите цифру для выбора действия."); + } + } + } + + @Override + public void printIdSortingOptions() { + boolean backToMain = false; + while (!backToMain && running) { + Printable.super.printIdSortingOptions(); + String input = scanner.nextLine().trim(); + + if (input.isEmpty()) { + System.out.println("Пожалуйста, введите номер опции."); + continue; + } + + try { + int idSortOption = Integer.parseInt(input); + + switch (idSortOption) { + case 1 -> sortIDByAscending(); + case 2 -> sortIDbyEven(); + case 0 -> { + System.out.println("Назад..."); + backToMain = true; + } + default -> System.out.println("Неверный выбор. Пожалуйста, выберите от 0 до 3"); + } + } + catch (NumberFormatException e) { + System.out.println("Пожалуйста, введите цифру для выбора действия."); + } + } + } - switch (mainOption){ - case 1 -> printFillingDatabaseMenu(); - case 2 -> printSortingMenu(); + @Override + public void printNameSortingOptions() { + appController.startDynamicSorting(Field.NAME); + boolean backToMain = false; + while (!backToMain && running) { + Printable.super.printNameSortingOptions(); + backToMain = true; } + } - switch (mainOption){ - case 1 -> System.out.println("Future custom collection"); - case 2 -> printNameSortingOptions(); - case 3 -> printIdSortingOptions(); - case 4 -> printPhoneSortingOptions(); + @Override + public void printPhoneSortingOptions() { + appController.startDynamicSorting(Field.PHONE_NUMBER); + boolean backToMain = false; + while (!backToMain && running) { + Printable.super.printPhoneSortingOptions(); + backToMain = true; } + } + + private void sortIDByAscending() { + appController.startDynamicSorting(Field.ID_NUMBER); + System.out.println("Клиенты отсортированы по возрастанию ID"); + } + + private void sortIDbyEven() { + appController.startEvenIdsSorting(); + System.out.println("Клиенты отсортированы по четным ID"); + } - // ПРОДОЛЖИТЬ + private void fillManually() { + System.out.println("Выбран способ ввода данных вручную"); + System.out.println("Введите данные..."); + appController.startManualInputStrategy(); + System.out.println("Данные успешно сохранены"); + + } + + private void fillFromFile() { + System.out.println("Выбран способ ввода данных из файла\n"); + System.out.println("Введите путь к файлу: "); + String filePath = scanner.nextLine().trim(); + appController.startFileReaderStrategy(filePath); + System.out.println("Загрузка из файла: " + filePath); + + } + + private void fillRandom() { + System.out.println("Выбран способ ввода случайных данных"); + int count = inputLengthOfValue("Сколько записей создать? ", 10); + appController.startRandomDataStrategy(count); + System.out.println("Создано: " + count + " записей"); + } + + private void printDefaultOrder() { + System.out.println("Список клиентов"); + appController.startDefaultSorting(); + } + + private void exitByChoice() { + System.out.println("Выход из программы..."); + running = false; + scanner.close(); + } + + private int inputLengthOfValue(String prompt, int defaultValue) { + while (true) { + System.out.println(prompt + "(по умолчанию: " + defaultValue + "): "); + String input = scanner.nextLine().trim(); + + if (input.isEmpty()) { + System.out.println("Значение по умолчанию: " + defaultValue); + return defaultValue; + } + + try { + int value = Integer.parseInt(input); + if (value > 0) { + return value; + } + else if (value == 0) { + System.out.println("Используется значение по умолчанию: " + defaultValue); + return defaultValue; + } + else { + System.out.println("Число должно быть положительным! Попробуйте еще раз."); + } + } + catch (NumberFormatException e) { + System.out.println("Пожалуйста, введите целое число! Попробуйте еще раз"); + } + } } } diff --git a/src/main/java/userInterface/Printable.java b/src/main/java/userInterface/Printable.java index 57ebd24..a2f6554 100644 --- a/src/main/java/userInterface/Printable.java +++ b/src/main/java/userInterface/Printable.java @@ -4,69 +4,53 @@ public interface Printable { default void printMainMenu(){ System.out.println( - "1) Fill the client database\n" + - "2) Show sorting options" + "1) Наполнить клиентскую базу\n" + + "2) Показать параметры сортировки\n" + + "0) Выход" ); } default void printFillingDatabaseMenu(){ - System.out.println( - "Fill database: \n" + - "1) Manual input\n" + - "2) File\n" + - "3) Random" + + System.out.println(""" + Заполнить базу данных: + 1) Ручной ввод + 2) Файлом + 3) Случайный набор + 0) Возврат в главное меню""" ); } default void printSortingMenu(){ - System.out.println( - "Chose an option:\n" + - "1) Show clients in default order\n" + - "2) Sort by name\n" + - "3) Sort by ID\n" + - "4) Sort by phone number" + System.out.println(""" + Выберите вариант: + 1) Базовая сортировка клиентов + 2) Сортировать клиентов по имени + 3) Сортировать клиентов по ID + 4) Сортировать клиентов по номеру телефона + 0) Возврат в главное меню""" ); } default void printNameSortingOptions(){ System.out.println( - "Sort clients by:\n" + - "1) Names alphabet order\n" + - "2) Name length\n" + - "3) Amount of vowels in name\n" + - "4) Amount of consonants in name\n" + - "5) Name unicode\n" + - "6) Combine name criteria\n" + // Придумать что-то комбинированное - "0) Go back" + "Клиенты отсортированы по умолчанию\n" ); } default void printIdSortingOptions(){ - System.out.println( - "Sort clients by:\n" + - "1) ID Ascending \n" + - "2) ID Descending \n" + - "3) Amount of numbers in ID\n" + - "4) ID even number\n" + // ЭТО ДОП ЗАДАНИЕ - "5) ID odd number\n" + // ЭТО ДОП ЗАДАНИЕ - "6) Unicode\n" + - "7) Combine criteria\n" + - "9) ID creation date" // если сделать ID включающим дату + System.out.println(""" + Сортировать клиентов: + 1) По возрастанию ID + 2) По четным ID + 0) Назад""" ); } default void printPhoneSortingOptions(){ System.out.println( - "Sort clients by phone:\n" + - "1) Normalized number\n" + // приведенный к международному формату - "2) Country code\n" + - "3) Operator code\n" + - "4) Last numbers\n" + - "5) Number length\n" + - "6) Clean number\n" + // без знаков - "7) Combine criteria\n" - + "Клиенты отсортированы по умолчанию\n" ); } } diff --git a/src/test/java/dto/ClientTest.java b/src/test/java/dto/ClientTest.java new file mode 100644 index 0000000..f09dc17 --- /dev/null +++ b/src/test/java/dto/ClientTest.java @@ -0,0 +1,149 @@ +package dto; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ClientTest { + + @Test + @DisplayName("Конструктор Client должен корректно инициализировать поля из ClientBuilder") + void testClientConstructorInitializesFieldsCorrectly() { + // Arrange + Client.ClientBuilder builder = new Client.ClientBuilder() + .name("Иван Иванов") + .phoneNumber("+79991234567") + .idNumber(1); + + // Act + Client client = new Client(builder); + + // Assert + assertEquals("Иван Иванов", client.getName()); + assertEquals("+79991234567", client.getPhoneNumber()); + assertEquals(1, client.getIdNumber()); + } + + @Test + @DisplayName("Геттеры должны возвращать корректные значения полей") + void testGettersReturnCorrectValues() { + // Arrange + Client.ClientBuilder builder = new Client.ClientBuilder() + .name("Петр Петров") + .phoneNumber("+78887654321") + .idNumber(2); + Client client = new Client(builder); + + // Act & Assert + assertEquals("Петр Петров", client.getName()); + assertEquals("+78887654321", client.getPhoneNumber()); + assertEquals(2, client.getIdNumber()); + } + + @Test + @DisplayName("Метод build() ClientBuilder должен создавать корректный объект Client") + void testClientBuilderBuildCreatesCorrectClient() { + // Arrange + Client.ClientBuilder builder = new Client.ClientBuilder() + .name("Мария Сидорова") + .phoneNumber("+77776543210") + .idNumber(3); + + // Act + Client client = builder.build(); + + // Assert + assertNotNull(client); + assertEquals("Мария Сидорова", client.getName()); + assertEquals("+77776543210", client.getPhoneNumber()); + assertEquals(3, client.getIdNumber()); + } + + @Test + @DisplayName("Цепочка методов в ClientBuilder должна работать корректно") + void testClientBuilderMethodChaining() { + // Act + Client client = new Client.ClientBuilder() + .name("Алексей Козлов") + .phoneNumber("+75554443322") + .idNumber(4) + .build(); + + // Assert + assertNotNull(client); + assertEquals("Алексей Козлов", client.getName()); + assertEquals("+75554443322", client.getPhoneNumber()); + assertEquals(4, client.getIdNumber()); + } + + @Test + @DisplayName("toString() должен возвращать строку в ожидаемом формате") + void testToStringReturnsExpectedFormat() { + // Arrange + Client.ClientBuilder builder = new Client.ClientBuilder() + .name("Анна Волкова") + .phoneNumber("+73332221100") + .idNumber(5); + Client client = builder.build(); + String expectedString = "Client{name='Анна Волкова', phoneNumber='+73332221100', idNumber=5}"; + + // Act + String actualString = client.toString(); + + // Assert + assertEquals(expectedString, actualString); + } + + @Test + @DisplayName("Поля Client должны быть final и не изменяться после создания") + void testClientFieldsAreImmutable() { + // Arrange + Client.ClientBuilder builder = new Client.ClientBuilder() + .name("Сергей Новиков") + .phoneNumber("+71112223344") + .idNumber(6); + Client client = builder.build(); + + // Act & Assert (проверяем, что геттеры возвращают те же значения) + assertEquals("Сергей Новиков", client.getName()); + assertEquals("+71112223344", client.getPhoneNumber()); + assertEquals(6, client.getIdNumber()); + + // Попытка изменить состояние через builder не должна повлиять на существующий объект + builder.name("Другое имя").idNumber(999); + Client anotherClient = builder.build(); + + // Проверяем, что первый клиент остался неизменным + assertEquals("Сергей Новиков", client.getName()); + assertEquals(6, client.getIdNumber()); + + // Проверяем, что новый клиент имеет новые значения + assertEquals("Другое имя", anotherClient.getName()); + assertEquals(999, anotherClient.getIdNumber()); + } + + @Test + @DisplayName("Два клиента с одинаковыми данными должны быть разными объектами") + void testClientsWithSameDataAreDifferentObjects() { + // Arrange + Client.ClientBuilder builder1 = new Client.ClientBuilder() + .name("Ольга Смирнова") + .phoneNumber("+79998887766") + .idNumber(7); + Client.ClientBuilder builder2 = new Client.ClientBuilder() + .name("Ольга Смирнова") + .phoneNumber("+79998887766") + .idNumber(7); + + // Act + Client client1 = builder1.build(); + Client client2 = builder2.build(); + + // Assert + assertNotSame(client1, client2); // Разные объекты + assertEquals(client1.getName(), client2.getName()); + assertEquals(client1.getPhoneNumber(), client2.getPhoneNumber()); + assertEquals(client1.getIdNumber(), client2.getIdNumber()); + } +} \ No newline at end of file diff --git a/src/test/java/input/CustomCollectionTest.java b/src/test/java/input/CustomCollectionTest.java new file mode 100644 index 0000000..548dbf3 --- /dev/null +++ b/src/test/java/input/CustomCollectionTest.java @@ -0,0 +1,300 @@ +package input; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import static org.junit.jupiter.api.Assertions.*; + +class CustomCollectionTest { + + private CustomCollection collection; + + @BeforeEach + void setUp() { + collection = new CustomCollection<>(); + } + + @Test + void testDefaultConstructorCapacity() { + assertEquals(10, collection.getElements().length); + } + + @Test + void testInitialCapacityConstructor() { + CustomCollection coll = new CustomCollection<>(5); + assertEquals(5, coll.getElements().length); + } + + @Test + void testInitialCapacityConstructorWithZero() { + assertThrows(IllegalArgumentException.class, () -> new CustomCollection<>(0)); + } + + @Test + void testInitialCapacityConstructorWithNegative() { + assertThrows(IllegalArgumentException.class, () -> new CustomCollection<>(-1)); + } + + @Test + void testAddElement() { + collection.add("test"); + assertEquals(1, collection.size()); + assertEquals("test", collection.get(0)); + } + + @Test + void testAddMultipleElements() { + collection.add("one"); + collection.add("two"); + collection.add("three"); + + assertEquals(3, collection.size()); + assertEquals("one", collection.get(0)); + assertEquals("two", collection.get(1)); + assertEquals("three", collection.get(2)); + } + + @Test + void testAutoExpansion() { + // Заполняем до предела начальной ёмкости (10 элементов) + for (int i = 0; i < 10; i++) { + collection.add("item" + i); + } + + // Добавляем ещё один — должно произойти расширение + collection.add("expanded"); + + assertEquals(11, collection.size()); + assertEquals("expanded", collection.get(10)); + assertTrue(collection.getElements().length > 10); + } + + @Test + void testGetWithValidIndex() { + collection.add("first"); + collection.add("second"); + + assertEquals("first", collection.get(0)); + assertEquals("second", collection.get(1)); + } + + @Test + void testGetWithNegativeIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> collection.get(-1)); + } + + @Test + void testGetWithIndexEqualToSize() { + collection.add("item"); + assertThrows(IndexOutOfBoundsException.class, () -> collection.get(1)); + } + + @Test + void testGetWithIndexGreaterThanSize() { + assertThrows(IndexOutOfBoundsException.class, () -> collection.get(5)); + } + + @Test + void testSetWithValidIndex() { + collection.add("old"); + String result = collection.set(0, "new"); + + assertEquals("old", result); + assertEquals("new", collection.get(0)); + } + + @Test + void testSetWithNegativeIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> collection.set(-1, "value")); + } + + @Test + void testSetWithIndexEqualToSize() { + collection.add("item"); + assertThrows(IndexOutOfBoundsException.class, () -> collection.set(1, "value")); + } + + @Test + void testRemoveByIndexWithValidIndex() { + collection.add("one"); + collection.add("two"); + collection.add("three"); + + collection.removeByIndex(1); + + assertEquals(2, collection.size()); + assertEquals("one", collection.get(0)); + assertEquals("three", collection.get(1)); + } + + @Test + void testRemoveByIndexFirstElement() { + collection.add("first"); + collection.add("second"); + + collection.removeByIndex(0); + + assertEquals(1, collection.size()); + assertEquals("second", collection.get(0)); + } + + @Test + void testRemoveByIndexLastElement() { + collection.add("first"); + collection.add("last"); + + collection.removeByIndex(1); + + assertEquals(1, collection.size()); + assertEquals("first", collection.get(0)); + } + + @Test + void testRemoveByIndexWithNegativeIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> collection.removeByIndex(-1)); + } + + @Test + void testRemoveByIndexWithIndexEqualToSize() { + collection.add("item"); + assertThrows(IndexOutOfBoundsException.class, () -> collection.removeByIndex(1)); + } + + @Test + void testRemoveByIndexWithIndexGreaterThanSize() { + assertThrows(IndexOutOfBoundsException.class, () -> collection.removeByIndex(5)); + } + + @Test + void testSize() { + assertEquals(0, collection.size()); + + collection.add("item"); + assertEquals(1, collection.size()); + + collection.add("another"); + assertEquals(2, collection.size()); + } + + @Test + void testIsEmpty() { + assertTrue(collection.isEmpty()); + + collection.add("item"); + assertFalse(collection.isEmpty()); + + collection.removeByIndex(0); + assertTrue(collection.isEmpty()); + } + + @Test + void testClear() { + collection.add("one"); + collection.add("two"); + + collection.clear(); + + assertEquals(0, collection.size()); + assertTrue(collection.isEmpty()); + } + + @Test + void testIteratorHasNext() { + assertFalse(collection.iterator().hasNext()); + + collection.add("item"); + assertTrue(collection.iterator().hasNext()); + } + + @Test + void testIteratorNext() { + collection.add("first"); + collection.add("second"); + + Iterator iterator = collection.iterator(); + assertEquals("first", iterator.next()); + assertEquals("second", iterator.next()); + } + + @Test + void testIteratorNextNoSuchElement() { + Iterator iterator = collection.iterator(); + assertThrows(NoSuchElementException.class, iterator::next); + } + + @Test + void testIteratorRemove() { + collection.add("one"); + collection.add("two"); + collection.add("three"); + + Iterator iterator = collection.iterator(); + iterator.next(); // переходим к первому элементу + iterator.remove(); + + assertEquals(2, collection.size()); + assertEquals("two", collection.get(0)); + assertEquals("three", collection.get(1)); + } + + @Test + void testIteratorRemoveWithoutNext() { + Iterator iterator = collection.iterator(); + assertThrows(IllegalStateException.class, iterator::remove); + } + + @Test + void testIteratorRemoveTwice() { + collection.add("item"); + + Iterator iterator = collection.iterator(); + iterator.next(); + iterator.remove(); + + assertThrows(IllegalStateException.class, iterator::remove); + } + + @Test + void testStream() { + collection.add("one"); + collection.add("two"); + collection.add("three"); + + long count = collection.stream().count(); + assertEquals(3, count); + + String joined = collection.stream() + .map(String::toUpperCase) + .reduce("", String::concat); + assertEquals("ONETWOTHREE", joined); + } + + @Test + void testAddAllWithNullCollection() { + collection.addAll(null); + assertEquals(0, collection.size()); + } + + @Test + void testAddAllWithEmptyCollection() { + CustomCollection empty = new CustomCollection<>(); + collection.addAll(empty); + assertEquals(0, collection.size()); + } + + @Test + void testAddAllWithNonEmptyCollection() { + CustomCollection source = new CustomCollection<>(); + source.add("a"); + source.add("b"); + + collection.addAll(source); + + assertEquals(2, collection.size()); + assertEquals("a", collection.get(0)); + assertEquals("b", collection.get(1)); + } +} \ No newline at end of file diff --git a/src/test/java/input/InputManagerTest.java b/src/test/java/input/InputManagerTest.java new file mode 100644 index 0000000..40666da --- /dev/null +++ b/src/test/java/input/InputManagerTest.java @@ -0,0 +1,162 @@ +package input; + +import dto.Client; +import input.strategy.ClientInputStrategy; +import input.strategy.FileReaderStrategy; +import input.strategy.ManualInputReaderStrategy; +import input.strategy.RandomDataGeneratorStrategy; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +class InputManagerTest { + + private InputManager inputManager; + + @BeforeEach + void setUp() { + inputManager = new InputManager(); + } + + @Test + @DisplayName("Конструктор без стратегии должен создавать InputManager с null-стратегией") + void testConstructorWithoutStrategy() { + assertNull(inputManager.getCurrentStrategy()); + } + + @Test + @DisplayName("Конструктор с null-стратегией должен выбрасывать IllegalArgumentException") + void testConstructorWithNullStrategyThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new InputManager(null)); + } + + @Test + @DisplayName("Конструктор с валидной стратегией должен устанавливать её как текущую") + void testConstructorWithValidStrategy() { + ClientInputStrategy strategy = new TestClientInputStrategy(); + InputManager manager = new InputManager(strategy); + assertEquals(strategy, manager.getCurrentStrategy()); + } + + @Test + @DisplayName("setStrategy с null должен выбрасывать IllegalArgumentException") + void testSetStrategyWithNullThrowsException() { + assertThrows(IllegalArgumentException.class, () -> inputManager.setStrategy(null)); + } + + @Test + @DisplayName("setStrategy должен корректно устанавливать новую стратегию") + void testSetStrategy() { + ClientInputStrategy strategy = new TestClientInputStrategy(); + inputManager.setStrategy(strategy); + assertEquals(strategy, inputManager.getCurrentStrategy()); + } + + @Test + @DisplayName("loadData без установленной стратегии должен выбрасывать IllegalStateException") + void testLoadDataWithoutStrategyThrowsException() throws IOException { + assertThrows(IllegalStateException.class, inputManager::loadData); + } + + @Test + @DisplayName("loadData должен возвращать данные от текущей стратегии") + void testLoadDataReturnsDataFromStrategy() throws IOException { + ClientInputStrategy strategy = new TestClientInputStrategy(); + inputManager.setStrategy(strategy); + + CustomCollection result = inputManager.loadData(); + + assertNotNull(result); + assertEquals(1, result.size()); // Ожидаем 1 клиента от тестовой стратегии + } + + @Test + @DisplayName("loadData должен пробрасывать IOException от стратегии") + void testLoadDataPropagatesIOException() throws IOException { + ClientInputStrategy strategy = new ClientInputStrategy() { + @Override + public CustomCollection getData() throws IOException { + throw new IOException("Ошибка ввода-вывода"); + } + }; + inputManager.setStrategy(strategy); + + assertThrows(IOException.class, inputManager::loadData); + } + + @Test + @DisplayName("createFileStrategy с null-путём должен выбрасывать IllegalArgumentException") + void testCreateFileStrategyWithNullPathThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> inputManager.createFileStrategy(null)); + } + + @Test + @DisplayName("createFileStrategy с пустым путём должен выбрасывать IllegalArgumentException") + void testCreateFileStrategyWithEmptyPathThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> inputManager.createFileStrategy("")); + } + + @Test + @DisplayName("createFileStrategy должен создавать FileReaderStrategy с указанным путём") + void testCreateFileStrategy() { + String filePath = "test.txt"; + FileReaderStrategy strategy = inputManager.createFileStrategy(filePath); + + assertNotNull(strategy); + // Если у FileReaderStrategy есть геттер для пути: + // assertEquals(filePath, strategy.getFilePath()); + } + + @Test + @DisplayName("createManualStrategy должен создавать ManualInputReaderStrategy") + void testCreateManualStrategy() { + ManualInputReaderStrategy strategy = inputManager.createManualStrategy(); + + assertNotNull(strategy); + } + + @Test + @DisplayName("createRandomStrategy с отрицательным count должен выбрасывать IllegalArgumentException") + void testCreateRandomStrategyWithNegativeCountThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> inputManager.createRandomStrategy(-1)); + } + + @Test + @DisplayName("createRandomStrategy с нулевым count должен выбрасывать IllegalArgumentException") + void testCreateRandomStrategyWithZeroCountThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> inputManager.createRandomStrategy(0)); + } + + @Test + @DisplayName("createRandomStrategy должен создавать RandomDataGeneratorStrategy с указанным количеством") + void testCreateRandomStrategy() { + int count = 5; + RandomDataGeneratorStrategy strategy = inputManager.createRandomStrategy(count); + + assertNotNull(strategy); + // Если у RandomDataGeneratorStrategy есть геттер для count: + // assertEquals(count, strategy.getCount()); + } +} + + +// Тестовая реализация ClientInputStrategy для использования в тестах + class TestClientInputStrategy implements ClientInputStrategy { + @Override + public CustomCollection getData() throws IOException { + CustomCollection clients = new CustomCollection<>(); + clients.add(new Client.ClientBuilder().name("Test Client") + .phoneNumber("+79991234567").idNumber(1).build()); + + // Предполагаем, что CustomCollection имеет конструктор от Collection + return clients; + } + } \ No newline at end of file diff --git a/src/test/java/input/strategy/FileReaderStrategyTest.java b/src/test/java/input/strategy/FileReaderStrategyTest.java new file mode 100644 index 0000000..f5a57d5 --- /dev/null +++ b/src/test/java/input/strategy/FileReaderStrategyTest.java @@ -0,0 +1,271 @@ +package input.strategy; + +import dto.Client; +import input.CustomCollection; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +class FileReaderStrategyTest { + + private static final String TEST_FILE_PATH = "test_clients.txt"; + private Path testFilePath; + + @BeforeEach + void setUp() throws IOException { + testFilePath = Paths.get(TEST_FILE_PATH); + } + + @AfterEach + void tearDown() { + // Удаляем тестовый файл после каждого теста, если он существует + if (Files.exists(testFilePath)) { + try { + Files.delete(testFilePath); + } catch (IOException e) { + // Игнорируем ошибки удаления в tearDown + } + } + } + + @Test + @DisplayName("getData должен прочитать корректные данные из файла") + void testGetDataReadsValidClients() throws IOException { + // Arrange: создаём файл с корректными данными + String content = "Иван Иванов|+79991234567|1\n" + + "Мария Петрова|+79997654321|2"; + Files.write(testFilePath, content.getBytes()); + + ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert + assertEquals(2, clients.size()); + assertEquals("Иван Иванов", clients.get(0).getName()); + assertEquals("+79991234567", clients.get(0).getPhoneNumber()); + assertEquals(1, clients.get(0).getIdNumber()); + + assertEquals("Мария Петрова", clients.get(1).getName()); + assertEquals("+79997654321", clients.get(1).getPhoneNumber()); + assertEquals(2, clients.get(1).getIdNumber()); + } + + @Test + @DisplayName("getData должен пропускать пустые строки") + void testGetDataSkipsEmptyLines() throws IOException { + // Arrange: файл с пустыми строками и одним корректным клиентом + String content = "\n\n" + + "Иван Иванов|+79991234567|1\n" + + "\n"; + Files.write(testFilePath, content.getBytes()); + + ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: должна быть прочитана только одна запись + assertEquals(1, clients.size()); + assertEquals("Иван Иванов", clients.get(0).getName()); + } + + @Test + @DisplayName("getData должен пропускать строки с неверным форматом (не 3 поля)") + void testGetDataSkipsInvalidFormatLines() throws IOException { + // Arrange: строка с двумя полями вместо трёх + String content = "Иван Иванов|+79991234567\n" + + "Мария Петрова|+79997654321|2"; + Files.write(testFilePath, content.getBytes()); + + ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: только вторая строка должна быть прочитана + assertEquals(1, clients.size()); + assertEquals("Мария Петрова", clients.get(0).getName()); + } + + @Test + @DisplayName("getData должен пропускать строки с невалидным ID (не число)") + void testGetDataSkipsInvalidId() throws IOException { + // Arrange + String content = "Иван Иванов|+79991234567|abc\n" + + "Мария Петрова|+79997654321|2"; + Files.write(testFilePath, content.getBytes()); + + ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: первая строка с невалидным ID пропускается + assertEquals(1, clients.size()); + assertEquals("Мария Петрова", clients.get(0).getName()); + } + + @Test + @DisplayName("getData должен пропускать строки с пустым именем") + void testGetDataSkipsEmptyName() throws IOException { + // Arrange + String content = "|+79991234567|1\n" + + "Мария Петрова|+79997654321|2"; + Files.write(testFilePath, content.getBytes()); + + ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: первая строка пропускается изза пустого имени + assertEquals(1, clients.size()); + assertEquals("Мария Петрова", clients.get(0).getName()); + } + + @Test + @DisplayName("getData должен пропускать строки с невалидным номером телефона") + void testGetDataSkipsInvalidPhone() throws IOException { + // Arrange: номер без +7 и неправильный формат + String content = "Иван Иванов|79991234567|1\n" + // без +7 + "Мария Петрова|+7999765432|2\n" + // 9 цифр вместо 10 + "Анна Сидорова|+79998887766|3"; // корректный + Files.write(testFilePath, content.getBytes()); + + ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: прочитан только последний клиент + assertEquals(1, clients.size()); + assertEquals("Анна Сидорова", clients.get(0).getName()); + } + + @Test + @DisplayName("getData должен выбрасывать RuntimeException, если файл не найден") + void testGetDataThrowsExceptionWhenFileNotFound() { + ClientInputStrategy strategy = new FileReaderStrategy("nonexistent.txt"); + + assertThrows(RuntimeException.class, + strategy::getData, + "getData должен выбрасывать RuntimeException для несуществующего файла" + ); + } + + @Test + @DisplayName("getData должен выбрасывать RuntimeException при ошибке вводавывода") + void testGetDataThrowsExceptionOnIOError() throws IOException { + // Arrange: создаём файл и удаляем его перед чтением + Files.createFile(testFilePath); + Files.delete(testFilePath); // файл удаляется, чтобы вызвать ошибку + + ClientInputStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + + assertThrows(RuntimeException.class, + strategy::getData, + "getData должен выбрасывать RuntimeException при IO ошибке" + ); + } + + @Test + @DisplayName("parseToClient должен возвращать Optional.empty для строки с неверным форматом") + void testParseToClientReturnsEmptyForInvalidFormat() { + FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + Optional result = strategy.parseToClient("Иван Иванов|+79991234567"); + assertFalse(result.isPresent()); + } + + @Test + @DisplayName("parseToClient должен возвращать Optional.empty для строки с невалидным ID") + void testParseToClientReturnsEmptyForInvalidId() { + FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + Optional result = strategy.parseToClient("Иван Иванов|+79991234567|abc"); + assertFalse(result.isPresent()); + } + + @Test + @DisplayName("parseToClient должен возвращать Optional.empty для строки с пустым именем") + void testParseToClientReturnsEmptyForEmptyName() { + FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + Optional result = strategy.parseToClient("|+79991234567|1"); + assertFalse(result.isPresent()); + } + + + @Test + @DisplayName("parseToClient должен возвращать Optional.empty для строки с некорректным форматом телефона") + void testParseToClientReturnsEmptyForInvalidPhoneFormat() { + FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + + // Тестируем разные варианты некорректного телефона + Optional result1 = strategy.parseToClient("Иван Иванов|79991234567|1"); // нет +7 + Optional result2 = strategy.parseToClient("Иван Иванов|+7123|1"); // недостаточно цифр + Optional result3 = strategy.parseToClient("Иван Иванов|+89991234567|1"); // не +7 + + assertFalse(result1.isPresent(), "Должен возвращать Optional.empty, если отсутствует +7 в номере"); + assertFalse(result2.isPresent(), "Должен возвращать Optional.empty, если недостаточно цифр в номере"); + assertFalse(result3.isPresent(), "Должен возвращать Optional.empty, если код страны не +7"); + } + + @Test + @DisplayName("parseToClient должен возвращать Optional.empty для строки с неверным количеством полей") + void testParseToClientReturnsEmptyForWrongFieldCount() { + FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + + // Слишком мало полей + Optional result1 = strategy.parseToClient("Иван Иванов|+79991234567"); + // Слишком много полей + Optional result2 = strategy.parseToClient("Иван Иванов|+79991234567|1|extra"); + + assertFalse(result1.isPresent(), "Должен возвращать Optional.empty, если полей меньше трёх"); + assertFalse(result2.isPresent(), "Должен возвращать Optional.empty, если полей больше трёх"); + } + + @Test + @DisplayName("parseToClient должен возвращать Optional.empty для пустой строки") + void testParseToClientReturnsEmptyForEmptyString() { + FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + + Optional result = strategy.parseToClient(""); + + assertFalse(result.isPresent(), "Должен возвращать Optional.empty для пустой строки"); + } + + @Test + @DisplayName("parseToClient должен возвращать Optional.empty для строки, состоящей только из пробелов") + void testParseToClientReturnsEmptyForWhitespaceString() { + FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + + Optional result = strategy.parseToClient(" "); + + assertFalse(result.isPresent(), "Должен возвращать Optional.empty для строки из пробелов"); + } + + @Test + @DisplayName("parseToClient должен корректно парсить валидную строку") + void testParseToClientParsesValidLineCorrectly() { + FileReaderStrategy strategy = new FileReaderStrategy(TEST_FILE_PATH); + + Optional result = strategy.parseToClient("Иван Иванов|+79991234567|1"); + + assertTrue(result.isPresent(), "Должен возвращать непустой Optional для валидной строки"); + + Client.ClientBuilder builder = result.get(); + assertEquals("Иван Иванов", builder.getName(), "Имя должно быть корректно извлечено из строки"); + assertEquals("+79991234567", builder.getPhoneNumber(), "Номер телефона должен быть корректно извлечён из строки"); + assertEquals(1, builder.getIdNumber(), "ID должен быть корректно преобразован в число"); + } + +} \ No newline at end of file diff --git a/src/test/java/input/strategy/ManualInputReaderStrategyTest.java b/src/test/java/input/strategy/ManualInputReaderStrategyTest.java new file mode 100644 index 0000000..0ebea04 --- /dev/null +++ b/src/test/java/input/strategy/ManualInputReaderStrategyTest.java @@ -0,0 +1,197 @@ +package input.strategy; + +import dto.Client; +import input.CustomCollection; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ManualInputReaderStrategyTest { + + private InputStream originalSystemIn; + private final ByteArrayInputStream testInput = new ByteArrayInputStream(new byte[0]); + + @BeforeEach + void setUp() { + // Сохраняем оригинальный System.in + originalSystemIn = System.in; + } + + @AfterEach + void tearDown() { + // Восстанавливаем оригинальный System.in после теста + System.setIn(originalSystemIn); + } + + @Test + @DisplayName("getData должен вернуть пустую коллекцию при отказе от добавления первого клиента") + void testGetDataReturnsEmptyCollectionWhenUserDeclinesFirstClient() throws IOException { + // Arrange: пользователь отказывается добавлять первого клиента + String input = "н\n"; // 'нет' на вопрос «Добавляем клиента?» + System.setIn(new ByteArrayInputStream(input.getBytes())); + + ClientInputStrategy strategy = new ManualInputReaderStrategy(); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert + assertEquals(0, clients.size()); + } + + @Test + @DisplayName("getData должен корректно добавить одного клиента с валидными данными") + void testGetDataAddsOneValidClient() throws IOException { + // Arrange: последовательность ввода для одного клиента + String input = "да\n" + // добавляем клиента + "Иван Иванов\n" + // имя + "+79991234567\n" + // телефон + "1\n" + // ID + "да\n" + // подтверждение данных + "нет\n"; // отказ от добавления следующего клиента + System.setIn(new ByteArrayInputStream(input.getBytes())); + + ClientInputStrategy strategy = new ManualInputReaderStrategy(); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert + assertEquals(1, clients.size()); + Client client = clients.get(0); + assertEquals("Иван Иванов", client.getName()); + assertEquals("+79991234567", client.getPhoneNumber()); + assertEquals(1, client.getIdNumber()); + } + + @Test + @DisplayName("getData должен пропускать клиента при отмене подтверждения данных") + void testGetDataSkipsClientWhenUserCancelsConfirmation() throws IOException { + // Arrange: добавляем клиента, но отменяем подтверждение данных + String input = "да\n" + + "Мария Петрова\n" + + "+79997654321\n" + + "2\n" + + "нет\n" + // отмена подтверждения + "нет\n"; // отказ от следующего клиента + System.setIn(new ByteArrayInputStream(input.getBytes())); + + ClientInputStrategy strategy = new ManualInputReaderStrategy(); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: клиент не добавлен + assertEquals(0, clients.size()); + } + + @Test + @DisplayName("promptForNumber должен запрашивать повторный ввод при неверном формате телефона") + void testPromptForNumberRepeatsOnInvalidPhoneFormat() throws IOException { + // Arrange: неверный формат телефона, затем корректный + String input = "да\n" + + "Анна Сидорова\n" + + "79998887766\n" + // без +7 + "+79998887766\n" + // корректный формат + "3\n" + + "да\n" + + "нет\n"; + System.setIn(new ByteArrayInputStream(input.getBytes())); + + ClientInputStrategy strategy = new ManualInputReaderStrategy(); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: клиент добавлен с корректным номером + assertEquals(1, clients.size()); + assertEquals("+79998887766", clients.get(0).getPhoneNumber()); + } + + @Test + @DisplayName("promptForId должен отклонять занятый ID и запрашивать новый") + void testPromptForIdRejectsDuplicateId() throws IOException { + // Arrange: попытка ввести дублирующий ID + String input = "да\n" + + "Иван Иванов\n" + + "+79991234567\n" + + "1\n" + + "да\n" + // подтверждение первого клиента + "да\n" + // добавляем второго клиента + "Мария Петрова\n" + + "+79997654321\n" + + "1\n" + // дублирующий ID + "2\n" + // новый ID + "да\n" + + "нет\n"; + System.setIn(new ByteArrayInputStream(input.getBytes())); + + ClientInputStrategy strategy = new ManualInputReaderStrategy(); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: два клиента с разными ID + assertEquals(2, clients.size()); + assertEquals(1, clients.get(0).getIdNumber()); + assertEquals(2, clients.get(1).getIdNumber()); + } + + @Test + @DisplayName("promptForId должен отклонять ID вне диапазона 0–1000") + void testPromptForIdRejectsOutOfRangeId() throws IOException { + // Arrange: ID вне диапазона, затем корректный + String input = "да\n" + + "Анна Сидорова\n" + + "+79998887766\n" + + "-5\n" + // меньше 0 + "1500\n" + // больше 1000 + "5\n" + // корректный ID + "да\n" + + "нет\n"; + System.setIn(new ByteArrayInputStream(input.getBytes())); + + ClientInputStrategy strategy = new ManualInputReaderStrategy(); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: клиент добавлен с ID=5 + assertEquals(1, clients.size()); + assertEquals(5, clients.get(0).getIdNumber()); + } + + @Test + @DisplayName("getData должен остановить ввод при достижении лимита в 1000 клиентов") + void testGetDataStopsAtLimitOf1000Clients() throws IOException { + // Arrange: имитируем достижение лимита + StringBuilder inputBuilder = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + inputBuilder.append("да\n"); + inputBuilder.append("Клиент ").append(i).append("\n"); + inputBuilder.append("+7").append(String.format("%010d", i)).append("\n"); + inputBuilder.append(i).append("\n"); + inputBuilder.append("да\n"); + } + inputBuilder.append("да\n"); // попытка добавить 1001-го клиента + + System.setIn(new ByteArrayInputStream(inputBuilder.toString().getBytes())); + + ClientInputStrategy strategy = new ManualInputReaderStrategy(); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: добавлено ровно 1000 клиентов + assertEquals(1000, clients.size()); + } + + + } \ No newline at end of file diff --git a/src/test/java/input/strategy/RandomDataGeneratorStrategyTest.java b/src/test/java/input/strategy/RandomDataGeneratorStrategyTest.java new file mode 100644 index 0000000..b73de13 --- /dev/null +++ b/src/test/java/input/strategy/RandomDataGeneratorStrategyTest.java @@ -0,0 +1,170 @@ +package input.strategy; + +import dto.Client; +import input.CustomCollection; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class RandomDataGeneratorStrategyTest { + + @Test + @DisplayName("Конструктор с положительным количеством записей должен создавать стратегию") + void testConstructorWithPositiveCount() { + ClientInputStrategy strategy = new RandomDataGeneratorStrategy(5); + assertNotNull(strategy); + assertEquals(5, ((RandomDataGeneratorStrategy) strategy).getCount()); + } + + @Test + @DisplayName("Конструктор с нулевым количеством записей должен выбрасывать IllegalArgumentException") + void testConstructorWithZeroCountThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> new RandomDataGeneratorStrategy(0), + "Конструктор должен выбрасывать исключение при нулевом количестве записей" + ); + } + + @Test + @DisplayName("Конструктор с отрицательным количеством записей должен выбрасывать IllegalArgumentException") + void testConstructorWithNegativeCountThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> new RandomDataGeneratorStrategy(-5), + "Конструктор должен выбрасывать исключение при отрицательном количестве записей" + ); + } + + @Test + @DisplayName("getData должен генерировать указанное количество клиентов") + void testGetDataGeneratesCorrectNumberOfClients() throws IOException { + // Arrange + int expectedCount = 3; + ClientInputStrategy strategy = new RandomDataGeneratorStrategy(expectedCount); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert + assertEquals(expectedCount, clients.size()); + } + + @Test + @DisplayName("Все сгенерированные клиенты должны иметь уникальные ID") + void testGeneratedClientsHaveUniqueIds() throws IOException { + // Arrange + int count = 10; + ClientInputStrategy strategy = new RandomDataGeneratorStrategy(count); + + // Act + CustomCollection clients = strategy.getData(); + Set ids = new HashSet<>(); + + // Assert: проверяем уникальность ID + for (Client client : clients) { + assertTrue(ids.add(client.getIdNumber()), + "ID клиента не уникален: " + client.getIdNumber()); + } + assertEquals(count, ids.size()); + } + + @Test + @DisplayName("ID клиентов должны быть в диапазоне 0–1000") + void testIdsAreInRange() throws IOException { + // Arrange + int count = 50; + ClientInputStrategy strategy = new RandomDataGeneratorStrategy(count); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: каждый ID должен быть в диапазоне [0, 1000] + for (Client client : clients) { + int id = client.getIdNumber(); + assertTrue(id >= 0 && id <= 1000, + "ID " + id + " выходит за пределы диапазона 0–1000"); + } + } + + @Test + @DisplayName("Номера телефонов должны соответствовать формату +79XXXXXXXXX") + void testPhoneNumbersMatchFormat() throws IOException { + // Arrange + int count = 20; + ClientInputStrategy strategy = new RandomDataGeneratorStrategy(count); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: проверяем формат номера телефона + for (Client client : clients) { + String phone = client.getPhoneNumber(); + assertTrue(phone.matches("^\\+79\\d{9}$"), + "Номер телефона '" + phone + "' не соответствует формату +79XXXXXXXXX"); + } + } + + @Test + @DisplayName("Имена клиентов не должны быть пустыми") + void testNamesAreNotEmpty() throws IOException { + // Arrange + int count = 15; + ClientInputStrategy strategy = new RandomDataGeneratorStrategy(count); + + // Act + CustomCollection clients = strategy.getData(); + + // Assert: имя не должно быть пустым или null + for (Client client : clients) { + assertNotNull(client.getName(), "Имя клиента не должно быть null"); + assertFalse(client.getName().trim().isEmpty(), + "Имя клиента не должно быть пустым"); + } + } + + @Test + @DisplayName("getData должен выбрасывать IllegalStateException при попытке сгенерировать >1000 уникальных ID") + void testGetDataThrowsIllegalStateExceptionWhenExceedingUniqueIdLimit() throws IOException { + // Arrange: пытаемся сгенерировать больше уникальных ID, чем доступно в диапазоне 0–1000 + ClientInputStrategy strategy = new RandomDataGeneratorStrategy(1001); + + // Act & Assert + assertThrows(IllegalStateException.class, + strategy::getData, + "getData должен выбрасывать IllegalStateException, когда все ID в диапазоне 0–1000 использованы" + ); + } + + @Test + @DisplayName("Повторный вызов getData должен генерировать новые случайные данные") + void testRepeatedGetDataCallsGenerateDifferentData() throws IOException { + // Arrange + int count = 2; + RandomDataGeneratorStrategy strategy = new RandomDataGeneratorStrategy(count); + + // Act: первый вызов + CustomCollection firstResult = strategy.getData(); + // Второй вызов + CustomCollection secondResult = strategy.getData(); + + // Assert: сравниваем ID клиентов из двух вызовов + Set firstIds = new HashSet<>(); + for (Client client : firstResult) { + firstIds.add(client.getIdNumber()); + } + + Set secondIds = new HashSet<>(); + for (Client client : secondResult) { + secondIds.add(client.getIdNumber()); + } + + // Ожидаем, что хотя бы один ID отличается (высокая вероятность при случайной генерации) + boolean hasDifferentIds = !firstIds.equals(secondIds); + assertTrue(hasDifferentIds, + "Повторные вызовы getData должны генерировать разные наборы данных"); + } +} \ No newline at end of file diff --git a/src/test/java/output/FileDataWriterTest.java b/src/test/java/output/FileDataWriterTest.java new file mode 100644 index 0000000..3c95e60 --- /dev/null +++ b/src/test/java/output/FileDataWriterTest.java @@ -0,0 +1,137 @@ +package output; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import static org.junit.jupiter.api.Assertions.*; + +class FileDataWriterTest { + + private FileDataWriter fileDataWriter; + private final String testFilePath = "savedData.txt"; + private File testFile; + + @BeforeEach + void setUp() { + fileDataWriter = new FileDataWriter(); + testFile = new File(testFilePath); + // Удаляем файл перед каждым тестом, если он существует + if (testFile.exists()) { + testFile.delete(); + } + } + + @AfterEach + void tearDown() { + // Удаляем тестовый файл после каждого теста + if (testFile.exists()) { + testFile.delete(); + } + } + + @Test + @DisplayName("writeDataToFile должен создавать файл, если он не существует") + void testWriteDataToFileCreatesFileIfNotExists() { + // Act + fileDataWriter.writeDataToFile("Тестовые данные"); + + // Assert + assertTrue(testFile.exists(), "Файл должен быть создан"); + assertTrue(testFile.isFile(), "Созданный объект должен быть файлом"); + } + + @Test + @DisplayName("writeDataToFile должен записывать данные в файл") + void testWriteDataToFileWritesDataToFile() throws IOException { + // Arrange + String testData = "Тестовые данные для записи"; + + // Act + fileDataWriter.writeDataToFile(testData); + + // Assert + assertTrue(testFile.exists()); + String fileContent = new String(Files.readAllBytes(testFile.toPath())); + assertEquals(testData, fileContent, "Содержимое файла должно совпадать с записанными данными"); + } + + @Test + @DisplayName("writeDataToFile должен дописывать данные в конец файла (append)") + void testWriteDataToFileAppendsDataToExistingFile() throws IOException { + // Arrange + String firstData = "Первая строка\n"; + String secondData = "Вторая строка\n"; + + // Act — записываем данные дважды + fileDataWriter.writeDataToFile(firstData); + fileDataWriter.writeDataToFile(secondData); + + // Assert + assertTrue(testFile.exists()); + String fileContent = new String(Files.readAllBytes(testFile.toPath())); + assertTrue(fileContent.contains(firstData), "Файл должен содержать первую строку"); + assertTrue(fileContent.contains(secondData), "Файл должен содержать вторую строку"); + assertEquals(firstData + secondData, fileContent, "Полное содержимое файла должно быть корректным"); + } + + @Test + @DisplayName("writeDataToFile с пустой строкой должен создавать файл без содержимого") + void testWriteDataToFileWithEmptyStringCreatesEmptyFile() throws IOException { + // Act + fileDataWriter.writeDataToFile(""); + + // Assert + assertTrue(testFile.exists()); + long fileSize = testFile.length(); + assertEquals(0, fileSize, "Файл с пустой строкой должен иметь нулевой размер"); + } + + @Test + @DisplayName("writeDataToFile с null должен создавать файл и не записывать ничего (изза обработки исключения)") + void testWriteDataToFileWithNullCreatesFile() { + // Act + fileDataWriter.writeDataToFile(null); + + // Assert + assertTrue(testFile.exists(), "Файл должен быть создан даже при записи null"); + // В текущем коде при записи null в файл запишется строка "null" + // Если ожидается другое поведение, нужно изменить реализацию метода + } + + @Test + @DisplayName("writeDataToFile должен корректно обрабатывать специальные символы") + void testWriteDataToFileHandlesSpecialCharacters() throws IOException { + // Arrange + String specialData = "Текст с символами: !@#$%^&*()_+-=[]{}|;':\",./<>?\n\t\n"; + + // Act + fileDataWriter.writeDataToFile(specialData); + + // Assert + assertTrue(testFile.exists()); + String fileContent = new String(Files.readAllBytes(testFile.toPath())); + assertEquals(specialData, fileContent, "Файл должен корректно сохранять специальные символы"); + } + + @Test + @DisplayName("writeDataToFile при ошибке создания файла должен выбрасывать RuntimeException") + void testWriteDataToFileThrowsRuntimeExceptionOnCreateNewFileFailure() { + // Arrange — создаём директорию с тем же именем, что и файл, чтобы вызвать ошибку при createNewFile() + File conflictingDir = new File("savedData.txt"); + conflictingDir.mkdir(); + + try { + // Act & Assert + assertThrows(RuntimeException.class, () -> fileDataWriter.writeDataToFile("Данные")); + } finally { + // Очищаем — удаляем директорию после теста + conflictingDir.delete(); + } + } +} \ No newline at end of file diff --git a/src/test/java/sorting/AbstractMergeSortStrategyTest.java b/src/test/java/sorting/AbstractMergeSortStrategyTest.java new file mode 100644 index 0000000..96defe1 --- /dev/null +++ b/src/test/java/sorting/AbstractMergeSortStrategyTest.java @@ -0,0 +1,164 @@ +package sorting; + +import dto.Client; +import input.CustomCollection; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Comparator; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AbstractMergeSortStrategyTest { + + // Конкретная реализация абстрактного класса для тестирования + static class TestMergeSortStrategy extends AbstractMergeSortStrategy { + @Override + protected Comparator getComparator() { + return Comparator.comparing(Client::getName); + } + + @Override + public void sort(CustomCollection clients) { + } + } + + @Test + @DisplayName("sortWithComparator с null-коллекцией не должен вызывать исключений") + void testSortWithComparatorWithNullCollection() { + AbstractMergeSortStrategy strategy = new TestMergeSortStrategy(); + strategy.sortWithComparator(null, Comparator.comparing(Client::getIdNumber)); + // Тест проходит, если не выброшено исключение + } + + @Test + @DisplayName("sortWithComparator с пустой коллекцией не должен изменять коллекцию") + void testSortWithComparatorWithEmptyCollection() { + AbstractMergeSortStrategy strategy = new TestMergeSortStrategy(); + CustomCollection clients = new CustomCollection<>(); + strategy.sortWithComparator(clients, Comparator.comparing(Client::getIdNumber)); + assertEquals(0, clients.size()); + } + + @Test + @DisplayName("sortWithComparator с коллекцией из одного элемента не должен изменять коллекцию") + void testSortWithComparatorWithSingleElement() { + AbstractMergeSortStrategy strategy = new TestMergeSortStrategy(); + CustomCollection clients = new CustomCollection<>(); + Client client = new Client.ClientBuilder().name("Иван").phoneNumber("+79991234567").idNumber(1).build(); + clients.add(client); + + strategy.sortWithComparator(clients, Comparator.comparing(Client::getIdNumber)); + + assertEquals(1, clients.size()); + assertEquals("Иван", clients.get(0).getName()); + } + + @Test + @DisplayName("sortWithComparator должен корректно сортировать коллекцию по idNumber") + void testSortWithComparatorSortsByIdNumber() { + AbstractMergeSortStrategy strategy = new TestMergeSortStrategy(); + CustomCollection clients = new CustomCollection<>(); + + // Добавляем клиентов в неупорядоченном порядке + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(3).build()); + clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(1).build()); + clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(2).build()); + + Comparator idComparator = Comparator.comparing(Client::getIdNumber); + strategy.sortWithComparator(clients, idComparator); + + // Проверяем, что клиенты отсортированы по idNumber: 1, 2, 3 + assertEquals(1, clients.get(0).getIdNumber()); + assertEquals(2, clients.get(1).getIdNumber()); + assertEquals(3, clients.get(2).getIdNumber()); + } + + @Test + @DisplayName("getStrategyName должен возвращать ожидаемую строку") + void testGetStrategyName() { + AbstractMergeSortStrategy strategy = new TestMergeSortStrategy(); + String expectedName = "Abstract Merge Sort (сортировка слиянием)"; + assertEquals(expectedName, strategy.getStrategyName()); + } + + @Test + @DisplayName("sortEvenValuesOnly с null-коллекцией не должен вызывать исключений") + void testSortEvenValuesOnlyWithNullCollection() { + AbstractMergeSortStrategy strategy = new TestMergeSortStrategy(); + strategy.sortEvenValuesOnly(null); + // Тест проходит, если не выброшено исключение + } + + @Test + @DisplayName("sortEvenValuesOnly с пустой коллекцией не должен изменять коллекцию") + void testSortEvenValuesOnlyWithEmptyCollection() { + AbstractMergeSortStrategy strategy = new TestMergeSortStrategy(); + CustomCollection clients = new CustomCollection<>(); + strategy.sortEvenValuesOnly(clients); + assertEquals(0, clients.size()); + } + + @Test + @DisplayName("sortEvenValuesOnly должен сортировать только элементы с чётными idNumber") + void testSortEvenValuesOnlySortsEvenIdNumbers() { + AbstractMergeSortStrategy strategy = new TestMergeSortStrategy(); + CustomCollection clients = new CustomCollection<>(); + + // Порядок: нечётный, чётный, нечётный, чётный, чётный + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build()); // нечётный + clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(4).build()); // чётный + clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(3).build()); // нечётный + clients.add(new Client.ClientBuilder().name("Галина").phoneNumber("+7444").idNumber(8).build()); // чётный + clients.add(new Client.ClientBuilder().name("Дмитрий").phoneNumber("+7555").idNumber(6).build()); // чётный + + strategy.sortEvenValuesOnly(clients); + + // Нечётные элементы остаются на своих местах + assertEquals(1, clients.get(0).getIdNumber()); // Анна на позиции 0 + assertEquals(3, clients.get(2).getIdNumber()); // Виктор на позиции 2 + + // Чётные элементы отсортированы по возрастанию: 4, 6, 8 + assertEquals(4, clients.get(1).getIdNumber()); // Борис на позиции 1 + assertEquals(6, clients.get(3).getIdNumber()); // Дмитрий на позиции 3 + assertEquals(8, clients.get(4).getIdNumber()); // Галина на позиции 4 + } + + @Test + @DisplayName("sortEvenValuesOnly не должен ничего делать, если нет чётных idNumber") + void testSortEvenValuesOnlyDoesNothingWhenNoEvenIdNumbers() { + AbstractMergeSortStrategy strategy = new TestMergeSortStrategy(); + CustomCollection clients = new CustomCollection<>(); + + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build()); + clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(3).build()); + clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(5).build()); + + strategy.sortEvenValuesOnly(clients); + + // Все элементы остались на своих местах в исходном порядке + assertEquals(1, clients.get(0).getIdNumber()); + assertEquals(3, clients.get(1).getIdNumber()); + assertEquals(5, clients.get(2).getIdNumber()); + } + + @Test + @DisplayName("sortEvenValuesOnly с пользовательским компаратором должен сортировать по заданному критерию") + void testSortEvenValuesOnlyWithCustomComparator() { + AbstractMergeSortStrategy strategy = new TestMergeSortStrategy(); + CustomCollection clients = new CustomCollection<>(); + + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(4).build()); + clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(8).build()); + clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(6).build()); + + // Компаратор по убыванию idNumber + Comparator descendingIdComparator = Comparator.comparing(Client::getIdNumber).reversed(); + strategy.sortEvenValuesOnly(clients, descendingIdComparator); + + // Чётные idNumber отсортированы по убыванию: 8, 6, 4 + assertEquals(8, clients.get(0).getIdNumber()); + assertEquals(6, clients.get(1).getIdNumber()); + assertEquals(4, clients.get(2).getIdNumber()); + } +} \ No newline at end of file diff --git a/src/test/java/sorting/MergeSortDefaultStrategyTest.java b/src/test/java/sorting/MergeSortDefaultStrategyTest.java new file mode 100644 index 0000000..a3b25ae --- /dev/null +++ b/src/test/java/sorting/MergeSortDefaultStrategyTest.java @@ -0,0 +1,159 @@ +package sorting; + +import dto.Client; +import input.CustomCollection; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MergeSortDefaultStrategyTest { + + @Test + @DisplayName("sort с null-коллекцией не должен вызывать исключений") + void testSortWithNullCollection() { + MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy(); + strategy.sort(null); + // Тест проходит, если не выброшено исключение + } + + @Test + @DisplayName("sort с пустой коллекцией не должен изменять коллекцию") + void testSortWithEmptyCollection() { + MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy(); + CustomCollection clients = new CustomCollection<>(); + strategy.sort(clients); + assertEquals(0, clients.size()); + } + + @Test + @DisplayName("sort с коллекцией из одного элемента не должен изменять коллекцию") + void testSortWithSingleElement() { + MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy(); + CustomCollection clients = new CustomCollection<>(); + Client client = new Client.ClientBuilder() + .name("Иван") + .phoneNumber("+79991234567") + .idNumber(1) + .build(); + clients.add(client); + + strategy.sort(clients); + + assertEquals(1, clients.size()); + assertEquals("Иван", clients.get(0).getName()); + assertEquals("+79991234567", clients.get(0).getPhoneNumber()); + assertEquals(1, clients.get(0).getIdNumber()); + } + + @Test + @DisplayName("sort должен сортировать по имени (основной критерий)") + void testSortByName() { + MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy(); + CustomCollection clients = new CustomCollection<>(); + + clients.add(new Client.ClientBuilder().name("Сергей").phoneNumber("+7111").idNumber(3).build()); + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7222").idNumber(2).build()); + clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7333").idNumber(1).build()); + + strategy.sort(clients); + + // Ожидаемый порядок: Анна, Борис, Сергей + assertEquals("Анна", clients.get(0).getName()); + assertEquals("Борис", clients.get(1).getName()); + assertEquals("Сергей", clients.get(2).getName()); + } + + @Test + @DisplayName("sort должен сортировать по ID, если имена одинаковые") + void testSortByIdWhenNamesAreEqual() { + MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy(); + CustomCollection clients = new CustomCollection<>(); + + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(3).build()); + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7222").idNumber(1).build()); + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7333").idNumber(2).build()); + + strategy.sort(clients); + + // Все имена одинаковые, сортируем по ID: 1, 2, 3 + assertEquals(1, clients.get(0).getIdNumber()); + assertEquals(2, clients.get(1).getIdNumber()); + assertEquals(3, clients.get(2).getIdNumber()); + } + + @Test + @DisplayName("sort должен сортировать по телефону, если имена и ID одинаковые") + void testSortByPhoneWhenNamesAndIdsAreEqual() { + MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy(); + CustomCollection clients = new CustomCollection<>(); + + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7333").idNumber(1).build()); + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build()); + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7222").idNumber(1).build()); + + strategy.sort(clients); + + // Имена и ID одинаковые, сортируем по телефону: +7111, +7222, +7333 + assertEquals("+7111", clients.get(0).getPhoneNumber()); + assertEquals("+7222", clients.get(1).getPhoneNumber()); + assertEquals("+7333", clients.get(2).getPhoneNumber()); + } + + @Test + @DisplayName("sort должен корректно обрабатывать дубликаты") + void testSortHandlesDuplicates() { + MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy(); + CustomCollection clients = new CustomCollection<>(); + + Client duplicate1 = new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build(); + Client duplicate2 = new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build(); + + clients.add(duplicate1); + clients.add(duplicate2); + clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(2).build()); + + strategy.sort(clients); + + // Дубликаты должны остаться в коллекции + assertEquals("Анна", clients.get(0).getName()); + assertEquals("Анна", clients.get(1).getName()); + assertEquals("Борис", clients.get(2).getName()); + } + + @Test + @DisplayName("getStrategyName должен возвращать ожидаемую строку") + void testGetStrategyName() { + MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy(); + String expectedName = "Merge Sort Default Strategy (сортировка по имени -> ID -> телефону)"; + assertEquals(expectedName, strategy.getStrategyName()); + } + + @Test + @DisplayName("sort должен сохранять стабильность сортировки для равных элементов") + void testSortStabilityForEqualElements() { + MergeSortDefaultStrategy strategy = new MergeSortDefaultStrategy(); + CustomCollection clients = new CustomCollection<>(); + + // Добавляем два клиента с одинаковыми данными в разном порядке + Client first = new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build(); + Client second = new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build(); + + clients.add(second); + clients.add(first); + + strategy.sort(clients); + + // Для стабильной сортировки порядок равных элементов должен сохраняться + // В данном случае оба элемента равны, поэтому порядок может быть любым, + // но оба элемента должны присутствовать + assertEquals(2, clients.size()); + assertTrue(clients.get(0).getName().equals("Анна") && + clients.get(0).getPhoneNumber().equals("+7111") && + clients.get(0).getIdNumber() == 1); + assertTrue(clients.get(1).getName().equals("Анна") && + clients.get(1).getPhoneNumber().equals("+7111") && + clients.get(1).getIdNumber() == 1); + } +} \ No newline at end of file diff --git a/src/test/java/sorting/MergeSortDynamicStrategyTest.java b/src/test/java/sorting/MergeSortDynamicStrategyTest.java new file mode 100644 index 0000000..3d73303 --- /dev/null +++ b/src/test/java/sorting/MergeSortDynamicStrategyTest.java @@ -0,0 +1,183 @@ +package sorting; + +import dto.Client; +import enums.Field; +import input.CustomCollection; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class MergeSortDynamicStrategyTest { + + @Test + @DisplayName("Конструктор с валидными параметрами должен создавать стратегию") + void testConstructorWithValidParameters() { + MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.NAME, true); + assertNotNull(strategy); + } + + @Test + @DisplayName("sort с null-коллекцией не должен вызывать исключений") + void testSortWithNullCollection() { + MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.NAME, true); + strategy.sort(null); + // Тест проходит, если не выброшено исключение + } + + @Test + @DisplayName("sort с пустой коллекцией не должен изменять коллекцию") + void testSortWithEmptyCollection() { + MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.ID_NUMBER, false); + CustomCollection clients = new CustomCollection<>(); + strategy.sort(clients); + assertEquals(0, clients.size()); + } + + @Test + @DisplayName("sort с коллекцией из одного элемента не должен изменять коллекцию") + void testSortWithSingleElement() { + MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.PHONE_NUMBER, true); + CustomCollection clients = new CustomCollection<>(); + Client client = new Client.ClientBuilder() + .name("Иван") + .phoneNumber("+79991234567") + .idNumber(1) + .build(); + clients.add(client); + + strategy.sort(clients); + + assertEquals(1, clients.size()); + assertEquals("Иван", clients.get(0).getName()); + assertEquals("+79991234567", clients.get(0).getPhoneNumber()); + assertEquals(1, clients.get(0).getIdNumber()); + } + + @Test + @DisplayName("sort по имени в порядке возрастания должен корректно сортировать") + void testSortByNameAscending() { + MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.NAME, true); + CustomCollection clients = new CustomCollection<>(); + + clients.add(new Client.ClientBuilder().name("Сергей").phoneNumber("+7111").idNumber(3).build()); + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7222").idNumber(2).build()); + clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7333").idNumber(1).build()); + + strategy.sort(clients); + + // Ожидаемый порядок: Анна, Борис, Сергей + assertEquals("Анна", clients.get(0).getName()); + assertEquals("Борис", clients.get(1).getName()); + assertEquals("Сергей", clients.get(2).getName()); + } + + @Test + @DisplayName("sort по имени в порядке убывания должен корректно сортировать") + void testSortByNameDescending() { + MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.NAME, false); + CustomCollection clients = new CustomCollection<>(); + + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build()); + clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(2).build()); + clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(3).build()); + + strategy.sort(clients); + + // Ожидаемый порядок: Виктор, Борис, Анна + assertEquals("Виктор", clients.get(0).getName()); + assertEquals("Борис", clients.get(1).getName()); + assertEquals("Анна", clients.get(2).getName()); + } + + @Test + @DisplayName("sort по ID в порядке возрастания должен корректно сортировать") + void testSortByIdAscending() { + MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.ID_NUMBER, true); + CustomCollection clients = new CustomCollection<>(); + + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(3).build()); + clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(1).build()); + clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(2).build()); + + strategy.sort(clients); + + // Ожидаемый порядок: 1, 2, 3 + assertEquals(1, clients.get(0).getIdNumber()); + assertEquals(2, clients.get(1).getIdNumber()); + assertEquals(3, clients.get(2).getIdNumber()); + } + + @Test + @DisplayName("sort по ID в порядке убывания должен корректно сортировать") + void testSortByIdDescending() { + MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.ID_NUMBER, false); + CustomCollection clients = new CustomCollection<>(); + + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build()); + clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7222").idNumber(3).build()); + clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7333").idNumber(2).build()); + + strategy.sort(clients); + + // Ожидаемый порядок: 3, 2, 1 + assertEquals(3, clients.get(0).getIdNumber()); + assertEquals(2, clients.get(1).getIdNumber()); + assertEquals(1, clients.get(2).getIdNumber()); + } + + @Test + @DisplayName("sort по номеру телефона в порядке возрастания должен корректно сортировать") + void testSortByPhoneAscending() { + MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.PHONE_NUMBER, true); + CustomCollection clients = new CustomCollection<>(); + + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7333").idNumber(1).build()); + clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7111").idNumber(2).build()); + clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7222").idNumber(3).build()); + + strategy.sort(clients); + + // Ожидаемый порядок: +7111, +7222, +7333 + assertEquals("+7111", clients.get(0).getPhoneNumber()); + assertEquals("+7222", clients.get(1).getPhoneNumber()); + assertEquals("+7333", clients.get(2).getPhoneNumber()); + } + + @Test + @DisplayName("sort по номеру телефона в порядке убывания должен корректно сортировать") + void testSortByPhoneDescending() { + MergeSortDynamicStrategy strategy = new MergeSortDynamicStrategy(Field.PHONE_NUMBER, false); + CustomCollection clients = new CustomCollection<>(); + + clients.add(new Client.ClientBuilder().name("Анна").phoneNumber("+7111").idNumber(1).build()); + clients.add(new Client.ClientBuilder().name("Борис").phoneNumber("+7333").idNumber(2).build()); + clients.add(new Client.ClientBuilder().name("Виктор").phoneNumber("+7222").idNumber(3).build()); + + strategy.sort(clients); + + // Ожидаемый порядок: +7333, +7222, +7111 + assertEquals("+7333", clients.get(0).getPhoneNumber()); + assertEquals("+7222", clients.get(1).getPhoneNumber()); + assertEquals("+7111", clients.get(2).getPhoneNumber()); + } + + + @Test + @DisplayName("getStrategyName должен возвращать ожидаемую строку с указанием поля сортировки") + void testGetStrategyName() { + MergeSortDynamicStrategy ascendingStrategy = new MergeSortDynamicStrategy(Field.NAME, true); + MergeSortDynamicStrategy descendingStrategy = new MergeSortDynamicStrategy(Field.ID_NUMBER, false); + + // Act & Assert + String expectedAscending = "Dynamic Merge Sort (Динамическая сортировка по: NAME)"; + String expectedDescending = "Dynamic Merge Sort (Динамическая сортировка по: ID_NUMBER)"; + + assertEquals(expectedAscending, ascendingStrategy.getStrategyName(), + "Для стратегии с сортировкой по NAME (возрастание) должна возвращаться корректная строка"); + assertEquals(expectedDescending, descendingStrategy.getStrategyName(), + "Для стратегии с сортировкой по ID_NUMBER (убывание) должна возвращаться корректная строка"); + } + +} \ No newline at end of file diff --git a/src/test/java/sorting/SortingManagerTest.java b/src/test/java/sorting/SortingManagerTest.java new file mode 100644 index 0000000..5ff9342 --- /dev/null +++ b/src/test/java/sorting/SortingManagerTest.java @@ -0,0 +1,148 @@ +package sorting; + +import dto.Client; +import input.CustomCollection; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class SortingManagerTest { + + + @Test + @DisplayName("Конструктор без параметров должен создавать SortingManager с null-стратегией") + void testConstructorWithoutParameters() { + SortingManager manager = new SortingManager(); + assertNull(manager.getCurrentStrategy()); + } + + @Test + @DisplayName("Конструктор с nullстратегией должен выбрасывать IllegalArgumentException") + void testConstructorWithNullStrategyThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> new SortingManager(null), + "Конструктор должен выбрасывать исключение при nullстратегии" + ); + } + + @Test + @DisplayName("Конструктор с валидной стратегией должен устанавливать её как текущую") + void testConstructorWithValidStrategy() { + // Создаём тестовую реализацию SortingStrategy + SortingStrategy testStrategy = new SortingStrategy() { + @Override + public void sort(CustomCollection clients) { + // Пустая реализация для теста + } + + @Override + public String getStrategyName() { + return "Test Strategy"; + } + + @Override + public void sortEvenValuesOnly(CustomCollection clients) { + } + }; + + SortingManager manager = new SortingManager(testStrategy); + assertEquals(testStrategy, manager.getCurrentStrategy(), + "Текущая стратегия должна совпадать с переданной в конструкторе" + ); + } + + @Test + @DisplayName("setStrategy с null должен выбрасывать IllegalArgumentException") + void testSetStrategyWithNullThrowsException() { + SortingManager manager = new SortingManager(); + + assertThrows(IllegalArgumentException.class, + () -> manager.setStrategy(null), + "setStrategy должен выбрасывать исключение при nullстратегии" + ); + } + + @Test + @DisplayName("setStrategy должен корректно устанавливать новую стратегию") + void testSetStrategy() { + SortingManager manager = new SortingManager(); + + // Первая стратегия + SortingStrategy strategy1 = new SortingStrategy() { + @Override + public void sort(CustomCollection clients) {} + + @Override + public String getStrategyName() { return "Strategy 1"; } + + @Override + public void sortEvenValuesOnly(CustomCollection clients) { + } + }; + + // Вторая стратегия + SortingStrategy strategy2 = new SortingStrategy() { + @Override + public void sort(CustomCollection clients) {} + + @Override + public String getStrategyName() { return "Strategy 2"; } + + @Override + public void sortEvenValuesOnly(CustomCollection clients) { + + } + }; + + // Устанавливаем первую стратегию + manager.setStrategy(strategy1); + assertEquals(strategy1, manager.getCurrentStrategy(), + "После setStrategy текущая стратегия должна быть strategy1" + ); + + // Заменяем на вторую стратегию + manager.setStrategy(strategy2); + assertEquals(strategy2, manager.getCurrentStrategy(), + "После повторного setStrategy текущая стратегия должна быть strategy2" + ); + } + + @Test + @DisplayName("getStrategy после конструктора без параметров должен возвращать null") + void testGetStrategyAfterDefaultConstructor() { + SortingManager manager = new SortingManager(); + assertNull(manager.getCurrentStrategy(), + "getStrategy должен возвращать null после конструктора без параметров" + ); + } + + @Test + @DisplayName("Последовательность операций: конструктор без параметров → setStrategy → getStrategy") + void testSequenceOfOperations() { + SortingManager manager = new SortingManager(); + + // Изначально стратегия null + assertNull(manager.getCurrentStrategy()); + + // Устанавливаем стратегию + SortingStrategy testStrategy = new SortingStrategy() { + @Override + public void sort(CustomCollection clients) {} + + @Override + public String getStrategyName() { return "Test Strategy"; } + + @Override + public void sortEvenValuesOnly(CustomCollection clients) { + + } + }; + manager.setStrategy(testStrategy); + + // Проверяем, что стратегия установилась + assertEquals(testStrategy, manager.getCurrentStrategy(), + "После setStrategy getStrategy должен возвращать установленную стратегию" + ); + } +} \ No newline at end of file diff --git a/src/test/java/sorting/SortingStrategyTest.java b/src/test/java/sorting/SortingStrategyTest.java new file mode 100644 index 0000000..6c95f87 --- /dev/null +++ b/src/test/java/sorting/SortingStrategyTest.java @@ -0,0 +1,148 @@ +package sorting; + +import dto.Client; +import input.CustomCollection; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class SortingStrategyTest { + + // Тестовая реализация интерфейса SortingStrategy для использования в тестах + private static class TestSortingStrategy implements SortingStrategy { + private boolean sortCalled = false; + private boolean sortEvenValuesOnlyCalled = false; + private String strategyName; + + public TestSortingStrategy(String strategyName) { + this.strategyName = strategyName; + } + + @Override + public void sort(CustomCollection clients) { + sortCalled = true; + } + + @Override + public String getStrategyName() { + return strategyName; + } + + @Override + public void sortEvenValuesOnly(CustomCollection clients) { + sortEvenValuesOnlyCalled = true; + } + + // Вспомогательные методы для проверки вызовов + public boolean isSortCalled() { + return sortCalled; + } + + public boolean isSortEvenValuesOnlyCalled() { + return sortEvenValuesOnlyCalled; + } + } + + @Test + @DisplayName("sort должен быть вызываемым без исключений") + void testSortIsCallable() { + SortingStrategy strategy = new TestSortingStrategy("Test Strategy"); + CustomCollection clients = new CustomCollection<>(); + + strategy.sort(clients); + + assertTrue(((TestSortingStrategy) strategy).isSortCalled(), + "Метод sort должен быть вызван без исключений"); + } + + @Test + @DisplayName("getStrategyName должен возвращать корректное имя стратегии") + void testGetStrategyNameReturnsCorrectName() { + String expectedName = "Test Strategy"; + SortingStrategy strategy = new TestSortingStrategy(expectedName); + + String actualName = strategy.getStrategyName(); + + assertEquals(expectedName, actualName, + "getStrategyName должен возвращать имя, установленное при создании стратегии"); + } + + @Test + @DisplayName("sortEvenValuesOnly должен быть вызываемым без исключений") + void testSortEvenValuesOnlyIsCallable() { + SortingStrategy strategy = new TestSortingStrategy("Test Strategy"); + CustomCollection clients = new CustomCollection<>(); + + strategy.sortEvenValuesOnly(clients); + + assertTrue(((TestSortingStrategy) strategy).isSortEvenValuesOnlyCalled(), + "Метод sortEvenValuesOnly должен быть вызван без исключений"); + } + + @Test + @DisplayName("sort с null-коллекцией не должен вызывать исключений (по контракту интерфейса)") + void testSortWithNullCollection() { + SortingStrategy strategy = new TestSortingStrategy("Test Strategy"); + + assertDoesNotThrow(() -> strategy.sort(null), + "sort не должен выбрасывать исключений при null-коллекции"); + } + + @Test + @DisplayName("sortEvenValuesOnly с null-коллекцией не должен вызывать исключений (по контракту интерфейса)") + void testSortEvenValuesOnlyWithNullCollection() { + SortingStrategy strategy = new TestSortingStrategy("Test Strategy"); + + assertDoesNotThrow(() -> strategy.sortEvenValuesOnly(null), + "sortEvenValuesOnly не должен выбрасывать исключений при null-коллекции"); + } + + @Test + @DisplayName("Методы sort и sortEvenValuesOnly могут работать с пустой коллекцией") + void testMethodsWorkWithEmptyCollection() { + SortingStrategy strategy = new TestSortingStrategy("Test Strategy"); + CustomCollection emptyClients = new CustomCollection<>(); + + // Вызываем sort с пустой коллекцией + strategy.sort(emptyClients); + assertTrue(((TestSortingStrategy) strategy).isSortCalled(), + "sort должен корректно обрабатывать пустую коллекцию"); + + // Сбрасываем флаг для следующего теста + ((TestSortingStrategy) strategy).sortCalled = false; + + // Вызываем sortEvenValuesOnly с пустой коллекцией + strategy.sortEvenValuesOnly(emptyClients); + assertTrue(((TestSortingStrategy) strategy).isSortEvenValuesOnlyCalled(), + "sortEvenValuesOnly должен корректно обрабатывать пустую коллекцию"); + } + + @Test + @DisplayName("Несколько вызовов sort должны обрабатываться корректно") + void testMultipleSortCalls() { + SortingStrategy strategy = new TestSortingStrategy("Test Strategy"); + CustomCollection clients1 = new CustomCollection<>(); + CustomCollection clients2 = new CustomCollection<>(); + + strategy.sort(clients1); + strategy.sort(clients2); + + assertTrue(((TestSortingStrategy) strategy).isSortCalled(), + "После нескольких вызовов sort флаг вызова должен оставаться установленным"); + } + + @Test + @DisplayName("Несколько вызовов sortEvenValuesOnly должны обрабатываться корректно") + void testMultipleSortEvenValuesOnlyCalls() { + SortingStrategy strategy = new TestSortingStrategy("Test Strategy"); + CustomCollection clients1 = new CustomCollection<>(); + CustomCollection clients2 = new CustomCollection<>(); + + strategy.sortEvenValuesOnly(clients1); + strategy.sortEvenValuesOnly(clients2); + + assertTrue(((TestSortingStrategy) strategy).isSortEvenValuesOnlyCalled(), + "После нескольких вызовов sortEvenValuesOnly флаг вызова должен оставаться установленным"); + } +} \ No newline at end of file diff --git a/src/test/java/userInterface/AppControllerTest.java b/src/test/java/userInterface/AppControllerTest.java new file mode 100644 index 0000000..3bcc855 --- /dev/null +++ b/src/test/java/userInterface/AppControllerTest.java @@ -0,0 +1,152 @@ +package userInterface; + +import dto.Client; +import enums.Field; +import input.CustomCollection; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.*; + +import static org.junit.jupiter.api.Assertions.*; + +class AppControllerTest { + + private AppController controller; + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + void setUp() { + controller = new AppController(); + // Перенаправляем System.out в ByteArrayOutputStream для перехвата вывода + System.setOut(new PrintStream(outputStream)); + } + + @Test + @DisplayName("startDefaultSorting должен отсортировать список и вывести всех клиентов") + void testStartDefaultSorting() { + // Arrange: добавляем тестовых клиентов + Client client1 = new Client.ClientBuilder().name("Иван").phoneNumber("+79991234567").idNumber(2).build(); + Client client2 = new Client.ClientBuilder().name("Пётр").phoneNumber("+79997654321").idNumber(1).build(); + controller.getFullList().add(client1); + controller.getFullList().add(client2); + + // Act + controller.startDefaultSorting(); + + // Assert: проверяем, что вывод содержит обоих клиентов (порядок после сортировки: client2, client1) + String output = outputStream.toString(); + assertTrue(output.contains("Иван") && output.contains("Пётр"), + "Вывод должен содержать всех клиентов после сортировки"); + } + + @Test + @DisplayName("startDynamicSorting должен отсортировать по указанному полю") + void testStartDynamicSorting() { + // Arrange: сортировка по имени (Field.NAME) + Client client1 = new Client.ClientBuilder().name("Сергей").phoneNumber("+79993333333").idNumber(5).build(); + Client client2 = new Client.ClientBuilder().name("Алексей").phoneNumber("+79994444444").idNumber(6).build(); + controller.getFullList().add(client1); + controller.getFullList().add(client2); + + // Act: сортировка по полю имени + controller.startDynamicSorting(Field.NAME); + + // Assert: после сортировки первым должен идти «Алексей» + String output = outputStream.toString(); + int alexeyIndex = output.indexOf("Алексей"); + int sergeyIndex = output.indexOf("Сергей"); + assertTrue(alexeyIndex < sergeyIndex, + "После сортировки по имени 'Алексей' должен идти перед 'Сергеем'"); + } + + @Test + @DisplayName("getFullList должен возвращать актуальную коллекцию клиентов") + void testGetFullList() { + // Arrange: заполняем коллекцию + Client client = new Client.ClientBuilder().name("Тест").phoneNumber("+70000000000").idNumber(99).build(); + controller.getFullList().add(client); + + // Act + CustomCollection list = controller.getFullList(); + + // Assert + assertEquals(1, list.size()); + assertEquals("Тест", list.get(0).getName()); + } + + @Test + @DisplayName("showAndWriteAllClients должен выводить и записывать всех клиентов в файл") + void testShowAndWriteAllClients() { + // Arrange + Client client = new Client.ClientBuilder().name("ВыводТест").phoneNumber("+71111111111").idNumber(77).build(); + controller.getFullList().add(client); + + // Act + controller.showAndWriteAllClients(); + + // Assert: вывод должен содержать имя клиента + String output = outputStream.toString(); + assertTrue(output.contains("ВыводТест"), + "showAndWriteAllClients должен вывести всех клиентов из fullList"); + } + + @Test + @DisplayName("startFileReaderStrategy должен загрузить клиентов из файла и посчитать Алексеев") + void testStartFileReaderStrategy() { + + File testFile = new File("test_clients.txt"); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(testFile))) { + writer.write("Алексей|+79991112233|1\n"); + writer.write("Мария|+79992223344|2\n"); + } catch (Exception e){ + throw new RuntimeException(e); + } + + // Arrange: предполагаем, что createFileStrategy корректно создаёт стратегию для тестового файла + // В реальных тестах нужно подготовить файл с данными, содержащими хотя бы одного «Алексея» + + String testFilePath = "test_clients.txt"; + + // Act: запускаем загрузку из файла + controller.startFileReaderStrategy(testFilePath); + + // Assert: проверяем, что fullList не пуста (данные загружены) + assertFalse(controller.getFullList().isEmpty(), + "fullList должен быть заполнен после загрузки из файла"); + + // Проверяем, что в выводе есть строка с количеством Алексеев + String output = outputStream.toString(); + assertTrue(output.contains("Добавлено Алексеев:"), + "Вывод должен содержать количество добавленных Алексеев"); + } + + @Test + @DisplayName("startRandomDataStrategy должен сгенерировать случайных клиентов и посчитать Алексеев") + void testStartRandomDataStrategy() { + // Arrange: генерируем 3 случайных клиента + + int count = 3; + + // Act + controller.startRandomDataStrategy(count); + + // Assert: fullList должен содержать 3 клиента + assertEquals(count, controller.getFullList().size(), + "fullList должен содержать указанное количество сгенерированных клиентов"); + + // Проверяем вывод количества Алексеев + String output = outputStream.toString(); + assertTrue(output.contains("Добавлено Алексеев:"), + "Вывод должен содержать количество добавленных Алексеев"); + } + + @AfterEach + void tearDown() { + // Восстанавливаем оригинальный System.out после теста + System.setOut(originalOut); + } +} diff --git a/src/test/java/userInterface/ConcurrentCounterTest.java b/src/test/java/userInterface/ConcurrentCounterTest.java new file mode 100644 index 0000000..a9e047c --- /dev/null +++ b/src/test/java/userInterface/ConcurrentCounterTest.java @@ -0,0 +1,225 @@ +package userInterface; + +import dto.Client; +import input.CustomCollection; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ConcurrentCounterTest { + + + private final ConcurrentCounter counter = new ConcurrentCounter(); + + @Test + @DisplayName("countAlexes должен вернуть 0 для пустой коллекции") + void testCountAlexesEmptyCollection() { + // Arrange + CustomCollection emptyClients = new CustomCollection<>(); + + // Act + int result = counter.countAlexes(emptyClients); + + // Assert + assertEquals(0, result, "Для пустой коллекции должно быть 0 Алексеев"); + } + + @Test + @DisplayName("countAlexes должен вернуть 0 если нет клиентов с именем Алексей") + void testCountAlexesNoAlexes() { + // Arrange + CustomCollection clients = new CustomCollection<>(); + clients.add(new Client.ClientBuilder().name("Иван Иванов").build()); + clients.add(new Client.ClientBuilder().name("Мария Петрова").build()); + clients.add(new Client.ClientBuilder().name("Пётр Сидоров").build()); + + // Act + int result = counter.countAlexes(clients); + + // Assert + assertEquals(0, result, "Если нет Алексеев, результат должен быть 0"); + } + + @Test + @DisplayName("countAlexes должен корректно посчитать одного Алексея") + void testCountAlexesOneAlex() { + // Arrange + CustomCollection clients = new CustomCollection<>(); + clients.add(new Client.ClientBuilder().name("Алексей Иванов").build()); + clients.add(new Client.ClientBuilder().name("Мария Петрова").build()); + clients.add(new Client.ClientBuilder().name("Пётр Сидоров").build()); + + // Act + int result = counter.countAlexes(clients); + + // Assert + assertEquals(1, result, "Должен быть найден 1 Алексей"); + } + + @Test + @DisplayName("countAlexes должен корректно посчитать нескольких Алексеев") + void testCountAlexesMultipleAlexes() { + // Arrange + CustomCollection clients = new CustomCollection<>(); + clients.add(new Client.ClientBuilder().name("Алексей Иванов").build()); + clients.add(new Client.ClientBuilder().name("Мария Петрова").build()); + clients.add(new Client.ClientBuilder().name("Алексей Сидоров").build()); + clients.add(new Client.ClientBuilder().name("Анна Козлова").build()); + clients.add(new Client.ClientBuilder().name("Алексей Николаев").build()); + + // Act + int result = counter.countAlexes(clients); + + // Assert + assertEquals(3, result, "Должно быть найдено 3 Алексея"); + } + + @Test + @DisplayName("countAlexes должен найти Алексея даже если имя содержит дополнительные слова") + void testCountAlexesNameWithAdditionalWords() { + // Arrange + CustomCollection clients = new CustomCollection<>(); + clients.add(new Client.ClientBuilder().name("Алексей Владимирович Иванов").build()); + clients.add(new Client.ClientBuilder().name("Сергей Алексеевич Петров").build()); // не должен учитываться + clients.add(new Client.ClientBuilder().name("Алексей").build()); + + // Act + int result = counter.countAlexes(clients); + + // Assert + assertEquals(2, result, "Должны быть найдены оба Алексея, даже с дополнительными словами в имени"); + } + + @Test + @DisplayName("countAlexes должен корректно работать с нечётным количеством клиентов") + void testCountAlexesOddNumberOfClients() { + // Arrange: 5 клиентов, 2 из которых — Алексей + CustomCollection clients = new CustomCollection<>(); + clients.add(new Client.ClientBuilder().name("Алексей Иванов").build()); + clients.add(new Client.ClientBuilder().name("Мария Петрова").build()); + clients.add(new Client.ClientBuilder().name("Алексей Сидоров").build()); + clients.add(new Client.ClientBuilder().name("Анна Козлова").build()); + clients.add(new Client.ClientBuilder().name("Пётр Николаев").build()); + + // Act + int result = counter.countAlexes(clients); + + // Assert: коллекция делится на 2 и 3 элемента, но результат должен быть корректным + assertEquals(2, result, "Для нечётного количества клиентов должно быть найдено 2 Алексея"); + } + + @Test + @DisplayName("countAlexes должен корректно работать с чётным количеством клиентов") + void testCountAlexesEvenNumberOfClients() { + // Arrange: 4 клиента, 2 из которых — Алексей + CustomCollection clients = new CustomCollection<>(); + clients.add(new Client.ClientBuilder().name("Алексей Иванов").build()); + clients.add(new Client.ClientBuilder().name("Мария Петрова").build()); + clients.add(new Client.ClientBuilder().name("Алексей Сидоров").build()); + clients.add(new Client.ClientBuilder().name("Анна Козлова").build()); + + // Act + int result = counter.countAlexes(clients); + + // Assert: коллекция делится на 2 части по 2 элемента + assertEquals(2, result, "Для чётного количества клиентов должно быть найдено 2 Алексея"); + } + + @Test + @DisplayName("countAlexes должен обработать коллекцию с одним клиентом — Алексеем") + void testCountAlexesSingleClientAlex() { + // Arrange + CustomCollection clients = new CustomCollection<>(); + clients.add(new Client.ClientBuilder().name("Алексей").build()); + + // Act + int result = counter.countAlexes(clients); + + // Assert + assertEquals(1, result, "Для одного клиента-Алексея результат должен быть 1"); + } + + @Test + @DisplayName("countAlexes должен обработать коллекцию с одним клиентом не-Алексеем") + void testCountAlexesSingleClientNotAlex() { + // Arrange + CustomCollection clients = new CustomCollection<>(); + clients.add(new Client.ClientBuilder().name("Иван").build()); + + // Act + int result = counter.countAlexes(clients); + + // Assert + assertEquals(0, result, "Для одного клиента не-Алексея результат должен быть 0"); + } + + @Test + @DisplayName("countAlexes должен выбрасывать RuntimeException при InterruptedException") + void testCountAlexesThrowsOnInterruptedException() throws Exception { + // Arrange: создаём кастомный ConcurrentCounter, который имитирует InterruptedException + ConcurrentCounter faultyCounter = new ConcurrentCounter() { + @Override + public int countAlexes(CustomCollection clients) { + ExecutorService executorService = Executors.newFixedThreadPool(2); + Future count1 = executorService.submit(() -> 0L); + executorService.shutdown(); + try { + Thread.currentThread().interrupt(); + count1.get(); // вызовет InterruptedException + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + return 0; + } + }; + + CustomCollection clients = new CustomCollection<>(); + clients.add(new Client.ClientBuilder().name("Алексей").build()); + + // Act & Assert + assertThrows(RuntimeException.class, + () -> faultyCounter.countAlexes(clients), + "При InterruptedException должен выбрасываться RuntimeException" + ); + } + + @Test + @DisplayName("countAlexes должен выбрасывать RuntimeException при ExecutionException") + void testCountAlexesThrowsOnExecutionException() throws Exception { + // Arrange: имитируем ExecutionException + ConcurrentCounter faultyCounter = new ConcurrentCounter() { + @Override + public int countAlexes(CustomCollection clients) { + ExecutorService executorService = Executors.newFixedThreadPool(2); + Future count1 = executorService.submit(new Callable() { + @Override + public Long call() throws Exception { + throw new Exception("Simulated execution error"); + } + }); + executorService.shutdown(); + try { + count1.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + return 0; + } + }; + + CustomCollection clients = new CustomCollection<>(); + clients.add(new Client.ClientBuilder().name("Алексей").build()); + +// Act & Assert + assertThrows(RuntimeException.class, + () -> faultyCounter.countAlexes(clients), + "При ExecutionException должен выбрасываться RuntimeException" + ); + } +} \ No newline at end of file diff --git a/test_clients.txt b/test_clients.txt new file mode 100644 index 0000000..ff44f20 --- /dev/null +++ b/test_clients.txt @@ -0,0 +1,2 @@ +Алексей|+79991112233|1 +Мария|+79992223344|2 diff --git a/testfile_clients.txt b/testfile_clients.txt new file mode 100644 index 0000000..de72660 --- /dev/null +++ b/testfile_clients.txt @@ -0,0 +1,30 @@ +Шаров Юрий|+79408809462|699 +Кулагин Дмитрий|+79019463584|93 +Мухина Раиса Валентиновна|+79204742853|512 +Мухина Алина Сергеевна|+79841015291|296 +Аркадий Игнатьевич Тихонов|+79337132205|23 +Большаков Матвей Эдуардович|+79051246042|591 +Игнатьева Зинаида Вячеславовна|+79468715374|813 +Оксана Кулагина|+79538510155|860 +Бобылев Вячеслав|+79549089324|573 +Андреева Дарья|+79384195474|999 +Игнатий Ильич Антонов|+79946035137|829 +Фокин Матвей Витальевич|+79274394409|933 +Анжела Викторовна Блохина|+79296568824|661 +Ефремова Ольга Вячеславовна|+79201039302|778 +Русакова Валентина|+79499915311|3 +Антон Воронов|+79938685129|262 +Субботина Евгения|+79883134731|275 +Фадеев Игорь Даниилович|+79907800005|194 +Панфилова Арина|+79678575110|927 +Попов Роман Евгеньевич|+79392077705|996 +Гаврилова Алла Владимировна|+79303034501|387 +Екатерина Филатова|+79524244998|669 +Виктор Леонидович Зиновьев|+79272614762|627 +Анжелика Кошелева|+79804707713|78 +Медведева Валентина|+79282932301|594 +Василиса Антоновна Лапина|+79971387677|853 +Вера Артёмовна Капустина|+79591168118|461 +Лаврентьев Вячеслав Сергеевич|+79639495445|459 +Маргарита Викторовна Кузьмина|+79571424972|549 +Вероника Моисеева|+79058575995|889 diff --git a/wrongTestfile_clients.txt b/wrongTestfile_clients.txt new file mode 100644 index 0000000..43c97a0 --- /dev/null +++ b/wrongTestfile_clients.txt @@ -0,0 +1,30 @@ +Шаров Юрий;+79408809462;699 +Кулагин Дмитрий;+79019463584;93 +Мухина Раиса Валентиновна;+79204742853;512 +Мухина Алина Сергеевна;+79841015291;296 +Аркадий Игнатьевич Тихонов;+79337132205;23 +Большаков Матвей Эдуардович;+79051246042;591 +Игнатьева Зинаида Вячеславовна;+79468715374;813 +Оксана Кулагина;+79538510155;860 +Бобылев Вячеслав;+79549089324;573 +Андреева Дарья;+79384195474;999 +Игнатий Ильич Антонов;+79946035137;829 +Фокин Матвей Витальевич;+79274394409;933 +Анжела Викторовна Блохина;+79296568824;661 +Ефремова Ольга Вячеславовна;+79201039302;778 +Русакова Валентина;+79499915311;3 +Антон Воронов;+79938685129;262 +Субботина Евгения;+79883134731;275 +Фадеев Игорь Даниилович;+79907800005;194 +Панфилова Арина;+79678575110;927 +Попов Роман Евгеньевич;+79392077705;996 +Гаврилова Алла Владимировна;+79303034501;387 +Екатерина Филатова;+79524244998;669 +Виктор Леонидович Зиновьев;+79272614762;627 +Анжелика Кошелева;+79804707713;78 +Медведева Валентина;+79282932301;594 +Василиса Антоновна Лапина;+79971387677;853 +Вера Артёмовна Капустина;+79591168118;461 +Лаврентьев Вячеслав Сергеевич;+79639495445;459 +Маргарита Викторовна Кузьмина;+79571424972;549 +Вероника Моисеева;+79058575995;889