diff --git a/Lab.Client/pom.xml b/Lab.Client/pom.xml index 0a63c58..efe3f2c 100644 --- a/Lab.Client/pom.xml +++ b/Lab.Client/pom.xml @@ -41,6 +41,16 @@ log4j-core 2.23.1 + + org.slf4j + slf4j-api + 2.0.12 + + + org.slf4j + slf4j-log4j12 + 2.0.12 + me.zinch Lab6-Domain diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/client/Client.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/client/Client.java index 6a1e4b4..ecb3777 100644 --- a/Lab.Client/src/main/java/me/zinch/Lab6/Client/client/Client.java +++ b/Lab.Client/src/main/java/me/zinch/Lab6/Client/client/Client.java @@ -6,47 +6,79 @@ 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 final String address; - private final int port; + 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; + 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("Соединение с сервером %s:%s установлено", address, port)); - } - } catch (IOException | ClassNotFoundException e) { - throw new ResponseException(); + 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 { - try (var socket = new Socket(address, port); - 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(); - } - } catch (IOException e) { - throw new IOException("Сервер не доступен"); + 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(); + } + } + } } - } } diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/AddIfMax.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/AddIfMax.java deleted file mode 100644 index e6e38d3..0000000 --- a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/AddIfMax.java +++ /dev/null @@ -1,32 +0,0 @@ -package me.zinch.Lab6.Client.commands; - -import me.zinch.Lab6.Client.client.Client; -import me.zinch.Lab6.Client.console.Console; -import me.zinch.Lab6.Client.exceptions.CommandActionException; -import me.zinch.Lab6.Domain.dto.BodyfulMessage; -import me.zinch.Lab6.Domain.dto.BodylessMessage; -import me.zinch.Lab6.Domain.dto.MessageType; - -import java.io.IOException; -import java.util.regex.Pattern; - -/** - * The AddIfMax class represents a command to add a new element to a collection if its price value exceeds the maximum price value in the collection. - * It extends the Command class. - */ -public class AddIfMax extends Command { - public AddIfMax() { - super("add_if_max {element}", "добавить новый элемент в коллекцию, если его значение цены превышает значение наибольшей цены этой коллекции", Pattern.compile("^add_if_max")); - } - - @Override - public String action(Client client) throws CommandActionException { - try { - var productDTO = Console.readProductDTO(); - var response = (BodylessMessage) client.sendMessage(new BodyfulMessage(MessageType.POST, Console.getLastCommand(), productDTO)); - return response.getBody().toString(); - } catch (IOException | ClassNotFoundException e) { - throw new CommandActionException(e); - } - } -} diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/AddIfMin.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/AddIfMin.java deleted file mode 100644 index 409c368..0000000 --- a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/AddIfMin.java +++ /dev/null @@ -1,32 +0,0 @@ -package me.zinch.Lab6.Client.commands; - -import me.zinch.Lab6.Client.client.Client; -import me.zinch.Lab6.Client.console.Console; -import me.zinch.Lab6.Client.exceptions.CommandActionException; -import me.zinch.Lab6.Domain.dto.BodyfulMessage; -import me.zinch.Lab6.Domain.dto.BodylessMessage; -import me.zinch.Lab6.Domain.dto.MessageType; - -import java.io.IOException; -import java.util.regex.Pattern; - -/** - * The AddIfMin class represents a command to add a new element to a collection if its price is lower than the lowest price in the collection. - * It extends the Command class. - */ -public class AddIfMin extends Command { - public AddIfMin() { - super("add_if_min {element}", "добавить новый элемент в коллекцию, если его значение цены меньше, чем у наименьшей цены этой коллекции", Pattern.compile("^add_if_min")); - } - - @Override - public String action(Client client) { - try { - var productDTO = Console.readProductDTO(); - var response = (BodylessMessage) client.sendMessage(new BodyfulMessage(MessageType.POST, Console.getLastCommand(), productDTO)); - return response.getBody().toString(); - } catch (IOException | ClassNotFoundException e) { - throw new CommandActionException(e); - } - } -} diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/CommandManager.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/CommandManager.java index 65804d3..9d41d66 100644 --- a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/CommandManager.java +++ b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/CommandManager.java @@ -11,22 +11,21 @@ public class CommandManager { private static final List commandList = new ArrayList<>(); static { - registerCommand(new Show()); - registerCommand(new Exit()); + registerCommand(new Help()); registerCommand(new Info()); - registerCommand(new Clear()); - registerCommand(new PrintAscending()); - registerCommand(new PrintUniqueManufactureCost()); - registerCommand(new FilterContainsName()); + registerCommand(new Show()); registerCommand(new Add()); registerCommand(new Update()); registerCommand(new Remove()); - registerCommand(new AddIfMax()); - registerCommand(new AddIfMin()); - registerCommand(new RemoveLower()); + registerCommand(new Clear()); registerCommand(new ExecuteScript()); - - registerCommand(new Help()); + 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) { @@ -41,6 +40,8 @@ public class CommandManager { } public static Optional getCommandByInput(String input) { - return commandList.stream().filter(command -> command.checkPattern(input)).findFirst(); + return commandList.stream() + .filter(command -> command.checkPattern(input)) + .findFirst(); } } diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/FilterContainsName.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/FilterContainsName.java deleted file mode 100644 index f61c58c..0000000 --- a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/FilterContainsName.java +++ /dev/null @@ -1,31 +0,0 @@ -package me.zinch.Lab6.Client.commands; - -import me.zinch.Lab6.Client.client.Client; -import me.zinch.Lab6.Client.console.Console; -import me.zinch.Lab6.Client.exceptions.CommandActionException; -import me.zinch.Lab6.Domain.dto.BodyfulMessage; -import me.zinch.Lab6.Domain.dto.BodylessMessage; -import me.zinch.Lab6.Domain.dto.MessageType; - -import java.io.IOException; -import java.util.regex.Pattern; - -/** - * A command to filter elements whose 'name' field contains the specified substring. - */ -public class FilterContainsName extends Command { - public FilterContainsName() { - super("filter_contains_name name", "вывести элементы, значение поля name которых содержит заданную подстроку", Pattern.compile("^filter_contains_name \\w+")); - } - - @Override - public String action(Client client) throws CommandActionException { - try { - String name = Console.getLastCommand().split(" ")[1]; - var response = (BodylessMessage) client.sendMessage(new BodyfulMessage(MessageType.POST, Console.getLastCommand(), name)); - return response.getBody().toString(); - } catch (IOException | ClassNotFoundException e) { - throw new CommandActionException(e); - } - } -} diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/FilterLessThanCharacter.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/FilterLessThanCharacter.java new file mode 100644 index 0000000..6b781ce --- /dev/null +++ b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/FilterLessThanCharacter.java @@ -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); + } + } +} \ No newline at end of file diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/GetProductById.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/GetProductById.java deleted file mode 100644 index 73dc7db..0000000 --- a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/GetProductById.java +++ /dev/null @@ -1,29 +0,0 @@ -package me.zinch.Lab6.Client.commands; - -import me.zinch.Lab6.Client.client.Client; -import me.zinch.Lab6.Client.console.Console; -import me.zinch.Lab6.Client.exceptions.CommandActionException; -import me.zinch.Lab6.Domain.dto.BodyfulMessage; -import me.zinch.Lab6.Domain.dto.BodylessMessage; -import me.zinch.Lab6.Domain.dto.MessageType; - -import java.io.IOException; -import java.util.regex.Pattern; - -public class GetProductById extends Command { - public GetProductById() { - super("get_product_by_id {element}", "", Pattern.compile("^get_product_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)); - if (response.getType() == MessageType.ERROR) throw new CommandActionException(response.getBody().toString()); - return response.getBody().toString(); - } catch (IOException | ClassNotFoundException e) { - throw new CommandActionException(e); - } - } -} diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/GroupCountingByType.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/GroupCountingByType.java new file mode 100644 index 0000000..246b566 --- /dev/null +++ b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/GroupCountingByType.java @@ -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); + } + } +} \ No newline at end of file diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/Head.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/Head.java new file mode 100644 index 0000000..741f627 --- /dev/null +++ b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/Head.java @@ -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); + } + } +} \ No newline at end of file diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/History.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/History.java new file mode 100644 index 0000000..6abe48b --- /dev/null +++ b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/History.java @@ -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 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); + } +} \ No newline at end of file diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/IsIdExists.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/IsIdExists.java deleted file mode 100644 index 04a81fc..0000000 --- a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/IsIdExists.java +++ /dev/null @@ -1,29 +0,0 @@ -package me.zinch.Lab6.Client.commands; - -import me.zinch.Lab6.Client.client.Client; -import me.zinch.Lab6.Client.console.Console; -import me.zinch.Lab6.Client.exceptions.CommandActionException; -import me.zinch.Lab6.Domain.dto.BodyfulMessage; -import me.zinch.Lab6.Domain.dto.BodylessMessage; -import me.zinch.Lab6.Domain.dto.MessageType; - -import java.io.IOException; -import java.util.regex.Pattern; - -public class IsIdExists extends Command { - public IsIdExists() { - super("is_id_exists {element}", "", Pattern.compile("^is_id_exists +.")); - } - - @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)); - if (response.getType() == MessageType.ERROR) throw new CommandActionException(response.getBody().toString()); - return response.getBody().toString(); - } catch (IOException | ClassNotFoundException e) { - throw new CommandActionException(e); - } - } -} diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/MaxByWeight.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/MaxByWeight.java new file mode 100644 index 0000000..92eedd3 --- /dev/null +++ b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/MaxByWeight.java @@ -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); + } + } +} \ No newline at end of file diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/PrintAscending.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/PrintAscending.java deleted file mode 100644 index 724c624..0000000 --- a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/PrintAscending.java +++ /dev/null @@ -1,10 +0,0 @@ -package me.zinch.Lab6.Client.commands; - -/** - * A command to print the elements of the collection in ascending order. - */ -public class PrintAscending extends Command { - public PrintAscending() { - super("print_ascending", "вывести элементы коллекции в порядке возрастания"); - } -} diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/PrintUniqueManufactureCost.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/PrintUniqueManufactureCost.java deleted file mode 100644 index 9ffb3db..0000000 --- a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/PrintUniqueManufactureCost.java +++ /dev/null @@ -1,10 +0,0 @@ -package me.zinch.Lab6.Client.commands; - -/** - * A command to print the unique values of the 'manufactureCost' field of all elements in the collection. - */ -public class PrintUniqueManufactureCost extends Command { - public PrintUniqueManufactureCost() { - super("print_unique_manufacture_cost", "вывести уникальные значения поля manufactureCost всех элементов в коллекции"); - } -} diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/RemoveHead.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/RemoveHead.java new file mode 100644 index 0000000..5331731 --- /dev/null +++ b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/RemoveHead.java @@ -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); + } + } +} \ No newline at end of file diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/RemoveLower.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/RemoveLower.java deleted file mode 100644 index 7f92f1f..0000000 --- a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/RemoveLower.java +++ /dev/null @@ -1,33 +0,0 @@ -package me.zinch.Lab6.Client.commands; - -import me.zinch.Lab6.Client.client.Client; -import me.zinch.Lab6.Client.console.Console; -import me.zinch.Lab6.Client.exceptions.CommandActionException; -import me.zinch.Lab6.Domain.dto.BodyfulMessage; -import me.zinch.Lab6.Domain.dto.BodylessMessage; -import me.zinch.Lab6.Domain.dto.MessageType; - -import java.io.IOException; -import java.util.regex.Pattern; - -/** - * A command to remove all elements from the collection that have ids lower than the specified id. - */ -public class RemoveLower extends Command { - public RemoveLower() { - super("remove_lower id", "удалить из коллекции все элементы, меньшие, чем заданный по id", Pattern.compile("^remove_lower .+")); - } - - @Override - public String action(Client client) { - 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); - } - } -} diff --git a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/Save.java b/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/Save.java deleted file mode 100644 index e0d2d3e..0000000 --- a/Lab.Client/src/main/java/me/zinch/Lab6/Client/commands/Save.java +++ /dev/null @@ -1,10 +0,0 @@ -package me.zinch.Lab6.Client.commands; - -/** - * A command to save the collection to a file. - */ -public class Save extends Command { - public Save() { - super("save", "save the collection to a file"); - } -} diff --git a/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Coordinates.java b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Coordinates.java index e2574ce..23a6917 100644 --- a/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Coordinates.java +++ b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Coordinates.java @@ -2,10 +2,7 @@ package me.zinch.Lab6.Domain.models; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; -import me.zinch.Lab6.Domain.exceptions.ValidationException; - import java.io.Serializable; import java.util.Objects; @@ -13,60 +10,47 @@ import java.util.Objects; * Represents coordinates with x and y values. */ public class Coordinates implements Serializable { - @Max(value = 883, message = "Coordinates: Максимальное значение координаты x: 883") - @JsonProperty("x") - private long x; //Максимальное значение поля: 883 + @Max(value = 353, message = "Coordinates: Максимальное значение поля x: 353") + @JsonProperty("x") + private long x; // Максимальное значение поля: 353 - @NotNull(message = "Coordinates: Поле y не может быть null") - @Min(value = -427, message = "Значение координаты y должно быть больше -427") - @JsonProperty("y") - private Long y; //Значение поля должно быть больше -427, Поле не может быть null + @NotNull(message = "Coordinates: Поле y не может быть null") + @JsonProperty("y") + private Integer y; // Поле не может быть null - public Coordinates() { - } + public Coordinates() {} - public Coordinates(long x, Long y) { - this.x = x; - this.y = y; - } + public Coordinates(long x, Integer y) { + this.x = x; + this.y = y; + } - public long getX() { - return x; - } + public long getX() { + return x; + } - public void setX(long x) { - if (x > 883) throw new ValidationException("Максимальное значение координаты x: 883"); - this.x = x; - } + public Integer getY() { + return y; + } - public Long 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); + } - public void setY(Long y) { - if (y == null) throw new ValidationException("Поле y не может быть null"); - if (y < -427) throw new ValidationException("Значение координаты y должно быть больше -427"); - this.y = y; - } + @Override + public int hashCode() { + return Objects.hash(x, y); + } - @Override - public String toString() { - return "Coordinates{" + - "x=" + x + - ", y=" + 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 + + '}'; + } } diff --git a/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Dragon.java b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Dragon.java new file mode 100644 index 0000000..4ac80e0 --- /dev/null +++ b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Dragon.java @@ -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 { + @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); + } +} \ No newline at end of file diff --git a/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/DragonCharacter.java b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/DragonCharacter.java new file mode 100644 index 0000000..a6cefed --- /dev/null +++ b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/DragonCharacter.java @@ -0,0 +1,9 @@ +package me.zinch.Lab6.Domain.models; + +public enum DragonCharacter { + CUNNING, + WISE, + EVIL, + CHAOTIC, + FICKLE +} \ No newline at end of file diff --git a/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/DragonHead.java b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/DragonHead.java new file mode 100644 index 0000000..6972048 --- /dev/null +++ b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/DragonHead.java @@ -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 + + '}'; + } +} \ No newline at end of file diff --git a/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/DragonType.java b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/DragonType.java new file mode 100644 index 0000000..0f7cc1b --- /dev/null +++ b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/DragonType.java @@ -0,0 +1,7 @@ +package me.zinch.Lab6.Domain.models; + +public enum DragonType { + WATER, + AIR, + FIRE +} \ No newline at end of file diff --git a/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Product.java b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Product.java index e7e4290..41f8aaf 100644 --- a/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Product.java +++ b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Product.java @@ -14,7 +14,7 @@ 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 implements Serializable { +public class Product implements Serializable, Comparable { @NotNull(message = "Product: Поле id не может быть null") @Min(value = 1, message = "Product: Значение поля id должно быть больше 0") @JsonProperty("id") @@ -140,4 +140,9 @@ public class Product implements Serializable { 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); + } } diff --git a/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/wrapper/IStorage.java b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/wrapper/IStorage.java index 8d6dfa6..b6beec0 100644 --- a/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/wrapper/IStorage.java +++ b/Lab.Domain/src/main/java/me/zinch/Lab6/Domain/wrapper/IStorage.java @@ -1,34 +1,23 @@ package me.zinch.Lab6.Domain.wrapper; -import me.zinch.Lab6.Domain.models.Product; -import me.zinch.Lab6.Domain.models.ProductDTO; +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 { - public Product getProductById(Long id); - - public Product addProduct(ProductDTO productDTO); - - public Product updateProduct(Long id, ProductDTO productDTO); - - public Product removeProduct(Long id); - - public String getInfo(); - - public void clear(); - - public Long getMaxPrice(); - - public Long getMinPrice(); - - public boolean isProductIdExists(Long id); - - public Integer removeLover(Long id); - - public String filterContainsName(String name); - - public String getUniqueManufactureCost(); - - public List toList(); + 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 groupCountingByType(); + List filterLessThanCharacter(DragonCharacter character); + List toList(); } diff --git a/Lab.Server/pom.xml b/Lab.Server/pom.xml index 3bb7508..f394844 100644 --- a/Lab.Server/pom.xml +++ b/Lab.Server/pom.xml @@ -68,6 +68,16 @@ jline 3.26.1 + + org.apache.logging.log4j + log4j-core + 2.23.1 + + + com.opencsv + opencsv + 5.9 + diff --git a/Lab.Server/src/main/java/me/zinch/Lab6/Server/Server.java b/Lab.Server/src/main/java/me/zinch/Lab6/Server/Server.java index 6072db7..bdaa8b6 100644 --- a/Lab.Server/src/main/java/me/zinch/Lab6/Server/Server.java +++ b/Lab.Server/src/main/java/me/zinch/Lab6/Server/Server.java @@ -1,185 +1,97 @@ package me.zinch.Lab6.Server; -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 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.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; -import java.nio.channels.ServerSocketChannel; 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 ServerSocketChannel serverSocketChannel; - private final Map usersMessages = new HashMap<>(); - private final IStorage storage; - private boolean isRunning = false; + private static final Logger log = LoggerFactory.getLogger(Server.class); + private final Selector selector; + private final Map 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(); - serverSocketChannel = ServerSocketChannel.open(); - serverSocketChannel.bind(new InetSocketAddress(port)); - serverSocketChannel.configureBlocking(false); - serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); - isRunning = true; - - if (collection == null) collection = new ProductCollection(new ArrayList<>()); - storage = collection; - - log.info("The server has been assigned to port {}", port); - } - - public 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()); - } - } - - public void run() throws IOException { - run(() -> { - }); - } - - public void run(Runnable middleware) throws IOException { - log.info("The server is running"); - - while (isRunning) { - selector.select(this::handleKey); - middleware.run(); - } - } - - private void handleKey(SelectionKey key) { - try { - if (key.isAcceptable()) handleAccept(key); - if (key.isReadable()) handleRead(key); - if (key.isWritable()) handleWrite(key); - } catch (IOException | ClassNotFoundException e) { - log.error(e.getMessage(), e); - key.cancel(); - } - } - - private 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("Received connection from {}", client.getRemoteAddress()); - } - } - - private 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(); - } else { - buffer.flip(); - var oIn = new ObjectInputStream(new ByteArrayInputStream(buffer.array())); - var msg = (Message) oIn.readObject(); - log.info("Received message {} from {}", msg, client.getRemoteAddress()); - usersMessages.put(client.getRemoteAddress(), msg); - client.register(selector, SelectionKey.OP_WRITE); - oIn.close(); - } - } - - private void handleWrite(SelectionKey key) throws IOException { - var client = (SocketChannel) key.channel(); - var message = usersMessages.get(client.getRemoteAddress()); - if (message == null) { - log.error("Message for {} not found", client.getRemoteAddress()); - client.close(); - return; + 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); } - ByteBuffer responseBuffer = createResponse(message); - if (responseBuffer != null) { - client.write(responseBuffer); - log.info("Sent message to {}", client.getRemoteAddress()); + public void run() throws IOException { + run(() -> {}); } - client.close(); - } - - private ByteBuffer createResponse(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))); + public void run(Runnable middleware) throws IOException { + log.info("Server is running"); + + while (isRunning) { + selector.select(this::handleKey); + middleware.run(); + } } - 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 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; - serverSocketChannel.close(); - selector.close(); - DbController.saveDb(storage.toList()); - } + public void stop() throws IOException { + isRunning = false; + connectionModule.close(); + selector.close(); + DbController.saveDb(storage.toList()); + log.info("Server stopped"); + } } diff --git a/Lab.Server/src/main/java/me/zinch/Lab6/Server/files/CsvController.java b/Lab.Server/src/main/java/me/zinch/Lab6/Server/files/CsvController.java new file mode 100644 index 0000000..a8127b1 --- /dev/null +++ b/Lab.Server/src/main/java/me/zinch/Lab6/Server/files/CsvController.java @@ -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 loadFromCsv(String filename) throws IOException { + List 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 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])) + ); + } +} \ No newline at end of file diff --git a/Lab.Server/src/main/java/me/zinch/Lab6/Server/modules/CommandModule.java b/Lab.Server/src/main/java/me/zinch/Lab6/Server/modules/CommandModule.java new file mode 100644 index 0000000..f383b73 --- /dev/null +++ b/Lab.Server/src/main/java/me/zinch/Lab6/Server/modules/CommandModule.java @@ -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()); + } + } +} \ No newline at end of file diff --git a/Lab.Server/src/main/java/me/zinch/Lab6/Server/modules/ConnectionModule.java b/Lab.Server/src/main/java/me/zinch/Lab6/Server/modules/ConnectionModule.java new file mode 100644 index 0000000..fbd7be3 --- /dev/null +++ b/Lab.Server/src/main/java/me/zinch/Lab6/Server/modules/ConnectionModule.java @@ -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(); + } +} \ No newline at end of file diff --git a/Lab.Server/src/main/java/me/zinch/Lab6/Server/modules/RequestModule.java b/Lab.Server/src/main/java/me/zinch/Lab6/Server/modules/RequestModule.java new file mode 100644 index 0000000..81da07a --- /dev/null +++ b/Lab.Server/src/main/java/me/zinch/Lab6/Server/modules/RequestModule.java @@ -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 clientMessages; + + public RequestModule(Map 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); + } + } +} \ No newline at end of file diff --git a/Lab.Server/src/main/java/me/zinch/Lab6/Server/modules/ResponseModule.java b/Lab.Server/src/main/java/me/zinch/Lab6/Server/modules/ResponseModule.java new file mode 100644 index 0000000..eabe9f0 --- /dev/null +++ b/Lab.Server/src/main/java/me/zinch/Lab6/Server/modules/ResponseModule.java @@ -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 clientMessages; + private final CommandModule commandModule; + + public ResponseModule(Map 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(); + } +} \ No newline at end of file diff --git a/Lab.Server/src/main/java/me/zinch/Lab6/Server/wrapper/DragonCollection.java b/Lab.Server/src/main/java/me/zinch/Lab6/Server/wrapper/DragonCollection.java new file mode 100644 index 0000000..82d1509 --- /dev/null +++ b/Lab.Server/src/main/java/me/zinch/Lab6/Server/wrapper/DragonCollection.java @@ -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 dragons; + private Integer idIncrementor; + + public DragonCollection(List 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 groupCountingByType() { + return dragons.stream() + .collect(Collectors.groupingBy( + Dragon::getType, + Collectors.counting() + )); + } + + public List filterLessThanCharacter(DragonCharacter character) { + return dragons.stream() + .filter(d -> d.getCharacter().compareTo(character) < 0) + .collect(Collectors.toList()); + } + + public List 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); + } +} \ No newline at end of file diff --git a/Lab.Server/src/main/java/me/zinch/Lab6/Server/wrapper/ProductCollection.java b/Lab.Server/src/main/java/me/zinch/Lab6/Server/wrapper/ProductCollection.java index bf3a70e..57ff028 100644 --- a/Lab.Server/src/main/java/me/zinch/Lab6/Server/wrapper/ProductCollection.java +++ b/Lab.Server/src/main/java/me/zinch/Lab6/Server/wrapper/ProductCollection.java @@ -23,13 +23,12 @@ public class ProductCollection implements IStorage { private Long idIncrementor; public ProductCollection(List list) throws ValidationException { - productList = new TreeSet<>((o1, o2) -> { - var o1Length = o1.getCoordinates().getX() * o1.getCoordinates().getX() + o1.getCoordinates().getY() * o1.getCoordinates().getY(); - var o2Length = o2.getCoordinates().getX() * o2.getCoordinates().getX() + o2.getCoordinates().getY() * o2.getCoordinates().getY(); - return Math.toIntExact(o1Length - o2Length); - }); + productList = new TreeSet<>(Comparator.comparing(Product::getName)); productList.addAll(list); - idIncrementor = productList.isEmpty() ? 1L : productList.last().getId() + 1; + idIncrementor = productList.stream() + .mapToLong(Product::getId) + .max() + .orElse(0) + 1; } private Long generateId() { @@ -37,8 +36,10 @@ public class ProductCollection implements IStorage { } public Product getProductById(Long id) { - Optional product = productList.stream().filter(i -> Objects.equals(i.getId(), id)).findFirst(); - return product.orElse(null); + return productList.stream() + .filter(p -> Objects.equals(p.getId(), id)) + .findFirst() + .orElse(null); } public Product addProduct(ProductDTO productDTO) { @@ -66,48 +67,62 @@ public class ProductCollection implements IStorage { } 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%nДата инициализации: %s%nКоличество элементов: %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 toList() { @@ -116,7 +131,10 @@ public class ProductCollection implements IStorage { @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