Merge branch 'lab5-dev' into develop

This commit is contained in:
Ivan Zinchenko 2024-04-03 19:19:23 +03:00
commit 79f1884634
Signed by: zinch
GPG Key ID: 6D45AA2C8FD6A37E
44 changed files with 358 additions and 43 deletions

View File

@ -1,4 +0,0 @@
### Этапы работы программы
1. Проверка файла с базой данных
2. Пользовательский ввод

View File

@ -64,6 +64,11 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
</plugin>
</plugins>
</build>
</project>

View File

@ -6,6 +6,9 @@ import me.zinch.exceptions.ValidationException;
import java.io.IOException;
/**
* Main class for running the application.
*/
public class App {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new ShutdownHook());

View File

@ -6,6 +6,10 @@ import me.zinch.wrapper.ProductCollection;
import java.util.regex.Pattern;
/**
* The Add class represents a command to add a new element to a collection.
* It extends the Command class.
*/
public class Add extends Command {
public Add() {
super("add {element}", "добавить новый элемент в коллекцию", Pattern.compile("^add"));

View File

@ -5,6 +5,10 @@ import me.zinch.wrapper.ProductCollection;
import java.util.regex.Pattern;
/**
* The AddIfMax class represents a command to add a new element to a collection if its price value exceeds the maximum price value in the collection.
* It extends the Command class.
*/
public class AddIfMax extends Command {
public AddIfMax() {
super("add_if_max {element}", "добавить новый элемент в коллекцию, если его значение цены превышает значение наибольшей цены этой коллекции", Pattern.compile("^add_if_max \\d+"));

View File

@ -5,6 +5,10 @@ import me.zinch.wrapper.ProductCollection;
import java.util.regex.Pattern;
/**
* The AddIfMin class represents a command to add a new element to a collection if its price is lower than the lowest price in the collection.
* It extends the Command class.
*/
public class AddIfMin extends Command {
public AddIfMin() {
super("add_if_min {element}", "добавить новый элемент в коллекцию, если его значение цены меньше, чем у наименьшей цены этой коллекции", Pattern.compile("^add_if_min \\d+"));

View File

@ -2,6 +2,10 @@ package me.zinch.commands;
import me.zinch.wrapper.ProductCollection;
/**
* The Clear class represents a command to clear a collection.
* It extends the Command class.
*/
public class Clear extends Command {
public Clear() {
super("clear", "очистить коллекцию");

View File

@ -5,6 +5,9 @@ import me.zinch.wrapper.ProductCollection;
import java.util.regex.Pattern;
/**
* Represents a command that can be executed.
*/
public abstract class Command {
private final String name;
private final String description;

View File

@ -4,6 +4,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* Manages the registration and retrieval of commands.
*/
public class CommandManager {
private static final List<Command> commandList = new ArrayList<>();

View File

@ -0,0 +1,61 @@
package me.zinch.commands;
import me.zinch.console.Console;
import me.zinch.files.ScriptLoader;
import me.zinch.wrapper.ProductCollection;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
/**
* A command to execute a script from a specified file.
*/
public class ExecuteScript extends Command {
private static final List<String> executeStack = new ArrayList<>();
private void addFileToStack(String filePath) {
executeStack.add(filePath);
}
private void clearStack() {
executeStack.clear();
}
private boolean checkFileInStack(String filePath) {
return executeStack.contains(filePath);
}
public ExecuteScript() {
super("execute_script file_name", "считать и исполнить скрипт из указанного файла. В скрипте содержатся команды в таком же виде, в котором их вводит пользователь в интерактивном режиме", Pattern.compile("^execute_script .+"));
}
@Override
public void action(ProductCollection productCollection) {
try {
var scriptFile = Console.getLastCommand().split(" ")[1];
addFileToStack(scriptFile);
var commandList = ScriptLoader.loadList(scriptFile);
for (var input: commandList) {
input = input.trim();
var command = CommandManager.getCommandByInput(input);
if (command.isPresent()) {
Console.appendHistory(input);
if (command.get() instanceof ExecuteScript) {
var nextFile = input.split(" ")[0];
if (checkFileInStack(nextFile)) continue;
addFileToStack(nextFile);
}
command.get().action(productCollection);
} else {
new UnknownCommand().action(productCollection);
}
}
clearStack();
System.out.format("Скрипт %s завершил работу.%n", scriptFile);
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}

View File

@ -3,6 +3,9 @@ package me.zinch.commands;
import me.zinch.console.Console;
import me.zinch.wrapper.ProductCollection;
/**
* A command to exit the program without saving to a file.
*/
public class Exit extends Command {
public Exit() {
super("exit", "завершить программу (без сохранения в файл)");

View File

@ -5,6 +5,9 @@ import me.zinch.wrapper.ProductCollection;
import java.util.regex.Pattern;
/**
* A command to filter elements whose 'name' field contains the specified substring.
*/
public class FilterContainsName extends Command {
public FilterContainsName() {
super("filter_contains_name name", "вывести элементы, значение поля name которых содержит заданную подстроку", Pattern.compile("^filter_contains_name \\w+"));

View File

@ -2,6 +2,9 @@ package me.zinch.commands;
import me.zinch.wrapper.ProductCollection;
/**
* A command to display help for available commands.
*/
public class Help extends Command {
public Help() {
super("help", "вывести справку по доступным командам");

View File

@ -2,6 +2,9 @@ package me.zinch.commands;
import me.zinch.wrapper.ProductCollection;
/**
* A command to display information about the collection (type, initialization date, number of elements, etc.) to the standard output stream.
*/
public class Info extends Command {
public Info() {
super("info", "вывести в стандартный поток вывода информацию о коллекции (тип, дата инициализации, количество элементов и т.д.)");

View File

@ -2,6 +2,9 @@ package me.zinch.commands;
import me.zinch.wrapper.ProductCollection;
/**
* A command to print the elements of the collection in ascending order.
*/
public class PrintAscending extends Command {
public PrintAscending() {
super("print_ascending", "вывести элементы коллекции в порядке возрастания");

View File

@ -2,6 +2,9 @@ package me.zinch.commands;
import me.zinch.wrapper.ProductCollection;
/**
* A command to print the unique values of the 'manufactureCost' field of all elements in the collection.
*/
public class PrintUniqueManufactureCost extends Command {
public PrintUniqueManufactureCost() {
super("print_unique_manufacture_cost", "вывести уникальные значения поля manufactureCost всех элементов в коллекции");

View File

@ -5,6 +5,9 @@ import me.zinch.wrapper.ProductCollection;
import java.util.regex.Pattern;
/**
* A command to remove an element from the collection by its id.
*/
public class Remove extends Command {
public Remove() {
super("remove_by_id id", "удалить элемент из коллекции по его id", Pattern.compile("^remove_by_id \\d+"));

View File

@ -5,6 +5,9 @@ import me.zinch.wrapper.ProductCollection;
import java.util.regex.Pattern;
/**
* A command to remove all elements from the collection that have ids lower than the specified id.
*/
public class RemoveLower extends Command {
public RemoveLower() {
super("remove_lower id", "удалить из коллекции все элементы, меньшие, чем заданный по id", Pattern.compile("^remove_lower \\d+"));

View File

@ -6,6 +6,9 @@ import me.zinch.wrapper.ProductCollection;
import java.io.IOException;
/**
* A command to save the collection to a file.
*/
public class Save extends Command {
public Save() {
super("save", "сохранить коллекцию в файл");

View File

@ -2,6 +2,9 @@ package me.zinch.commands;
import me.zinch.wrapper.ProductCollection;
/**
* A command to display all elements of the collection in string representation to the standard output stream.
*/
public class Show extends Command {
public Show() {
super("show", "вывести в стандартный поток вывода все элементы коллекции в строковом представлении");

View File

@ -0,0 +1,17 @@
package me.zinch.commands;
import me.zinch.wrapper.ProductCollection;
/**
* Represents an unknown command.
*/
public class UnknownCommand extends Command {
public UnknownCommand() {
super("", "");
}
@Override
public void action(ProductCollection productCollection) {
System.out.println("Команда не распознана.");
}
}

View File

@ -5,6 +5,9 @@ import me.zinch.wrapper.ProductCollection;
import java.util.regex.Pattern;
/**
* A command to update the value of an element in the collection, whose id is specified.
*/
public class Update extends Command {
public Update() {
super("update id {element}", "обновить значение элемента коллекции, id которого равен заданному", Pattern.compile("^update \\d+"));

View File

@ -1,8 +1,10 @@
package me.zinch.console;
import me.zinch.commands.CommandManager;
import me.zinch.exceptions.ColorFormatException;
import me.zinch.exceptions.CommandActionException;
import me.zinch.exceptions.IllegalProductDtoException;
import me.zinch.exceptions.UnitOfMeasureFormatException;
import me.zinch.files.DbController;
import me.zinch.models.Color;
import me.zinch.models.Coordinates;
@ -14,10 +16,15 @@ import me.zinch.wrapper.ProductCollection;
import java.io.IOException;
import java.util.ArrayList;
import java.util.IllegalFormatConversionException;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Scanner;
/**
Console class provides methods for interacting with the console, reading user input, and managing application flow.
This class includes methods for appending input history, stopping the application, reading product DTO from the console, and running the application loop.
*/
public class Console {
private static final Scanner scanner = new Scanner(System.in);
private static boolean isRunning = true;
@ -36,50 +43,91 @@ public class Console {
return history.get(history.size() - 1);
}
private static String readString() {
var input = scanner.nextLine().trim();
return input.isEmpty() ? null : input;
}
private static Long readLong() {
var input = scanner.nextLine().trim();
try {
return Long.parseLong(input);
} catch (NumberFormatException e) {
return null;
}
}
private static Color readColor() throws ColorFormatException {
System.out.format("Цвет волос(%s): ", Color.getColorsString());
var input = readString();
if (input == null || input.isEmpty()) return null;
try {
return Color.create(Integer.parseInt(input));
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
try {
return Color.create(input);
} catch (IllegalArgumentException ex) {
throw new ColorFormatException(ex.getMessage());
}
}
}
private static UnitOfMeasure readUnitOfMeasure() throws UnitOfMeasureFormatException {
System.out.format("Единица измерения(%s): ", UnitOfMeasure.getUnitOfMeasuerString());
var input = readString();
try {
return UnitOfMeasure.create(Integer.parseInt(input));
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
try {
return UnitOfMeasure.create(input);
} catch (IllegalArgumentException ex) {
throw new ColorFormatException(ex.getMessage());
}
}
}
private static Location readLocation() throws NumberFormatException {
System.out.print("Создать поле Location? (Y/n): ");
var input = readString();
if (input != null && input.equalsIgnoreCase("n")) return null;
System.out.print("Координата x локации: ");
var locationX = Float.parseFloat(readString());
System.out.print("Координата y локации: ");
var locationY = Integer.parseInt(readString());
System.out.print("Название: ");
var locationName = readString();
return new Location(locationX, locationY, locationName);
}
public static ProductDTO readProductDTO() throws IllegalProductDtoException {
try {
var productDTO = new ProductDTO();
System.out.println("Заполните следующие поля: ");
System.out.print("Название: ");
productDTO.setName(scanner.nextLine().trim());
productDTO.setName(readString());
System.out.print("Координата x: ");
long x = Long.parseLong(scanner.nextLine().trim());
long x = readLong();
System.out.print("Координата y: ");
long y = Long.parseLong(scanner.nextLine().trim());
long y = readLong();
productDTO.setCoordinates(new Coordinates(x, y));
System.out.print("Цена: ");
productDTO.setPrice(Long.parseLong(scanner.nextLine().trim()));
productDTO.setPrice(readLong());
System.out.print("Номер части: ");
productDTO.setPartNumber(scanner.nextLine().trim());
productDTO.setPartNumber(readString());
System.out.print("Себестоимость: ");
productDTO.setManufactureCost(Long.parseLong(scanner.nextLine().trim()));
System.out.print("Единица измерения(Килограммы[0], Сантиметры[1], Граммы[2]): ");
try {
productDTO.setUnitOfMeasure(UnitOfMeasure.create(Integer.parseInt(scanner.nextLine().trim())));
} catch (NumberFormatException e) {
productDTO.setUnitOfMeasure(UnitOfMeasure.create(scanner.nextLine().trim()));
}
productDTO.setManufactureCost(readLong());
productDTO.setUnitOfMeasure(readUnitOfMeasure());
System.out.print("Имя владельца: ");
var ownerName = scanner.nextLine().trim();
var ownerName = readString();
System.out.print("Данные паспорта: ");
var passportId = scanner.nextLine().trim();
System.out.print("Цвет волос(Зелёный[0], Чёрный[1], Синий[2], Оранжевый[3], Белый[4]): ");
Color color;
try {
color = Color.create(Integer.parseInt(scanner.nextLine().trim()));
} catch (NumberFormatException e) {
color = Color.create(scanner.nextLine().trim());
}
System.out.print("Координата x локации: ");
var locationX = Float.parseFloat(scanner.nextLine().trim());
System.out.print("Координата y локации: ");
var locationY = Integer.parseInt(scanner.nextLine().trim());
System.out.print("Название: ");
var locationName = scanner.nextLine().trim();
productDTO.setOwner(new Person(ownerName, passportId, color, new Location(locationX, locationY, locationName)));
var passportId = readString();
Color color = readColor();
productDTO.setOwner(new Person(ownerName, passportId, color, readLocation()));
return productDTO;
} catch (NumberFormatException e) {
} catch (ColorFormatException | UnitOfMeasureFormatException e) {
throw new IllegalProductDtoException(e.getMessage());
} catch (NumberFormatException | NullPointerException | IllegalFormatConversionException e) {
throw new IllegalProductDtoException();
}
}

View File

@ -1,5 +1,8 @@
package me.zinch.console;
/**
* Represents a shutdown hook that is executed when the application is stopped.
*/
public class ShutdownHook extends Thread {
public ShutdownHook() {
super(new Thread(Console::stopApp));

View File

@ -0,0 +1,11 @@
package me.zinch.exceptions;
public class ColorFormatException extends IllegalArgumentException {
public ColorFormatException() {
super();
}
public ColorFormatException(String message) {
super(message);
}
}

View File

@ -1,5 +1,8 @@
package me.zinch.exceptions;
/**
* Represents an exception that occurs during the execution of a command action.
*/
public class CommandActionException extends RuntimeException {
public CommandActionException() {
super("Ошибка во время выполнения команды");

View File

@ -2,6 +2,9 @@ package me.zinch.exceptions;
import java.io.IOException;
/**
* Represents an exception that occurs when the database initialization fails.
*/
public class DbInitializationException extends IOException {
public DbInitializationException() {
super("Ошибка! Не удалось инициализировать БД. Проверьте, что структура БД верна.");

View File

@ -1,5 +1,8 @@
package me.zinch.exceptions;
/**
* Represents an exception that occurs when an illegal product DTO is encountered during the creation or modification of a product.
*/
public class IllegalProductDtoException extends CommandActionException {
public IllegalProductDtoException() {
super("Ошибка при создании или изменении продукта. Были введены некорректные значения.");

View File

@ -0,0 +1,11 @@
package me.zinch.exceptions;
public class UnitOfMeasureFormatException extends IllegalArgumentException {
public UnitOfMeasureFormatException() {
super();
}
public UnitOfMeasureFormatException(String message) {
super(message);
}
}

View File

@ -1,5 +1,8 @@
package me.zinch.exceptions;
/**
* Represents an exception related to validation errors.
*/
public class ValidationException extends jakarta.validation.ValidationException {
public ValidationException() {}

View File

@ -18,6 +18,9 @@ import java.io.OutputStreamWriter;
import java.util.List;
import java.util.Map;
/**
* The DbController class provides methods for loading and saving product data to a database file.
*/
public class DbController {
private static final XmlMapper xmlMapper = XmlMapper.builder().addModule(new JavaTimeModule()).build();
private static final String path;
@ -28,6 +31,9 @@ public class DbController {
xmlMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
}
/**
* Represents a collection of products.
*/
private static class Products {
@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "Product")

View File

@ -0,0 +1,32 @@
package me.zinch.files;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* The ScriptLoader class provides methods to load scripts from files.
*/
public class ScriptLoader {
public static List<String> loadList(String path) throws IOException {
try {
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(path));
Scanner scanner = new Scanner(bufferedInputStream);
List<String> list = new ArrayList<>();
while (scanner.hasNextLine()) {
list.add(scanner.nextLine());
}
bufferedInputStream.close();
scanner.close();
return list;
} catch (FileNotFoundException e) {
throw new FileNotFoundException(String.format("Файл %s не найден.", path));
} catch (IOException e) {
throw new IOException("Произошла неожиданная ошибка во время работы с файлом!");
}
}
}

View File

@ -1,7 +1,12 @@
package me.zinch.models;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Represents a color enumeration.
*/
public enum Color {
GREEN("Зелёный"),
BLACK("Чёрный"),
@ -15,21 +20,28 @@ public enum Color {
this.color = color;
}
public String getColor() {
private String getColor() {
return color;
}
public static Color create(String input) {
public static Color create(String input) throws IllegalArgumentException {
for (var unit : List.of(Color.values())) {
if (unit.getColor().toLowerCase() == input.toLowerCase()) return unit;
if (unit.getColor().equalsIgnoreCase(input)) return unit;
}
return Color.BLUE;
throw new IllegalArgumentException("Не существует такого цвета");
}
public static Color create(Integer num) {
public static Color create(Integer num) throws ArrayIndexOutOfBoundsException {
return Color.values()[num];
}
public static String getColorsString() {
AtomicInteger i = new AtomicInteger();
return String.join(", ", Arrays.stream(Color.values())
.map(color -> String.format("%s[%d]", color.getColor(), i.getAndIncrement()))
.toList());
}
@Override
public String toString() {
return "Color{" +

View File

@ -7,6 +7,9 @@ import jakarta.validation.constraints.NotNull;
import java.util.Objects;
/**
* Represents coordinates with x and y values.
*/
public class Coordinates {
@Max(value = 883, message = "Coordinates: Максимальное значение координаты x: 883")
@JsonProperty("x")

View File

@ -6,6 +6,9 @@ import me.zinch.validator.NotEmpty;
import java.util.Objects;
/**
* Represents a location with x and y coordinates and a name.
*/
public class Location {
@JsonProperty("x")
private float x;

View File

@ -7,6 +7,9 @@ import jakarta.validation.constraints.Size;
import java.util.Objects;
/**
* Represents a person with a name, passport ID, hair color, and location.
*/
public class Person {
@NotBlank(message = "Person: Имя персоны не может быть пустым или null")
@JsonProperty("name")

View File

@ -10,6 +10,9 @@ import me.zinch.validator.NotEmpty;
import java.time.ZonedDateTime;
import java.util.Objects;
/**
* Represents a product with various attributes such as ID, name, coordinates, creation date, price, part number, manufacture cost, unit of measure, and owner.
*/
public class Product {
@NotNull(message = "Product: Поле id не может быть null")
@Min(value = 1, message = "Product: Значение поля id должно быть больше 0")

View File

@ -11,6 +11,9 @@ import me.zinch.validator.NotEmpty;
import java.time.ZonedDateTime;
/**
* Represents a Data Transfer Object (DTO) for a product with attributes such as name, coordinates, price, part number, manufacture cost, unit of measure, and owner.
*/
public class ProductDTO {
@NotBlank(message = "Строка name не может быть пустой или null")
@JsonProperty("name")

View File

@ -1,7 +1,12 @@
package me.zinch.models;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Represents a unit of measure enumeration.
*/
public enum UnitOfMeasure {
KILOGRAMS("Килограммы"),
CENTIMETERS("Сантиметры"),
@ -17,17 +22,24 @@ public enum UnitOfMeasure {
return unitOfMeasure;
}
public static UnitOfMeasure create(String input) {
public static UnitOfMeasure create(String input) throws IllegalArgumentException {
for (var unit : List.of(UnitOfMeasure.values())) {
if (unit.getUnitOfMeasure().toLowerCase() == input.toLowerCase()) return unit;
if (unit.getUnitOfMeasure().equalsIgnoreCase(input)) return unit;
}
return UnitOfMeasure.KILOGRAMS;
throw new IllegalArgumentException("Не существует такой единицы измерения");
}
public static UnitOfMeasure create(Integer num) {
public static UnitOfMeasure create(Integer num) throws ArrayIndexOutOfBoundsException {
return UnitOfMeasure.values()[num];
}
public static String getUnitOfMeasuerString() {
AtomicInteger i = new AtomicInteger();
return String.join(", ", Arrays.stream(UnitOfMeasure.values())
.map(unitOfMeasure -> String.format("%s[%d]", unitOfMeasure.getUnitOfMeasure(), i.getAndIncrement()))
.toList());
}
@Override
public String toString() {

View File

@ -8,6 +8,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation indicating that the annotated element must not be empty.
*/
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = NotEmptyValidator.class)

View File

@ -3,6 +3,9 @@ package me.zinch.validator;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
/**
* Validator implementation for the {@link NotEmpty} annotation, ensuring that the value is not empty.
*/
public class NotEmptyValidator implements ConstraintValidator<NotEmpty, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {

View File

@ -5,6 +5,10 @@ import jakarta.validation.ValidationException;
import java.util.Set;
/**
* Represents the result of a validation process.
* @param <T> the type of object being validated
*/
public class ValidateResult<T> {
private final Set<ConstraintViolation<T>> violations;
private final boolean isValid;

View File

@ -8,6 +8,9 @@ import org.hibernate.validator.HibernateValidator;
import java.util.List;
/**
* Utility class for performing validation operations on products.
*/
public class Validators {
private static final Validator validator;

View File

@ -16,6 +16,9 @@ import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
/**
* Represents a collection of products.
*/
public class ProductCollection {
private final TreeSet<Product> productList;
private Long idIncrementor;