diff --git a/src/main/java/HW/models/DefaultIOOperations.java b/src/main/java/HW/models/DefaultIOOperations.java new file mode 100644 index 0000000..52393b0 --- /dev/null +++ b/src/main/java/HW/models/DefaultIOOperations.java @@ -0,0 +1,21 @@ +package HW.models; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Path; + +public class DefaultIOOperations { + public static String getPath(String path) throws IOException { + if (Path.of(path).isAbsolute()) { + return path; + } + + URL resource; + if ((resource = DefaultIOOperations.class.getClassLoader().getResource(path)) != null) { + return resource.getPath(); + } else if ((resource = DefaultIOOperations.class.getResource(path)) != null) { + return resource.getPath(); + } + throw new IOException("Can't find file by path: " + path); + } +} diff --git a/src/main/java/HW/models/Main.java b/src/main/java/HW/models/Main.java new file mode 100644 index 0000000..f49b5d4 --- /dev/null +++ b/src/main/java/HW/models/Main.java @@ -0,0 +1,32 @@ +package HW.models; + +import HW.models.basket.BasketRepository; +import HW.models.basket.BasketRepositoryInMemory; +import HW.models.enums.Product; +import HW.models.parser.CommandParserTXT; +import HW.models.storage.Storage; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +public class Main { + private static final Logger logger = Logger.getLogger(Main.class.getSimpleName()); + + public static void main(String[] args) { + Map storageContent = new HashMap<>(); + storageContent.put(Product.Bread, 100f); + storageContent.put(Product.Flour, 100f); + storageContent.put(Product.Water, 100f); + Storage storage = new Storage(storageContent); + BasketRepository basketRepository = new BasketRepositoryInMemory(storage); + String path = "commands/list1.txt"; + try { + CommandParserTXT parser = new CommandParserTXT(path, basketRepository); + parser.applyAllCommands(); + } catch (IOException e) { + logger.warning("Failed while executing commands from txt file: " + e.getMessage()); + } + } +} diff --git a/src/main/java/HW/models/basket/Basket.java b/src/main/java/HW/models/basket/Basket.java new file mode 100644 index 0000000..909e0e6 --- /dev/null +++ b/src/main/java/HW/models/basket/Basket.java @@ -0,0 +1,44 @@ +package HW.models.basket; + +import HW.models.enums.Product; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class Basket { + private ConcurrentHashMap products = new ConcurrentHashMap<>(); + + public Basket (Map products) { + this.products = new ConcurrentHashMap<>(); + this.products.putAll(products); + } + + public Basket () { + this.products = new ConcurrentHashMap<>(); + } + + public synchronized void alterContent (Product product, float change) { + if (products.containsKey(product)) { + float newValue = products.get(product) + change; + if (newValue < 0) { + throw new IllegalArgumentException("Basket content can't be negative"); + } + products.put(product, newValue); + } else { + products.put(product, change); + } + } + + public synchronized void clearBasket () { + products.clear(); + } + + public Map getProducts () { + return Map.copyOf(products); + } + + public Basket getCopy() { + return new Basket(getProducts()); + } +} diff --git a/src/main/java/HW/models/basket/BasketRepository.java b/src/main/java/HW/models/basket/BasketRepository.java new file mode 100644 index 0000000..e3cb7a4 --- /dev/null +++ b/src/main/java/HW/models/basket/BasketRepository.java @@ -0,0 +1,13 @@ +package HW.models.basket; + +import HW.models.enums.Product; + +import java.util.HashMap; +import java.util.Map; + +public interface BasketRepository { + void changeBasketContent (long id, Product product, float change); + void tryOrderBasket (long id); + void removeBasket(long id); + Map getContent(); +} diff --git a/src/main/java/HW/models/basket/BasketRepositoryInMemory.java b/src/main/java/HW/models/basket/BasketRepositoryInMemory.java new file mode 100644 index 0000000..e695595 --- /dev/null +++ b/src/main/java/HW/models/basket/BasketRepositoryInMemory.java @@ -0,0 +1,64 @@ +package HW.models.basket; + +import HW.models.enums.Product; +import HW.models.storage.Storage; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +public class BasketRepositoryInMemory implements BasketRepository{ + private static final Logger logger = Logger.getLogger(BasketRepositoryInMemory.class.getSimpleName()); + private final ConcurrentHashMap baskets = new ConcurrentHashMap<>(); + private final Storage storage; + + public BasketRepositoryInMemory(Storage storage) { + this.storage = storage; + } + + @Override + public void changeBasketContent(long id, Product product, float change) { + synchronized (Long.valueOf(id)) { + logger.info("Basket with id " + id + " was changed: product->" + product.displayName + " change->" + change + " " + product.unitName); + if (!baskets.containsKey(id)) { + baskets.put(id, new Basket()); + } + try { + baskets.get(id).alterContent(product, change); + } catch (IllegalArgumentException e) { + logger.info("Change to basket with id " + id + " wasn't made"); + } + } + } + + @Override + public void tryOrderBasket(long id) { + synchronized (Long.valueOf(id)) { + logger.info("Basket with id " + id + " was ordered"); + try { + Map basketContent = baskets.get(id).getProducts(); + this.storage.takeAway(basketContent); + baskets.get(id).clearBasket(); + } catch (RuntimeException e) { + logger.info("Basket with id " + id + " was ordered but failed!"); + } + } + } + + @Override + public void removeBasket(long id) { + synchronized (Long.valueOf(id)) { + logger.info("Basket with id " + id + " was removed"); + baskets.remove(id); + } + } + + @Override + public Map getContent() { + return baskets.entrySet().stream() + .map(entry -> Map.entry(entry.getKey(), entry.getValue().getCopy())) + .collect(Collectors.toMap(val -> val.getKey(), val -> val.getValue())); + } +} diff --git a/src/main/java/HW/models/parser/CommandParserTXT.java b/src/main/java/HW/models/parser/CommandParserTXT.java new file mode 100644 index 0000000..fc25eb0 --- /dev/null +++ b/src/main/java/HW/models/parser/CommandParserTXT.java @@ -0,0 +1,62 @@ +package HW.models.parser; + +import HW.models.DefaultIOOperations; +import HW.models.basket.BasketRepository; +import HW.models.enums.Product; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.logging.Logger; + +public class CommandParserTXT { + private static final Logger logger = Logger.getLogger(CommandParserTXT.class.getSimpleName()); + private final String sourcePath; + private final BasketRepository basketRepository; + private final BufferedReader reader; + + public CommandParserTXT(String sourcePath, BasketRepository basketRepository) throws IOException { + this.basketRepository = basketRepository; + if (!sourcePath.contains(".txt")) { + throw new IllegalArgumentException("File for CommandParserTXT has to be of type txt"); + } + this.sourcePath = sourcePath; + this.reader = new BufferedReader(new FileReader(DefaultIOOperations.getPath(this.sourcePath))); + } + + public boolean applyNextCommand () throws IOException { + String newLine = reader.readLine(); + if (newLine == null) { + return false; + } + applyCommand(newLine); + return true; + } + + public void applyAllCommands () throws IOException { + reader.lines().forEachOrdered(this::applyCommand); + reader.close(); + } + + private void applyCommand (String command) { + String[] parts = command.split(" "); + if (parts[0].equalsIgnoreCase("get")) { + try { + long id = Long.parseLong(parts[1]); + Product product = Product.valueOf(parts[2]); + float change = Float.parseFloat(parts[3]); + basketRepository.changeBasketContent(id, product, change); + } catch (IllegalArgumentException e) { + logger.info("Unknown product: " + parts[2]); + } + } else if (parts[0].equalsIgnoreCase("order")) { + long id = Long.parseLong(parts[1]); + basketRepository.tryOrderBasket(id); + } else if (parts[0].equalsIgnoreCase("remove")) { + long id = Long.parseLong(parts[1]); + basketRepository.removeBasket(id); + } else { + logger.info("Unknown command: " + parts[0]); + } + } +} diff --git a/src/main/java/HW/models/storage/Storage.java b/src/main/java/HW/models/storage/Storage.java new file mode 100644 index 0000000..73abe46 --- /dev/null +++ b/src/main/java/HW/models/storage/Storage.java @@ -0,0 +1,42 @@ +package HW.models.storage; + +import HW.models.basket.Basket; +import HW.models.enums.Product; + +import java.util.HashMap; +import java.util.Map; + +public class Storage{ + private Map content; + + public Storage() { + this.content = new HashMap(); + } + + public Storage(Map content) { + this.content = content; + } + + public synchronized void takeAway(Map basketContent) + throws UnavailablePurchaseException { + for (Product product : basketContent.keySet()) { + if (this.content.containsKey(product)) { + if (this.content.get(product) < basketContent.get(product)) { + throw new UnavailablePurchaseException("Shortage of product!", + product + " не в наличии или его недостаточно для осуществления заказа"); + } + } else { + throw new UnavailablePurchaseException("Unavailable product!", + product + " не хранится на складе"); + } + } + + for (Product product : basketContent.keySet()) { + this.content.put(product, this.content.get(product) - basketContent.get(product)); + } + } + + public synchronized Map getContent() { + return Map.copyOf(content); + } +} diff --git a/src/main/java/HW/models/storage/UnavailablePurchaseException.java b/src/main/java/HW/models/storage/UnavailablePurchaseException.java new file mode 100644 index 0000000..d1c3ee8 --- /dev/null +++ b/src/main/java/HW/models/storage/UnavailablePurchaseException.java @@ -0,0 +1,10 @@ +package HW.models.storage; + +public class UnavailablePurchaseException extends RuntimeException{ + public final String reasonDescription; + + public UnavailablePurchaseException(String message, String reasonDescription) { + super(message); + this.reasonDescription = reasonDescription; + } +} diff --git a/src/main/resources/commands/list1.txt b/src/main/resources/commands/list1.txt new file mode 100644 index 0000000..08395d1 --- /dev/null +++ b/src/main/resources/commands/list1.txt @@ -0,0 +1,16 @@ +get 1 Water 10 +get 1 Bread 20 +get 2 Flour 5 +get 3 Water 7 +order 1 +get 2 Bread 1000 +remove 2 +get 2 Flour 3 +order 2 +order 3 +get 1 Water 5 +order 1 +get 4 Flour 1 +remove 4 +get 2 Bread 2 +order 2 \ No newline at end of file diff --git a/src/main/resources/commands/testIllegal.txt b/src/main/resources/commands/testIllegal.txt new file mode 100644 index 0000000..dd07b0c --- /dev/null +++ b/src/main/resources/commands/testIllegal.txt @@ -0,0 +1,6 @@ +get 1 Water 8 +get 1 Bread 5 +order 1 +get 2 Flour 4 +order 2 +order 3 \ No newline at end of file diff --git a/src/main/resources/commands/testOverall.txt b/src/main/resources/commands/testOverall.txt new file mode 100644 index 0000000..11529cd --- /dev/null +++ b/src/main/resources/commands/testOverall.txt @@ -0,0 +1,15 @@ +get 1 Water 2 +get 1 Bread 8 +get 2 Water 4 +order 2 +order 1 +get 3 Bread 4 +order 3 +remove 3 +get 2 Water 7 +get 2 Bread 9 +remove 2 +get 2 Water 0.5 +get 2 Bread 2 +order 2 +get 5 Bread 2 \ No newline at end of file diff --git a/src/test/java/HW/models/parser/CommandParserTXTTest.java b/src/test/java/HW/models/parser/CommandParserTXTTest.java new file mode 100644 index 0000000..b3527bd --- /dev/null +++ b/src/test/java/HW/models/parser/CommandParserTXTTest.java @@ -0,0 +1,70 @@ +package HW.models.parser; + +import HW.models.basket.Basket; +import HW.models.basket.BasketRepository; +import HW.models.basket.BasketRepositoryInMemory; +import HW.models.enums.Product; +import HW.models.storage.Storage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class CommandParserTXTTest { + Map storageContent; + Storage storage; + BasketRepository basketRepository; + + @BeforeEach + public void setUp() { + this.storageContent = new HashMap<>(); + this.storageContent.put(Product.Bread, 10f); + this.storageContent.put(Product.Water, 5f); + this.storage = new Storage(storageContent); + this.basketRepository = new BasketRepositoryInMemory(this.storage); + } + + @Test + public void testIllegalOrders() { + // На складе недостаточно воды и нет муки. Корзина 3 не была создана. + String path = "commands/testIllegal.txt"; + try { + CommandParserTXT parser = new CommandParserTXT(path, this.basketRepository); + parser.applyAllCommands(); + Map storageContent = this.storage.getContent(); + Map baskets = this.basketRepository.getContent(); + // Тразакции не совершены. Корзины сохранились в памяти неизменными в изначальном количестве. + assertEquals(10f, storageContent.get(Product.Bread)); + assertEquals(5f, storageContent.get(Product.Water)); + assertEquals(2, baskets.size()); + assertEquals(5, baskets.get(1l).getProducts().get(Product.Bread)); + assertEquals(8, baskets.get(1l).getProducts().get(Product.Water)); + assertEquals(4, baskets.get(2l).getProducts().get(Product.Flour)); + } catch (IOException e) {} + } + + @Test + public void testOverall() { + String path = "commands/testOverall.txt"; + // Заказ второй корзины уменьшает количество воды ниже требований первой. Заказ третьей прошёл, корзина удалена. + try { + CommandParserTXT parser = new CommandParserTXT(path, this.basketRepository); + parser.applyAllCommands(); + Map storageContent = this.storage.getContent(); + Map baskets = this.basketRepository.getContent(); + // Корзины 2 и 3 заказаны и опустошены, причём третья удалена из памяти. Корзина один не была заказана и сохранилась в памяти. + assertEquals(4f, storageContent.get(Product.Bread)); + assertEquals(0.5f, storageContent.get(Product.Water)); + assertEquals(3, baskets.size()); + assertEquals(false, baskets.containsKey(3)); + assertEquals(0, baskets.get(2l).getProducts().size()); + assertEquals(2f, baskets.get(1l).getProducts().get(Product.Water)); + assertEquals(8f, baskets.get(1l).getProducts().get(Product.Bread)); + assertEquals(2f, baskets.get(5l).getProducts().get(Product.Bread)); + } catch (IOException e) {} + } +} \ No newline at end of file