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..7ec3efd 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") {
@@ -23,6 +25,10 @@ tasks.compileJava {
options.encoding = "UTF-8"
}
+tasks.javadoc {
+ options.encoding = "UTF-8"
+}
+
tasks.jar {
manifest {
attributes(
@@ -31,7 +37,3 @@ 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..89c191b 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.CommandLineHandler;
+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);
+ CommandLineHandler cmd = CommandLineHandler.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..06bbbe1
--- /dev/null
+++ b/src/main/java/ru/erius/lab5/collection/Database.java
@@ -0,0 +1,57 @@
+package ru.erius.lab5.collection;
+
+/**
+ * Интерфейс баз данных с методами {@link #load()} и {@link #save()}
+ */
+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..fdec3a1
--- /dev/null
+++ b/src/main/java/ru/erius/lab5/collection/PeopleDatabase.java
@@ -0,0 +1,138 @@
+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;
+
+/**
+ * Класс базы данных людей, реализующий интерфейс Database
+ * @see ru.erius.lab5.collection.Database
+ */
+@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());
+ }
+
+ /**
+ * Метод, инициализирующий базу данных из файла, находящемся по пути, указанном в
+ * переменной окружения {@link #ENV_VAR}
+ *
+ * @throws Database.DatabaseLoadFailedException если переменная окружения {@link #ENV_VAR} не задана,
+ * файла не существует, либо отсутствуют права на запись или чтение
+ */
+ @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("Инициализация успешно выполнена");
+ }
+
+ /**
+ * Метод, сохраняющий базу данных в файл, находящемся по пути, указанном в
+ * переменной окружения {@link #ENV_VAR}
+ *
+ * @throws Database.DatabaseSaveFailedException если переменная окружения {@link #ENV_VAR} не задана,
+ * файла не существует, либо отсутствуют права на запись или чтение, или если структура xml файла
+ * была каким-либо образом нарушена
+ */
+ @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..4690e9e
--- /dev/null
+++ b/src/main/java/ru/erius/lab5/commandline/Command.java
@@ -0,0 +1,10 @@
+package ru.erius.lab5.commandline;
+
+/**
+ * Функциональный интерфейс с одним методом {@link #execute(String[])},
+ * используется для реализации шаблон проектирования Command
+ */
+@FunctionalInterface
+public interface Command {
+ void execute(String[] args);
+}
diff --git a/src/main/java/ru/erius/lab5/commandline/CommandLineHandler.java b/src/main/java/ru/erius/lab5/commandline/CommandLineHandler.java
new file mode 100644
index 0000000..cd0aff1
--- /dev/null
+++ b/src/main/java/ru/erius/lab5/commandline/CommandLineHandler.java
@@ -0,0 +1,223 @@
+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;
+
+/**
+ * Класс обработчика командной строки, реализует шаблон проектирования Singleton,
+ * читает ввод с командной строки, обрабатывает его и вызывает соответствующую команду
+ * на выполнение из регистра команд, используйте метод {@link #start()} для его запуска
+ *
+ * @see CommandRegistry
+ */
+public final class CommandLineHandler {
+
+ private final static CommandLineHandler instance = new CommandLineHandler();
+
+ private final Deque inputs = new LinkedList<>();
+ private final List history = new LinkedList<>();
+ private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ private boolean isActive = false;
+
+ static {
+ clearScreen();
+ CommandLineHandler.registerBasicCommands();
+ }
+
+ private CommandLineHandler() {}
+
+ public static CommandLineHandler 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} : считать и исполнить скрипт из указанного файла. В скрипте содержатся команды в таком же виде, в котором их вводит пользователь в интерактивном режиме.");
+ }
+
+ /**
+ * Метод, запускающий обработчик командной строки, для остановки требуется ввести команду "exit"
+ */
+ 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..3d866b2
--- /dev/null
+++ b/src/main/java/ru/erius/lab5/commandline/CommandRegistry.java
@@ -0,0 +1,85 @@
+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 final 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..39d2fa3
--- /dev/null
+++ b/src/main/java/ru/erius/lab5/commandline/DescriptiveCommand.java
@@ -0,0 +1,15 @@
+package ru.erius.lab5.commandline;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NonNull;
+
+/**
+ * Класс команд с описанием, оболочка для обычных команд, используются для корректной работы команды help
+ */
+@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..373d94b
--- /dev/null
+++ b/src/main/java/ru/erius/lab5/commandline/PeopleDatabaseCommands.java
@@ -0,0 +1,235 @@
+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.*;
+
+/**
+ * Класс объявления всех команд, связанных с манипуляциями над объектом класса PeopleDatabase
+ *
+ * @see PeopleDatabase
+ */
+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 CommandLineHandler CMD = CommandLineHandler.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..552f87c
--- /dev/null
+++ b/src/main/java/ru/erius/lab5/data/Person.java
@@ -0,0 +1,182 @@
+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;
+
+ /**
+ * Конструктор для корректного преобразования человека из xml в объект
+ */
+ 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);
+ }
+
+ /**
+ * Метод, меняющий все значения полей человека (кроме {@link #id} и {@link #creationDate}) в соответствии
+ * со значениями полей другого
+ *
+ * @param newPerson человек, чьи поля будут присвоены текущему
+ */
+ 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..5d637b9
--- /dev/null
+++ b/src/main/java/ru/erius/lab5/parser/LocalDateAdapter.java
@@ -0,0 +1,20 @@
+package ru.erius.lab5.parser;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Класс парсера для корректного преобразования объекта LocalDate в xml формат и наоборот
+ */
+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..0fd7c47
--- /dev/null
+++ b/src/main/java/ru/erius/lab5/util/UtilFunctions.java
@@ -0,0 +1,57 @@
+package ru.erius.lab5.util;
+
+/**
+ * Класс функций-утилит для избавления от повторяющихся участков кода и выноса их в методы для общего пользования
+ */
+public final 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;
+ }
+ }
+}