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