diff --git a/src/main/java/core/basesyntax/HelloWorld.java b/src/main/java/core/basesyntax/HelloWorld.java index 05f94be41b..21e1911059 100644 --- a/src/main/java/core/basesyntax/HelloWorld.java +++ b/src/main/java/core/basesyntax/HelloWorld.java @@ -1,9 +1,61 @@ package core.basesyntax; +import core.basesyntax.dao.FruitRepository; +import core.basesyntax.dao.impl.FruitRepositoryImpl; +import core.basesyntax.model.FruitTransaction; +import core.basesyntax.model.enums.Operation; +import core.basesyntax.service.DataConverter; +import core.basesyntax.service.FileService; +import core.basesyntax.service.ReportGenerator; +import core.basesyntax.service.ShopService; +import core.basesyntax.service.handler.OperationHandler; +import core.basesyntax.service.handler.OperationStrategy; +import core.basesyntax.service.handler.impl.BalanceOperationHandler; +import core.basesyntax.service.handler.impl.OperationStrategyImpl; +import core.basesyntax.service.handler.impl.PurchaseOperationHandler; +import core.basesyntax.service.handler.impl.ReturnOperationHandler; +import core.basesyntax.service.handler.impl.SupplyOperationHandler; +import core.basesyntax.service.impl.DataConverterImpl; +import core.basesyntax.service.impl.FileServiceImpl; +import core.basesyntax.service.impl.ReportGeneratorImpl; +import core.basesyntax.service.impl.ShopServiceImpl; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * Feel free to remove this class and create your own. */ public class HelloWorld { - // HINT: In the `public static void main(String[] args)` it is better to create instances of your classes, - // and call their methods, but do not write any business logic in the `main` method! + public static void main(String[] arg) { + // 1. Read the data from the input CSV file + FileService fileService = new FileServiceImpl(); + List inputReport = fileService.read("transactions.csv"); + + // 2. Convert the incoming data into FruitTransactions list + DataConverter dataConverter = new DataConverterImpl(); + + // 3. Create and feel the map with all OperationHandler implementations + FruitRepository repository = new FruitRepositoryImpl(); + Map operationHandlers = new HashMap<>(); + operationHandlers.put(Operation.BALANCE, new BalanceOperationHandler(repository)); + operationHandlers.put(Operation.PURCHASE, new PurchaseOperationHandler(repository)); + operationHandlers.put(Operation.RETURN, new ReturnOperationHandler(repository)); + operationHandlers.put(Operation.SUPPLY, new SupplyOperationHandler(repository)); + OperationStrategy operationStrategy = new OperationStrategyImpl(operationHandlers); + + // 4. Process the incoming transactions with applicable OperationHandler implementations + + List transactions = dataConverter + .convertToFruitTransactions(inputReport); + ShopService shopService = new ShopServiceImpl(operationStrategy); + shopService.process(transactions); + + // 5.Generate report based on the current Storage state + ReportGenerator reportGenerator = new ReportGeneratorImpl(repository); + String resultingReport = reportGenerator.generateReport(); + + // 6. Write the received report into the destination file + fileService.write("report.csv", resultingReport); + } } diff --git a/src/main/java/core/basesyntax/dao/FruitRepository.java b/src/main/java/core/basesyntax/dao/FruitRepository.java new file mode 100644 index 0000000000..c91dc910a3 --- /dev/null +++ b/src/main/java/core/basesyntax/dao/FruitRepository.java @@ -0,0 +1,13 @@ +package core.basesyntax.dao; + +import java.util.Map; + +public interface FruitRepository { + void add(String fruit, int quantity); + + void remove(String fruit, int quantity); + + boolean hasFruit(String fruit); + + Map getAll(); +} diff --git a/src/main/java/core/basesyntax/dao/impl/FruitRepositoryImpl.java b/src/main/java/core/basesyntax/dao/impl/FruitRepositoryImpl.java new file mode 100644 index 0000000000..44f39909ea --- /dev/null +++ b/src/main/java/core/basesyntax/dao/impl/FruitRepositoryImpl.java @@ -0,0 +1,33 @@ +package core.basesyntax.dao.impl; + +import core.basesyntax.dao.FruitRepository; +import core.basesyntax.db.Database; +import core.basesyntax.exception.StorageException; +import java.util.Map; + +public class FruitRepositoryImpl implements FruitRepository { + private final Map db = Database.storage; + + @Override + public void add(String fruit, int quantity) { + db.merge(fruit, quantity, Integer::sum); + } + + @Override + public void remove(String fruit, int quantity) { + if (db.get(fruit) - quantity < 0) { + throw new StorageException("Not enough " + fruit + " in storage to remove"); + } + db.put(fruit, db.get(fruit) - quantity); + } + + @Override + public boolean hasFruit(String fruit) { + return db.containsKey(fruit); + } + + @Override + public Map getAll() { + return Map.copyOf(db); + } +} diff --git a/src/main/java/core/basesyntax/db/Database.java b/src/main/java/core/basesyntax/db/Database.java new file mode 100644 index 0000000000..28b0dedb12 --- /dev/null +++ b/src/main/java/core/basesyntax/db/Database.java @@ -0,0 +1,8 @@ +package core.basesyntax.db; + +import java.util.HashMap; +import java.util.Map; + +public class Database { + public static final Map storage = new HashMap<>(); +} diff --git a/src/main/java/core/basesyntax/exception/InvalidInputException.java b/src/main/java/core/basesyntax/exception/InvalidInputException.java new file mode 100644 index 0000000000..4b8dbd1efd --- /dev/null +++ b/src/main/java/core/basesyntax/exception/InvalidInputException.java @@ -0,0 +1,7 @@ +package core.basesyntax.exception; + +public class InvalidInputException extends RuntimeException { + public InvalidInputException(String message) { + super(message); + } +} diff --git a/src/main/java/core/basesyntax/exception/StorageException.java b/src/main/java/core/basesyntax/exception/StorageException.java new file mode 100644 index 0000000000..739e6b1843 --- /dev/null +++ b/src/main/java/core/basesyntax/exception/StorageException.java @@ -0,0 +1,7 @@ +package core.basesyntax.exception; + +public class StorageException extends RuntimeException { + public StorageException(String message) { + super(message); + } +} diff --git a/src/main/java/core/basesyntax/model/FruitTransaction.java b/src/main/java/core/basesyntax/model/FruitTransaction.java new file mode 100644 index 0000000000..f7b7b00c9c --- /dev/null +++ b/src/main/java/core/basesyntax/model/FruitTransaction.java @@ -0,0 +1,39 @@ +package core.basesyntax.model; + +import core.basesyntax.model.enums.Operation; + +public class FruitTransaction { + private Operation operation; + private String fruit; + private int quantity; + + public FruitTransaction(Operation operation, String fruit, int quantity) { + this.operation = operation; + this.fruit = fruit; + this.quantity = quantity; + } + + public Operation getOperation() { + return operation; + } + + public String getFruit() { + return fruit; + } + + public int getQuantity() { + return quantity; + } + + public void setOperation(Operation operation) { + this.operation = operation; + } + + public void setFruit(String fruit) { + this.fruit = fruit; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } +} diff --git a/src/main/java/core/basesyntax/model/enums/Operation.java b/src/main/java/core/basesyntax/model/enums/Operation.java new file mode 100644 index 0000000000..82d39c1ba8 --- /dev/null +++ b/src/main/java/core/basesyntax/model/enums/Operation.java @@ -0,0 +1,29 @@ +package core.basesyntax.model.enums; + +import java.util.NoSuchElementException; + +public enum Operation { + BALANCE("b"), + SUPPLY("s"), + PURCHASE("p"), + RETURN("r"); + + private final String code; + + Operation(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public static Operation getByCode(String code) { + for (Operation operation : values()) { + if (operation.code.equals(code)) { + return operation; + } + } + throw new NoSuchElementException("Unsupported operation code: " + code); + } +} diff --git a/src/main/java/core/basesyntax/service/DataConverter.java b/src/main/java/core/basesyntax/service/DataConverter.java new file mode 100644 index 0000000000..980e5d95d1 --- /dev/null +++ b/src/main/java/core/basesyntax/service/DataConverter.java @@ -0,0 +1,8 @@ +package core.basesyntax.service; + +import core.basesyntax.model.FruitTransaction; +import java.util.List; + +public interface DataConverter { + List convertToFruitTransactions(List stringsData); +} diff --git a/src/main/java/core/basesyntax/service/FileService.java b/src/main/java/core/basesyntax/service/FileService.java new file mode 100644 index 0000000000..12e80ce102 --- /dev/null +++ b/src/main/java/core/basesyntax/service/FileService.java @@ -0,0 +1,9 @@ +package core.basesyntax.service; + +import java.util.List; + +public interface FileService { + List read(String filePath); + + void write(String filePath, String data); +} diff --git a/src/main/java/core/basesyntax/service/ReportGenerator.java b/src/main/java/core/basesyntax/service/ReportGenerator.java new file mode 100644 index 0000000000..cdaaf96717 --- /dev/null +++ b/src/main/java/core/basesyntax/service/ReportGenerator.java @@ -0,0 +1,5 @@ +package core.basesyntax.service; + +public interface ReportGenerator { + String generateReport(); +} diff --git a/src/main/java/core/basesyntax/service/ShopService.java b/src/main/java/core/basesyntax/service/ShopService.java new file mode 100644 index 0000000000..f964d7a652 --- /dev/null +++ b/src/main/java/core/basesyntax/service/ShopService.java @@ -0,0 +1,8 @@ +package core.basesyntax.service; + +import core.basesyntax.model.FruitTransaction; +import java.util.List; + +public interface ShopService { + void process(List transactions); +} diff --git a/src/main/java/core/basesyntax/service/handler/OperationHandler.java b/src/main/java/core/basesyntax/service/handler/OperationHandler.java new file mode 100644 index 0000000000..352ed6a1aa --- /dev/null +++ b/src/main/java/core/basesyntax/service/handler/OperationHandler.java @@ -0,0 +1,7 @@ +package core.basesyntax.service.handler; + +import core.basesyntax.model.FruitTransaction; + +public interface OperationHandler { + void handle(FruitTransaction transaction); +} diff --git a/src/main/java/core/basesyntax/service/handler/OperationStrategy.java b/src/main/java/core/basesyntax/service/handler/OperationStrategy.java new file mode 100644 index 0000000000..f0bff2c650 --- /dev/null +++ b/src/main/java/core/basesyntax/service/handler/OperationStrategy.java @@ -0,0 +1,7 @@ +package core.basesyntax.service.handler; + +import core.basesyntax.model.enums.Operation; + +public interface OperationStrategy { + OperationHandler getOperationHandler(Operation operation); +} diff --git a/src/main/java/core/basesyntax/service/handler/impl/BalanceOperationHandler.java b/src/main/java/core/basesyntax/service/handler/impl/BalanceOperationHandler.java new file mode 100644 index 0000000000..2f958a1153 --- /dev/null +++ b/src/main/java/core/basesyntax/service/handler/impl/BalanceOperationHandler.java @@ -0,0 +1,18 @@ +package core.basesyntax.service.handler.impl; + +import core.basesyntax.dao.FruitRepository; +import core.basesyntax.model.FruitTransaction; +import core.basesyntax.service.handler.OperationHandler; + +public class BalanceOperationHandler implements OperationHandler { + private final FruitRepository fruitRepository; + + public BalanceOperationHandler(FruitRepository fruitRepository) { + this.fruitRepository = fruitRepository; + } + + @Override + public void handle(FruitTransaction transaction) { + fruitRepository.add(transaction.getFruit(), transaction.getQuantity()); + } +} diff --git a/src/main/java/core/basesyntax/service/handler/impl/OperationStrategyImpl.java b/src/main/java/core/basesyntax/service/handler/impl/OperationStrategyImpl.java new file mode 100644 index 0000000000..688f2ef55a --- /dev/null +++ b/src/main/java/core/basesyntax/service/handler/impl/OperationStrategyImpl.java @@ -0,0 +1,19 @@ +package core.basesyntax.service.handler.impl; + +import core.basesyntax.model.enums.Operation; +import core.basesyntax.service.handler.OperationHandler; +import core.basesyntax.service.handler.OperationStrategy; +import java.util.Map; + +public class OperationStrategyImpl implements OperationStrategy { + private final Map handlersMap; + + public OperationStrategyImpl(Map handlersMap) { + this.handlersMap = handlersMap; + } + + @Override + public OperationHandler getOperationHandler(Operation operation) { + return handlersMap.get(operation); + } +} diff --git a/src/main/java/core/basesyntax/service/handler/impl/PurchaseOperationHandler.java b/src/main/java/core/basesyntax/service/handler/impl/PurchaseOperationHandler.java new file mode 100644 index 0000000000..0500cac6d7 --- /dev/null +++ b/src/main/java/core/basesyntax/service/handler/impl/PurchaseOperationHandler.java @@ -0,0 +1,25 @@ +package core.basesyntax.service.handler.impl; + +import core.basesyntax.dao.FruitRepository; +import core.basesyntax.exception.InvalidInputException; +import core.basesyntax.model.FruitTransaction; +import core.basesyntax.service.handler.OperationHandler; + +public class PurchaseOperationHandler implements OperationHandler { + private final FruitRepository repository; + + public PurchaseOperationHandler(FruitRepository repository) { + this.repository = repository; + } + + @Override + public void handle(FruitTransaction transaction) { + if (repository.hasFruit(transaction.getFruit())) { + repository.remove(transaction.getFruit(), transaction.getQuantity()); + } else { + throw new InvalidInputException("There are no " + + transaction.getFruit() + + " in storage"); + } + } +} diff --git a/src/main/java/core/basesyntax/service/handler/impl/ReturnOperationHandler.java b/src/main/java/core/basesyntax/service/handler/impl/ReturnOperationHandler.java new file mode 100644 index 0000000000..d60355dd0e --- /dev/null +++ b/src/main/java/core/basesyntax/service/handler/impl/ReturnOperationHandler.java @@ -0,0 +1,23 @@ +package core.basesyntax.service.handler.impl; + +import core.basesyntax.dao.FruitRepository; +import core.basesyntax.exception.InvalidInputException; +import core.basesyntax.model.FruitTransaction; +import core.basesyntax.service.handler.OperationHandler; + +public class ReturnOperationHandler implements OperationHandler { + private final FruitRepository fruitRepository; + + public ReturnOperationHandler(FruitRepository fruitRepository) { + this.fruitRepository = fruitRepository; + } + + @Override + public void handle(FruitTransaction transaction) { + if (fruitRepository.hasFruit(transaction.getFruit())) { + fruitRepository.add(transaction.getFruit(), transaction.getQuantity()); + } else { + throw new InvalidInputException("Can't return fruits that are/were not in storage"); + } + } +} diff --git a/src/main/java/core/basesyntax/service/handler/impl/SupplyOperationHandler.java b/src/main/java/core/basesyntax/service/handler/impl/SupplyOperationHandler.java new file mode 100644 index 0000000000..b2655e1d0c --- /dev/null +++ b/src/main/java/core/basesyntax/service/handler/impl/SupplyOperationHandler.java @@ -0,0 +1,18 @@ +package core.basesyntax.service.handler.impl; + +import core.basesyntax.dao.FruitRepository; +import core.basesyntax.model.FruitTransaction; +import core.basesyntax.service.handler.OperationHandler; + +public class SupplyOperationHandler implements OperationHandler { + private final FruitRepository repository; + + public SupplyOperationHandler(FruitRepository repository) { + this.repository = repository; + } + + @Override + public void handle(FruitTransaction transaction) { + repository.add(transaction.getFruit(), transaction.getQuantity()); + } +} diff --git a/src/main/java/core/basesyntax/service/impl/DataConverterImpl.java b/src/main/java/core/basesyntax/service/impl/DataConverterImpl.java new file mode 100644 index 0000000000..b33af0b35c --- /dev/null +++ b/src/main/java/core/basesyntax/service/impl/DataConverterImpl.java @@ -0,0 +1,43 @@ +package core.basesyntax.service.impl; + +import core.basesyntax.exception.InvalidInputException; +import core.basesyntax.model.FruitTransaction; +import core.basesyntax.model.enums.Operation; +import core.basesyntax.service.DataConverter; +import java.util.ArrayList; +import java.util.List; + +public class DataConverterImpl implements DataConverter { + private static final String DATA_SPLITTER = ","; + private static final Integer OPERATION_INDEX = 0; + private static final Integer FRUIT_INDEX = 1; + private static final Integer QUANTITY_INDEX = 2; + private static final int EXPECTED_ARRAY_LENGTH = 3; + + @Override + public List convertToFruitTransactions( + List stringsData) { + List list = new ArrayList<>(); + for (int index = 1; index < stringsData.size(); index++) { + String row = stringsData.get(index).trim(); + String[] arr = row.split(DATA_SPLITTER); + checkInputData(arr); + FruitTransaction fruitTransaction = new FruitTransaction( + Operation.getByCode(arr[OPERATION_INDEX]), + arr[FRUIT_INDEX], + Integer.parseInt(arr[QUANTITY_INDEX]) + ); + list.add(fruitTransaction); + } + return list; + } + + private void checkInputData(String[] arr) { + if (arr.length != EXPECTED_ARRAY_LENGTH) { + throw new InvalidInputException("Invalid input"); + } + if (Integer.parseInt(arr[QUANTITY_INDEX]) < 0) { + throw new InvalidInputException("Quantity can't be less than 0"); + } + } +} diff --git a/src/main/java/core/basesyntax/service/impl/FileServiceImpl.java b/src/main/java/core/basesyntax/service/impl/FileServiceImpl.java new file mode 100644 index 0000000000..47b8f0289f --- /dev/null +++ b/src/main/java/core/basesyntax/service/impl/FileServiceImpl.java @@ -0,0 +1,29 @@ +package core.basesyntax.service.impl; + +import core.basesyntax.service.FileService; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; + +public class FileServiceImpl implements FileService { + @Override + public List read(String filePath) { + try { + return Files.readAllLines(Path.of(filePath)); + } catch (IOException e) { + throw new RuntimeException("Can't read from file " + filePath, e); + } + } + + @Override + public void write(String filePath, String data) { + try { + Files.write(Path.of(filePath), data.getBytes(), + StandardOpenOption.APPEND); + } catch (IOException e) { + throw new RuntimeException("Can't write data to file " + filePath, e); + } + } +} diff --git a/src/main/java/core/basesyntax/service/impl/ReportGeneratorImpl.java b/src/main/java/core/basesyntax/service/impl/ReportGeneratorImpl.java new file mode 100644 index 0000000000..73ca8e4c62 --- /dev/null +++ b/src/main/java/core/basesyntax/service/impl/ReportGeneratorImpl.java @@ -0,0 +1,25 @@ +package core.basesyntax.service.impl; + +import core.basesyntax.dao.FruitRepository; +import core.basesyntax.service.ReportGenerator; +import java.util.stream.Collectors; + +public class ReportGeneratorImpl implements ReportGenerator { + private static final String DATA_SPLITTER = ","; + private static final String REPORT_FIRST_LINE = "fruit,quantity" + System.lineSeparator(); + private final FruitRepository repository; + + public ReportGeneratorImpl(FruitRepository repository) { + this.repository = repository; + } + + @Override + public String generateReport() { + return REPORT_FIRST_LINE + + repository.getAll() + .entrySet() + .stream() + .map(entry -> entry.getKey() + DATA_SPLITTER + entry.getValue()) + .collect(Collectors.joining(System.lineSeparator())); + } +} diff --git a/src/main/java/core/basesyntax/service/impl/ShopServiceImpl.java b/src/main/java/core/basesyntax/service/impl/ShopServiceImpl.java new file mode 100644 index 0000000000..b03d465417 --- /dev/null +++ b/src/main/java/core/basesyntax/service/impl/ShopServiceImpl.java @@ -0,0 +1,24 @@ +package core.basesyntax.service.impl; + +import core.basesyntax.model.FruitTransaction; +import core.basesyntax.service.ShopService; +import core.basesyntax.service.handler.OperationHandler; +import core.basesyntax.service.handler.OperationStrategy; +import java.util.List; + +public class ShopServiceImpl implements ShopService { + private final OperationStrategy operationStrategy; + + public ShopServiceImpl(OperationStrategy operationStrategy) { + this.operationStrategy = operationStrategy; + } + + @Override + public void process(List transactions) { + for (FruitTransaction transaction : transactions) { + OperationHandler handler = + operationStrategy.getOperationHandler(transaction.getOperation()); + handler.handle(transaction); + } + } +}