Compare commits

..

1 Commits

Author SHA1 Message Date
Nadezhda Naumova
7bff0acdd2 Merge branch 'develop' into 'main'
Лаб. 5

See merge request itmo_programming_2023/programming_2.12/student-3!1
2024-05-15 17:33:02 +00:00
115 changed files with 968 additions and 2860 deletions

View File

@ -1,95 +0,0 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.zinch</groupId>
<artifactId>Lab6-Client</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>Lab6-Client</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>5.0.0-M1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.23.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>me.zinch</groupId>
<artifactId>Lab6-Domain</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>3.7.1</version>
<configuration>
<archive>
<manifest>
<mainClass>me.zinch.Lab6.Client.App</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</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

@ -1,14 +0,0 @@
package me.zinch.Lab6.Client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.Lab6.Client.console.ShutdownHook;
/**
* Main class for running the application.
*/
public class App {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
Console.run();
}
}

View File

@ -1,84 +0,0 @@
package me.zinch.Lab6.Client.client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.Lab6.Client.exceptions.ConnectionErrorException;
import me.zinch.Lab6.Client.exceptions.ResponseException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.Message;
import me.zinch.Lab6.Domain.dto.MessageType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;
public class Client {
private static final Logger log = LoggerFactory.getLogger(Client.class);
private static final int MAX_RETRIES = 3;
private static final int RETRY_DELAY_MS = 1000;
private static final int SOCKET_TIMEOUT_MS = 5000;
private final String address;
private final int port;
public Client(String address, int port) throws ConnectionErrorException, ResponseException {
this.address = address;
this.port = port;
try {
if (sendMessage(new BodylessMessage(MessageType.HELLO)).getType() == MessageType.HELLO) {
Console.log(String.format("Connected to server %s:%s", address, port));
}
} catch (IOException | ClassNotFoundException e) {
throw new ResponseException();
}
}
public Message sendMessage(Message message) throws ClassNotFoundException, IOException {
int retries = 0;
while (true) {
try {
return trySendMessage(message);
} catch (ConnectException | SocketTimeoutException e) {
if (++retries > MAX_RETRIES) {
throw new IOException("Server is unavailable after " + MAX_RETRIES + " retries");
}
log.warn("Connection failed, retrying in {} ms... ({}/{})",
RETRY_DELAY_MS, retries, MAX_RETRIES);
try {
TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while waiting to retry");
}
}
}
}
private Message trySendMessage(Message message) throws IOException, ClassNotFoundException {
try (var socket = new Socket(address, port)) {
socket.setSoTimeout(SOCKET_TIMEOUT_MS);
try (var bufferedSocketOutputStream = new BufferedOutputStream(socket.getOutputStream());
var byteArrayOutputStream = new ByteArrayOutputStream();
var objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {
objectOutputStream.writeObject(message);
objectOutputStream.flush();
bufferedSocketOutputStream.write(byteArrayOutputStream.toByteArray());
bufferedSocketOutputStream.flush();
try (var objectInputStream = new ObjectInputStream(socket.getInputStream())) {
return (Message) objectInputStream.readObject();
}
}
}
}
}

View File

@ -1,33 +0,0 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodyfulMessage;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
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"));
}
@Override
public String action(Client client) throws CommandActionException {
try {
var productDTO = Console.readProductDTO();
var request = new BodyfulMessage(MessageType.POST, Console.getLastCommand(), productDTO);
var response = (BodylessMessage) client.sendMessage(request);
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -1,11 +0,0 @@
package me.zinch.Lab6.Client.commands;
/**
* 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

@ -1,59 +0,0 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
import java.util.regex.Pattern;
/**
* Represents a command that can be executed.
*/
public abstract class Command {
private final String name;
private final String description;
private final Pattern pattern;
public Command(String name, String description, Pattern pattern) {
this.name = name;
this.description = description;
this.pattern = pattern;
}
public Command(String name, String description) {
this.name = name;
this.description = description;
this.pattern = Pattern.compile("^" + name);
}
public final String getName() {
return name;
}
public final String getSignature() {
return name.split(" ")[0];
}
public final String getDescription() {
return description;
}
public final Pattern getPattern() {
return pattern;
}
public final boolean checkPattern(String input) {
return pattern.matcher(input).matches();
}
public String action(Client client) throws CommandActionException {
try {
var response = (BodylessMessage) client.sendMessage(new BodylessMessage(MessageType.GET, this.getName()));
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -1,47 +0,0 @@
package me.zinch.Lab6.Client.commands;
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<>();
static {
registerCommand(new Help());
registerCommand(new Info());
registerCommand(new Show());
registerCommand(new Add());
registerCommand(new Update());
registerCommand(new Remove());
registerCommand(new Clear());
registerCommand(new ExecuteScript());
registerCommand(new Exit());
registerCommand(new Head());
registerCommand(new RemoveHead());
registerCommand(new History());
registerCommand(new MaxByWeight());
registerCommand(new GroupCountingByType());
registerCommand(new FilterLessThanCharacter());
}
public static void registerCommand(Command command) {
commandList.add(command);
}
public static String getRegisteredCommand() {
return String.join("\n", commandList
.stream()
.map(command -> String.format("%s - %s", command.getName(), command.getDescription()))
.toList());
}
public static Optional<Command> getCommandByInput(String input) {
return commandList.stream()
.filter(command -> command.checkPattern(input))
.findFirst();
}
}

View File

@ -1,33 +0,0 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import me.zinch.Lab6.Domain.models.DragonCharacter;
import java.io.IOException;
import java.util.regex.Pattern;
public class FilterLessThanCharacter extends Command {
public FilterLessThanCharacter() {
super(
"filter_less_than_character",
"вывести элементы, значение поля character которых меньше заданного",
Pattern.compile("^filter_less_than_character\\s+(CUNNING|WISE|EVIL|CHAOTIC|FICKLE)\\s*$")
);
}
@Override
public String action(Client client) throws CommandActionException {
try {
var character = DragonCharacter.valueOf(getName().split("\\s+")[1]);
var response = (BodylessMessage) client.sendMessage(
new BodylessMessage(MessageType.GET, "filter_less_than_character " + character.name())
);
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -1,24 +0,0 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
public class GroupCountingByType extends Command {
public GroupCountingByType() {
super("group_counting_by_type", "сгруппировать элементы коллекции по значению поля type, вывести количество элементов в каждой группе");
}
@Override
public String action(Client client) throws CommandActionException {
try {
var response = (BodylessMessage) client.sendMessage(new BodylessMessage(MessageType.GET, "group_counting_by_type"));
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -1,24 +0,0 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
public class Head extends Command {
public Head() {
super("head", "вывести первый элемент коллекции");
}
@Override
public String action(Client client) throws CommandActionException {
try {
var response = (BodylessMessage) client.sendMessage(new BodylessMessage(MessageType.GET, "head"));
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -1,31 +0,0 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
public class History extends Command {
private static final int HISTORY_SIZE = 10;
private static final Queue<String> commandHistory = new LinkedList<>();
public History() {
super("history", "вывести последние 10 команд (без их аргументов)");
}
public static void addCommand(String command) {
commandHistory.offer(command);
if (commandHistory.size() > HISTORY_SIZE) {
commandHistory.poll();
}
}
@Override
public String action(Client client) throws CommandActionException {
return String.join("\n", commandHistory);
}
}

View File

@ -1,10 +0,0 @@
package me.zinch.Lab6.Client.commands;
/**
* 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

@ -1,24 +0,0 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
public class MaxByWeight extends Command {
public MaxByWeight() {
super("max_by_weight", "вывести любой объект из коллекции, значение поля weight которого является максимальным");
}
@Override
public String action(Client client) throws CommandActionException {
try {
var response = (BodylessMessage) client.sendMessage(new BodylessMessage(MessageType.GET, "max_by_weight"));
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -1,33 +0,0 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodyfulMessage;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
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 .+"));
}
@Override
public String action(Client client) throws CommandActionException {
try {
Long id = Long.parseLong(Console.getLastCommand().split(" ")[1]);
var response = (BodylessMessage) client.sendMessage(new BodyfulMessage(MessageType.POST, Console.getLastCommand(), id));
return response.getBody().toString();
} catch (NumberFormatException e) {
return "Неверный аргумент, id может быть только число";
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -1,24 +0,0 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
public class RemoveHead extends Command {
public RemoveHead() {
super("remove_head", "вывести первый элемент коллекции и удалить его");
}
@Override
public String action(Client client) throws CommandActionException {
try {
var response = (BodylessMessage) client.sendMessage(new BodylessMessage(MessageType.GET, "remove_head"));
return response.getBody().toString();
} catch (IOException | ClassNotFoundException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -1,48 +0,0 @@
package me.zinch.Lab6.Client.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Domain.dto.BodyfulMessage;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.MessageBody;
import me.zinch.Lab6.Domain.dto.MessageType;
import java.io.IOException;
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 .+"));
}
@Override
public String action(Client client) throws CommandActionException {
try {
var command = Console.getLastCommand();
Long id = Long.parseLong(command.split(" ")[1]);
var isIdExists = new IsIdExists();
Console.appendHistory(String.format("%s %s", isIdExists.getSignature(), id));
isIdExists.action(client);
var getProductById = new GetProductById();
Console.appendHistory(String.format("%s %s", getProductById.getSignature(), id));
var product = getProductById.action(client);
Console.log("Вы собираетесь обновить следующий продукт");
Console.log(product);
var productDTO = Console.readProductDTO();
var response = (BodylessMessage) client.sendMessage(
new BodyfulMessage(MessageType.POST, command,
new MessageBody(String.format("%s %s", command, id), productDTO)));
return response.getBody().toString();
} catch (IOException | ClassNotFoundException | NumberFormatException e) {
throw new CommandActionException(e);
}
}
}

View File

@ -1,208 +0,0 @@
package me.zinch.Lab6.Client.console;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.commands.CommandManager;
import me.zinch.Lab6.Client.exceptions.ColorFormatException;
import me.zinch.Lab6.Client.exceptions.CommandActionException;
import me.zinch.Lab6.Client.exceptions.ConnectionErrorException;
import me.zinch.Lab6.Client.exceptions.ResponseException;
import me.zinch.Lab6.Client.exceptions.UnitOfMeasureFormatException;
import me.zinch.Lab6.Domain.models.Color;
import me.zinch.Lab6.Domain.models.Coordinates;
import me.zinch.Lab6.Domain.models.Location;
import me.zinch.Lab6.Domain.models.Person;
import me.zinch.Lab6.Domain.models.ProductDTO;
import me.zinch.Lab6.Domain.models.UnitOfMeasure;
import java.util.ArrayList;
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 final List<String> history = new ArrayList<>();
private static boolean isRunning = true;
private static boolean isExitMessageShowed = false;
private static Client client;
public static void appendHistory(String input) {
history.add(input);
}
public static void log(Object msg) {
if (isRunning) System.out.println(msg.toString());
}
public static void log() {
log("");
}
public static void stopAppWithoutSaving() {
showExitMessage();
isRunning = false;
}
public static void stopAppWithSaving() {
stopAppWithoutSaving();
}
public static void showExitMessage() {
if (!isExitMessageShowed) log("\nВсего хорошего!");
isExitMessageShowed = true;
}
public static String getLastCommand() {
return history.get(history.size() - 1);
}
private static String readString() {
if (!scanner.hasNextLine()) Console.stopAppWithSaving();
var input = scanner.nextLine().trim();
return input.isEmpty() ? null : input;
}
private static Long readLong() {
var input = readString();
if (input == null || input.isEmpty()) return null;
try {
return Long.parseLong(input);
} catch (NumberFormatException e) {
throw new NumberFormatException(String.format("Не могу распознать %s как число", input));
}
}
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);
}
private static void setField(String fieldName, Runnable function) {
while (isRunning) {
if (fieldName != null) System.out.format("%s: ", fieldName);
try {
function.run();
return;
} catch (NullPointerException e) {
log("Произошла ошибка при заполнении поля\оле не может быть пустым");
} catch (Exception e) {
System.out.format("Произошла ошибка при заполнении поля%n%s%n", e.getMessage());
}
}
}
private static void setField(Runnable function) {
setField(null, function);
}
private static String readField(String message) {
System.out.print(message + ": ");
return readString();
}
public static ProductDTO readProductDTO() {
var productDTO = new ProductDTO();
var coordinates = new Coordinates();
var owner = new Person();
log("Заполните следующие поля: ");
setField("Название [не может быть пустым]", () -> productDTO.setName(readString()));
setField("Координата x [должна быть меньше 884]", () -> coordinates.setX(readLong()));
setField("Координата y [должна быть больше -427, не может быть пустой]", () -> coordinates.setY(readLong()));
setField("Цена [должна быть больше 0, не может быть пустой]", () -> productDTO.setPrice(readLong()));
setField("Номер части [не больше 82 символов, должен быть уникальным]", () -> productDTO.setPartNumber(readString()));
setField("Себестоимость [не может быть пустым]", () -> productDTO.setManufactureCost(readLong()));
setField(() -> productDTO.setUnitOfMeasure(readUnitOfMeasure()));
setField("Имя владельца [не может быть пустым]", () -> owner.setName(readString()));
setField("Данные паспорта [длина строки от 5 до 22, не может быть пустым]", () -> owner.setPassportID(readString()));
setField(() -> owner.setHairColor(readColor()));
setField(() -> owner.setLocation(readLocation()));
productDTO.setCoordinates(coordinates);
productDTO.setOwner(owner);
return productDTO;
}
public static void run() {
while (client == null) {
try {
var address = readField("Хост");
int port = Integer.parseInt(readField("Порт"));
client = new Client(address, port);
} catch (NumberFormatException e) {
log("Порт должен быть числом от 1 до 65535");
} catch (ResponseException | ConnectionErrorException e) {
log("Ошибка при подключении\опробуйте ещё раз");
}
}
log("Добро пожаловать! Для просмотра команд введите help");
while (isRunning) {
System.out.print(":");
try {
var input = scanner.nextLine().trim();
var command = CommandManager.getCommandByInput(input);
if (command.isPresent()) {
appendHistory(input);
try {
log(command.get().action(client));
} catch (CommandActionException e) {
log(e.getMessage());
}
} else {
log("Такой команды не существует. Напишите help, чтобы посмотреть список доступных команд.");
}
} catch (NoSuchElementException e) {
stopAppWithoutSaving();
}
log();
}
scanner.close();
}
}

View File

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

View File

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

View File

@ -1,9 +0,0 @@
package me.zinch.Lab6.Client.exceptions;
import java.io.IOException;
public class ConnectionErrorException extends IOException {
public ConnectionErrorException() {
super("Произошла ошибка при подключении, сервер не доступен");
}
}

View File

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

View File

@ -1,18 +0,0 @@
package me.zinch.Lab6.Client.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("Ошибка при создании или изменении продукта. Были введены некорректные значения.");
}
public IllegalProductDtoException(String message) {
super(message);
}
public IllegalProductDtoException(Throwable e) {
super(e);
}
}

View File

@ -1,7 +0,0 @@
package me.zinch.Lab6.Client.exceptions;
public class ResponseException extends ClassNotFoundException {
public ResponseException() {
super("Ошибка при обработке данных от сервера.");
}
}

View File

@ -1,32 +0,0 @@
package me.zinch.Lab6.Client.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,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.zinch</groupId>
<artifactId>Lab6-Domain</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.17.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,10 +0,0 @@
package me.zinch.Lab6.Domain.dto;
import java.io.Serializable;
public class BodyfulMessage extends Message {
public BodyfulMessage(MessageType type, String command, Serializable body) {
this.setType(type);
this.setBody(new MessageBody(command, body));
}
}

View File

@ -1,12 +0,0 @@
package me.zinch.Lab6.Domain.dto;
public class BodylessMessage extends Message {
public BodylessMessage(MessageType type) {
this.setType(type);
}
public BodylessMessage(MessageType type, String command) {
this.setType(type);
this.setBody(command);
}
}

View File

@ -1,32 +0,0 @@
package me.zinch.Lab6.Domain.dto;
import java.io.Serializable;
public abstract class Message implements Serializable {
private MessageType type;
private Serializable body;
public final MessageType getType() {
return type;
}
public final void setType(MessageType type) {
this.type = type;
}
public final Object getBody() {
return body;
}
public final void setBody(Serializable body) {
this.body = body;
}
@Override
public String toString() {
return "Message{" +
"type=" + type +
", body=" + body +
'}';
}
}

View File

@ -1,29 +0,0 @@
package me.zinch.Lab6.Domain.dto;
import java.io.Serializable;
public class MessageBody implements Serializable {
private String command;
private Serializable body;
public MessageBody(String command, Serializable body) {
this.command = command;
this.body = body;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
public Serializable getBody() {
return body;
}
public void setBody(Serializable body) {
this.body = body;
}
}

View File

@ -1,11 +0,0 @@
package me.zinch.Lab6.Domain.dto;
import java.io.Serializable;
public enum MessageType implements Serializable {
GET,
POST,
ERROR,
OK,
HELLO
}

View File

@ -1,31 +0,0 @@
package me.zinch.Lab6.Domain.dto;
import me.zinch.Lab6.Domain.models.ProductDTO;
import java.io.Serializable;
public class UpdateBody implements Serializable {
private Long id;
private ProductDTO productDTO;
public UpdateBody(Long id, ProductDTO productDTO) {
this.id = id;
this.productDTO = productDTO;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public ProductDTO getProductDTO() {
return productDTO;
}
public void setProductDTO(ProductDTO productDTO) {
this.productDTO = productDTO;
}
}

View File

@ -1,56 +0,0 @@
package me.zinch.Lab6.Domain.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
/**
* Represents coordinates with x and y values.
*/
public class Coordinates implements Serializable {
@Max(value = 353, message = "Coordinates: Максимальное значение поля x: 353")
@JsonProperty("x")
private long x; // Максимальное значение поля: 353
@NotNull(message = "Coordinates: Поле y не может быть null")
@JsonProperty("y")
private Integer y; // Поле не может быть null
public Coordinates() {}
public Coordinates(long x, Integer y) {
this.x = x;
this.y = y;
}
public long getX() {
return x;
}
public Integer getY() {
return y;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Coordinates that = (Coordinates) o;
return x == that.x && Objects.equals(y, that.y);
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "Coordinates{" +
"x=" + x +
", y=" + y +
'}';
}
}

View File

@ -1,148 +0,0 @@
package me.zinch.Lab6.Domain.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Objects;
public class Dragon implements Serializable, Comparable<Dragon> {
@NotNull(message = "Dragon: Поле id не может быть null")
@Min(value = 1, message = "Dragon: Значение поля id должно быть больше 0")
@JsonProperty("id")
private Integer id; // Поле не может быть null, Значение поля должно быть больше 0, Значение этого поля должно быть уникальным, Значение этого поля должно генерироваться автоматически
@NotBlank(message = "Dragon: Строка name не может быть пустой или null")
@JsonProperty("name")
private String name; // Поле не может быть null, Строка не может быть пустой
@NotNull(message = "Dragon: Поле coordinates не может быть null")
@JsonProperty("coordinates")
private Coordinates coordinates; // Поле не может быть null
@NotNull(message = "Dragon: Поле creationDate не может быть null")
@JsonProperty("creationDate")
private LocalDateTime creationDate; // Поле не может быть null, Значение этого поля должно генерироваться автоматически
@NotNull(message = "Dragon: Поле age не может быть null")
@Min(value = 1, message = "Dragon: Значение поля age должно быть больше 0")
@JsonProperty("age")
private Integer age; // Значение поля должно быть больше 0, Поле не может быть null
@Min(value = 1, message = "Dragon: Значение поля weight должно быть больше 0")
@JsonProperty("weight")
private long weight; // Значение поля должно быть больше 0
@NotNull(message = "Dragon: Поле type не может быть null")
@JsonProperty("type")
private DragonType type; // Поле не может быть null
@NotNull(message = "Dragon: Поле character не может быть null")
@JsonProperty("character")
private DragonCharacter character; // Поле не может быть null
@JsonProperty("head")
private DragonHead head;
public Dragon() {}
public Dragon(Integer id,
String name,
Coordinates coordinates,
LocalDateTime creationDate,
Integer age,
long weight,
DragonType type,
DragonCharacter character,
DragonHead head) {
this.id = id;
this.name = name;
this.coordinates = coordinates;
this.creationDate = creationDate;
this.age = age;
this.weight = weight;
this.type = type;
this.character = character;
this.head = head;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public Coordinates getCoordinates() {
return coordinates;
}
public LocalDateTime getCreationDate() {
return creationDate;
}
public Integer getAge() {
return age;
}
public long getWeight() {
return weight;
}
public DragonType getType() {
return type;
}
public DragonCharacter getCharacter() {
return character;
}
public DragonHead getHead() {
return head;
}
@Override
public String toString() {
return "Dragon{" +
"id=" + id +
", name='" + name + '\'' +
", coordinates=" + coordinates +
", creationDate=" + creationDate +
", age=" + age +
", weight=" + weight +
", type=" + type +
", character=" + character +
", head=" + head +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Dragon dragon = (Dragon) o;
return weight == dragon.weight &&
Objects.equals(id, dragon.id) &&
Objects.equals(name, dragon.name) &&
Objects.equals(coordinates, dragon.coordinates) &&
Objects.equals(creationDate, dragon.creationDate) &&
Objects.equals(age, dragon.age) &&
type == dragon.type &&
character == dragon.character &&
Objects.equals(head, dragon.head);
}
@Override
public int hashCode() {
return Objects.hash(id, name, coordinates, creationDate, age, weight, type, character, head);
}
@Override
public int compareTo(Dragon other) {
return this.name.compareTo(other.name);
}
}

View File

@ -1,9 +0,0 @@
package me.zinch.Lab6.Domain.models;
public enum DragonCharacter {
CUNNING,
WISE,
EVIL,
CHAOTIC,
FICKLE
}

View File

@ -1,40 +0,0 @@
package me.zinch.Lab6.Domain.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import java.util.Objects;
public class DragonHead implements Serializable {
@JsonProperty("size")
private Float size; // Поле может быть null
public DragonHead() {}
public DragonHead(Float size) {
this.size = size;
}
public Float getSize() {
return size;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DragonHead that = (DragonHead) o;
return Objects.equals(size, that.size);
}
@Override
public int hashCode() {
return Objects.hash(size);
}
@Override
public String toString() {
return "DragonHead{" +
"size=" + size +
'}';
}
}

View File

@ -1,7 +0,0 @@
package me.zinch.Lab6.Domain.models;
public enum DragonType {
WATER,
AIR,
FIRE
}

View File

@ -1,23 +0,0 @@
package me.zinch.Lab6.Domain.wrapper;
import me.zinch.Lab6.Domain.models.Dragon;
import me.zinch.Lab6.Domain.models.DragonCharacter;
import me.zinch.Lab6.Domain.models.DragonType;
import java.util.List;
import java.util.Map;
public interface IStorage {
Dragon getDragonById(Integer id);
Dragon addDragon(Dragon dragon);
Dragon updateDragon(Integer id, Dragon dragon);
Dragon removeDragon(Integer id);
String getInfo();
void clear();
Dragon getHead();
Dragon removeHead();
Dragon getMaxByWeight();
Map<DragonType, Long> groupCountingByType();
List<Dragon> filterLessThanCharacter(DragonCharacter character);
List<Dragon> toList();
}

View File

@ -1,116 +0,0 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.zinch</groupId>
<artifactId>Lab6-Server</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>Lab6-Server</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>5.0.0-M1</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
<dependency>
<groupId>me.zinch</groupId>
<artifactId>Lab6-Domain</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.17.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline</artifactId>
<version>3.26.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.23.1</version>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>3.7.1</version>
<configuration>
<archive>
<manifest>
<mainClass>me.zinch.Lab6.Server.App</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</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

@ -1,14 +0,0 @@
package me.zinch.Lab6.Server;
import me.zinch.Lab6.Server.console.Console;
import java.io.IOException;
/**
* Main class for running the application.
*/
public class App {
public static void main(String[] args) throws IOException {
Console.run();
}
}

View File

@ -1,97 +0,0 @@
package me.zinch.Lab6.Server;
import me.zinch.Lab6.Domain.dto.Message;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.files.DbController;
import me.zinch.Lab6.Server.modules.CommandModule;
import me.zinch.Lab6.Server.modules.ConnectionModule;
import me.zinch.Lab6.Server.modules.RequestModule;
import me.zinch.Lab6.Server.modules.ResponseModule;
import me.zinch.Lab6.Server.wrapper.ProductCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class Server {
private static final Logger log = LoggerFactory.getLogger(Server.class);
private final Selector selector;
private final Map<SocketChannel, Message> clientMessages;
private final ConnectionModule connectionModule;
private final RequestModule requestModule;
private final ResponseModule responseModule;
private final CommandModule commandModule;
private final IStorage storage;
private boolean isRunning;
public Server(int port, IStorage collection) throws IOException {
selector = Selector.open();
clientMessages = new HashMap<>();
if (collection == null) {
collection = new ProductCollection(new ArrayList<>());
}
storage = collection;
commandModule = new CommandModule(storage);
connectionModule = new ConnectionModule(port, selector);
requestModule = new RequestModule(clientMessages);
responseModule = new ResponseModule(clientMessages, commandModule);
isRunning = true;
log.info("Server initialized on port {}", port);
}
public void run() throws IOException {
run(() -> {});
}
public void run(Runnable middleware) throws IOException {
log.info("Server is running");
while (isRunning) {
selector.select(this::handleKey);
middleware.run();
}
}
private void handleKey(SelectionKey key) {
try {
if (!key.isValid()) {
return;
}
if (key.isAcceptable()) {
connectionModule.handleAccept(key);
}
if (key.isReadable()) {
requestModule.handleRead(key);
}
if (key.isWritable()) {
responseModule.handleWrite(key);
}
} catch (IOException | ClassNotFoundException e) {
log.error("Error handling key: {}", e.getMessage());
key.cancel();
try {
key.channel().close();
} catch (IOException ex) {
log.error("Error closing channel: {}", ex.getMessage());
}
}
}
public void stop() throws IOException {
isRunning = false;
connectionModule.close();
selector.close();
DbController.saveDb(storage.toList());
log.info("Server stopped");
}
}

View File

@ -1,37 +0,0 @@
package me.zinch.Lab6.Server;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.configs.ServerConfig;
import me.zinch.Lab6.Server.wrapper.ProductCollection;
import java.io.IOException;
import java.util.ArrayList;
public class ServerBuilder {
private ServerConfig serverConfig = new ServerConfig();
public ServerBuilder() {
serverConfig.setPort(3000);
serverConfig.setCollection(new ProductCollection(new ArrayList<>()));
}
public ServerBuilder(ServerConfig serverConfig) {
this.serverConfig = serverConfig;
}
public ServerBuilder setPort(int port) {
if (port < 1 || port > 65535) throw new IllegalArgumentException("Port must be between 1 and 65535");
serverConfig.setPort(port);
return new ServerBuilder(serverConfig);
}
public ServerBuilder setCollection(IStorage collection) {
serverConfig.setCollection(collection);
return new ServerBuilder(serverConfig);
}
public Server build() throws IOException {
return new Server(serverConfig.getPort(),
serverConfig.getCollection());
}
}

View File

@ -1,31 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.models.ProductDTO;
import me.zinch.Lab6.Domain.validator.Validators;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
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 PostCommand {
public Add() {
super("add {element}", "добавить новый элемент в коллекцию", Pattern.compile("^add"));
}
@Override
public String action(IStorage productCollection, Object object) throws CommandActionException {
var productDTO = (ProductDTO) object;
try {
Validators.validateObject(productDTO).throwIfNotValid();
var product = productCollection.addProduct(productDTO);
return String.format("Продукт %s был добавлен под id %d", product.getName(), product.getId());
} catch (ValidationException e) {
throw new CommandActionException(String.format("Произошла ошибка при добавлении%n%s", e.getMessage()));
}
}
}

View File

@ -1,38 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.models.ProductDTO;
import me.zinch.Lab6.Domain.validator.Validators;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
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 PostCommand {
public AddIfMax() {
super("add_if_max {element}", "добавить новый элемент в коллекцию, если его значение цены превышает значение наибольшей цены этой коллекции", Pattern.compile("^add_if_max .+"));
}
@Override
public String action(IStorage productCollection, Object object) throws CommandActionException {
try {
var productDTO = (ProductDTO) object;
try {
Validators.validateObject(productDTO).throwIfNotValid();
if (productCollection.getMaxPrice() == null || productCollection.getMaxPrice() < productDTO.getPrice()) {
var product = productCollection.addProduct(productDTO);
return String.format("Продукт %s был добавлен под id %d", product.getName(), product.getId());
}
return "Продукт не подходит под условие";
} catch (ValidationException e) {
throw new CommandActionException(e.getMessage());
}
} catch (NumberFormatException e) {
return "Неверный аргумент, id может быть только число";
}
}
}

View File

@ -1,38 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.models.ProductDTO;
import me.zinch.Lab6.Domain.validator.Validators;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
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 PostCommand {
public AddIfMin() {
super("add_if_min {element}", "добавить новый элемент в коллекцию, если его значение цены меньше, чем у наименьшей цены этой коллекции", Pattern.compile("^add_if_min .+"));
}
@Override
public String action(IStorage productCollection, Object object) {
try {
var productDTO = (ProductDTO) object;
try {
Validators.validateObject(productDTO).throwIfNotValid();
if (productCollection.getMinPrice() == null || productCollection.getMinPrice() > productDTO.getPrice()) {
var product = productCollection.addProduct(productDTO);
return String.format("Продукт %s был добавлен под id %d", product.getName(), product.getId());
}
return "Продукт не подходит под условие";
} catch (ValidationException e) {
throw new CommandActionException(e.getMessage());
}
} catch (NumberFormatException e) {
return "Неверный аргумент, id может быть только число";
}
}
}

View File

@ -1,19 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.console.Console;
/**
* A command to exit the program without saving to a file.
*/
public class Exit extends GetCommand {
public Exit() {
super("exit", "terminate the program (without saving to a file)");
}
@Override
public String action(IStorage productCollection) {
Console.stopApp();
return "";
}
}

View File

@ -1,21 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import java.util.regex.Pattern;
/**
* A command to filter elements whose 'name' field contains the specified substring.
*/
public class FilterContainsName extends PostCommand {
public FilterContainsName() {
super("filter_contains_name name", "вывести элементы, значение поля name которых содержит заданную подстроку", Pattern.compile("^filter_contains_name .+"));
}
@Override
public String action(IStorage productCollection, Object object) {
String name = object.toString();
var result = productCollection.filterContainsName(name);
return result.isEmpty() ? "Таких элементов не найдено" : result;
}
}

View File

@ -1,18 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
public abstract class GetCommand extends Command {
public GetCommand(String name, String description, Pattern pattern) {
super(name, description, pattern);
}
public GetCommand(String name, String description) {
super(name, description);
}
public abstract String action(IStorage productCollection) throws CommandActionException;
}

View File

@ -1,19 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
public class GetProduct extends PostCommand {
public GetProduct() {
super("get_product_by_id", "", Pattern.compile("^get_product_by_id +."));
}
@Override
public String action(IStorage productCollection, Object obj) throws CommandActionException {
if (!productCollection.isProductIdExists((Long) obj))
throw new CommandActionException("Продукт с таким ID не существует");
return productCollection.getProductById((Long) obj).toString();
}
}

View File

@ -1,17 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
/**
* A command to display help for available commands.
*/
public class Help extends GetCommand {
public Help() {
super("help", "display help for available commands");
}
@Override
public String action(IStorage productCollection) {
return SystemCommandManager.getRegisteredCommand();
}
}

View File

@ -1,19 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
public class IsIdExists extends PostCommand {
public IsIdExists() {
super("is_id_exists", "", Pattern.compile("^is_id_exists +."));
}
@Override
public String action(IStorage productCollection, Object obj) throws CommandActionException {
if (!productCollection.isProductIdExists((Long) obj))
throw new CommandActionException("Продукт с таким ID не существует");
return "Exists";
}
}

View File

@ -1,18 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
public abstract class PostCommand extends Command {
public PostCommand(String name, String description, Pattern pattern) {
super(name, description, pattern);
}
public PostCommand(String name, String description) {
super(name, description);
}
public abstract String action(IStorage productCollection, Object obj) throws CommandActionException;
}

View File

@ -1,18 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
/**
* A command to print the elements of the collection in ascending order.
*/
public class PrintAscending extends GetCommand {
public PrintAscending() {
super("print_ascending", "вывести элементы коллекции в порядке возрастания");
}
@Override
public String action(IStorage productCollection) {
var result = productCollection.toString();
return result.isEmpty() ? "Коллекция пуста" : result;
}
}

View File

@ -1,29 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import java.util.regex.Pattern;
/**
* A command to remove an element from the collection by its id.
*/
public class Remove extends PostCommand {
public Remove() {
super("remove_by_id id", "удалить элемент из коллекции по его id", Pattern.compile("^remove_by_id .+"));
}
@Override
public String action(IStorage productCollection, Object object) throws CommandActionException {
try {
Long id = (Long) object;
if (productCollection.isProductIdExists(id)) {
var product = productCollection.removeProduct(id);
return String.format("Продукт %s был удалён", product.getName());
}
return "Продукта с таким id не существует";
} catch (NumberFormatException e) {
return "Неверный аргумент, id может быть только число";
}
}
}

View File

@ -1,25 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
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 PostCommand {
public RemoveLower() {
super("remove_lower id", "удалить из коллекции все элементы, меньшие, чем заданный по id", Pattern.compile("^remove_lower .+"));
}
@Override
public String action(IStorage productCollection, Object object) {
try {
Long id = (Long) object;
var size = productCollection.removeLover(id);
return String.format("Было удалено %d продуктов", size);
} catch (NumberFormatException e) {
return "Неверный аргумент, id может быть только число";
}
}
}

View File

@ -1,18 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
/**
* A command to display all elements of the collection in string representation to the standard output stream.
*/
public class Show extends GetCommand {
public Show() {
super("show", "вывести в стандартный поток вывода все элементы коллекции в строковом представлении");
}
@Override
public String action(IStorage productCollection) {
var result = productCollection.toString();
return result.isEmpty() ? "Коллекция пуста" : result;
}
}

View File

@ -1,34 +0,0 @@
package me.zinch.Lab6.Server.commands;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* Manages the registration and retrieval of commands.
*/
public class SystemCommandManager {
private static final List<Command> commandList = new ArrayList<>();
static {
registerCommand(new Exit());
registerCommand(new Save());
registerCommand(new Help());
}
public static void registerCommand(Command command) {
commandList.add(command);
}
public static String getRegisteredCommand() {
return String.join("\n", commandList
.stream()
.map(command -> String.format("%s - %s", command.getName(), command.getDescription()))
.toList());
}
public static Optional<Command> getCommandByInput(String input) {
return commandList.stream().filter(command -> command.checkPattern(input)).findFirst();
}
}

View File

@ -1,17 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
/**
* Represents an unknown command.
*/
public class UnknownCommand extends GetCommand {
public UnknownCommand() {
super("", "");
}
@Override
public String action(IStorage productCollection) {
return "Команда не распознана.";
}
}

View File

@ -1,40 +0,0 @@
package me.zinch.Lab6.Server.commands;
import me.zinch.Lab6.Domain.dto.MessageBody;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.models.ProductDTO;
import me.zinch.Lab6.Domain.validator.Validators;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
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 PostCommand {
public Update() {
super("update id {element}", "обновить значение элемента коллекции, id которого равен заданному", Pattern.compile("^update .+"));
}
@Override
public String action(IStorage productCollection, Object object) throws CommandActionException {
try {
var body = (MessageBody) object;
var id = Long.parseLong(body.getCommand().split(" ")[1]);
if (productCollection.isProductIdExists(id)) {
var productDTO = (ProductDTO) body.getBody();
try {
Validators.validateObject(productDTO).throwIfNotValid();
var product = productCollection.updateProduct(id, productDTO);
return String.format("Продукт %s был изменён", product.getName());
} catch (ValidationException e) {
throw new CommandActionException(e.getMessage());
}
}
return "Продукта с таким id не существует";
} catch (NumberFormatException e) {
return "Неверный аргумент, id может быть только число";
}
}
}

View File

@ -1,24 +0,0 @@
package me.zinch.Lab6.Server.configs;
import me.zinch.Lab6.Domain.wrapper.IStorage;
public class ServerConfig {
private Integer port;
private IStorage collection;
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public IStorage getCollection() {
return collection;
}
public void setCollection(IStorage collection) {
this.collection = collection;
}
}

View File

@ -1,86 +0,0 @@
package me.zinch.Lab6.Server.console;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.ServerBuilder;
import me.zinch.Lab6.Server.commands.GetCommand;
import me.zinch.Lab6.Server.commands.SystemCommandManager;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import me.zinch.Lab6.Server.files.DbController;
import me.zinch.Lab6.Server.wrapper.ProductCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
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 final Logger log = LoggerFactory.getLogger(Console.class);
private static boolean isRunning = true;
private static boolean isExitMessageShowed = false;
private static IStorage collection;
public static void stopApp() {
showExitMessage();
isRunning = false;
System.exit(0);
}
public static void showExitMessage() {
if (!isExitMessageShowed) log.info("Have a nice day!");
isExitMessageShowed = true;
}
public static void run() throws IOException, ValidationException {
collection = new ProductCollection(DbController.loadDb());
var server = new ServerBuilder()
.setPort(3000)
.setCollection(collection)
.build();
Console.handleInput();
server.run();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
Console.stopApp();
try {
server.stop();
} catch (IOException e) {
throw new RuntimeException(e);
}
}));
}
public static void handleInput() {
new Thread(() -> {
while (isRunning) {
try {
var input = scanner.nextLine().trim();
var command = SystemCommandManager.getCommandByInput(input);
if (command.isPresent()) {
var action = (GetCommand) command.get();
try {
log.info(action.action(collection));
} catch (CommandActionException e) {
log.info(e.getMessage());
}
} else {
log.info("This command does not exist. Write help to see a list of available commands.");
}
} catch (NoSuchElementException e) {
stopApp();
}
}
scanner.close();
}).start();
}
}

View File

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

View File

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

View File

@ -1,80 +0,0 @@
package me.zinch.Lab6.Server.files;
import com.opencsv.CSVReader;
import com.opencsv.CSVWriter;
import com.opencsv.exceptions.CsvValidationException;
import me.zinch.Lab6.Domain.models.Coordinates;
import me.zinch.Lab6.Domain.models.Dragon;
import me.zinch.Lab6.Domain.models.DragonCharacter;
import me.zinch.Lab6.Domain.models.DragonHead;
import me.zinch.Lab6.Domain.models.DragonType;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
public class CsvController {
private static final String[] HEADERS = {
"id", "name", "coordinates_x", "coordinates_y", "creation_date",
"age", "weight", "type", "character", "head_size"
};
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
public static List<Dragon> loadFromCsv(String filename) throws IOException {
List<Dragon> dragons = new ArrayList<>();
try (CSVReader reader = new CSVReader(new FileReader(filename))) {
// Skip headers
reader.readNext();
String[] line;
while ((line = reader.readNext()) != null) {
dragons.add(parseDragon(line));
}
} catch (CsvValidationException e) {
throw new IOException("Error reading CSV file: " + e.getMessage());
}
return dragons;
}
public static void saveToCsv(List<Dragon> dragons, String filename) throws IOException {
try (CSVWriter writer = new CSVWriter(new FileWriter(filename))) {
writer.writeNext(HEADERS);
for (Dragon dragon : dragons) {
writer.writeNext(new String[]{
String.valueOf(dragon.getId()),
dragon.getName(),
String.valueOf(dragon.getCoordinates().getX()),
String.valueOf(dragon.getCoordinates().getY()),
dragon.getCreationDate().format(DATE_FORMATTER),
String.valueOf(dragon.getAge()),
String.valueOf(dragon.getWeight()),
dragon.getType().name(),
dragon.getCharacter().name(),
dragon.getHead() != null ? String.valueOf(dragon.getHead().getSize()) : ""
});
}
}
}
private static Dragon parseDragon(String[] fields) {
return new Dragon(
Integer.parseInt(fields[0]),
fields[1],
new Coordinates(
Long.parseLong(fields[2]),
Integer.parseInt(fields[3])
),
LocalDateTime.parse(fields[4], DATE_FORMATTER),
Integer.parseInt(fields[5]),
Long.parseLong(fields[6]),
DragonType.valueOf(fields[7]),
DragonCharacter.valueOf(fields[8]),
fields[9].isEmpty() ? null : new DragonHead(Float.parseFloat(fields[9]))
);
}
}

View File

@ -1,81 +0,0 @@
package me.zinch.Lab6.Server.modules;
import me.zinch.Lab6.Domain.dto.BodylessMessage;
import me.zinch.Lab6.Domain.dto.Message;
import me.zinch.Lab6.Domain.dto.MessageBody;
import me.zinch.Lab6.Domain.dto.MessageType;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.commands.CommandManager;
import me.zinch.Lab6.Server.commands.GetCommand;
import me.zinch.Lab6.Server.commands.PostCommand;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
public class CommandModule {
private static final Logger log = LoggerFactory.getLogger(CommandModule.class);
private final IStorage storage;
public CommandModule(IStorage storage) {
this.storage = storage;
}
public ByteBuffer processCommand(Message message) throws IOException {
return switch (message.getType()) {
case HELLO -> serialize(new BodylessMessage(MessageType.HELLO));
case GET -> handleGetCommand((String) message.getBody());
case POST -> handlePostCommand((MessageBody) message.getBody());
default -> {
log.error("Message type not supported");
yield null;
}
};
}
private ByteBuffer handleGetCommand(String inputCommand) throws IOException {
var command = CommandManager.getCommandByInput(inputCommand);
if (command.isEmpty()) {
log.error("Command {} not found", inputCommand);
return serialize(new BodylessMessage(MessageType.ERROR, String.format("Command %s not found", inputCommand)));
}
try {
var action = (GetCommand) command.get();
return serialize(new BodylessMessage(MessageType.OK, action.action(storage)));
} catch (CommandActionException e) {
return serialize(new BodylessMessage(MessageType.ERROR, e.getMessage()));
}
}
private ByteBuffer handlePostCommand(MessageBody request) throws IOException {
var inputCommand = request.getCommand();
var body = request.getBody();
var command = CommandManager.getCommandByInput(inputCommand);
if (command.isEmpty()) {
log.error("Command {} not found", inputCommand);
return serialize(new BodylessMessage(MessageType.ERROR, String.format("Command %s not found", inputCommand)));
}
try {
var action = (PostCommand) command.get();
return serialize(new BodylessMessage(MessageType.OK, action.action(storage, body)));
} catch (CommandActionException e) {
return serialize(new BodylessMessage(MessageType.ERROR, e.getMessage()));
}
}
private static ByteBuffer serialize(Serializable obj) throws IOException {
try (var bOut = new ByteArrayOutputStream();
var oOut = new ObjectOutputStream(bOut)) {
oOut.writeObject(obj);
oOut.flush();
return ByteBuffer.wrap(bOut.toByteArray());
}
}
}

View File

@ -1,40 +0,0 @@
package me.zinch.Lab6.Server.modules;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class ConnectionModule {
private static final Logger log = LoggerFactory.getLogger(ConnectionModule.class);
private final ServerSocketChannel serverSocketChannel;
private final Selector selector;
public ConnectionModule(int port, Selector selector) throws IOException {
this.selector = selector;
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
log.info("Connection module initialized on port {}", port);
}
public void handleAccept(SelectionKey key) throws IOException {
var server = (ServerSocketChannel) key.channel();
var client = server.accept();
if (client != null) {
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
log.info("Accepted connection from {}", client.getRemoteAddress());
}
}
public void close() throws IOException {
serverSocketChannel.close();
}
}

View File

@ -1,41 +0,0 @@
package me.zinch.Lab6.Server.modules;
import me.zinch.Lab6.Domain.dto.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Map;
public class RequestModule {
private static final Logger log = LoggerFactory.getLogger(RequestModule.class);
private final Map<SocketChannel, Message> clientMessages;
public RequestModule(Map<SocketChannel, Message> clientMessages) {
this.clientMessages = clientMessages;
}
public void handleRead(SelectionKey key) throws IOException, ClassNotFoundException {
var client = (SocketChannel) key.channel();
var buffer = ByteBuffer.allocate(8192);
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
client.close();
return;
}
buffer.flip();
try (var oIn = new ObjectInputStream(new ByteArrayInputStream(buffer.array()))) {
var msg = (Message) oIn.readObject();
log.info("Received message {} from {}", msg, client.getRemoteAddress());
clientMessages.put(client, msg);
client.register(key.selector(), SelectionKey.OP_WRITE);
}
}
}

View File

@ -1,42 +0,0 @@
package me.zinch.Lab6.Server.modules;
import me.zinch.Lab6.Domain.dto.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Map;
public class ResponseModule {
private static final Logger log = LoggerFactory.getLogger(ResponseModule.class);
private final Map<SocketChannel, Message> clientMessages;
private final CommandModule commandModule;
public ResponseModule(Map<SocketChannel, Message> clientMessages, CommandModule commandModule) {
this.clientMessages = clientMessages;
this.commandModule = commandModule;
}
public void handleWrite(SelectionKey key) throws IOException {
var client = (SocketChannel) key.channel();
var message = clientMessages.get(client);
if (message == null) {
log.error("Message for {} not found", client.getRemoteAddress());
client.close();
return;
}
ByteBuffer responseBuffer = commandModule.processCommand(message);
if (responseBuffer != null) {
client.write(responseBuffer);
log.info("Sent response to {}", client.getRemoteAddress());
}
clientMessages.remove(client);
client.close();
}
}

View File

@ -1,123 +0,0 @@
package me.zinch.Lab6.Server.wrapper;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.models.Dragon;
import me.zinch.Lab6.Domain.models.DragonCharacter;
import me.zinch.Lab6.Domain.models.DragonType;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
public class DragonCollection implements IStorage {
private final LinkedList<Dragon> dragons;
private Integer idIncrementor;
public DragonCollection(List<Dragon> list) throws ValidationException {
dragons = new LinkedList<>(list);
idIncrementor = dragons.stream()
.mapToInt(Dragon::getId)
.max()
.orElse(0) + 1;
}
private Integer generateId() {
return idIncrementor++;
}
public Dragon getDragonById(Integer id) {
return dragons.stream()
.filter(d -> Objects.equals(d.getId(), id))
.findFirst()
.orElse(null);
}
public Dragon addDragon(Dragon dragon) {
dragons.add(dragon);
return dragon;
}
public Dragon updateDragon(Integer id, Dragon newDragon) {
var index = dragons.indexOf(getDragonById(id));
if (index != -1) {
dragons.set(index, newDragon);
}
return newDragon;
}
public Dragon removeDragon(Integer id) {
var dragon = getDragonById(id);
dragons.remove(dragon);
return dragon;
}
public String getInfo() {
var initDate = dragons.stream()
.map(Dragon::getCreationDate)
.min(Comparator.naturalOrder())
.orElse(LocalDateTime.MIN);
return String.format("Type: LinkedList%nInitialization date: %s%nNumber of elements: %s",
initDate, dragons.size());
}
public void clear() {
dragons.clear();
}
public Dragon getHead() {
return dragons.isEmpty() ? null : dragons.getFirst();
}
public Dragon removeHead() {
return dragons.isEmpty() ? null : dragons.removeFirst();
}
public Dragon getMaxByWeight() {
return dragons.stream()
.max(Comparator.comparingLong(Dragon::getWeight))
.orElse(null);
}
public Map<DragonType, Long> groupCountingByType() {
return dragons.stream()
.collect(Collectors.groupingBy(
Dragon::getType,
Collectors.counting()
));
}
public List<Dragon> filterLessThanCharacter(DragonCharacter character) {
return dragons.stream()
.filter(d -> d.getCharacter().compareTo(character) < 0)
.collect(Collectors.toList());
}
public List<Dragon> toList() {
return new LinkedList<>(dragons);
}
@Override
public String toString() {
return dragons.stream()
.map(Dragon::toString)
.collect(Collectors.joining("\n"));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DragonCollection that = (DragonCollection) o;
return Objects.equals(dragons, that.dragons) && Objects.equals(idIncrementor, that.idIncrementor);
}
@Override
public int hashCode() {
return Objects.hash(dragons, idIncrementor);
}
}

View File

@ -1,11 +0,0 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

View File

@ -1,8 +1,99 @@
# Лабораторная работа №6
# Лабораторная работа №5
## Выполнил
> ФИ: Зинченко Иван
>
> ИСУ: 408657
>
> Вариант: 21290
> Вариант: 1632
## Задание
Реализовать консольное приложение, которое реализует управление коллекцией объектов в интерактивном режиме.
В коллекции необходимо хранить объекты класса `Product`, описание которого приведено ниже.
Разработанная программа должна удовлетворять следующим требованиям:
- Класс, коллекцией экземпляров которого управляет программа, должен реализовывать сортировку по умолчанию.
- Все требования к полям класса (указанные в виде комментариев) должны быть выполнены.
- Для хранения необходимо использовать коллекцию типа `java.util.TreeSet`
- При запуске приложения коллекция должна автоматически заполняться значениями из файла.
- Имя файла должно передаваться программе с помощью: переменная окружения.
- Данные должны храниться в файле в формате `xml`
- Чтение данных из файла необходимо реализовать с помощью класса `java.io.BufferedInputStream`
- Запись данных в файл необходимо реализовать с помощью класса `java.io.OutputStreamWriter`
- Все классы в программе должны быть задокументированы в формате javadoc.
- Программа должна корректно работать с неправильными данными (ошибки пользовательского ввода, отсутсвие прав доступа к файлу и т.п.).
В интерактивном режиме программа должна поддерживать выполнение следующих команд:
- `help` : вывести справку по доступным командам
- `info` : вывести в стандартный поток вывода информацию о коллекции (тип, дата инициализации, количество элементов и т.д.)
- `show` : вывести в стандартный поток вывода все элементы коллекции в строковом представлении
- `add {element}` : добавить новый элемент в коллекцию
- `update id {element}` : обновить значение элемента коллекции, id которого равен заданному
- `remove_by_id id` : удалить элемент из коллекции по его id
- `clear` : очистить коллекцию
- `save` : сохранить коллекцию в файл
- `execute_script file_name` : считать и исполнить скрипт из указанного файла. В скрипте содержатся команды в таком же виде, в котором их вводит пользователь в интерактивном режиме.
- `exit` : завершить программу (без сохранения в файл)
- `add_if_max {element}` : добавить новый элемент в коллекцию, если его значение превышает значение наибольшего элемента этой коллекции
- `add_if_min {element}` : добавить новый элемент в коллекцию, если его значение меньше, чем у наименьшего элемента этой коллекции
- `remove_lower {element}` : удалить из коллекции все элементы, меньшие, чем заданный
- `filter_contains_name name` : вывести элементы, значение поля name которых содержит заданную подстроку
- `print_ascending` : вывести элементы коллекции в порядке возрастания
- `print_unique_manufacture_cost` : вывести уникальные значения поля manufactureCost всех элементов в коллекции
Формат ввода команд:
- Все аргументы команды, являющиеся стандартными типами данных (примитивные типы, классы-оболочки, String, классы для хранения дат), должны вводиться в той же строке, что и имя команды.
- Все составные типы данных (объекты классов, хранящиеся в коллекции) должны вводиться по одному полю в строку.
- При вводе составных типов данных пользователю должно показываться приглашение к вводу, содержащее имя поля (например, "Введите дату рождения:")
- Если поле является enum'ом, то вводится имя одной из его констант (при этом список констант должен быть предварительно выведен).
- При некорректном пользовательском вводе (введена строка, не являющаяся именем константы в enum'е; введена строка вместо числа; введённое число не входит в указанные границы и т.п.) должно быть показано сообщение об ошибке и предложено повторить ввод поля.
- Для ввода значений null использовать пустую строку.
- Поля с комментарием "Значение этого поля должно генерироваться автоматически" не должны вводиться пользователем вручную при добавлении.
Описание хранимых в коллекции классов:
```java
public class Product {
private Long id; //Поле не может быть null, Значение поля должно быть больше 0, Значение этого поля должно быть уникальным, Значение этого поля должно генерироваться автоматически
private String name; //Поле не может быть null, Строка не может быть пустой
private Coordinates coordinates; //Поле не может быть null
private java.time.ZonedDateTime creationDate; //Поле не может быть null, Значение этого поля должно генерироваться автоматически
private Long price; //Поле не может быть null, Значение поля должно быть больше 0
private String partNumber; //Длина строки не должна быть больше 82, Значение этого поля должно быть уникальным, Строка не может быть пустой, Поле может быть null
private long manufactureCost;
private UnitOfMeasure unitOfMeasure; //Поле не может быть null
private Person owner; //Поле не может быть null
}
public class Coordinates {
private long x; //Максимальное значение поля: 883
private Long y; //Значение поля должно быть больше -427, Поле не может быть null
}
public class Person {
private String name; //Поле не может быть null, Строка не может быть пустой
private String passportID; //Длина строки должна быть не меньше 5, Длина строки не должна быть больше 22, Поле не может быть null
private Color hairColor; //Поле может быть null
private Location location; //Поле может быть null
}
public class Location {
private float x;
private Integer y; //Поле не может быть null
private String name; //Строка не может быть пустой, Поле может быть null
}
public enum UnitOfMeasure {
KILOGRAMS,
CENTIMETERS,
GRAMS
}
public enum Color {
GREEN,
BLACK,
BLUE,
ORANGE,
WHITE
}
```

2
db.xml
View File

@ -1 +1 @@
<Products><Product><id>6</id><name>1</name><coordinates><x>1</x><y>11</y></coordinates><creationDate>1716766945.584662600</creationDate><price>1</price><partNumber>1</partNumber><manufactureCost>231</manufactureCost><unitOfMeasure>CENTIMETERS</unitOfMeasure><owner><name>1</name><passportID>313412</passportID><hairColor>BLACK</hairColor><location><x>1.0</x><y>1</y><name>1</name></location></owner></Product><Product><id>1</id><name>Product 1</name><coordinates><x>100</x><y>50</y></coordinates><creationDate>1709294400.000000000</creationDate><price>50</price><partNumber>PN123</partNumber><manufactureCost>30</manufactureCost><unitOfMeasure>KILOGRAMS</unitOfMeasure><owner><name>John Doe</name><passportID>AB12345</passportID><hairColor>BLACK</hairColor><location><x>10.5</x><y>20</y><name>Home</name></location></owner></Product><Product><id>2</id><name>Product 2</name><coordinates><x>200</x><y>100</y></coordinates><creationDate>1709294700.000000000</creationDate><price>80</price><partNumber>PN456</partNumber><manufactureCost>60</manufactureCost><unitOfMeasure>CENTIMETERS</unitOfMeasure><owner><name>Alice Smith</name><passportID>CD67890</passportID><hairColor>BLUE</hairColor><location><x>20.8</x><y>30</y><name>Office</name></location></owner></Product><Product><id>3</id><name>Product 3</name><coordinates><x>150</x><y>-200</y></coordinates><creationDate>1709295000.000000000</creationDate><price>120</price><manufactureCost>90</manufactureCost><unitOfMeasure>GRAMS</unitOfMeasure><owner><name>Emma Johnson</name><passportID>EF24680</passportID><hairColor>GREEN</hairColor><location><x>30.2</x><y>40</y><name>Warehouse</name></location></owner></Product><Product><id>4</id><name>Product 4</name><coordinates><x>300</x><y>-300</y></coordinates><creationDate>1709295300.000000000</creationDate><price>200</price><partNumber>PN789</partNumber><manufactureCost>150</manufactureCost><unitOfMeasure>KILOGRAMS</unitOfMeasure><owner><name>Bob Brown</name><passportID>GH13579</passportID><hairColor>ORANGE</hairColor><location><x>40.6</x><y>50</y><name>Factory</name></location></owner></Product><Product><id>5</id><name>Product 5</name><coordinates><x>400</x><y>400</y></coordinates><creationDate>1709295600.000000000</creationDate><price>150</price><partNumber>PN246</partNumber><manufactureCost>100</manufactureCost><unitOfMeasure>CENTIMETERS</unitOfMeasure><owner><name>Grace Lee</name><passportID>IJ35791</passportID><hairColor>WHITE</hairColor><location><x>50.9</x><y>60</y><name>Store</name></location></owner></Product></Products>
<Products><Product><id>341</id><name>ewreqr</name><coordinates><x>1432</x><y>132432</y></coordinates><creationDate>1711875600.000000000</creationDate><price>12324</price><partNumber>3214</partNumber><manufactureCost>342</manufactureCost><unitOfMeasure>CENTIMETERS</unitOfMeasure><owner><name>324324</name><passportID>32143214</passportID><hairColor>WHITE</hairColor><location><x>342.0</x><y>423</y><name>214</name></location></owner></Product><Product><id>342</id><name>Hello</name><coordinates><x>13</x><y>4</y></coordinates><creationDate>1713267558.813808800</creationDate><price>12312</price><partNumber>312</partNumber><manufactureCost>321</manufactureCost><unitOfMeasure>CENTIMETERS</unitOfMeasure><owner><name>123</name><passportID>431</passportID><hairColor>BLACK</hairColor><location><x>2321.0</x><y>432</y><name>31</name></location></owner></Product></Products>

117
db.xml.example Normal file
View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Products>
<Product>
<id>1</id>
<name>Product 1</name>
<coordinates>
<x>100</x>
<y>50</y>
</coordinates>
<creationDate>2024-03-01T12:00:00Z</creationDate>
<price>50</price>
<partNumber>PN123</partNumber>
<manufactureCost>30</manufactureCost>
<unitOfMeasure>KILOGRAMS</unitOfMeasure>
<owner>
<name>John Doe</name>
<passportID>AB12345</passportID>
<hairColor>BLACK</hairColor>
<location>
<x>10.5</x>
<y>20</y>
<name>Home</name>
</location>
</owner>
</Product>
<Product>
<id>2</id>
<name>Product 2</name>
<coordinates>
<x>200</x>
<y>100</y>
</coordinates>
<creationDate>2024-03-01T12:05:00Z</creationDate>
<price>80</price>
<partNumber>PN456</partNumber>
<manufactureCost>60</manufactureCost>
<unitOfMeasure>CENTIMETERS</unitOfMeasure>
<owner>
<name>Alice Smith</name>
<passportID>CD67890</passportID>
<hairColor>BLUE</hairColor>
<location>
<x>20.8</x>
<y>30</y>
<name>Office</name>
</location>
</owner>
</Product>
<Product>
<id>3</id>
<name>Product 3</name>
<coordinates>
<x>150</x>
<y>-200</y>
</coordinates>
<creationDate>2024-03-01T12:10:00Z</creationDate>
<price>120</price>
<manufactureCost>90</manufactureCost>
<unitOfMeasure>GRAMS</unitOfMeasure>
<owner>
<name>Emma Johnson</name>
<passportID>EF24680</passportID>
<hairColor>GREEN</hairColor>
<location>
<x>30.2</x>
<y>40</y>
<name>Warehouse</name>
</location>
</owner>
</Product>
<Product>
<id>4</id>
<name>Product 4</name>
<coordinates>
<x>300</x>
<y>-300</y>
</coordinates>
<creationDate>2024-03-01T12:15:00Z</creationDate>
<price>200</price>
<partNumber>PN789</partNumber>
<manufactureCost>150</manufactureCost>
<unitOfMeasure>KILOGRAMS</unitOfMeasure>
<owner>
<name>Bob Brown</name>
<passportID>GH13579</passportID>
<hairColor>ORANGE</hairColor>
<location>
<x>40.6</x>
<y>50</y>
<name>Factory</name>
</location>
</owner>
</Product>
<Product>
<id>5</id>
<name>Product 5</name>
<coordinates>
<x>400</x>
<y>400</y>
</coordinates>
<creationDate>2024-03-01T12:20:00Z</creationDate>
<price>150</price>
<partNumber>PN246</partNumber>
<manufactureCost>100</manufactureCost>
<unitOfMeasure>CENTIMETERS</unitOfMeasure>
<owner>
<name>Grace Lee</name>
<passportID>IJ35791</passportID>
<hairColor>WHITE</hairColor>
<location>
<x>50.9</x>
<y>60</y>
<name>Store</name>
</location>
</owner>
</Product>
</Products>

74
pom.xml Normal file
View File

@ -0,0 +1,74 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.zinch</groupId>
<artifactId>Lab5</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>Lab5</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>5.0.0-M1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>3.7.1</version>
<configuration>
<archive>
<manifest>
<mainClass>me.zinch.App</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</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

@ -0,0 +1,25 @@
package me.zinch;
import me.zinch.console.Console;
import me.zinch.console.ShutdownHook;
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());
try {
Console.run();
} catch (ValidationException e) {
System.out.println("Ошибка при валидации БД. Некоторые данные не валидны.");
System.out.println(e.getMessage());
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}

View File

@ -0,0 +1,31 @@
package me.zinch.commands;
import me.zinch.console.Console;
import me.zinch.exceptions.CommandActionException;
import me.zinch.exceptions.ValidationException;
import me.zinch.validator.Validators;
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"));
}
@Override
public String action(ProductCollection productCollection) throws CommandActionException {
var productDTO = Console.readProductDTO();
try {
Validators.validateObject(productDTO).throwIfNotValid();
var product = productCollection.addProduct(productDTO);
return String.format("Продукт %s был добавлен под id %d", product.getName(), product.getId());
} catch (ValidationException e) {
throw new CommandActionException(e.getMessage());
}
}
}

View File

@ -0,0 +1,34 @@
package me.zinch.commands;
import me.zinch.console.Console;
import me.zinch.exceptions.CommandActionException;
import me.zinch.exceptions.ValidationException;
import me.zinch.validator.Validators;
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+"));
}
@Override
public String action(ProductCollection productCollection) throws CommandActionException {
var productDTO = Console.readProductDTO();
try {
Validators.validateObject(productDTO).throwIfNotValid();
if (productCollection.getMaxPrice() < productDTO.getPrice()) {
var product = productCollection.addProduct(productDTO);
return String.format("Продукт %s был добавлен под id %d", product.getName(), product.getId());
}
return "Продукт не подходит под условие";
} catch (ValidationException e) {
throw new CommandActionException(e.getMessage());
}
}
}

View File

@ -0,0 +1,34 @@
package me.zinch.commands;
import me.zinch.console.Console;
import me.zinch.exceptions.CommandActionException;
import me.zinch.exceptions.ValidationException;
import me.zinch.validator.Validators;
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+"));
}
@Override
public String action(ProductCollection productCollection) {
var productDTO = Console.readProductDTO();
try {
Validators.validateObject(productDTO).throwIfNotValid();
if (productCollection.getMinPrice() > productDTO.getPrice()) {
var product = productCollection.addProduct(productDTO);
return String.format("Продукт %s был добавлен под id %d", product.getName(), product.getId());
}
return "Продукт не подходит под условие";
} catch (ValidationException e) {
throw new CommandActionException(e.getMessage());
}
}
}

View File

@ -1,18 +1,18 @@
package me.zinch.Lab6.Server.commands;
package me.zinch.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.wrapper.ProductCollection;
/**
* The Clear class represents a command to clear a collection.
* It extends the Command class.
*/
public class Clear extends GetCommand {
public class Clear extends Command {
public Clear() {
super("clear", "очистить коллекцию");
}
@Override
public String action(IStorage productCollection) {
public String action(ProductCollection productCollection) {
productCollection.clear();
return "Коллекция была очищена";
}

View File

@ -1,4 +1,7 @@
package me.zinch.Lab6.Server.commands;
package me.zinch.commands;
import me.zinch.exceptions.CommandActionException;
import me.zinch.wrapper.ProductCollection;
import java.util.regex.Pattern;
@ -37,4 +40,6 @@ public abstract class Command {
public final boolean checkPattern(String input) {
return pattern.matcher(input).matches();
}
public abstract String action(ProductCollection productCollection) throws CommandActionException;
}

View File

@ -1,4 +1,4 @@
package me.zinch.Lab6.Server.commands;
package me.zinch.commands;
import java.util.ArrayList;
import java.util.List;
@ -10,27 +10,6 @@ import java.util.Optional;
public class CommandManager {
private static final List<Command> commandList = new ArrayList<>();
static {
registerCommand(new Show());
registerCommand(new Exit());
registerCommand(new Info());
registerCommand(new Clear());
registerCommand(new PrintAscending());
registerCommand(new PrintUniqueManufactureCost());
registerCommand(new FilterContainsName());
registerCommand(new Add());
registerCommand(new IsIdExists());
registerCommand(new Update());
registerCommand(new Remove());
registerCommand(new AddIfMax());
registerCommand(new AddIfMin());
registerCommand(new RemoveLower());
registerCommand(new Save());
registerCommand(new GetProduct());
registerCommand(new Help());
}
public static void registerCommand(Command command) {
commandList.add(command);
}
@ -45,4 +24,24 @@ public class CommandManager {
public static Optional<Command> getCommandByInput(String input) {
return commandList.stream().filter(command -> command.checkPattern(input)).findFirst();
}
static {
registerCommand(new Show());
registerCommand(new Exit());
registerCommand(new Info());
registerCommand(new Clear());
registerCommand(new PrintAscending());
registerCommand(new PrintUniqueManufactureCost());
registerCommand(new FilterContainsName());
registerCommand(new Add());
registerCommand(new Update());
registerCommand(new Remove());
registerCommand(new AddIfMax());
registerCommand(new AddIfMin());
registerCommand(new RemoveLower());
registerCommand(new Save());
registerCommand(new ExecuteScript());
registerCommand(new Help());
}
}

View File

@ -1,8 +1,8 @@
package me.zinch.Lab6.Client.commands;
package me.zinch.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.Lab6.Client.files.ScriptLoader;
import me.zinch.console.Console;
import me.zinch.files.ScriptLoader;
import me.zinch.wrapper.ProductCollection;
import java.io.IOException;
import java.util.ArrayList;
@ -15,10 +15,6 @@ import java.util.regex.Pattern;
public class ExecuteScript extends Command {
private static final List<String> executeStack = new ArrayList<>();
public ExecuteScript() {
super("execute_script file_name", "считать и исполнить скрипт из указанного файла. В скрипте содержатся команды в таком же виде, в котором их вводит пользователь в интерактивном режиме", Pattern.compile("^execute_script .+"));
}
private void addFileToStack(String filePath) {
executeStack.add(filePath);
}
@ -31,15 +27,17 @@ public class ExecuteScript extends Command {
return executeStack.contains(filePath);
}
public ExecuteScript() {
super("execute_script file_name", "считать и исполнить скрипт из указанного файла. В скрипте содержатся команды в таком же виде, в котором их вводит пользователь в интерактивном режиме", Pattern.compile("^execute_script .+"));
}
@Override
public String action(Client client) {
public String action(ProductCollection productCollection) {
try {
var result = new StringBuilder();
var scriptFile = Console.getLastCommand().split(" ")[1];
result.append(String.format("Скрипт %s начинает работу.", scriptFile)).append("\n");
addFileToStack(scriptFile);
var commandList = ScriptLoader.loadList(scriptFile);
for (var input : commandList) {
for (var input: commandList) {
input = input.trim();
var command = CommandManager.getCommandByInput(input);
if (command.isPresent()) {
@ -49,14 +47,13 @@ public class ExecuteScript extends Command {
if (checkFileInStack(nextFile)) continue;
addFileToStack(nextFile);
}
result.append(command.get().action(client)).append("\n");
command.get().action(productCollection);
} else {
new UnknownCommand().action(client);
new UnknownCommand().action(productCollection);
}
}
clearStack();
result.append(String.format("Скрипт %s завершил работу.", scriptFile)).append("\n");
return result.toString();
return String.format("Скрипт %s завершил работу.%n", scriptFile);
} catch (IOException e) {
return e.getMessage();
}

View File

@ -1,7 +1,7 @@
package me.zinch.Lab6.Client.commands;
package me.zinch.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.console.Console;
import me.zinch.wrapper.ProductCollection;
/**
* A command to exit the program without saving to a file.
@ -12,8 +12,8 @@ public class Exit extends Command {
}
@Override
public String action(Client client) {
Console.stopAppWithoutSaving();
public String action(ProductCollection productCollection) {
Console.stopApp();
return "";
}
}

View File

@ -0,0 +1,21 @@
package me.zinch.commands;
import me.zinch.console.Console;
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+"));
}
@Override
public String action(ProductCollection productCollection) {
String name = Console.getLastCommand().split(" ")[1];
return productCollection.filterContainsName(name);
}
}

View File

@ -1,6 +1,6 @@
package me.zinch.Lab6.Client.commands;
package me.zinch.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.wrapper.ProductCollection;
/**
* A command to display help for available commands.
@ -11,7 +11,7 @@ public class Help extends Command {
}
@Override
public String action(Client client) {
public String action(ProductCollection productCollection) {
return CommandManager.getRegisteredCommand();
}
}

View File

@ -1,17 +1,17 @@
package me.zinch.Lab6.Server.commands;
package me.zinch.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
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 GetCommand {
public class Info extends Command {
public Info() {
super("info", "вывести в стандартный поток вывода информацию о коллекции (тип, дата инициализации, количество элементов и т.д.)");
}
@Override
public String action(IStorage productCollection) {
public String action(ProductCollection productCollection) {
return productCollection.getInfo();
}
}

View File

@ -0,0 +1,17 @@
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", "вывести элементы коллекции в порядке возрастания");
}
@Override
public String action(ProductCollection productCollection) {
return productCollection.toString();
}
}

View File

@ -1,18 +1,17 @@
package me.zinch.Lab6.Server.commands;
package me.zinch.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
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 GetCommand {
public class PrintUniqueManufactureCost extends Command {
public PrintUniqueManufactureCost() {
super("print_unique_manufacture_cost", "вывести уникальные значения поля manufactureCost всех элементов в коллекции");
}
@Override
public String action(IStorage productCollection) {
var result = productCollection.getUniqueManufactureCost();
return result.isEmpty() ? "Коллекция пуста" : result;
public String action(ProductCollection productCollection) {
return productCollection.getUniqueManufactureCost();
}
}

View File

@ -0,0 +1,22 @@
package me.zinch.commands;
import me.zinch.console.Console;
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+"));
}
@Override
public String action(ProductCollection productCollection) {
Long id = Long.parseLong(Console.getLastCommand().split(" ")[1]);
var product = productCollection.removeProduct(id);
return String.format("Продукт %s был удалён", product.getName());
}
}

View File

@ -0,0 +1,22 @@
package me.zinch.commands;
import me.zinch.console.Console;
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+"));
}
@Override
public String action(ProductCollection productCollection) {
Long id = Long.parseLong(Console.getLastCommand().split(" ")[1]);
var size = productCollection.removeLover(id);
return String.format("Было удалено %d продуктов", size);
}
}

View File

@ -1,21 +1,21 @@
package me.zinch.Lab6.Server.commands;
package me.zinch.commands;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import me.zinch.Lab6.Server.exceptions.CommandActionException;
import me.zinch.Lab6.Server.files.DbController;
import me.zinch.exceptions.CommandActionException;
import me.zinch.files.DbController;
import me.zinch.wrapper.ProductCollection;
import java.io.IOException;
/**
* A command to save the collection to a file.
*/
public class Save extends GetCommand {
public class Save extends Command {
public Save() {
super("save", "сохранить коллекцию в файл");
}
@Override
public String action(IStorage productCollection) throws CommandActionException {
public String action(ProductCollection productCollection) throws CommandActionException {
try {
DbController.saveDb(productCollection.toList());
return "Коллекция была сохранена";

View File

@ -1,4 +1,6 @@
package me.zinch.Lab6.Client.commands;
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.
@ -7,4 +9,9 @@ public class Show extends Command {
public Show() {
super("show", "вывести в стандартный поток вывода все элементы коллекции в строковом представлении");
}
@Override
public String action(ProductCollection productCollection) {
return productCollection.toString();
}
}

View File

@ -1,6 +1,6 @@
package me.zinch.Lab6.Client.commands;
package me.zinch.commands;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.wrapper.ProductCollection;
/**
* Represents an unknown command.
@ -11,7 +11,7 @@ public class UnknownCommand extends Command {
}
@Override
public String action(Client client) {
public String action(ProductCollection productCollection) {
return "Команда не распознана.";
}
}

View File

@ -0,0 +1,34 @@
package me.zinch.commands;
import me.zinch.console.Console;
import me.zinch.exceptions.CommandActionException;
import me.zinch.exceptions.ValidationException;
import me.zinch.validator.Validators;
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+"));
}
@Override
public String action(ProductCollection productCollection) throws CommandActionException {
Long id = Long.parseLong(Console.getLastCommand().split(" ")[1]);
if (productCollection.isProductIdExists(id)) {
var productDTO = Console.readProductDTO();
try {
Validators.validateObject(productDTO).throwIfNotValid();
var product = productCollection.updateProduct(id, productDTO);
return String.format("Продукт %s был изменён", product.getName());
} catch (ValidationException e) {
throw new CommandActionException(e.getMessage());
}
}
return "Продукта с таким id не существует";
}
}

View File

@ -0,0 +1,171 @@
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.exceptions.ValidationException;
import me.zinch.files.DbController;
import me.zinch.models.Color;
import me.zinch.models.Coordinates;
import me.zinch.models.Location;
import me.zinch.models.Person;
import me.zinch.models.ProductDTO;
import me.zinch.models.UnitOfMeasure;
import me.zinch.validator.ValidateResult;
import me.zinch.validator.Validators;
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;
private static final List<String> history = new ArrayList<>();
public static void appendHistory(String input) {
history.add(input);
}
public static void stopApp() {
isRunning = false;
scanner.close();
}
public static String getLastCommand() {
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(readString());
System.out.print("Координата x: ");
long x = readLong();
System.out.print("Координата y: ");
long y = readLong();
productDTO.setCoordinates(new Coordinates(x, y));
System.out.print("Цена: ");
productDTO.setPrice(readLong());
System.out.print("Номер части: ");
productDTO.setPartNumber(readString());
System.out.print("Себестоимость: ");
productDTO.setManufactureCost(readLong());
productDTO.setUnitOfMeasure(readUnitOfMeasure());
System.out.print("Имя владельца: ");
var ownerName = readString();
System.out.print("Данные паспорта: ");
var passportId = readString();
Color color = readColor();
productDTO.setOwner(new Person(ownerName, passportId, color, readLocation()));
return productDTO;
} catch (ColorFormatException | UnitOfMeasureFormatException e) {
throw new IllegalProductDtoException(e.getMessage());
} catch (NumberFormatException | NullPointerException | IllegalFormatConversionException e) {
throw new IllegalProductDtoException();
}
}
public static void run() throws IOException, ValidationException {
var productList = DbController.loadDb();
var validationResult = Validators.validateProductList(productList);
if (!validationResult.stream().allMatch(ValidateResult::isValid)) {
throw new ValidationException(String.join("\n", validationResult
.stream()
.map(ValidateResult::getMessage)
.toList()));
}
var productCollection = new ProductCollection(productList);
System.out.println("Добро пожаловать! Для просмотра команд введите help");
while(isRunning) {
System.out.print(":");
try {
var input = scanner.nextLine().trim();
var command = CommandManager.getCommandByInput(input);
if (command.isPresent()) {
appendHistory(input);
try {
System.out.println(command.get().action(productCollection));
} catch (CommandActionException e) {
System.out.println(e.getMessage());
}
} else {
System.out.println("Такой команды не существует. Напишите help, чтобы посмотреть список доступных команд.");
}
} catch (NoSuchElementException e) {
stopApp();
}
System.out.println();
}
System.out.println("Всего хорошего!");
}
}

View File

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

View File

@ -1,4 +1,4 @@
package me.zinch.Lab6.Client.exceptions;
package me.zinch.exceptions;
public class ColorFormatException extends IllegalArgumentException {
public ColorFormatException() {

View File

@ -1,4 +1,4 @@
package me.zinch.Lab6.Server.exceptions;
package me.zinch.exceptions;
/**
* Represents an exception that occurs during the execution of a command action.

View File

@ -1,4 +1,4 @@
package me.zinch.Lab6.Server.exceptions;
package me.zinch.exceptions;
import java.io.IOException;
@ -9,8 +9,4 @@ public class DbInitializationException extends IOException {
public DbInitializationException() {
super("Ошибка! Не удалось инициализировать БД. Проверьте, что структура БД верна.");
}
public DbInitializationException(String message) {
super(message);
}
}

View File

@ -1,4 +1,4 @@
package me.zinch.Lab6.Server.exceptions;
package me.zinch.exceptions;
/**
* Represents an exception that occurs when an illegal product DTO is encountered during the creation or modification of a product.

View File

@ -1,4 +1,4 @@
package me.zinch.Lab6.Client.exceptions;
package me.zinch.exceptions;
public class UnitOfMeasureFormatException extends IllegalArgumentException {
public UnitOfMeasureFormatException() {

Some files were not shown because too many files have changed in this diff Show More