Vibes �
This commit is contained in:
parent
5c8f333478
commit
979fb3c0c5
@ -41,6 +41,16 @@
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>2.23.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.12</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<version>2.0.12</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.zinch</groupId>
|
||||
<artifactId>Lab6-Domain</artifactId>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,22 +11,21 @@ public class CommandManager {
|
||||
private static final List<Command> 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<Command> getCommandByInput(String input) {
|
||||
return commandList.stream().filter(command -> command.checkPattern(input)).findFirst();
|
||||
return commandList.stream()
|
||||
.filter(command -> command.checkPattern(input))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package me.zinch.Lab6.Client.commands;
|
||||
|
||||
import me.zinch.Lab6.Client.client.Client;
|
||||
import me.zinch.Lab6.Client.exceptions.CommandActionException;
|
||||
import me.zinch.Lab6.Domain.dto.BodylessMessage;
|
||||
import me.zinch.Lab6.Domain.dto.MessageType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
public class History extends Command {
|
||||
private static final int HISTORY_SIZE = 10;
|
||||
private static final Queue<String> commandHistory = new LinkedList<>();
|
||||
|
||||
public History() {
|
||||
super("history", "вывести последние 10 команд (без их аргументов)");
|
||||
}
|
||||
|
||||
public static void addCommand(String command) {
|
||||
commandHistory.offer(command);
|
||||
if (commandHistory.size() > HISTORY_SIZE) {
|
||||
commandHistory.poll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String action(Client client) throws CommandActionException {
|
||||
return String.join("\n", commandHistory);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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", "вывести элементы коллекции в порядке возрастания");
|
||||
}
|
||||
}
|
||||
@ -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 всех элементов в коллекции");
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
148
Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Dragon.java
Normal file
148
Lab.Domain/src/main/java/me/zinch/Lab6/Domain/models/Dragon.java
Normal file
@ -0,0 +1,148 @@
|
||||
package me.zinch.Lab6.Domain.models;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Dragon implements Serializable, Comparable<Dragon> {
|
||||
@NotNull(message = "Dragon: Поле id не может быть null")
|
||||
@Min(value = 1, message = "Dragon: Значение поля id должно быть больше 0")
|
||||
@JsonProperty("id")
|
||||
private Integer id; // Поле не может быть null, Значение поля должно быть больше 0, Значение этого поля должно быть уникальным, Значение этого поля должно генерироваться автоматически
|
||||
|
||||
@NotBlank(message = "Dragon: Строка name не может быть пустой или null")
|
||||
@JsonProperty("name")
|
||||
private String name; // Поле не может быть null, Строка не может быть пустой
|
||||
|
||||
@NotNull(message = "Dragon: Поле coordinates не может быть null")
|
||||
@JsonProperty("coordinates")
|
||||
private Coordinates coordinates; // Поле не может быть null
|
||||
|
||||
@NotNull(message = "Dragon: Поле creationDate не может быть null")
|
||||
@JsonProperty("creationDate")
|
||||
private LocalDateTime creationDate; // Поле не может быть null, Значение этого поля должно генерироваться автоматически
|
||||
|
||||
@NotNull(message = "Dragon: Поле age не может быть null")
|
||||
@Min(value = 1, message = "Dragon: Значение поля age должно быть больше 0")
|
||||
@JsonProperty("age")
|
||||
private Integer age; // Значение поля должно быть больше 0, Поле не может быть null
|
||||
|
||||
@Min(value = 1, message = "Dragon: Значение поля weight должно быть больше 0")
|
||||
@JsonProperty("weight")
|
||||
private long weight; // Значение поля должно быть больше 0
|
||||
|
||||
@NotNull(message = "Dragon: Поле type не может быть null")
|
||||
@JsonProperty("type")
|
||||
private DragonType type; // Поле не может быть null
|
||||
|
||||
@NotNull(message = "Dragon: Поле character не может быть null")
|
||||
@JsonProperty("character")
|
||||
private DragonCharacter character; // Поле не может быть null
|
||||
|
||||
@JsonProperty("head")
|
||||
private DragonHead head;
|
||||
|
||||
public Dragon() {}
|
||||
|
||||
public Dragon(Integer id,
|
||||
String name,
|
||||
Coordinates coordinates,
|
||||
LocalDateTime creationDate,
|
||||
Integer age,
|
||||
long weight,
|
||||
DragonType type,
|
||||
DragonCharacter character,
|
||||
DragonHead head) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.coordinates = coordinates;
|
||||
this.creationDate = creationDate;
|
||||
this.age = age;
|
||||
this.weight = weight;
|
||||
this.type = type;
|
||||
this.character = character;
|
||||
this.head = head;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Coordinates getCoordinates() {
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreationDate() {
|
||||
return creationDate;
|
||||
}
|
||||
|
||||
public Integer getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public long getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public DragonType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public DragonCharacter getCharacter() {
|
||||
return character;
|
||||
}
|
||||
|
||||
public DragonHead getHead() {
|
||||
return head;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Dragon{" +
|
||||
"id=" + id +
|
||||
", name='" + name + '\'' +
|
||||
", coordinates=" + coordinates +
|
||||
", creationDate=" + creationDate +
|
||||
", age=" + age +
|
||||
", weight=" + weight +
|
||||
", type=" + type +
|
||||
", character=" + character +
|
||||
", head=" + head +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Dragon dragon = (Dragon) o;
|
||||
return weight == dragon.weight &&
|
||||
Objects.equals(id, dragon.id) &&
|
||||
Objects.equals(name, dragon.name) &&
|
||||
Objects.equals(coordinates, dragon.coordinates) &&
|
||||
Objects.equals(creationDate, dragon.creationDate) &&
|
||||
Objects.equals(age, dragon.age) &&
|
||||
type == dragon.type &&
|
||||
character == dragon.character &&
|
||||
Objects.equals(head, dragon.head);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, name, coordinates, creationDate, age, weight, type, character, head);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Dragon other) {
|
||||
return this.name.compareTo(other.name);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package me.zinch.Lab6.Domain.models;
|
||||
|
||||
public enum DragonCharacter {
|
||||
CUNNING,
|
||||
WISE,
|
||||
EVIL,
|
||||
CHAOTIC,
|
||||
FICKLE
|
||||
}
|
||||
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package me.zinch.Lab6.Domain.models;
|
||||
|
||||
public enum DragonType {
|
||||
WATER,
|
||||
AIR,
|
||||
FIRE
|
||||
}
|
||||
@ -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<Product> {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Product> 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<DragonType, Long> groupCountingByType();
|
||||
List<Dragon> filterLessThanCharacter(DragonCharacter character);
|
||||
List<Dragon> toList();
|
||||
}
|
||||
|
||||
@ -68,6 +68,16 @@
|
||||
<artifactId>jline</artifactId>
|
||||
<version>3.26.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>2.23.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.opencsv</groupId>
|
||||
<artifactId>opencsv</artifactId>
|
||||
<version>5.9</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -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<SocketAddress, Message> 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<SocketChannel, Message> clientMessages;
|
||||
private final ConnectionModule connectionModule;
|
||||
private final RequestModule requestModule;
|
||||
private final ResponseModule responseModule;
|
||||
private final CommandModule commandModule;
|
||||
private final IStorage storage;
|
||||
private boolean isRunning;
|
||||
|
||||
public Server(int port, IStorage collection) throws IOException {
|
||||
selector = Selector.open();
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,80 @@
|
||||
package me.zinch.Lab6.Server.files;
|
||||
|
||||
import com.opencsv.CSVReader;
|
||||
import com.opencsv.CSVWriter;
|
||||
import com.opencsv.exceptions.CsvValidationException;
|
||||
import me.zinch.Lab6.Domain.models.Coordinates;
|
||||
import me.zinch.Lab6.Domain.models.Dragon;
|
||||
import me.zinch.Lab6.Domain.models.DragonCharacter;
|
||||
import me.zinch.Lab6.Domain.models.DragonHead;
|
||||
import me.zinch.Lab6.Domain.models.DragonType;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CsvController {
|
||||
private static final String[] HEADERS = {
|
||||
"id", "name", "coordinates_x", "coordinates_y", "creation_date",
|
||||
"age", "weight", "type", "character", "head_size"
|
||||
};
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
|
||||
public static List<Dragon> loadFromCsv(String filename) throws IOException {
|
||||
List<Dragon> dragons = new ArrayList<>();
|
||||
try (CSVReader reader = new CSVReader(new FileReader(filename))) {
|
||||
// Skip headers
|
||||
reader.readNext();
|
||||
|
||||
String[] line;
|
||||
while ((line = reader.readNext()) != null) {
|
||||
dragons.add(parseDragon(line));
|
||||
}
|
||||
} catch (CsvValidationException e) {
|
||||
throw new IOException("Error reading CSV file: " + e.getMessage());
|
||||
}
|
||||
return dragons;
|
||||
}
|
||||
|
||||
public static void saveToCsv(List<Dragon> dragons, String filename) throws IOException {
|
||||
try (CSVWriter writer = new CSVWriter(new FileWriter(filename))) {
|
||||
writer.writeNext(HEADERS);
|
||||
|
||||
for (Dragon dragon : dragons) {
|
||||
writer.writeNext(new String[]{
|
||||
String.valueOf(dragon.getId()),
|
||||
dragon.getName(),
|
||||
String.valueOf(dragon.getCoordinates().getX()),
|
||||
String.valueOf(dragon.getCoordinates().getY()),
|
||||
dragon.getCreationDate().format(DATE_FORMATTER),
|
||||
String.valueOf(dragon.getAge()),
|
||||
String.valueOf(dragon.getWeight()),
|
||||
dragon.getType().name(),
|
||||
dragon.getCharacter().name(),
|
||||
dragon.getHead() != null ? String.valueOf(dragon.getHead().getSize()) : ""
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dragon parseDragon(String[] fields) {
|
||||
return new Dragon(
|
||||
Integer.parseInt(fields[0]),
|
||||
fields[1],
|
||||
new Coordinates(
|
||||
Long.parseLong(fields[2]),
|
||||
Integer.parseInt(fields[3])
|
||||
),
|
||||
LocalDateTime.parse(fields[4], DATE_FORMATTER),
|
||||
Integer.parseInt(fields[5]),
|
||||
Long.parseLong(fields[6]),
|
||||
DragonType.valueOf(fields[7]),
|
||||
DragonCharacter.valueOf(fields[8]),
|
||||
fields[9].isEmpty() ? null : new DragonHead(Float.parseFloat(fields[9]))
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package me.zinch.Lab6.Server.modules;
|
||||
|
||||
import me.zinch.Lab6.Domain.dto.Message;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Map;
|
||||
|
||||
public class RequestModule {
|
||||
private static final Logger log = LoggerFactory.getLogger(RequestModule.class);
|
||||
private final Map<SocketChannel, Message> clientMessages;
|
||||
|
||||
public RequestModule(Map<SocketChannel, Message> clientMessages) {
|
||||
this.clientMessages = clientMessages;
|
||||
}
|
||||
|
||||
public void handleRead(SelectionKey key) throws IOException, ClassNotFoundException {
|
||||
var client = (SocketChannel) key.channel();
|
||||
var buffer = ByteBuffer.allocate(8192);
|
||||
int bytesRead = client.read(buffer);
|
||||
|
||||
if (bytesRead == -1) {
|
||||
client.close();
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.flip();
|
||||
try (var oIn = new ObjectInputStream(new ByteArrayInputStream(buffer.array()))) {
|
||||
var msg = (Message) oIn.readObject();
|
||||
log.info("Received message {} from {}", msg, client.getRemoteAddress());
|
||||
clientMessages.put(client, msg);
|
||||
client.register(key.selector(), SelectionKey.OP_WRITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package me.zinch.Lab6.Server.modules;
|
||||
|
||||
import me.zinch.Lab6.Domain.dto.Message;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Map;
|
||||
|
||||
public class ResponseModule {
|
||||
private static final Logger log = LoggerFactory.getLogger(ResponseModule.class);
|
||||
private final Map<SocketChannel, Message> clientMessages;
|
||||
private final CommandModule commandModule;
|
||||
|
||||
public ResponseModule(Map<SocketChannel, Message> clientMessages, CommandModule commandModule) {
|
||||
this.clientMessages = clientMessages;
|
||||
this.commandModule = commandModule;
|
||||
}
|
||||
|
||||
public void handleWrite(SelectionKey key) throws IOException {
|
||||
var client = (SocketChannel) key.channel();
|
||||
var message = clientMessages.get(client);
|
||||
|
||||
if (message == null) {
|
||||
log.error("Message for {} not found", client.getRemoteAddress());
|
||||
client.close();
|
||||
return;
|
||||
}
|
||||
|
||||
ByteBuffer responseBuffer = commandModule.processCommand(message);
|
||||
if (responseBuffer != null) {
|
||||
client.write(responseBuffer);
|
||||
log.info("Sent response to {}", client.getRemoteAddress());
|
||||
}
|
||||
|
||||
clientMessages.remove(client);
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
package me.zinch.Lab6.Server.wrapper;
|
||||
|
||||
import me.zinch.Lab6.Domain.exceptions.ValidationException;
|
||||
import me.zinch.Lab6.Domain.models.Dragon;
|
||||
import me.zinch.Lab6.Domain.models.DragonCharacter;
|
||||
import me.zinch.Lab6.Domain.models.DragonType;
|
||||
import me.zinch.Lab6.Domain.wrapper.IStorage;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DragonCollection implements IStorage {
|
||||
private final LinkedList<Dragon> dragons;
|
||||
private Integer idIncrementor;
|
||||
|
||||
public DragonCollection(List<Dragon> list) throws ValidationException {
|
||||
dragons = new LinkedList<>(list);
|
||||
idIncrementor = dragons.stream()
|
||||
.mapToInt(Dragon::getId)
|
||||
.max()
|
||||
.orElse(0) + 1;
|
||||
}
|
||||
|
||||
private Integer generateId() {
|
||||
return idIncrementor++;
|
||||
}
|
||||
|
||||
public Dragon getDragonById(Integer id) {
|
||||
return dragons.stream()
|
||||
.filter(d -> Objects.equals(d.getId(), id))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public Dragon addDragon(Dragon dragon) {
|
||||
dragons.add(dragon);
|
||||
return dragon;
|
||||
}
|
||||
|
||||
public Dragon updateDragon(Integer id, Dragon newDragon) {
|
||||
var index = dragons.indexOf(getDragonById(id));
|
||||
if (index != -1) {
|
||||
dragons.set(index, newDragon);
|
||||
}
|
||||
return newDragon;
|
||||
}
|
||||
|
||||
public Dragon removeDragon(Integer id) {
|
||||
var dragon = getDragonById(id);
|
||||
dragons.remove(dragon);
|
||||
return dragon;
|
||||
}
|
||||
|
||||
public String getInfo() {
|
||||
var initDate = dragons.stream()
|
||||
.map(Dragon::getCreationDate)
|
||||
.min(Comparator.naturalOrder())
|
||||
.orElse(LocalDateTime.MIN);
|
||||
return String.format("Type: LinkedList%nInitialization date: %s%nNumber of elements: %s",
|
||||
initDate, dragons.size());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
dragons.clear();
|
||||
}
|
||||
|
||||
public Dragon getHead() {
|
||||
return dragons.isEmpty() ? null : dragons.getFirst();
|
||||
}
|
||||
|
||||
public Dragon removeHead() {
|
||||
return dragons.isEmpty() ? null : dragons.removeFirst();
|
||||
}
|
||||
|
||||
public Dragon getMaxByWeight() {
|
||||
return dragons.stream()
|
||||
.max(Comparator.comparingLong(Dragon::getWeight))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public Map<DragonType, Long> groupCountingByType() {
|
||||
return dragons.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
Dragon::getType,
|
||||
Collectors.counting()
|
||||
));
|
||||
}
|
||||
|
||||
public List<Dragon> filterLessThanCharacter(DragonCharacter character) {
|
||||
return dragons.stream()
|
||||
.filter(d -> d.getCharacter().compareTo(character) < 0)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Dragon> toList() {
|
||||
return new LinkedList<>(dragons);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return dragons.stream()
|
||||
.map(Dragon::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DragonCollection that = (DragonCollection) o;
|
||||
return Objects.equals(dragons, that.dragons) && Objects.equals(idIncrementor, that.idIncrementor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(dragons, idIncrementor);
|
||||
}
|
||||
}
|
||||
@ -23,13 +23,12 @@ public class ProductCollection implements IStorage {
|
||||
private Long idIncrementor;
|
||||
|
||||
public ProductCollection(List<Product> 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> 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<Product> 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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user