diff --git a/.idea/compiler.xml b/.idea/compiler.xml index fb7f4a8..1a29e22 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,15 @@ - + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..0853c6e --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 5d98256..2266f6b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 5f39c02..d7ae44e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } group = "ru.erius" -version = "1.0" +version = "1.1" val mainClass = "$group.${name.toLowerCase()}.$name" repositories { @@ -13,6 +13,8 @@ repositories { dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + compileOnly("org.projectlombok:lombok:1.18.22") + annotationProcessor("org.projectlombok:lombok:1.18.22") } tasks.getByName("test") { @@ -34,4 +36,4 @@ tasks.jar { tasks.javadoc { options.encoding = "UTF-8" -} \ No newline at end of file +} diff --git a/src/main/java/ru/erius/lab5/Lab5.java b/src/main/java/ru/erius/lab5/Lab5.java index 3e8575b..e96fa5f 100644 --- a/src/main/java/ru/erius/lab5/Lab5.java +++ b/src/main/java/ru/erius/lab5/Lab5.java @@ -1,18 +1,24 @@ package ru.erius.lab5; -import ru.erius.lab5.cli.CommandParser; -import ru.erius.lab5.collection.PersonTreeSet; +import ru.erius.lab5.collection.Database; +import ru.erius.lab5.collection.PeopleDatabase; +import ru.erius.lab5.commandline.CommandLine; +import ru.erius.lab5.commandline.PeopleDatabaseCommands; public class Lab5 { - /** - * Создание коллекции {@link PersonTreeSet PersonTreeSet} - * и парсера {@link CommandParser CommandParser}, запуск программы на выполнение - * - * @param args Аргументы командной строки - */ + public static void main(String[] args) { - PersonTreeSet pts = new PersonTreeSet(); - CommandParser cmd = new CommandParser(pts); + CommandLine cmd = CommandLine.getInstance(); + + PeopleDatabaseCommands.registerDatabaseCommands(); + PeopleDatabase peopleDatabase = new PeopleDatabase(); + try { + peopleDatabase.load(); + } catch (Database.DatabaseLoadFailedException e) { + e.printStackTrace(); + } + PeopleDatabaseCommands.setPeopleDatabase(peopleDatabase); + cmd.start(); } } diff --git a/src/main/java/ru/erius/lab5/cli/CommandParser.java b/src/main/java/ru/erius/lab5/cli/CommandParser.java deleted file mode 100644 index d0c753c..0000000 --- a/src/main/java/ru/erius/lab5/cli/CommandParser.java +++ /dev/null @@ -1,597 +0,0 @@ -package ru.erius.lab5.cli; - -import ru.erius.lab5.collection.*; - -import java.io.*; -import java.util.*; -import java.util.function.Function; -import java.util.function.Predicate; - -/** - * Класс, добавляющий возможность интерактивного взаимодействия пользователя с коллекцией через терминал, - * для своей работы требует экземпляр класса {@link PersonTreeSet PersonTreeSet} - */ -public class CommandParser { - /** - * Вспомогательная строка для создания красивого вывода - */ - private final static String LINE = "============================================================================================================================================================================================="; - /** - * Строка, выводимая при запуске программы - */ - private final static String GREETINGS = LINE + "\n" + - "Добро пожаловать в программу для управления коллекцией объектов в интерактивном режиме!\n" + - "Напишите help, чтобы увидеть доступные команды\n" + - "Напишите exit, чтобы выйти из программы\n" + - LINE + "\n"; - /** - * Строка, выводимая при написании команды help - */ - private final static String HELP = LINE + "\n" + - "help: вывести справку по доступным командам\n" + - "info : вывести в стандартный поток вывода информацию о коллекции (тип, дата инициализации, количество элементов и т.д.)\n" + - "show : вывести в стандартный поток вывода все элементы коллекции в строковом представлении\n" + - "add [element] : добавить новый элемент в коллекцию\n" + - "update {id} [element] : обновить значение элемента коллекции, id которого равен заданному\n" + - "remove_by_id {id} : удалить элемент из коллекции по его id\n" + - "clear : очистить коллекцию\n" + - "save : сохранить коллекцию в файл\n" + - "execute_script {file_name} : считать и исполнить скрипт из указанного файла. В скрипте содержатся команды в таком же виде, в котором их вводит пользователь в интерактивном режиме.\n" + - "exit : завершить программу (без сохранения в файл)\n" + - "add_if_max [element] : добавить новый элемент в коллекцию, если его значение превышает значение наибольшего элемента этой коллекции\n" + - "add_if_min [element] : добавить новый элемент в коллекцию, если его значение меньше, чем у наименьшего элемента этой коллекции\n" + - "history : вывести последние 6 команд (без их аргументов)\n" + - "sum_of_height : вывести сумму значений поля height для всех элементов коллекции\n" + - "filter_contains_name {name} : вывести элементы, значение поля name которых содержит заданную подстроку\n" + - "print_field_descending_location : вывести значения поля location всех элементов в порядке убывания\n" + - LINE + "\n"; - /** - * Коллекция, на основе которой функционирует данный класс - */ - private final PersonTreeSet personTreeSet; - /** - * Логическая переменная, хранящая состояние программы - */ - private boolean isActive = false; - /** - * Очередь входных потоков, используется для выполнения пользовательских скриптов, - * также требуется для реализации возможности рекурсивного выполнения скриптов - */ - private final Queue inputStreams = new LinkedList<>(); - /** - * Reader, использующийся для чтения пользовательских команд или скриптов - */ - private BufferedReader reader; - /** - * Список последних 6 выполненных команд - */ - private final LinkedList history = new LinkedList<>(); - - /** - * Конструктор класса - * - * @param personTreeSet Коллекция типа {@link PersonTreeSet PersonTreeSet} - */ - public CommandParser(PersonTreeSet personTreeSet) { - this.personTreeSet = personTreeSet; - } - - /** - * Метод, открывающий поток ввода данных и запускающий программу, - * циклически вызывая функцию {@link #parse() parse} - */ - public void start() { - System.out.println(GREETINGS); - reader = new BufferedReader(new InputStreamReader(System.in)); - this.isActive = true; - do { - parse(); - } while (this.isActive); - } - - /** - * Основной метод, который ждет от пользователя ввода данных, после чего - * делит строку на команду и её аргументы, и в зависимости от них, - * вызывает нужную функцию на выполнение - */ - public void parse() { - String input = awaitInput("", "Что-то пошло не так").toLowerCase(Locale.ROOT); - String[] split = input.split(" "); - String command = split[0], argument = split.length > 1 ? split[1] : null; - switch (command) { - case "help": help(); break; - case "info": info(); break; - case "show": show(); break; - case "add": add(); break; - case "update": update(argument); break; - case "remove_by_id": removeByID(argument); break; - case "clear": clear(); break; - case "save": save(); break; - case "execute_script": executeScript(argument); break; - case "exit": exit(); break; - case "add_if_max": addIfMax(); break; - case "add_if_min": addIfMin(); break; - case "history": history(); break; - case "sum_of_height": sumOfHeight(); break; - case "filter_contains_name": filterContainsName(argument); break; - case "print_field_descending_location": printFieldDescendingLocation(); break; - default: unknown(); return; - } - updateHistory(command); - } - - /** - * Метод, обновляющий историю введенных команд - * - * @param command Введенная команда - */ - private void updateHistory(String command) { - history.add(command); - if (this.history.size() > 6) - this.history.removeFirst(); - } - - /** - * Метод, вызываемый при вводе неизвестной команды - */ - public void unknown() { - System.err.println("Неизвестная команда. Введите help для отображения списка всех команд"); - } - - /** - * Метод, вызываемый при вводе команды help, - * печатает содержимое строки {@link #HELP HELP} - */ - public void help() { - System.out.println(CommandParser.HELP); - } - - /** - * Метод, вызываемый при вводе команды info, - * печатает информацию о коллекции - */ - public void info() { - System.out.println(this.personTreeSet.info()); - } - - /** - * Метод, вызываемый при вводе команды show, - * печатает содержимое коллекции - */ - public void show() { - System.out.println("Вся коллекция PersonTreeSet:"); - System.out.println(this.personTreeSet.toString()); - } - - /** - * Метод, вызываемый при вводе команды add, - * пытается добавить новый элемент в коллекцию, - * печатает результат добавления - */ - public void add() { - Person person = createPerson(); - boolean success = this.personTreeSet.add(person); - String msg = success ? "Объект был добавлен в коллекцию" : "Не удалось добавить объект"; - System.out.println(msg); - } - - /** - * Метод, вызываемый при вводе команды update, - * пытается изменить существующий элемент коллекции по заданному id - * печатает результат изменения - * - * @param id Id человека - */ - public void update(long id) { - if (Person.getExistingPeople() < id || id <= 0) { - System.err.println("Не удалось найти объект с id " + id); - return; - } - Person person = createPerson(); - boolean success = this.personTreeSet.update(id, person); - String msg = success ? "Объект был успешно изменен" : "Не удалось найти объект с id " + id; - System.out.println(msg); - } - - /** - * Метод, аналогичный {@link #update(long) update(long)}, - * пытается преобразовать данную строку в Long и вызвать {@link #update(long) update(long)} - * - * @param strId Id человека типа String - */ - public void update(String strId) { - Long id = idOrNull(strId); - if (id != null) - update(id); - } - - /** - * Метод, вызываемый при вводе команды remove_by_id, - * пытается удалить существующий элемент из коллекции по заданному id - * печатает результат изменения - * - * @param id Id человека - */ - public void removeByID(long id) { - boolean success = this.personTreeSet.remove(id); - String msg = success ? "Объект был удален" : "Не удалось найти объект с id " + id; - System.out.println(msg); - } - - /** - * Метод, аналогичный {@link #removeByID(long) update(long)}, - * пытается преобразовать данную строку в Long и вызвать {@link #update(long) update(long)} - * - * @param strId Id человека типа String - */ - public void removeByID(String strId) { - Long id = idOrNull(strId); - if (id != null) - removeByID(id); - } - - /** - * Метод, преобразующий строковый id в Long, - * печатает результат преобразования - * - * @param strId Преобразуемая строка - * @return Преобразованная строка в Long или null при ошибке - */ - public Long idOrNull(String strId) { - long id; - if (strId == null) { - System.err.println("Параметр id должен быть указан"); - return null; - } - try { - id = Long.parseLong(strId); - } catch (NumberFormatException e) { - System.err.println("Параметр id должен являться целым числом"); - return null; - } - return id; - } - - /** - * Метод, вызываемый при вводе команды clear, очищает коллекцию - */ - public void clear() { - this.personTreeSet.clear(); - System.out.println("Коллекция успешно очищена"); - } - - /** - * Метод, вызываемый при вводе команды save, - * пытается сохранить коллекцию в файл, - * печатает результат сохранения - */ - public void save() { - boolean success = this.personTreeSet.save(); - String msg = success ? "Коллекция была сохранена в файле " + this.personTreeSet.getFile().getAbsolutePath() - : "Не удалось сохранить коллекцию (обычно причина пишется при запуске программы)"; - System.out.println(msg); - } - - /** - * Метод, вызываемый при вводе команды execute_script, - * пытается выполнить скрипт, написанный в файле fileName, - * печатает результат выполнения скрипта - * - * @param fileName Имя или путь к файлу - */ - public void executeScript(String fileName) { - if (fileName == null || fileName.isEmpty()) { - System.err.println("Параметр file_name должен быть указан"); - return; - } - File file = new File(fileName); - if (!file.exists() || file.isDirectory()) { - System.err.println("Не удалось найти указанный файл со скриптом"); - return; - } - InputStream stream; - try { - stream = new FileInputStream(file); - } catch (FileNotFoundException e) { - System.err.println("Файл не найден"); - return; - } - newInputStream(stream); - } - - /** - * Метод, вызываемый при вводе команды exit, - * заканчивает выполнение программы, предлагая перед этим сохранить коллекцию - */ - public void exit() { - if (this.personTreeSet.getFile() != null) { - String answer = awaitInput("Сохранить коллекцию в файл? (Y - да, N - нет)", "Введите Y или N", - input -> input.toUpperCase(Locale.ROOT).equals("Y") || input.toUpperCase(Locale.ROOT).equals("N"), - input -> input.toUpperCase(Locale.ROOT)); - if (answer.equals("Y")) - save(); - } - System.out.println("Выход из программы..."); - this.isActive = false; - try { - reader.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Метод, вызываемый при вводе команды add_if_max, - * пытается добавить новый элемент в коллекцию, только если он больше максимального элемента в коллекции - * печатает результат добавления - */ - public void addIfMax() { - Person person = createPerson(); - boolean success = this.personTreeSet.addIfMax(person); - String msg = success ? "Объект был добавлен в коллекцию" : "Не удалось добавить объект"; - System.out.println(msg); - } - - /** - * Метод, вызываемый при вводе команды add_if_min, - * пытается добавить новый элемент в коллекцию, только если он меньше минимального элемента в коллекции - * печатает результат добавления - */ - public void addIfMin() { - Person person = createPerson(); - boolean success = this.personTreeSet.addIfMin(person); - String msg = success ? "Объект был добавлен в коллекцию" : "Не удалось добавить объект"; - System.out.println(msg); - } - - /** - * Метод, вызываемый при вводе команды history, - * печатает историю последних 6 введенных команд - */ - public void history() { - System.out.println("История последних 6 команд:"); - this.history.forEach(System.out::println); - } - - /** - * Метод, вызываемый при вводе команды sum_of_height, - * печатает суммарный рост всех людей в коллекции - */ - public void sumOfHeight() { - int sum = this.personTreeSet.sumOfHeight(); - System.out.println("Сумма ростов всех людей в коллекции: " + sum); - } - - /** - * Метод, фильтрующий и печатающий всех людей, в имени которых имеется строка name, - * проверка не чувствительна к регистру - * - * @param name Строка, по которой происходит фильтрация - */ - public void filterContainsName(String name) { - if (name == null || name.isEmpty()) { - System.err.println("Параметр name должен быть указан"); - return; - } - List people = this.personTreeSet.filterContainsName(name); - System.out.println("Список людей, в имени которых содержится " + name + ":"); - people.forEach(System.out::println); - } - - /** - * Метод, печатающий отсортированный по убыванию список местоположений всех людей в коллекции - */ - public void printFieldDescendingLocation() { - List locations = this.personTreeSet.fieldDescendingLocation(); - System.out.println("Список локаций в порядке убывания:"); - locations.forEach(System.out::println); - } - - /** - * Метод, интерактивно создающий нового человека, получая каждое новое значение из потока ввода {@link #reader reader} - * - * @return Новый экземпляр класса {@link Person Person} - */ - private Person createPerson() { - System.out.println("Создание нового объекта класса Person"); - Person person = new Person(); - person.setName( - awaitInput("Введите имя:", "Введите непустую строку", - input -> !input.isEmpty()) - ); - person.setHeight( - awaitInput("Введите рост:", "Введите целое число, большее нуля", - input -> { - try { - int num = Integer.parseInt(input); - return num > 0; - } catch (NumberFormatException e) { - return false; - } - }, Integer::parseInt) - ); - person.setPassportID( - awaitInput("Введите номер паспорта:", "Введите как минимум 8 символов", - input -> input.length() >= 8) - ); - person.setEyeColor( - awaitInput("Введите цвет глаз (BLACK, ORANGE, BROWN):", "Введите один из предложенных цветов", - input -> Color.isColorValid(input.toUpperCase(Locale.ROOT)), input -> Color.valueOf(input.toUpperCase(Locale.ROOT))) - ); - person.setNationality( - awaitInput("Введите страну (UNITED_KINGDOM, GERMANY, CHINA, THAILAND, JAPAN):", "Введите одну из предлженных стран", - input -> Country.isCountryValid(input.toUpperCase(Locale.ROOT)), input -> Country.valueOf(input.toUpperCase(Locale.ROOT))) - ); - person.setCoordinates(createCoordinates()); - person.setLocation(createLocation()); - return person; - } - - /** - * Метод, интерактивно создающий новые координаты, получая каждое новое значение из потока ввода {@link #reader reader} - * - * @return Новый экземпляр класса {@link Coordinates Coordinates} - */ - private Coordinates createCoordinates() { - System.out.println("Создание нового объекта класса Coordinates"); - Coordinates coordinates = new Coordinates(); - coordinates.setX( - awaitInput("Введите x:", "Введите дробное число", - input -> { - try { - Float.parseFloat(input); - return true; - } catch (NumberFormatException e) { - return false; - } - }, Float::parseFloat) - ); - coordinates.setY( - awaitInput("Введите y:", "Введите дробное число, большее -816", - input -> { - try { - float num = Float.parseFloat(input); - return num > -816F; - } catch (NumberFormatException e) { - return false; - } - }, Float::parseFloat) - ); - return coordinates; - } - - /** - * Метод, интерактивно создающий новое местоположение, получая каждое новое значение из потока ввода {@link #reader reader} - * - * @return Новый экземпляр класса {@link Location Location} - */ - private Location createLocation() { - System.out.println("Создание нового объекта класса Location"); - Location location = new Location(); - location.setX( - awaitInput("Введите x:", "Введите дробное число", - input -> { - try { - Double.parseDouble(input); - return true; - } catch (NumberFormatException e) { - return false; - } - }, Double::parseDouble) - ); - location.setY( - awaitInput("Введите y:", "Введите дробное число", - input -> { - try { - Double.parseDouble(input); - return true; - } catch (NumberFormatException e) { - return false; - } - }, Float::parseFloat) - ); - location.setZ( - awaitInput("Введите z:", "Введите целое число", - input -> { - try { - Long.parseLong(input); - return true; - } catch (NumberFormatException e) { - return false; - } - }, Long::parseLong) - ); - location.setName( - awaitInput("Введите название:", "Введите непустую строку", - input -> !input.isEmpty()) - ); - return location; - } - - /** - * Метод, ожидающий ввода из потока ввода {@link #reader reader} и возвращающий результат,, - * печатает запрос msg перед ожиданием данных (если их вводит пользователь), - * печатает ошибку err, если при вводе данных произошла ошибка - * - * @param msg Строка, печатающаяся как запрос данных от пользователя - * @param err Строка, печатающаяся во время ошибки - * - * @return Строка из потока ввода - */ - private String awaitInput(String msg, String err) { - return awaitInput(msg, err, input -> true); - } - - /** - * Метод, ожидающий ввода из потока ввода {@link #reader reader } и возвращающий результат,, - * печатает запрос msg перед ожиданием данных (если их вводит пользователь), - * печатает ошибку err, если введенные данные не соответствуют предикату predicate - * - * @param msg Строка, печатающаяся как запрос данных от пользователя - * @param err Строка, печатающаяся при несоответствии ввода предикату - * @param predicate Предикат, определяющий валидность введенных данных - * - * @return Строка из потока ввода - */ - private String awaitInput(String msg, String err, Predicate predicate) { - String input = null; - do { - if (inputStreams.isEmpty()) - System.out.print(msg + "\n>>> "); - try { - input = reader.readLine(); - } catch (IOException e) { - e.printStackTrace(); - } - if (input == null) { - prevInputStream(); - continue; - } - if (predicate.test(input.trim())) - break; - else - System.err.println(err); - } while (true); - System.out.println(); - return input.trim(); - } - - /** - * Метод, ожидающий ввода из потока ввода {@link #reader reader} и возвращающий результат, - * печатает запрос msg перед ожиданием данных (если их вводит пользователь), - * печатает ошибку err, если введенные данные не соответствуют предикату predicate, - * преобразует результат в тип T в соответствии с функцией transform - * - * @param msg Строка, печатающаяся как запрос данных от пользователя - * @param err Строка, печатающаяся при несоответствии ввода предикату - * @param predicate Предикат, определяющий валидность введенных данных - * @param Тип, к которому будет приведен результат - * @param transform Функция, преобразующая результат в тип T - * - * @return Результат типа T - */ - private T awaitInput(String msg, String err, Predicate predicate, Function transform) { - String result = awaitInput(msg, err, predicate); - return transform.apply(result); - } - - /** - * Метод, меняющий текущий поток ввода на stream и добавляет его в очередь {@link #inputStreams inputStreams} - * - * @param stream Новый поток ввода - */ - private void newInputStream(InputStream stream) { - this.reader = new BufferedReader(new InputStreamReader(stream)); - this.inputStreams.add(stream); - } - - /** - * Метод, убирающий текущий поток ввода из очереди {@link #inputStreams inputStreams} - * и меняющий его либо на следующий в очереди поток, либо на System.in, если очередь пуста - */ - private void prevInputStream() { - inputStreams.poll(); - InputStream stream = inputStreams.isEmpty() ? System.in : inputStreams.peek(); - this.reader = new BufferedReader(new InputStreamReader(stream)); - } -} diff --git a/src/main/java/ru/erius/lab5/collection/Color.java b/src/main/java/ru/erius/lab5/collection/Color.java deleted file mode 100644 index 71db3a1..0000000 --- a/src/main/java/ru/erius/lab5/collection/Color.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.erius.lab5.collection; - -/** - * Перечисление цветов - */ -public enum Color { - BLACK, - ORANGE, - BROWN; - - /** - * Метод, проверяющий, можно ли из данной строки получить значение типа Color - * - * @param strColor Проверяемая строка - * - * @return true, если из строки можно получить значение типа Color, иначе false - */ - public static boolean isColorValid(String strColor) { - try { - Color.valueOf(strColor); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } -} diff --git a/src/main/java/ru/erius/lab5/collection/Coordinates.java b/src/main/java/ru/erius/lab5/collection/Coordinates.java deleted file mode 100644 index c47c587..0000000 --- a/src/main/java/ru/erius/lab5/collection/Coordinates.java +++ /dev/null @@ -1,88 +0,0 @@ -package ru.erius.lab5.collection; - -import java.util.Locale; - -/** - * Класс данных координат - */ -public class Coordinates { - - /** - * Координата X типа float - */ - private float x; - /** - * Координата Y типа float, значение должно быть больше -816 - */ - private float y; - - /** - * Конструктор без параметров, задаёт значения всех полей по умолчанию, - * x = 0, y = 0 - */ - public Coordinates() { - this.x = 0F; - this.y = 0F; - } - - /** - * Конструктор с параметрами - * - * @param x Координата X - * @param y Координата Y - * - * @throws IllegalArgumentException Если Y меньше или равен -816 - */ - public Coordinates(float x, float y) { - this.x = x; - this.y = y; - } - - public float getX() { - return x; - } - - public float getY() { - return y; - } - - public void setX(float x) { - this.x = x; - } - - /** - * Сеттер для поля y - * - * @param y Координата Y - * - * @throws IllegalArgumentException Если Y меньше или равен -816 - */ - public void setY(float y) { - this.y = y; - if (y <= -816) - throw new IllegalArgumentException("Поле y класса Coordinates должно быть больше -816"); - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - if (other == null || getClass() != other.getClass()) return false; - - Coordinates that = (Coordinates) other; - - if (Float.compare(that.x, x) != 0) return false; - return Float.compare(that.y, y) == 0; - } - - @Override - public int hashCode() { - int result = (x != 0.0f ? Float.floatToIntBits(x) : 0); - result = 31 * result + (y != 0.0f ? Float.floatToIntBits(y) : 0); - return result; - } - - @Override - public String toString() { - return String.format(Locale.US, "Coordinates(x=%f, y=%f)", this.x, this.y); - } -} diff --git a/src/main/java/ru/erius/lab5/collection/Country.java b/src/main/java/ru/erius/lab5/collection/Country.java deleted file mode 100644 index fe4b328..0000000 --- a/src/main/java/ru/erius/lab5/collection/Country.java +++ /dev/null @@ -1,28 +0,0 @@ -package ru.erius.lab5.collection; - -/** - * Перечисление стран - */ -public enum Country { - UNITED_KINGDOM, - GERMANY, - CHINA, - THAILAND, - JAPAN; - - /** - * Метод, проверяющий, можно ли из данной строки получить значение типа Country - * - * @param strCountry Проверяемая строка - * - * @return true, если из строки можно получить значение типа Country, иначе false - */ - public static boolean isCountryValid(String strCountry) { - try { - Country.valueOf(strCountry); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } -} diff --git a/src/main/java/ru/erius/lab5/collection/Database.java b/src/main/java/ru/erius/lab5/collection/Database.java new file mode 100644 index 0000000..7c72ae1 --- /dev/null +++ b/src/main/java/ru/erius/lab5/collection/Database.java @@ -0,0 +1,54 @@ +package ru.erius.lab5.collection; + +public interface Database { + + void load() throws DatabaseLoadFailedException; + + void save() throws DatabaseSaveFailedException; + + class DatabaseLoadFailedException extends Exception { + + public DatabaseLoadFailedException(String message) { + super(message); + } + + public DatabaseLoadFailedException(String message, String path) { + super(String.format(message, path)); + } + + public DatabaseLoadFailedException(String message, Throwable cause) { + super(message, cause); + } + + public DatabaseLoadFailedException(String message, String path, Throwable cause) { + super(String.format(message, path), cause); + } + + public DatabaseLoadFailedException(Throwable cause) { + super(cause); + } + } + + class DatabaseSaveFailedException extends Exception { + + public DatabaseSaveFailedException(String message) { + super(message); + } + + public DatabaseSaveFailedException(String message, String path) { + super(String.format(message, path)); + } + + public DatabaseSaveFailedException(String message, Throwable cause) { + super(message, cause); + } + + public DatabaseSaveFailedException(String message, String path, Throwable cause) { + super(String.format(message, path), cause); + } + + public DatabaseSaveFailedException(Throwable cause) { + super(cause); + } + } +} diff --git a/src/main/java/ru/erius/lab5/collection/Location.java b/src/main/java/ru/erius/lab5/collection/Location.java deleted file mode 100644 index 43ab13d..0000000 --- a/src/main/java/ru/erius/lab5/collection/Location.java +++ /dev/null @@ -1,160 +0,0 @@ -package ru.erius.lab5.collection; - -import java.util.Comparator; -import java.util.Locale; -import java.util.Objects; - -/** - * Класс данных местоположения, реализует сортировку по умолчанию - * по имени и расстоянию до точки (0; 0; 0) - */ -public class Location implements Comparable { - - /** - * Координата X типа double - */ - private double x; - /** - * Координата Y типа float - */ - private float y; - /** - * Координата Z типа Long, не может быть null - */ - private Long z; - /** - * Имя локации, может быть null - */ - private String name; - - /** - * Конструктор без параметров, задаёт значения всех полей по умолчанию, - * x = 0, y = 0, z = 0, name = null - */ - public Location() { - this.x = 0D; - this.y = 0F; - this.z = 0L; - this.name = null; - } - - /** - * Конструктор с параметрами - * - * @param x Координата X - * @param y Координата Y - * @param z Координата Z - * @param name Имя локации - * - * @throws IllegalArgumentException Данное исключение будет брошено в следующих случаях: - * Z является null, - * name является пустой строкой - */ - public Location(double x, float y, Long z, String name) { - this.x = x; - this.y = y; - this.setZ(z); - this.setName(name); - } - - public double getX() { - return x; - } - - public float getY() { - return y; - } - - public Long getZ() { - return z; - } - - public String getName() { - return name; - } - - public void setX(double x) { - this.x = x; - } - - public void setY(float y) { - this.y = y; - } - - /** - * Сеттер для поля z - * @param z Координата Z - * - * @throws IllegalArgumentException Если Z является null - */ - public void setZ(Long z) { - this.z = z; - if (z == null) - throw new IllegalArgumentException("Поле z класса Location не может быть null"); - } - - /** - * Сеттер для поля name - * @param name Имя локации - * - * @throws IllegalArgumentException Если name является пустой строкой - */ - public void setName(String name) { - this.name = name; - if (name.isEmpty()) - throw new IllegalArgumentException("Поле name класса Location не может быть пустым"); - } - - /** - * Переопределенный метод сравнения двух местоположений, - * сравнение производится по имени локации и расстоянию до точки (0; 0; 0) - * - * @param other Объект для сравнения - * @return Целое число - результат сравнения - */ - @Override - public int compareTo(Location other) { - return Comparator.comparing(Location::getName) - .thenComparing(Location::distance) - .compare(this, other); - } - - /** - * Метод, вычисляющий расстояние до точки (0; 0; 0) - * - * @return Расстояние типа double - */ - private double distance() { - return Math.sqrt(x * x + y * y + z * z); - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - if (other == null || getClass() != other.getClass()) return false; - - Location location = (Location) other; - - if (Double.compare(location.x, x) != 0) return false; - if (Float.compare(location.y, y) != 0) return false; - if (!z.equals(location.z)) return false; - return Objects.equals(name, location.name); - } - - @Override - public int hashCode() { - int result; - long temp; - temp = Double.doubleToLongBits(x); - result = (int) (temp ^ (temp >>> 32)); - result = 31 * result + (y != 0.0f ? Float.floatToIntBits(y) : 0); - result = 31 * result + z.hashCode(); - result = 31 * result + (name != null ? name.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return String.format(Locale.US, "Location(x=%f, y=%f, z=%s, name=%s)", this.x, this.y, this.z, this.name); - } -} diff --git a/src/main/java/ru/erius/lab5/collection/PeopleDatabase.java b/src/main/java/ru/erius/lab5/collection/PeopleDatabase.java new file mode 100644 index 0000000..e06cf2f --- /dev/null +++ b/src/main/java/ru/erius/lab5/collection/PeopleDatabase.java @@ -0,0 +1,119 @@ +package ru.erius.lab5.collection; + +import lombok.Getter; +import ru.erius.lab5.data.Person; +import ru.erius.lab5.parser.LocalDateAdapter; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.io.File; +import java.io.IOException; +import java.time.LocalDate; +import java.util.TreeSet; + +@XmlRootElement +public class PeopleDatabase implements Database { + + private final static String + ENV_VAR = "LAB5_PATH", + DEFAULT_FILE_NAME = "lab5.xml", + TYPE = "TreeSet"; + + @XmlTransient + private JAXBContext context; + @XmlTransient + private File file; + @Getter @XmlElement(name = "person") + private TreeSet collection = new TreeSet<>(); + @Getter @XmlJavaTypeAdapter(LocalDateAdapter.class) @XmlElement(name = "initDate") + private LocalDate initDate = LocalDate.now(); + + { + try { + this.context = JAXBContext.newInstance(PeopleDatabase.class); + } catch (JAXBException e) { + e.printStackTrace(); + } + } + + public String info() { + return String.format("Тип коллекции: %s \n" + + "Дата инициализации: %s \n" + + "Количество элементов: %d \n", + TYPE, this.initDate, this.collection.size()); + } + + @Override + public void load() throws Database.DatabaseLoadFailedException { + System.out.println("Инициализация коллекции из файла..."); + + String path = System.getenv(ENV_VAR); + if (path == null) + throw new DatabaseLoadFailedException("Не найдена переменная окружения LAB5_PATH"); + file = new File(path); + + if (!file.exists()) + throw new DatabaseLoadFailedException("Файл %s не был найден. Поменяйте значение переменной окружения LAB5_PATH", path); + if (!file.canRead()) + throw new DatabaseLoadFailedException("У вас нет прав на чтение файла %s", path); + if (!file.canWrite()) + throw new DatabaseLoadFailedException("У вас нет прав на запись в файл %s", path); + if (file.isFile()) + System.out.println("Файл успешно найден"); + else + file = createFile(file); + + try { + Unmarshaller unmarshaller = context.createUnmarshaller(); + PeopleDatabase pd = (PeopleDatabase) unmarshaller.unmarshal(file); + this.collection = pd.collection; + this.initDate = pd.initDate; + } catch (JAXBException e) { + throw new DatabaseLoadFailedException("Не удалось загрузить коллекцию из файла %s", file.getPath(), e); + } + System.out.println("Инициализация успешно выполнена"); + } + + @Override + public void save() throws Database.DatabaseSaveFailedException { + if (file == null || context == null) + throw new DatabaseSaveFailedException("Не удалось сохранить коллекцию"); + try { + Marshaller marshaller = context.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + marshaller.marshal(this, file); + } catch (JAXBException e) { + throw new DatabaseSaveFailedException("Не удалось сохранить коллекцию в файл %s", file.getPath(), e); + } + } + + private File createFile(File file) { + System.out.println("Директория успешно найдена"); + file = new File(file.getAbsolutePath() + "/" + DEFAULT_FILE_NAME); + boolean exists; + try { + exists = file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + System.err.println("Что-то пошло не так"); + System.exit(-1); + return null; + } + System.out.println(exists ? "Файл не найден, создание файла " + DEFAULT_FILE_NAME : "Файл успешно найден"); + return file; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("PeopleDatabase(\n"); + this.collection.forEach(p -> sb.append("\t").append(p).append("\n")); + sb.append(")"); + return sb.toString(); + } +} diff --git a/src/main/java/ru/erius/lab5/collection/Person.java b/src/main/java/ru/erius/lab5/collection/Person.java deleted file mode 100644 index 2148b4c..0000000 --- a/src/main/java/ru/erius/lab5/collection/Person.java +++ /dev/null @@ -1,337 +0,0 @@ -package ru.erius.lab5.collection; - -import java.time.DateTimeException; -import java.time.LocalDate; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Класс данных человека, реализует сортировку по умолчанию по имени, номеру паспорта, - * росту, национальности, местоположению и цвету глаз - */ -public class Person implements Comparable { - - /** - * Количество созданных людей, используется для задания - * уникального id для каждого объекта данного класса - */ - private static long existingPeople = 0; - - /** - * Регулярное выражение для валидации и создания - * экземпляров данного класса из строк - */ - private final static Pattern PERSON_REGEX = Pattern.compile( - "Person\\(\\s*id=(?\\d+),\\s*name=(?\\S+)," + - "\\s*coordinates=Coordinates\\(x=(?[+-]?(\\d+([.,]\\d*)?|[.,]\\d+)),\\s*y=(?[+-]?(\\d+([.,]\\d*)?|[.,]\\d+))\\)," + - "\\s*creationDate=(?\\d{4}[\\-.]\\d{2}[\\-.]\\d{2}),\\s*height=(?\\d+),\\s*passportID=(?\\S{8,})," + - "\\s*eyeColor=(?\\w+),\\s*nationality=(?\\w+),\\" + - "s*location=Location\\(x=(?[+-]?(\\d+([.,]\\d*)?|[.,]\\d+)),\\s*y=(?[+-]?(\\d+([.,]\\d*)?|[.,]\\d+))," + - "\\s*z=(?[+-]?\\d+),\\s*name=(?\\S+)\\)\\)" - ); - - /** - * Id человека, не может быть null, значение поля должно быть больше 0, - * значение этого поля должно быть уникальным, значение этого поля должно генерироваться автоматически - */ - private Long id; - /** - * Имя человека, не может быть null, строка не может быть пустой - */ - private String name; - /** - * Координаты человека, не может быть null - */ - private Coordinates coordinates; - /** - * Дата создания объекта, не может быть null, значение этого поля должно генерироваться автоматически - */ - private LocalDate creationDate; - /** - * Рост человека, может быть null, значение поля должно быть больше 0 - */ - private Integer height; - /** - * Номер паспорта человека, длина строки должна быть не меньше 8, поле может быть null - */ - private String passportID; - /** - * Цвет глаз человека, не может быть null - */ - private Color eyeColor; - /** - * Национальность человека, не может быть null - */ - private Country nationality; - /** - * Местоположение человека, может быть null - */ - private Location location; - - /** - * Конструктор без параметров, задаёт значения всех полей по умолчанию, - * name = "None", coordinates = new Coordinates(), height = 1, - * passportID = null, eyeColor = Color.BLACK, nationality = Country.UNITED_KINGDOM - */ - public Person() { - this.id = ++existingPeople; - this.creationDate = LocalDate.now(); - this.location = null; - this.name = "None"; - this.coordinates = new Coordinates(); - this.height = 1; - this.passportID = null; - this.eyeColor = Color.BLACK; - this.nationality = Country.UNITED_KINGDOM; - } - - /** - * Конструктор с параметрами - * - * @param name Имя человека - * @param coordinates Координаты человека - * @param height Высота человека - * @param passportID Номер паспорта человека - * @param eyeColor Цвет глаз человека - * @param nationality Национальность человека - * @param location Местоположение человека - * - * @throws IllegalArgumentException Данное исключение будет брошено в следующих случаях: - * name является null или пустой строкой, - * coordinates является null, - * height меньше 0, - * Длина passportID меньше 8 символов, - * eyeColor является null, - * nationality является null - */ - public Person(String name, Coordinates coordinates, Integer height, String passportID, - Color eyeColor, Country nationality, Location location) { - this.id = ++existingPeople; - this.creationDate = LocalDate.now(); - this.location = location; - this.setName(name); - this.setCoordinates(coordinates); - this.setHeight(height); - this.setPassportID(passportID); - this.setEyeColor(eyeColor); - this.setNationality(nationality); - } - - public static long getExistingPeople() { - return existingPeople; - } - - public Long getId() { - return id; - } - - public String getName() { - return name; - } - - public Coordinates getCoordinates() { - return coordinates; - } - - public Integer getHeight() { - return height; - } - - public String getPassportID() { - return passportID; - } - - public Color getEyeColor() { - return eyeColor; - } - - public Country getNationality() { - return nationality; - } - - public Location getLocation() { - return location; - } - - /** - * Сеттер для поля name - * - * @param name Имя человека - * - * @throws IllegalArgumentException Если имя является null или пустой строкой - */ - public void setName(String name) { - this.name = name; - if (name == null || name.isEmpty()) - throw new IllegalArgumentException("Поле name класса Person не может быть null или пустым"); - } - - /** - * Сеттер для поля coordinates - * - * @param coordinates Координаты человека - * - * @throws IllegalArgumentException Если объект координат является null - */ - public void setCoordinates(Coordinates coordinates) { - this.coordinates = coordinates; - if (coordinates == null) - throw new IllegalArgumentException("Поле coordinates класса Person не может быть null"); - } - - /** - * Сеттер для поля height - * - * @param height Рост человека - * - * @throws IllegalArgumentException Если рост меньше 0 - */ - public void setHeight(Integer height) { - this.height = height; - if (height <= 0) - throw new IllegalArgumentException("Поле height класса Person должно быть больше 0"); - } - - /** - * Сеттер для поля passportID - * - * @param passportID Номер паспорта человека - * - * @throws IllegalArgumentException Если номер паспорта меньше 8 символов в длину - */ - public void setPassportID(String passportID) { - this.passportID = passportID; - if (passportID.length() < 8) - throw new IllegalArgumentException("Поле passportID класса Person не может быть меньше 8 символов в длину"); - } - - /** - * Сеттер для поля eyeColor - * - * @param eyeColor Координаты человека - * - * @throws IllegalArgumentException Если цвет глаз является null - */ - public void setEyeColor(Color eyeColor) { - this.eyeColor = eyeColor; - if (eyeColor == null) - throw new IllegalArgumentException("Поле eyeColor класса Person не может быть null"); - } - - /** - * Сеттер для поля nationality - * - * @param nationality Координаты человека - * - * @throws IllegalArgumentException Если национальность является null - */ - public void setNationality(Country nationality) { - this.nationality = nationality; - if (nationality == null) - throw new IllegalArgumentException("Поле nationality класса Person не может быть null"); - } - - public void setLocation(Location location) { - this.location = location; - } - - /** - * Статический метод, возвращающий список объектов Person, - * образованный из найденных в строке совпадений с - * регулярным выражением {@link #PERSON_REGEX PERSON_REGEX} - * - * @param str Строка, из которой требуется получить список людей - * @return Список объектов класса Person - */ - public static List peopleFromString(String str) { - List list = new ArrayList<>(); - Matcher matcher = PERSON_REGEX.matcher(str); - while (matcher.find()) { - try { - Person person = new Person(); - person.id = Long.parseLong(matcher.group("id")); - person.name = matcher.group("name"); - person.height = Integer.parseInt(matcher.group("height")); - person.passportID = matcher.group("passportID"); - person.eyeColor = Color.valueOf(matcher.group("eyeColor")); - person.nationality = Country.valueOf(matcher.group("nationality")); - person.coordinates = new Coordinates( - Float.parseFloat(matcher.group("coordX")), - Float.parseFloat(matcher.group("coordY")) - ); - person.location = new Location( - Double.parseDouble(matcher.group("locX")), - Float.parseFloat(matcher.group("locY")), - Long.parseLong(matcher.group("locZ")), - matcher.group("locName") - ); - String[] date = matcher.group("creationDate").split("[\\-.]"); - person.creationDate = LocalDate.of( - Integer.parseInt(date[0]), - Integer.parseInt(date[1]), - Integer.parseInt(date[2]) - ); - list.add(person); - } catch (NumberFormatException | DateTimeException e) { - e.printStackTrace(); - System.err.println("Что-то пошло не так при чтении данных из файла, не меняйте вручную данные в файле!!!"); - } - } - return list; - } - - /** - * Переопределенный метод сравнения двух людей, - * сравнение производится по имени, номеру пасспорта, - * росту, национальности, местоположению и цвету глаз - * - * @param other Объект для сравнения - * @return Целое число - результат сравнения - */ - @Override - public int compareTo(Person other) { - return Comparator.comparing(Person::getName) - .thenComparing(Person::getPassportID) - .thenComparing(Person::getHeight) - .thenComparing(Person::getNationality) - .thenComparing(Person::getLocation) - .thenComparing(Person::getEyeColor) - .compare(this, other); - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - if (other == null || getClass() != other.getClass()) return false; - - Person person = (Person) other; - - if (!name.equals(person.name)) return false; - if (!coordinates.equals(person.coordinates)) return false; - if (!Objects.equals(height, person.height)) return false; - if (!Objects.equals(passportID, person.passportID)) return false; - if (eyeColor != person.eyeColor) return false; - if (nationality != person.nationality) return false; - return Objects.equals(location, person.location); - } - - @Override - public int hashCode() { - int result = name.hashCode(); - result = 31 * result + coordinates.hashCode(); - result = 31 * result + (height != null ? height.hashCode() : 0); - result = 31 * result + (passportID != null ? passportID.hashCode() : 0); - result = 31 * result + eyeColor.hashCode(); - result = 31 * result + nationality.hashCode(); - result = 31 * result + (location != null ? location.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return String.format(Locale.US, "Person(id=%d, name=%s, coordinates=%s, creationDate=%s, height=%s, passportID=%s, " + - "eyeColor=%s, nationality=%s, location=%s)", this.id, this.name, this.coordinates, this.creationDate, - this.height, this.passportID, this.eyeColor, this.nationality, this.location); - } -} diff --git a/src/main/java/ru/erius/lab5/collection/PersonTreeSet.java b/src/main/java/ru/erius/lab5/collection/PersonTreeSet.java deleted file mode 100644 index cfce359..0000000 --- a/src/main/java/ru/erius/lab5/collection/PersonTreeSet.java +++ /dev/null @@ -1,295 +0,0 @@ -package ru.erius.lab5.collection; - -import java.io.*; -import java.time.LocalDate; -import java.util.*; -import java.util.stream.Collectors; - -/** - * Коллекция PersonTreeSet, использующая в основе своей работы коллекцию {@link TreeSet TreeSet}, - * имеет возможности записи коллекции в файл и чтения её же из файла - */ -public class PersonTreeSet { - /** - * Название переменной окружения, где должен быть указан путь к файлу - */ - private final static String ENV_VAR = "PTS_PATH"; - /** - * Тип коллекции, используется для вывода информации - */ - private final static String TYPE = "TreeSet"; - - /** - * Файл для записи и чтения коллекции, является null если путь к файлу некорректный - */ - private final File file = initFile(); - /** - * Логические переменные, определяющие возможность программы записывать в файл или читать из него - */ - private boolean canWrite = true, canRead = true; - /** - * Коллекция {@link TreeSet TreeSet}, лежащая в основе работы класса - */ - private final TreeSet set = new TreeSet<>(); - /** - * Дата инициализации коллекции - */ - private final LocalDate creationDate; - - /** - * Конструктор без параметров, инициализирует коллекцию из файла (если есть возможность) - * и задаёт дату инициализации - */ - public PersonTreeSet() { - this.creationDate = LocalDate.now(); - if (this.file != null && this.canRead) - this.initTreeSet(); - } - - /** - * Метод, возвращающий файл для записи и чтения коллекции, - * возвращает null если переменной окружении не существует, - * файла не существует, файл является директорией - * или имеет расширение, не являющееся .xml - * - * Устанавливает соответствующим полям {@link #canRead canRead} и {@link #canWrite canWrite} значения false, - * если нет прав на чтение или запись - * - * @return Файл, если все условия соблюдены, иначе null - */ - public File initFile() { - String path = System.getenv(ENV_VAR); - if (path == null) { - System.err.println("Переменная окружения " + ENV_VAR + " (путь к файлу) не задана, возможность сохранения и загрузки коллекции в файл отключена"); - return null; - } - - File file = new File(path); - if (!file.exists()) { - System.err.println("Файла по пути " + path + " не существует, возможность сохранения и загрузки коллекции в файл отключена"); - return null; - } - - if (!file.isFile()) { - System.err.println("В " + ENV_VAR + " записан путь к директории, возможность сохранения и загрузки коллекции в файл отключена"); - return null; - } - - String ext = this.getFileExtension(file.getPath()); - if (ext == null || !ext.equals("xml")) { - System.err.println("Файл должен иметь расширение xml, возможность сохранения и загрузки коллекции в файл отключена"); - return null; - } - - if (!file.canWrite()) { - System.err.println("Запись в этот файл невозможна, возможность сохранения коллекции файл отключена"); - this.canWrite = false; - } - - if (!file.canRead()) { - System.err.println("Чтение из этого файл невозможно, возможность загрузки коллекции из файла отключена"); - this.canRead = false; - } - - System.out.println("Файл для сохранения и загрузки коллекции был успешно найден"); - System.out.println("Путь: " + path); - - return file; - } - - /** - * Метод инициализации коллекции из файла, - * читает содержимое файла и передаёт его в метод {@link Person#peopleFromString(String)}, - * элементы полученного списка добавляются в коллекцию - */ - private void initTreeSet() { - StringBuilder fileContents = new StringBuilder(); - try (InputStreamReader reader = new InputStreamReader(new FileInputStream(this.file))) { - int read; - do { - char[] buffer = new char[8192]; - read = reader.read(buffer); - fileContents.append(buffer); - } while (read != -1); - } catch (IOException e) { - e.printStackTrace(); - System.err.println("Не удалось инициализировать коллекцию из файла"); - return; - } - String contents = fileContents.toString(); - List people = Person.peopleFromString(contents); - this.set.addAll(people); - System.out.println("Коллекция успешно инициализирована из файла"); - } - - public File getFile() { - return file; - } - - /** - * Метод, возвращающий расширение файла - * - * @param path Строка, в которой записан путь к файлу - * - * @return Расширение файла или null, если путь к файлу некорректный - */ - private String getFileExtension(String path) { - String[] split = path.split("\\.", 2); - return split.length > 1 ? split[1] : null; - } - - /** - * Метод, возвращающий строку с информацией о коллекции - * - * @return Строка с информацией о коллекции - */ - public String info() { - return String.format("Тип коллекции: %s \n" + - "Дата инициализации: %s \n" + - "Количество элементов: %d \n" + - "Путь к файлу хранения: %s", - PersonTreeSet.TYPE, this.creationDate, this.set.size(), this.file); - } - - /** - * Метод добавления нового элемента в коллекцию - * - * @param person Элемент для добавления - * - * @return true, если элемент был успешно добавлен, иначе false - */ - public boolean add(Person person) { - return this.set.add(person); - } - - /** - * Метод изменения элемента в коллекции по заданному id - * - * @param id Id человека, которого нужно изменить - * @param person Элемент для добавления - * - * @return true, если элемент был успешно найден и изменён, иначе false - */ - public boolean update(long id, Person person) { - Optional element = this.set.stream().filter(p -> p.getId() == id).findAny(); - if (element.isEmpty()) return false; - Person p = element.get(); - p.setName(person.getName()); - p.setCoordinates(person.getCoordinates()); - p.setHeight(person.getHeight()); - p.setPassportID(person.getPassportID()); - p.setEyeColor(person.getEyeColor()); - p.setNationality(person.getNationality()); - p.setLocation(person.getLocation()); - return true; - } - - /** - * Метод удаления элемента из коллекции по заданному id - * - * @param id Id человека, которого нужно удалить - * - * @return true, если элемент был успешно найден и удален, иначе false - */ - public boolean remove(long id) { - return this.set.removeIf(p -> p.getId() == id); - } - - /** - * Метод очистки коллекции - */ - public void clear() { - this.set.clear(); - } - - /** - * Метод сохранения коллекции в файл - * - * @return true, если коллекция была успешно сохранена, иначе false - */ - public boolean save() { - if (file == null || !this.canWrite) - return false; - try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file))) { - writer.write(this.toString()); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - return true; - } - - /** - * Метод добавления элемента в коллекцию, если он больше её максимального элемента - * - * @param person Элемент для добавления - * - * @return true, если элемент был успешно добавлен, иначе false - */ - public boolean addIfMax(Person person) { - Person last = this.set.last(); - if (person.compareTo(last) > 0) - return this.set.add(person); - return false; - } - - /** - * Метод добавления элемента в коллекцию, если он меньше её минимального элемента - * - * @param person Элемент для добавления - * - * @return true, если элемент был успешно добавлен, иначе false - */ - public boolean addIfMin(Person person) { - Person first = this.set.first(); - if (person.compareTo(first) < 0) - return this.set.add(person); - return false; - } - - /** - * Метод, складывающий рост каждого человека в коллекции - * - * @return Суммарный рост всех людей - */ - public int sumOfHeight() { - return this.set.stream() - .mapToInt(Person::getHeight) - .sum(); - } - - /** - * Метод, фильтрующий всех людей, в имени которых имеется строка name, - * проверка не чувствительна к регистру - * - * @param name Строка, по которой происходит фильтрация - * - * @return Список людей, в имени которых имеется строка name - */ - public List filterContainsName(String name) { - return this.set.stream() - .filter(p -> p.getName().toLowerCase(Locale.ROOT).contains(name.toLowerCase(Locale.ROOT))) - .collect(Collectors.toList()); - } - - /** - * Метод, возвращающий отсортированный по убыванию список местоположений всех людей в коллекции - * - * @return Отсортированный по убыванию список местоположений - */ - public List fieldDescendingLocation() { - return this.set.stream() - .map(Person::getLocation) - .sorted(Collections.reverseOrder()) - .collect(Collectors.toList()); - } - - @Override - public String toString() { - return "PersonTreeSet(\n" + - this.set.stream() - .map(p -> "\t" + p.toString()) - .collect(Collectors.joining(",\n")) + - "\n)"; - } -} diff --git a/src/main/java/ru/erius/lab5/commandline/Command.java b/src/main/java/ru/erius/lab5/commandline/Command.java new file mode 100644 index 0000000..acb5656 --- /dev/null +++ b/src/main/java/ru/erius/lab5/commandline/Command.java @@ -0,0 +1,6 @@ +package ru.erius.lab5.commandline; + +@FunctionalInterface +public interface Command { + void execute(String[] args); +} diff --git a/src/main/java/ru/erius/lab5/commandline/CommandLine.java b/src/main/java/ru/erius/lab5/commandline/CommandLine.java new file mode 100644 index 0000000..00c0321 --- /dev/null +++ b/src/main/java/ru/erius/lab5/commandline/CommandLine.java @@ -0,0 +1,210 @@ +package ru.erius.lab5.commandline; + +import ru.erius.lab5.util.UtilFunctions; + +import java.io.*; +import java.util.*; +import java.util.function.Function; +import java.util.function.Predicate; + +public class CommandLine { + + private final static CommandLine instance = new CommandLine(); + + private final Deque inputs = new LinkedList<>(); + private final ArrayList history = new ArrayList<>(); + private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + private boolean isActive = false; + + static { + clearScreen(); + CommandLine.registerBasicCommands(); + } + + private CommandLine() {} + + public static CommandLine getInstance() { + return instance; + } + + private static void registerBasicCommands() { + CommandRegistry.registerCommand("exit", args -> instance.exit(), + "exit : завершить программу (без сохранения в файл)"); + CommandRegistry.registerCommand("history", instance::showHistory, + "history [count] : вывести последние count введенных команд, по умолчанию count равен 6"); + CommandRegistry.registerCommand("execute_script", instance::executeScript, + "execute_script {file_name} : считать и исполнить скрипт из указанного файла. В скрипте содержатся команды в таком же виде, в котором их вводит пользователь в интерактивном режиме."); + } + + public void start() { + System.out.println(LongStrings.GREETINGS.getValue()); + this.isActive = true; + do { + input(); + } while (this.isActive); + } + + private static void clearScreen() { + try { + if (System.getProperty("os.name").contains("Windows")) + new ProcessBuilder("cmd", "/c", "cls").inheritIO().start().waitFor(); + else + Runtime.getRuntime().exec("clear"); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + + private void input() { + String line = awaitInput("lab5>", "Что-то пошло не так").toLowerCase(Locale.ROOT); + String[] split = line.split("\\s+"); + String alias = split[0]; + String[] args = new String[]{}; + if (split.length > 1) { + args = new String[split.length - 1]; + System.arraycopy(split, 1, args, 0, split.length - 1); + } + Command command = CommandRegistry.getCommand(alias); + if (command == null) { + System.out.println("Неизвестная команда " + alias + ", напишите help для отображения всех существующих команд"); + return; + } + command.execute(args); + updateHistory(alias); + } + + public void exit() { + this.isActive = false; + System.out.println("Выход из программы..."); + } + + public void showHistory(String[] args) { + int lines = 6; + if (args.length >= 1 && UtilFunctions.intOrNull(args[0]) != null) + lines = Integer.parseInt(args[0]); + if (lines <= 0) { + System.out.println("[count] должен быть больше 0"); + return; + } + int start = lines < history.size() ? history.size() - lines : 0; + System.out.println("История последних команд:"); + for (int i = start; i < history.size(); i++) + System.out.println(history.get(i)); + } + + public void executeScript(String[] args) { + if (args.length < 1) { + System.out.println("Недостаточно данных"); + return; + } + + String fileName = args[0]; + File file = new File(fileName); + if (!file.exists() || file.isDirectory()) { + System.out.println("Не удалось найти файл " + fileName); + return; + } + + Reader streamReader; + try { + streamReader = new InputStreamReader(new FileInputStream(file)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + System.out.println("Что-то пошло не так"); + return; + } + addNewInput(streamReader); + } + + private void updateHistory(String command) { + history.add(command); + } + + /** + * Метод, ожидающий ввода из потока ввода {@link #reader reader} и возвращающий результат, + * печатает запрос msg перед ожиданием данных (если их вводит пользователь), + * печатает ошибку err, если при вводе данных произошла ошибка + * + * @param msg Строка, печатающаяся как запрос данных от пользователя + * @param err Строка, печатающаяся во время ошибки + * + * @return Строка из потока ввода + */ + public String awaitInput(String msg, String err) { + return awaitInput(msg, err, input -> true); + } + + /** + * Метод, ожидающий ввода из потока ввода {@link #reader reader } и возвращающий результат, + * печатает запрос msg перед ожиданием данных (если их вводит пользователь), + * печатает ошибку err, если введенные данные не соответствуют предикату predicate + * + * @param msg Строка, печатающаяся как запрос данных от пользователя + * @param err Строка, печатающаяся при несоответствии ввода предикату + * @param predicate Предикат, определяющий валидность введенных данных + * + * @return Строка из потока ввода + */ + public String awaitInput(String msg, String err, Predicate predicate) { + String input = null; + do { + if (inputs.isEmpty()) + System.out.print(msg + " "); + try { + input = reader.readLine(); + } catch (IOException e) { + e.printStackTrace(); + } + if (input == null) { + removeInput(); + continue; + } + input = input.trim(); + if (predicate.test(input)) + break; + else + System.err.println(err); + } while (true); + System.out.println(); + return input; + } + + /** + * Метод, ожидающий ввода из потока ввода {@link #reader reader} и возвращающий результат, + * печатает запрос msg перед ожиданием данных (если их вводит пользователь), + * печатает ошибку err, если введенные данные не соответствуют предикату predicate, + * преобразует результат в тип T в соответствии с функцией transform + * + * @param msg Строка, печатающаяся как запрос данных от пользователя + * @param err Строка, печатающаяся при несоответствии ввода предикату + * @param predicate Предикат, определяющий валидность введенных данных + * @param Тип, к которому будет приведен результат + * @param transform Функция, преобразующая результат в тип T + * + * @return Результат типа T + */ + public T awaitInput(String msg, String err, Predicate predicate, Function transform) { + String result = awaitInput(msg, err, predicate); + return transform.apply(result); + } + + /** + * Метод, меняющий текущий поток ввода на stream и добавляет его в очередь {@link #inputs inputs} + * + * @param reader Новый поток ввода + */ + public void addNewInput(Reader reader) { + this.reader = new BufferedReader(reader); + this.inputs.add(reader); + } + + /** + * Метод, убирающий текущий поток ввода из очереди {@link #inputs inputs} + * и меняющий его либо на следующий в очереди поток, либо на System.in, если очередь пуста + */ + public void removeInput() { + inputs.poll(); + Reader reader = inputs.isEmpty() ? new InputStreamReader(System.in) : inputs.peek(); + this.reader = new BufferedReader(reader); + } +} diff --git a/src/main/java/ru/erius/lab5/commandline/CommandRegistry.java b/src/main/java/ru/erius/lab5/commandline/CommandRegistry.java new file mode 100644 index 0000000..04e8f24 --- /dev/null +++ b/src/main/java/ru/erius/lab5/commandline/CommandRegistry.java @@ -0,0 +1,82 @@ +package ru.erius.lab5.commandline; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class CommandRegistry { + + private static final Map COMMANDS = new HashMap<>(); + + private CommandRegistry() { + } + + static { + registerCommand("help", args -> help(), + "help : вывести справку по доступным командам"); + } + + public static void registerCommand(String alias, Command command) { + registerCommand(alias, command, null); + } + + public static void registerCommand(String alias, Command command, String desc) { + if (COMMANDS.containsKey(alias)) + throw new CommandAlreadyExistsException("Команда %s уже существует, " + + "используйте метод reassignCommand() для переопределения существующей команды", alias); + COMMANDS.put(alias, new DescriptiveCommand(command, desc)); + } + + public static void reassignCommand(String alias, Command command, String desc) { + if (!COMMANDS.containsKey(alias)) + throw new CommandNotFoundException("Не удалось переопределить несуществующую команду %s", alias); + COMMANDS.put(alias, new DescriptiveCommand(command, desc)); + } + + public static void unregisterCommand(String alias) { + if (!COMMANDS.containsKey(alias)) + throw new CommandNotFoundException("Не удалось удалить несуществующую команду %s", alias); + COMMANDS.remove(alias); + } + + public static Command getCommand(String alias) { + DescriptiveCommand command = COMMANDS.get(alias); + return command == null ? null : command.getCommand() ; + } + + public static void help() { + System.out.println(LongStrings.LINE.getValue() + "\n"); + List commands = COMMANDS.values().stream() + .sorted(Comparator.comparing(DescriptiveCommand::getDesc)) + .collect(Collectors.toList()); + for (DescriptiveCommand dc : commands) { + String desc = dc.getDesc(); + if (desc != null) System.out.println(desc + "\n"); + } + System.out.println(LongStrings.LINE.getValue()); + } + + public static class CommandNotFoundException extends RuntimeException { + + private CommandNotFoundException(String message) { + super(message); + } + + private CommandNotFoundException(String message, String alias) { + super(String.format(message, alias)); + } + } + + public static class CommandAlreadyExistsException extends RuntimeException { + + private CommandAlreadyExistsException(String message) { + super(message); + } + + private CommandAlreadyExistsException(String message, String alias) { + super(String.format(message, alias)); + } + } +} diff --git a/src/main/java/ru/erius/lab5/commandline/DescriptiveCommand.java b/src/main/java/ru/erius/lab5/commandline/DescriptiveCommand.java new file mode 100644 index 0000000..bb972bc --- /dev/null +++ b/src/main/java/ru/erius/lab5/commandline/DescriptiveCommand.java @@ -0,0 +1,12 @@ +package ru.erius.lab5.commandline; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NonNull; + +@Data @AllArgsConstructor +public class DescriptiveCommand { + @NonNull + private final Command command; + private final String desc; +} diff --git a/src/main/java/ru/erius/lab5/commandline/LongStrings.java b/src/main/java/ru/erius/lab5/commandline/LongStrings.java new file mode 100644 index 0000000..8f6cf00 --- /dev/null +++ b/src/main/java/ru/erius/lab5/commandline/LongStrings.java @@ -0,0 +1,21 @@ +package ru.erius.lab5.commandline; + +public enum LongStrings { + LINE("=================================================================================================="), + + GREETINGS(LINE.value + "\n" + + "Добро пожаловать в программу для управления коллекцией объектов в интерактивном режиме!\n" + + "Напишите help, чтобы увидеть доступные команды\n" + + "Напишите exit, чтобы выйти из программы\n" + + LINE.value + "\n"); + + private final String value; + + LongStrings(String value) { + this.value = value; + } + + public String getValue() { + return this.value; + } +} diff --git a/src/main/java/ru/erius/lab5/commandline/PeopleDatabaseCommands.java b/src/main/java/ru/erius/lab5/commandline/PeopleDatabaseCommands.java new file mode 100644 index 0000000..039afd0 --- /dev/null +++ b/src/main/java/ru/erius/lab5/commandline/PeopleDatabaseCommands.java @@ -0,0 +1,226 @@ +package ru.erius.lab5.commandline; + +import ru.erius.lab5.collection.Database; +import ru.erius.lab5.collection.PeopleDatabase; +import ru.erius.lab5.data.*; +import ru.erius.lab5.util.UtilFunctions; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; +import java.util.Optional; + +import static ru.erius.lab5.commandline.CommandRegistry.*; + +public final class PeopleDatabaseCommands { + + private static PeopleDatabase peopleDatabase; + private static final String COLORS = Arrays.toString(Color.values()), + COUNTRIES = Arrays.toString(Country.values()); + private static final CommandLine CMD = CommandLine.getInstance(); + + private PeopleDatabaseCommands() {} + + public static void registerDatabaseCommands() { + registerCommand("info", args -> info(peopleDatabase), + "info : вывести в стандартный поток вывода информацию о коллекции (тип, дата инициализации, количество элементов и т.д.)"); + registerCommand("show", args -> show(peopleDatabase), + "show : вывести в стандартный поток вывода все элементы коллекции в строковом представлении"); + registerCommand("add", args -> add(peopleDatabase), + "add : добавить новый элемент в коллекцию"); + registerCommand("update", args -> update(peopleDatabase, args), + "update {id} : обновить значение элемента коллекции, {id} которого равен заданному"); + registerCommand("remove_by_id", args -> remove(peopleDatabase, args), + "remove_by_id {id} : удалить элемент из коллекции по его {id}"); + registerCommand("clear", args -> clear(peopleDatabase), + "clear : очистить коллекцию"); + registerCommand("save", args -> save(peopleDatabase), + "save : сохранить коллекцию в файл"); + registerCommand("add_if_max", args -> addIfMax(peopleDatabase), + "add_if_max : добавить новый элемент в коллекцию, если его значение превышает значение наибольшего элемента этой коллекции"); + registerCommand("add_if_min", args -> addIfMin(peopleDatabase), + "add_if_min : добавить новый элемент в коллекцию, если его значение меньше, чем у наименьшего элемента этой коллекции"); + registerCommand("sum_of_height", args -> sumOfHeight(peopleDatabase), + "sum_of_height : вывести сумму значений поля height для всех элементов коллекции"); + registerCommand("filter_contains_name", args -> filterContainsName(peopleDatabase, args), + "filter_contains_name {name} : вывести элементы, значение поля name которых содержит заданную подстроку"); + registerCommand("print_field_descending_location", args -> printFieldDescendingLocation(peopleDatabase), + "print_field_descending_location : вывести значения поля location всех элементов в порядке убывания"); + } + + public static void setPeopleDatabase(PeopleDatabase peopleDatabase) { + PeopleDatabaseCommands.peopleDatabase = peopleDatabase; + } + + public static void info(PeopleDatabase peopleDatabase) { + System.out.println(peopleDatabase.info()); + } + + public static void show(PeopleDatabase peopleDatabase) { + System.out.println(peopleDatabase); + } + + public static void add(PeopleDatabase peopleDatabase) { + Person person = createPerson(); + boolean success = peopleDatabase.getCollection().add(person); + System.out.println(success ? "Человек успешно добавлен в коллекцию" : "Не удалось добавить человека в коллекцию"); + } + + public static void update(PeopleDatabase peopleDatabase, String[] args) { + Long id = getIdOrNull(args); + if (id == null) return; + + Optional optionalPerson = peopleDatabase.getCollection() + .stream() + .filter(p -> p.getId().equals(id)) + .findFirst(); + if (!optionalPerson.isPresent()) { + System.out.println("Человек с {id} " + id + " не был найден"); + return; + } + Person oldPerson = optionalPerson.get(); + peopleDatabase.getCollection().remove(oldPerson); + + Person newPerson = createPerson(); + oldPerson.update(newPerson); + peopleDatabase.getCollection().add(oldPerson); + System.out.println("Человек с {id} " + oldPerson.getId() + " был успешно изменен"); + } + + public static void remove(PeopleDatabase peopleDatabase, String[] args) { + Long id = getIdOrNull(args); + if (id == null) return; + boolean success = peopleDatabase.getCollection().removeIf(p -> p.getId().equals(id)); + System.out.println("Человек с {id} " + id + " " + (success ? "был успешно удален" : "не был найден")); + } + + public static void clear(PeopleDatabase peopleDatabase) { + peopleDatabase.getCollection().clear(); + System.out.println("Коллекция была очищена"); + } + + public static void save(PeopleDatabase peopleDatabase) { + try { + peopleDatabase.save(); + System.out.println("Коллекция была успешно сохранена"); + } catch (Database.DatabaseSaveFailedException e) { + e.printStackTrace(); + System.out.println("Не удалось сохранить коллекцию"); + } + } + + public static void addIfMax(PeopleDatabase peopleDatabase) { + Person person = createPerson(); + Person last = peopleDatabase.getCollection().last(); + if (person.compareTo(last) > 0) { + peopleDatabase.getCollection().add(person); + System.out.println("Человек успешно добавлен в коллекцию"); + return; + } + System.out.println("Не удалось добавить человека в коллекцию"); + } + + public static void addIfMin(PeopleDatabase peopleDatabase) { + Person person = createPerson(); + Person first = peopleDatabase.getCollection().first(); + if (person.compareTo(first) < 0) { + peopleDatabase.getCollection().add(person); + System.out.println("Человек успешно добавлен в коллекцию"); + return; + } + System.out.println("Не удалось добавить человека в коллекцию"); + } + + public static void sumOfHeight(PeopleDatabase peopleDatabase) { + int sum = peopleDatabase.getCollection() + .stream() + .mapToInt(Person::getHeight) + .sum(); + System.out.println("Сумма ростов всех людей в коллекции - " + sum); + } + + public static void filterContainsName(PeopleDatabase peopleDatabase, String[] args) { + if (args.length < 1) { + System.out.println("Недостаточно данных"); + return; + } + + String name = args[0]; + System.out.println("Список людей, в имени которых содержится " + name + ":"); + peopleDatabase.getCollection() + .stream() + .filter(p -> p.getName().contains(name)) + .forEach(System.out::println); + } + + public static void printFieldDescendingLocation(PeopleDatabase peopleDatabase) { + System.out.println("Список локаций в порядке убывания"); + peopleDatabase.getCollection() + .stream() + .map(Person::getLocation) + .sorted(Collections.reverseOrder()) + .forEach(System.out::println); + } + + private static Long getIdOrNull(String[] args) { + if (args.length < 1) { + System.out.println("Недостаточно данных"); + return null; + } + + Long id = UtilFunctions.longOrNull(args[0]); + if (id == null) { + System.out.println("{id} должен быть целым числом"); + return null; + } + + return id; + } + + public static Person createPerson() { + System.out.println("Создание нового объекта класса Person"); + String name = CMD.awaitInput("Введите имя:", "Введите непустую строку", + input -> !input.isEmpty()); + int height = CMD.awaitInput("Введите рост:", "Введите целое число, большее нуля", + input -> { + Integer result = UtilFunctions.intOrNull(input); + return result != null && result > 0; + }, Integer::parseInt); + String passportID = CMD.awaitInput("Введите номер паспорта:", "Введите минимум 8 символов", + input -> input.length() >= 8); + Color eyeColor = CMD.awaitInput("Введите цвет глаз " + COLORS + ":", "Введите один из предложенных цветов", + input -> UtilFunctions.enumOrNull(input.toUpperCase(Locale.ROOT), Color.class) != null, + input -> Color.valueOf(input.toUpperCase(Locale.ROOT))); + Country nationality = CMD.awaitInput("Введите национальность " + COUNTRIES + ":", "Введите одну из предложенных стран", + input -> UtilFunctions.enumOrNull(input.toUpperCase(Locale.ROOT), Country.class) != null, + input -> Country.valueOf(input.toUpperCase(Locale.ROOT))); + Location location = createLocation(); + Coordinates coordinates = createCoordinates(); + return new Person(name, coordinates, height, passportID, eyeColor, nationality, location); + } + + public static Location createLocation() { + System.out.println("Создание нового объекта класса Location"); + double x = CMD.awaitInput("Введите x:", "Введите дробное число", + input -> UtilFunctions.doubleOrNull(input) != null, Double::parseDouble); + float y = CMD.awaitInput("Введите y:", "Введите дробное число", + input -> UtilFunctions.floatOrNull(input) != null, Float::parseFloat); + long z = CMD.awaitInput("Введите z:", "Введите целое число", + input -> UtilFunctions.longOrNull(input) != null, Long::parseLong); + String name = CMD.awaitInput("Введите название:", "Строка не может быть пустой", + input -> !input.isEmpty()); + return new Location(x, y, z, name); + } + + public static Coordinates createCoordinates() { + System.out.println("Создание нового объекта класса Coordinates"); + float x = CMD.awaitInput("Введите x:", "Введите дробное число", + input -> UtilFunctions.floatOrNull(input) != null, Float::parseFloat); + float y = CMD.awaitInput("Введите y:", "Введите дробное число, большее -816", + input -> { + Float result = UtilFunctions.floatOrNull(input); + return result != null && result > -816F; + }, Float::parseFloat); + return new Coordinates(x, y); + } +} diff --git a/src/main/java/ru/erius/lab5/data/Color.java b/src/main/java/ru/erius/lab5/data/Color.java new file mode 100644 index 0000000..3576102 --- /dev/null +++ b/src/main/java/ru/erius/lab5/data/Color.java @@ -0,0 +1,10 @@ +package ru.erius.lab5.data; + +/** + * Перечисление цветов + */ +public enum Color { + BLACK, + ORANGE, + BROWN +} diff --git a/src/main/java/ru/erius/lab5/data/Coordinates.java b/src/main/java/ru/erius/lab5/data/Coordinates.java new file mode 100644 index 0000000..0f0f0dc --- /dev/null +++ b/src/main/java/ru/erius/lab5/data/Coordinates.java @@ -0,0 +1,48 @@ +package ru.erius.lab5.data; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * Класс данных координат + */ +@Data @NoArgsConstructor @EqualsAndHashCode @ToString +public class Coordinates { + + /** + * Координата X типа float + */ + private float x; + /** + * Координата Y типа float, значение должно быть больше -816 + */ + private float y; + + /** + * Конструктор с параметрами + * + * @param x Координата X + * @param y Координата Y + * + * @throws IllegalArgumentException Если Y меньше или равен -816 + */ + public Coordinates(float x, float y) { + this.x = x; + this.setY(y); + } + + /** + * Сеттер для поля y + * + * @param y Координата Y + * + * @throws IllegalArgumentException Если Y меньше или равен -816 + */ + public void setY(float y) { + this.y = y; + if (y <= -816) + throw new IllegalArgumentException("Поле y класса Coordinates должно быть больше -816"); + } +} diff --git a/src/main/java/ru/erius/lab5/data/Country.java b/src/main/java/ru/erius/lab5/data/Country.java new file mode 100644 index 0000000..ecc3322 --- /dev/null +++ b/src/main/java/ru/erius/lab5/data/Country.java @@ -0,0 +1,12 @@ +package ru.erius.lab5.data; + +/** + * Перечисление стран + */ +public enum Country { + UNITED_KINGDOM, + GERMANY, + CHINA, + THAILAND, + JAPAN +} diff --git a/src/main/java/ru/erius/lab5/data/Location.java b/src/main/java/ru/erius/lab5/data/Location.java new file mode 100644 index 0000000..33271d6 --- /dev/null +++ b/src/main/java/ru/erius/lab5/data/Location.java @@ -0,0 +1,84 @@ +package ru.erius.lab5.data; + +import lombok.*; + +import java.util.Comparator; + +/** + * Класс данных местоположения, реализует сортировку по умолчанию + * по имени и расстоянию до точки (0; 0; 0) + */ +@Data @NoArgsConstructor @EqualsAndHashCode @ToString +public class Location implements Comparable { + + /** + * Координата X типа double + */ + private double x; + /** + * Координата Y типа float + */ + private float y; + /** + * Координата Z типа Long, не может быть null + */ + private Long z; + /** + * Имя локации, может быть null + */ + private String name; + + /** + * Конструктор с параметрами + * + * @param x Координата X + * @param y Координата Y + * @param z Координата Z + * @param name Имя локации + * + * @throws IllegalArgumentException будет брошено, если name является пустой строкой + * + * @throws NullPointerException будет брошено в случае, если Z является null + */ + public Location(double x, float y, @NonNull Long z, String name) { + this.x = x; + this.y = y; + this.z = z; + this.setName(name); + } + + /** + * Сеттер для поля name + * @param name Имя локации + * + * @throws IllegalArgumentException Если name является пустой строкой + */ + public void setName(String name) { + this.name = name; + if (name.isEmpty()) + throw new IllegalArgumentException("Поле name класса Location не может быть пустым"); + } + + /** + * Переопределенный метод сравнения двух местоположений, + * сравнение производится по имени локации и расстоянию до точки (0; 0; 0) + * + * @param other Объект для сравнения + * @return Целое число - результат сравнения + */ + @Override + public int compareTo(Location other) { + return Comparator.comparing((Location loc) -> loc.name) + .thenComparing(Location::distance) + .compare(this, other); + } + + /** + * Метод, вычисляющий расстояние до точки (0; 0; 0) + * + * @return Расстояние типа double + */ + private double distance() { + return Math.sqrt(x * x + y * y + z * z); + } +} diff --git a/src/main/java/ru/erius/lab5/data/Person.java b/src/main/java/ru/erius/lab5/data/Person.java new file mode 100644 index 0000000..caae89c --- /dev/null +++ b/src/main/java/ru/erius/lab5/data/Person.java @@ -0,0 +1,173 @@ +package ru.erius.lab5.data; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import lombok.ToString; +import ru.erius.lab5.parser.LocalDateAdapter; + +import javax.xml.bind.annotation.*; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.LocalDate; +import java.util.*; + +/** + * Класс данных человека, реализует сортировку по умолчанию по имени, номеру паспорта, + * росту, национальности, местоположению и цвету глаз + */ +@Data @EqualsAndHashCode @ToString +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class Person implements Comparable { + /** + * Количество созданных людей, используется для задания + * уникального id для каждого объекта данного класса + */ + @XmlTransient + private static long existingPeople = 0; + + /** + * Id человека, не может быть null, значение поля должно быть больше 0, + * значение этого поля должно быть уникальным, значение этого поля должно генерироваться автоматически + */ + @XmlTransient + private Long id; + /** + * Имя человека, не может быть null, строка не может быть пустой + */ + private String name; + /** + * Координаты человека, не может быть null + */ + private Coordinates coordinates; + /** + * Дата создания объекта, не может быть null, значение этого поля должно генерироваться автоматически + */ + @XmlJavaTypeAdapter(LocalDateAdapter.class) + private LocalDate creationDate; + /** + * Рост человека, может быть null, значение поля должно быть больше 0 + */ + private Integer height; + /** + * Номер паспорта человека, длина строки должна быть не меньше 8, поле может быть null + */ + private String passportID; + /** + * Цвет глаз человека, не может быть null + */ + private Color eyeColor; + /** + * Национальность человека, не может быть null + */ + private Country nationality; + /** + * Местоположение человека, может быть null + */ + private Location location; + + private Person() { + this.id = ++existingPeople; + } + + /** + * Конструктор с параметрами + * + * @param name Имя человека + * @param coordinates Координаты человека + * @param height Высота человека + * @param passportID Номер паспорта человека + * @param eyeColor Цвет глаз человека + * @param nationality Национальность человека + * @param location Местоположение человека + * + * @throws IllegalArgumentException Если: + * name является пустой строкой, + * height меньше 0, + * Длина passportID меньше 8 символов + * + * @throws NullPointerException Если coordinates, eyeColor или nationality являются null + */ + public Person(String name, @NonNull Coordinates coordinates, Integer height, String passportID, + @NonNull Color eyeColor, @NonNull Country nationality, Location location) { + this.id = ++existingPeople; + this.creationDate = LocalDate.now(); + this.location = location; + this.coordinates = coordinates; + this.eyeColor = eyeColor; + this.nationality = nationality; + this.setName(name); + this.setHeight(height); + this.setPassportID(passportID); + } + + public void update(Person newPerson) { + this.location = newPerson.location; + this.coordinates = newPerson.coordinates; + this.eyeColor = newPerson.eyeColor; + this.nationality = newPerson.nationality; + this.setName(newPerson.name); + this.setHeight(newPerson.height); + this.setPassportID(newPerson.passportID); + } + + /** + * Сеттер для поля name + * + * @param name + * Имя человека + * + * @throws IllegalArgumentException + * Если имя является пустой строкой + */ + public void setName(String name) { + this.name = name; + if (name.isEmpty()) + throw new IllegalArgumentException("Поле name класса Person не может быть null или пустым"); + } + + /** + * Сеттер для поля height + * + * @param height Рост человека + * + * @throws IllegalArgumentException Если рост меньше 0 + */ + public void setHeight(Integer height) { + this.height = height; + if (height <= 0) + throw new IllegalArgumentException("Поле height класса Person должно быть больше 0"); + } + + /** + * Сеттер для поля passportID + * + * @param passportID Номер паспорта человека + * + * @throws IllegalArgumentException Если номер паспорта меньше 8 символов в длину + */ + public void setPassportID(String passportID) { + this.passportID = passportID; + if (passportID.length() < 8) + throw new IllegalArgumentException("Поле passportID класса Person не может быть меньше 8 символов в длину"); + } + + /** + * Переопределенный метод сравнения двух людей, + * сравнение производится по имени, номеру пасспорта, + * росту, национальности, местоположению и цвету глаз + * + * @param other Объект для сравнения + * @return Целое число - результат сравнения + */ + @Override + public int compareTo(Person other) { + return Comparator.comparing((Person p) -> p.name) + .thenComparing(p -> p.passportID) + .thenComparing(p -> p.height) + .thenComparing(p -> p.nationality) + .thenComparing(p -> p.location) + .thenComparing(p -> p.eyeColor) + .compare(this, other); + } +} diff --git a/src/main/java/ru/erius/lab5/parser/LocalDateAdapter.java b/src/main/java/ru/erius/lab5/parser/LocalDateAdapter.java new file mode 100644 index 0000000..782d4d2 --- /dev/null +++ b/src/main/java/ru/erius/lab5/parser/LocalDateAdapter.java @@ -0,0 +1,17 @@ +package ru.erius.lab5.parser; + +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public class LocalDateAdapter extends XmlAdapter { + @Override + public LocalDate unmarshal(String v) throws Exception { + return LocalDate.parse(v); + } + + @Override + public String marshal(LocalDate v) throws Exception { + return v.format(DateTimeFormatter.ISO_LOCAL_DATE); + } +} diff --git a/src/main/java/ru/erius/lab5/util/UtilFunctions.java b/src/main/java/ru/erius/lab5/util/UtilFunctions.java new file mode 100644 index 0000000..67706a0 --- /dev/null +++ b/src/main/java/ru/erius/lab5/util/UtilFunctions.java @@ -0,0 +1,54 @@ +package ru.erius.lab5.util; + +public class UtilFunctions { + + private UtilFunctions() {} + + public static Integer intOrNull(String number) { + int result; + try { + result = Integer.parseInt(number); + } catch (NumberFormatException e) { + return null; + } + return result; + } + + public static Long longOrNull(String number) { + long result; + try { + result = Long.parseLong(number); + } catch (NumberFormatException e) { + return null; + } + return result; + } + + public static Float floatOrNull(String number) { + float result; + try { + result = Float.parseFloat(number); + } catch (NumberFormatException e) { + return null; + } + return result; + } + + public static Double doubleOrNull(String number) { + double result; + try { + result = Double.parseDouble(number); + } catch (NumberFormatException e) { + return null; + } + return result; + } + + public static > T enumOrNull(String value, Class enumType) { + try { + return T.valueOf(enumType, value); + } catch (IllegalArgumentException e) { + return null; + } + } +}