Compare commits

..

19 Commits

Author SHA1 Message Date
979fb3c0c5
Vibes � 2025-05-18 01:33:39 +03:00
5c8f333478
Автореформат кода 2024-05-30 13:25:17 +03:00
2ffcca7eeb
Команда получить продукт по id 2024-05-30 13:20:22 +03:00
5b95fff14d
Добавлена обработка инпута на сервере 2024-05-29 18:59:45 +03:00
db490ed5ec
Добавил README.md 2024-05-27 03:23:51 +03:00
b114a45cd8
Рефакторинг Server.java 2024-05-27 03:21:11 +03:00
723aceb374
6 лаба 2024-05-27 03:17:47 +03:00
ad354595d1 Merge branch 'lab5-dev' of https://gitlab.com/itmo_programming_2023/programming_2.12/student-3 into develop 2024-05-19 20:29:16 +03:00
3ac2784373
Добавил dml диаграмму 2024-05-19 20:23:57 +03:00
cd428e532b
Добавил описание ограничений у команд добавления 2024-05-15 17:47:28 +03:00
f3e6257e8e
Исправил обработку ctrl+c, ctrl+d 2024-05-15 17:01:29 +03:00
afc63b0772
Добавил обработку неверных аргументов 2024-05-02 22:07:48 +03:00
ca28cbab7d
Добавил валидацию при вводе значений 2024-05-02 21:48:25 +03:00
96a055a29d
User-Friendly при заполнении поелй 2024-05-02 20:25:35 +03:00
f00ba84c10
remove_by_id non existing elem #1 2024-05-02 19:39:17 +03:00
a630bf28f8
Убрал логи валидатора 2024-05-02 19:31:33 +03:00
d33c983dbb
Решил execute_script не хочет #7 2024-04-17 18:20:47 +03:00
d4fe517eb8
Починил ошибку с валидацией null значений 2024-04-17 17:53:37 +03:00
722b9f4d8c
Add dockerfile 2024-04-17 13:19:48 +03:00
115 changed files with 2860 additions and 968 deletions

95
Lab.Client/pom.xml Normal file
View File

@ -0,0 +1,95 @@
<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

@ -0,0 +1,14 @@
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

@ -0,0 +1,84 @@
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

@ -0,0 +1,33 @@
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

@ -0,0 +1,11 @@
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

@ -0,0 +1,59 @@
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

@ -0,0 +1,47 @@
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,8 +1,8 @@
package me.zinch.commands;
package me.zinch.Lab6.Client.commands;
import me.zinch.console.Console;
import me.zinch.files.ScriptLoader;
import me.zinch.wrapper.ProductCollection;
import me.zinch.Lab6.Client.client.Client;
import me.zinch.Lab6.Client.console.Console;
import me.zinch.Lab6.Client.files.ScriptLoader;
import java.io.IOException;
import java.util.ArrayList;
@ -15,6 +15,10 @@ 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);
}
@ -27,17 +31,15 @@ public class ExecuteScript extends Command {
return executeStack.contains(filePath);
}
public ExecuteScript() {
super("execute_script file_name", "считать и исполнить скрипт из указанного файла. В скрипте содержатся команды в таком же виде, в котором их вводит пользователь в интерактивном режиме", Pattern.compile("^execute_script .+"));
}
@Override
public String action(ProductCollection productCollection) {
public String action(Client client) {
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()) {
@ -47,13 +49,14 @@ public class ExecuteScript extends Command {
if (checkFileInStack(nextFile)) continue;
addFileToStack(nextFile);
}
command.get().action(productCollection);
result.append(command.get().action(client)).append("\n");
} else {
new UnknownCommand().action(productCollection);
new UnknownCommand().action(client);
}
}
clearStack();
return String.format("Скрипт %s завершил работу.%n", scriptFile);
result.append(String.format("Скрипт %s завершил работу.", scriptFile)).append("\n");
return result.toString();
} catch (IOException e) {
return e.getMessage();
}

View File

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

View File

@ -0,0 +1,33 @@
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

@ -0,0 +1,24 @@
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

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

View File

@ -0,0 +1,31 @@
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

@ -0,0 +1,10 @@
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

@ -0,0 +1,24 @@
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

@ -0,0 +1,33 @@
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

@ -0,0 +1,24 @@
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,6 +1,4 @@
package me.zinch.commands;
import me.zinch.wrapper.ProductCollection;
package me.zinch.Lab6.Client.commands;
/**
* A command to display all elements of the collection in string representation to the standard output stream.
@ -9,9 +7,4 @@ 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.commands;
package me.zinch.Lab6.Client.commands;
import me.zinch.wrapper.ProductCollection;
import me.zinch.Lab6.Client.client.Client;
/**
* Represents an unknown command.
@ -11,7 +11,7 @@ public class UnknownCommand extends Command {
}
@Override
public String action(ProductCollection productCollection) {
public String action(Client client) {
return "Команда не распознана.";
}
}

View File

@ -0,0 +1,48 @@
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

@ -0,0 +1,208 @@
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

@ -0,0 +1,10 @@
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,4 +1,4 @@
package me.zinch.exceptions;
package me.zinch.Lab6.Client.exceptions;
public class ColorFormatException extends IllegalArgumentException {
public ColorFormatException() {

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package me.zinch.exceptions;
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.

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package me.zinch.files;
package me.zinch.Lab6.Client.files;
import java.io.BufferedInputStream;
import java.io.FileInputStream;

37
Lab.Domain/pom.xml Normal file
View File

@ -0,0 +1,37 @@
<?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

@ -0,0 +1,10 @@
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

@ -0,0 +1,12 @@
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

@ -0,0 +1,32 @@
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

@ -0,0 +1,29 @@
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

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

View File

@ -0,0 +1,31 @@
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,10 +1,11 @@
package me.zinch.exceptions;
package me.zinch.Lab6.Domain.exceptions;
/**
* Represents an exception related to validation errors.
*/
public class ValidationException extends jakarta.validation.ValidationException {
public ValidationException() {}
public ValidationException() {
}
public ValidationException(String message) {
super(message);

View File

@ -1,5 +1,6 @@
package me.zinch.models;
package me.zinch.Lab6.Domain.models;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@ -7,7 +8,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* Represents a color enumeration.
*/
public enum Color {
public enum Color implements Serializable {
GREEN("Зелёный"),
BLACK("Чёрный"),
BLUE("Синий"),
@ -20,10 +21,6 @@ public enum Color {
this.color = color;
}
private String getColor() {
return color;
}
public static Color create(String input) throws IllegalArgumentException {
for (var unit : List.of(Color.values())) {
if (unit.getColor().equalsIgnoreCase(input)) return unit;
@ -42,6 +39,10 @@ public enum Color {
.toList());
}
private String getColor() {
return color;
}
@Override
public String toString() {
return "Color{" +

View File

@ -0,0 +1,56 @@
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

@ -0,0 +1,148 @@
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

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

View File

@ -0,0 +1,40 @@
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

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

View File

@ -1,15 +1,16 @@
package me.zinch.models;
package me.zinch.Lab6.Domain.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotNull;
import me.zinch.validator.NotEmpty;
import me.zinch.Lab6.Domain.validator.NotEmpty;
import java.io.Serializable;
import java.util.Objects;
/**
* Represents a location with x and y coordinates and a name.
*/
public class Location {
public class Location implements Serializable {
@JsonProperty("x")
private float x;
@ -21,7 +22,8 @@ public class Location {
@JsonProperty("name")
private String name; //Строка не может быть пустой, Поле может быть null
public Location() {}
public Location() {
}
public Location(float x, Integer y, String name) {
this.x = x;

View File

@ -1,16 +1,18 @@
package me.zinch.models;
package me.zinch.Lab6.Domain.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import java.io.Serializable;
import java.util.Objects;
/**
* Represents a person with a name, passport ID, hair color, and location.
*/
public class Person {
public class Person implements Serializable {
@NotBlank(message = "Person: Имя персоны не может быть пустым или null")
@JsonProperty("name")
private String name; //Поле не может быть null, Строка не может быть пустой
@ -26,7 +28,8 @@ public class Person {
@JsonProperty("location")
private Location location; //Поле может быть null
public Person() {}
public Person() {
}
public Person(String name, String passportID, Color hairColor, Location location) {
this.name = name;
@ -36,10 +39,14 @@ public class Person {
}
public void setName(String name) {
if (name == null || name.isEmpty()) throw new ValidationException("Имя персоны не может быть пустым или null");
this.name = name;
}
public void setPassportID(String passportID) {
if (passportID == null) throw new ValidationException("Поле passportID не может быть null");
if (passportID.length() < 5 || passportID.length() > 22)
throw new ValidationException("Длина строки passportID должна быть не меньше 5 и не должна быть больше 22");
this.passportID = passportID;
}

View File

@ -1,19 +1,20 @@
package me.zinch.models;
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 jakarta.validation.constraints.Size;
import me.zinch.validator.NotEmpty;
import me.zinch.Lab6.Domain.validator.NotEmpty;
import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.Objects;
/**
* Represents a product with various attributes such as ID, name, coordinates, creation date, price, part number, manufacture cost, unit of measure, and owner.
*/
public class Product {
public class Product implements Serializable, Comparable<Product> {
@NotNull(message = "Product: Поле id не может быть null")
@Min(value = 1, message = "Product: Значение поля id должно быть больше 0")
@JsonProperty("id")
@ -52,18 +53,19 @@ public class Product {
@JsonProperty("owner")
private Person owner; //Поле не может быть null
public Product() {}
public Product() {
}
public Product (Long id,
String name,
Coordinates coordinates,
ZonedDateTime creationDate,
Long price,
String partNumber,
long manufactureCost,
UnitOfMeasure unitOfMeasure,
Person owner
) {
public Product(Long id,
String name,
Coordinates coordinates,
ZonedDateTime creationDate,
Long price,
String partNumber,
long manufactureCost,
UnitOfMeasure unitOfMeasure,
Person owner
) {
this.id = id;
this.name = name;
this.coordinates = coordinates;
@ -138,4 +140,9 @@ public class Product {
public int hashCode() {
return Objects.hash(id, name, coordinates, creationDate, price, partNumber, manufactureCost, unitOfMeasure, owner);
}
@Override
public int compareTo(Product other) {
return this.name.compareTo(other.name);
}
}

View File

@ -1,20 +1,20 @@
package me.zinch.models;
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 jakarta.validation.constraints.Size;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.validator.NotEmpty;
import me.zinch.validator.NotEmpty;
import java.io.Serializable;
import java.time.ZonedDateTime;
/**
* Represents a Data Transfer Object (DTO) for a product with attributes such as name, coordinates, price, part number, manufacture cost, unit of measure, and owner.
*/
public class ProductDTO {
public class ProductDTO implements Serializable {
@NotBlank(message = "ProductDTO: Строка name не может быть пустой или null")
@JsonProperty("name")
private String name; //Поле не может быть null, Строка не может быть пустой
@ -24,7 +24,7 @@ public class ProductDTO {
private Coordinates coordinates; //Поле не может быть null
@NotNull(message = "ProductDTO: Поле price не может быть null")
@Min(value = 1, message = "Поле price не может быть null")
@Min(value = 1, message = "ProductDTO: Значение поля price должно быть больше 0")
@JsonProperty("price")
private Long price; //Поле не может быть null, Значение поля должно быть больше 0
@ -44,7 +44,8 @@ public class ProductDTO {
@JsonProperty("owner")
private Person owner; //Поле не может быть null
public ProductDTO() {}
public ProductDTO() {
}
public ProductDTO(String name, Coordinates coordinates, Long price, String partNumber, long manufactureCost, UnitOfMeasure unitOfMeasure, Person owner) {
this.name = name;
@ -72,55 +73,63 @@ public class ProductDTO {
return name;
}
public void setName(String name) {
if (name == null || name.isEmpty()) throw new ValidationException("Строка name не может быть пустой или null");
this.name = name;
}
public Coordinates getCoordinates() {
return coordinates;
}
public void setCoordinates(Coordinates coordinates) {
if (coordinates == null) throw new ValidationException("Поле coordinates не может быть null");
this.coordinates = coordinates;
}
public Long getPrice() {
return price;
}
public void setPrice(Long price) {
if (price == null) throw new ValidationException("Поле price не может быть null");
if (price <= 0) throw new ValidationException("Поле price не может быть null");
this.price = price;
}
public String getPartNumber() {
return partNumber;
}
public void setPartNumber(String partNumber) {
if (partNumber.isEmpty()) throw new ValidationException("Строка partNumber не может быть пустой");
if (partNumber.length() > 82) throw new ValidationException("Длина строки partNumber не должна быть больше 82");
this.partNumber = partNumber;
}
public long getManufactureCost() {
return manufactureCost;
}
public void setManufactureCost(long manufactureCost) {
this.manufactureCost = manufactureCost;
}
public UnitOfMeasure getUnitOfMeasure() {
return unitOfMeasure;
}
public void setUnitOfMeasure(UnitOfMeasure unitOfMeasure) {
if (unitOfMeasure == null) throw new ValidationException("Поле unitOfMeasure не может быть null");
this.unitOfMeasure = unitOfMeasure;
}
public Person getOwner() {
return owner;
}
public void setName(String name) {
this.name = name;
}
public void setCoordinates(Coordinates coordinates) {
this.coordinates = coordinates;
}
public void setPrice(Long price) {
this.price = price;
}
public void setPartNumber(String partNumber) {
this.partNumber = partNumber;
}
public void setManufactureCost(long manufactureCost) {
this.manufactureCost = manufactureCost;
}
public void setUnitOfMeasure(UnitOfMeasure unitOfMeasure) {
this.unitOfMeasure = unitOfMeasure;
}
public void setOwner(Person owner) {
if (owner == null) throw new ValidationException("Поле owner не может быть null");
this.owner = owner;
}
}

View File

@ -1,5 +1,6 @@
package me.zinch.models;
package me.zinch.Lab6.Domain.models;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@ -7,7 +8,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* Represents a unit of measure enumeration.
*/
public enum UnitOfMeasure {
public enum UnitOfMeasure implements Serializable {
KILOGRAMS("Килограммы"),
CENTIMETERS("Сантиметры"),
GRAMS("Граммы");
@ -18,10 +19,6 @@ public enum UnitOfMeasure {
this.unitOfMeasure = unitOfMeasure;
}
public String getUnitOfMeasure() {
return unitOfMeasure;
}
public static UnitOfMeasure create(String input) throws IllegalArgumentException {
for (var unit : List.of(UnitOfMeasure.values())) {
if (unit.getUnitOfMeasure().equalsIgnoreCase(input)) return unit;
@ -40,6 +37,9 @@ public enum UnitOfMeasure {
.toList());
}
public String getUnitOfMeasure() {
return unitOfMeasure;
}
@Override
public String toString() {

View File

@ -1,4 +1,4 @@
package me.zinch.validator;
package me.zinch.Lab6.Domain.validator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
@ -16,6 +16,8 @@ import java.lang.annotation.Target;
@Constraint(validatedBy = NotEmptyValidator.class)
public @interface NotEmpty {
String message() default "Value must not be blank";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -1,4 +1,4 @@
package me.zinch.validator;
package me.zinch.Lab6.Domain.validator;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

View File

@ -1,12 +1,13 @@
package me.zinch.validator;
package me.zinch.Lab6.Domain.validator;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ValidationException;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import java.util.Set;
/**
* Represents the result of a validation process.
*
* @param <T> the type of object being validated
*/
public class ValidateResult<T> {

View File

@ -1,12 +1,13 @@
package me.zinch.validator;
package me.zinch.Lab6.Domain.validator;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import me.zinch.models.Product;
import me.zinch.Lab6.Domain.models.Product;
import org.hibernate.validator.HibernateValidator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
@ -30,7 +31,7 @@ public class Validators {
public static List<ValidateResult<Product>> validateProductList(List<Product> db) {
var ids = db.stream().map(Product::getId).toList();
var partNumbers = db.stream().map(Product::getPartNumber).toList();
var partNumbers = db.stream().map(Product::getPartNumber).filter(Objects::nonNull).toList();
var validations = new java.util.ArrayList<>(db.stream().map(Validators::validateObject).toList());
if (ids.size() != Set.copyOf(ids).size()) {
validations.add(new ValidateResult<>("Поле id должно быть уникальным"));

View File

@ -0,0 +1,23 @@
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();
}

116
Lab.Server/pom.xml Normal file
View File

@ -0,0 +1,116 @@
<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

@ -0,0 +1,14 @@
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

@ -0,0 +1,97 @@
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

@ -0,0 +1,37 @@
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

@ -0,0 +1,31 @@
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

@ -0,0 +1,38 @@
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

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

View File

@ -1,7 +1,4 @@
package me.zinch.commands;
import me.zinch.exceptions.CommandActionException;
import me.zinch.wrapper.ProductCollection;
package me.zinch.Lab6.Server.commands;
import java.util.regex.Pattern;
@ -40,6 +37,4 @@ 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.commands;
package me.zinch.Lab6.Server.commands;
import java.util.ArrayList;
import java.util.List;
@ -10,6 +10,27 @@ 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);
}
@ -24,24 +45,4 @@ 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

@ -0,0 +1,19 @@
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

@ -0,0 +1,21 @@
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

@ -0,0 +1,18 @@
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

@ -0,0 +1,19 @@
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

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

View File

@ -0,0 +1,19 @@
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

@ -0,0 +1,18 @@
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

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

View File

@ -0,0 +1,29 @@
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

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

View File

@ -0,0 +1,18 @@
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

@ -0,0 +1,34 @@
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

@ -0,0 +1,17 @@
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

@ -0,0 +1,40 @@
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

@ -0,0 +1,24 @@
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

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

View File

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

View File

@ -0,0 +1,18 @@
package me.zinch.Lab6.Server.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

@ -0,0 +1,16 @@
package me.zinch.Lab6.Server.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

@ -0,0 +1,18 @@
package me.zinch.Lab6.Server.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

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

View File

@ -0,0 +1,80 @@
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,20 +1,18 @@
package me.zinch.files;
package me.zinch.Lab6.Server.files;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DatabindException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import me.zinch.exceptions.DbInitializationException;
import me.zinch.models.Product;
import me.zinch.Lab6.Domain.models.Product;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -27,10 +25,32 @@ public class DbController {
static {
Map<String, String> env = System.getenv();
path = env.getOrDefault("DB_FILE", "");
path = env.getOrDefault("DB_FILE", "db.xml");
xmlMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
}
public static List<Product> loadDb() {
try {
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(path));
List<Product> collection = xmlMapper.readValue(bufferedInputStream, Products.class).getProducts();
bufferedInputStream.close();
if (collection == null) return new ArrayList<>();
return collection;
} catch (IOException e) {
return new ArrayList<>();
}
}
public static void saveDb(List<Product> list) throws IOException {
try {
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(path));
xmlMapper.writeValue(outputStreamWriter, new Products(list));
outputStreamWriter.close();
} catch (IOException e) {
throw new IOException("Не удалось записать в файл");
}
}
/**
* Represents a collection of products.
*/
@ -39,7 +59,8 @@ public class DbController {
@JacksonXmlProperty(localName = "Product")
private List<Product> products;
public Products() {}
public Products() {
}
public Products(List<Product> list) {
this.products = list;
@ -49,31 +70,4 @@ public class DbController {
return products;
}
}
public static List<Product> loadDb() throws IOException {
try {
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(path));
Products collection = xmlMapper.readValue(bufferedInputStream, Products.class);
bufferedInputStream.close();
return collection.getProducts();
} catch (FileNotFoundException e) {
throw new FileNotFoundException("Не удалось найти файл " + path);
} catch (DatabindException e) {
throw new DbInitializationException();
} catch (IOException e) {
throw new IOException("Произошла неожиданная ошибка во время работы с файлом!");
}
}
public static void saveDb(List<Product> list) throws IOException {
try {
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(path));
xmlMapper.writeValue(outputStreamWriter, new Products(list));
outputStreamWriter.close();
} catch (FileNotFoundException e) {
throw new FileNotFoundException(e.getMessage());
} catch (IOException e) {
throw new IOException(e);
}
}
}

View File

@ -0,0 +1,32 @@
package me.zinch.Lab6.Server.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

@ -0,0 +1,81 @@
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

@ -0,0 +1,40 @@
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

@ -0,0 +1,41 @@
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

@ -0,0 +1,42 @@
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

@ -0,0 +1,123 @@
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,8 +1,9 @@
package me.zinch.wrapper;
package me.zinch.Lab6.Server.wrapper;
import me.zinch.exceptions.ValidationException;
import me.zinch.models.Product;
import me.zinch.models.ProductDTO;
import me.zinch.Lab6.Domain.exceptions.ValidationException;
import me.zinch.Lab6.Domain.models.Product;
import me.zinch.Lab6.Domain.models.ProductDTO;
import me.zinch.Lab6.Domain.wrapper.IStorage;
import java.time.Instant;
import java.time.ZoneId;
@ -17,23 +18,28 @@ import java.util.TreeSet;
/**
* Represents a collection of products.
*/
public class ProductCollection {
public class ProductCollection implements IStorage {
private final TreeSet<Product> productList;
private Long idIncrementor;
public ProductCollection(List<Product> list) throws ValidationException {
productList = new TreeSet<>(Comparator.comparingLong(Product::getId));
productList = new TreeSet<>(Comparator.comparing(Product::getName));
productList.addAll(list);
idIncrementor = productList.last().getId() + 1;
idIncrementor = productList.stream()
.mapToLong(Product::getId)
.max()
.orElse(0) + 1;
}
private Long generateId() {
return idIncrementor++;
}
private Product getProductById(Long id) {
Optional<Product> product = productList.stream().filter(i -> Objects.equals(i.getId(), id)).findFirst();
return product.orElse(null);
public Product getProductById(Long id) {
return productList.stream()
.filter(p -> Objects.equals(p.getId(), id))
.findFirst()
.orElse(null);
}
public Product addProduct(ProductDTO productDTO) {
@ -61,48 +67,62 @@ public class ProductCollection {
}
public String getInfo() {
var optionalInitDate = productList.stream().map(Product::getCreationDate).sorted().findFirst();
ZonedDateTime initDate = optionalInitDate.orElse(ZonedDateTime.ofInstant(Instant.EPOCH, ZoneId.systemDefault()));
return String.format("TreeSet%nInit Date: %s%nNumber of elements: %s", initDate, productList.size());
var initDate = productList.stream()
.map(Product::getCreationDate)
.min(Comparator.naturalOrder())
.orElse(ZonedDateTime.ofInstant(Instant.EPOCH, ZoneId.systemDefault()));
return String.format("Type: TreeSet%nInitialization date: %s%nNumber of elements: %s",
initDate, productList.size());
}
public void clear() {
productList.clear();
}
public Long getMaxPrice() {
return productList.stream().max(Comparator.comparingLong(Product::getPrice)).map(Product::getPrice).orElse(null);
return productList.stream()
.mapToLong(Product::getPrice)
.max()
.orElse(0);
}
public Long getMinPrice() {
return productList.stream().min(Comparator.comparingLong(Product::getPrice)).map(Product::getPrice).orElse(null);
return productList.stream()
.mapToLong(Product::getPrice)
.min()
.orElse(0);
}
public boolean isProductIdExists(Long id) {
return getProductById(id) != null;
return productList.stream()
.anyMatch(p -> Objects.equals(p.getId(), id));
}
public Integer removeLover(Long id) {
var size = productList.size();
productList.headSet(getProductById(id)).stream().toList().forEach(productList::remove);
var targetProduct = getProductById(id);
productList.stream()
.filter(p -> p.compareTo(targetProduct) < 0)
.toList()
.forEach(productList::remove);
return size - productList.size();
}
public String filterContainsName(String name) {
return String.join("\n", productList.stream()
.filter(product -> product.getName().toLowerCase().contains(name.toLowerCase()))
.map(Product::toString)
.toList());
return productList.stream()
.filter(product -> product.getName().toLowerCase().contains(name.toLowerCase()))
.map(Product::toString)
.reduce((a, b) -> a + "\n" + b)
.orElse("");
}
public String getUniqueManufactureCost() {
return String.join(", ", Set.copyOf(productList.stream()
.map(Product::getManufactureCost)
.toList())
.stream()
.map(Object::toString)
.toList());
return productList.stream()
.map(Product::getManufactureCost)
.distinct()
.map(Object::toString)
.reduce((a, b) -> a + ", " + b)
.orElse("");
}
public List<Product> toList() {
@ -111,7 +131,10 @@ public class ProductCollection {
@Override
public String toString() {
return String.join("\n", productList.stream().map(Product::toString).toList());
return productList.stream()
.map(Product::toString)
.reduce((a, b) -> a + "\n" + b)
.orElse("");
}
@Override

View File

@ -0,0 +1,11 @@
<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>

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