diff --git a/build.gradle b/build.gradle index 67e032179..d10d94f65 100644 --- a/build.gradle +++ b/build.gradle @@ -14,11 +14,11 @@ dependencies { implementation 'com.github.woowacourse-projects:mission-utils:1.0.0' } -java { - toolchain { - languageVersion = JavaLanguageVersion.of(8) - } -} +//java { +// toolchain { +// languageVersion = JavaLanguageVersion.of(8) +// } +//} test { useJUnitPlatform() diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..19cef1e6f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,76 @@ +# 구현 기능 목록 + + +- [X] 반환되는 동전이 최소한이 되는 자판기를 구현 -> 가능한 한 적은 수의 동전 반환 = 큰 단위의 동전 최대한 많이 만들기 + + +- [X] 자판기가 보유한 금액으로 동전을 무작위 생성 + - [X] 자판기 보유 금액 입력받기 + - [X] 10 으로 나눠떨어져야 함 + - [X] 입력받은 자판기 보유 금액으로 동전 생성 (500원, 100원, 50원, 10원) + - [X] 투입 금액으로는 동전을 생성하지 않는다. + + +- [X] 상품명, 가격, 수량을 입력하여 상품을 추가할 수 있다. + - [X] 상품 가격은 100원부터 시작하며, 10원으로 나누어떨어져야 한다. + - [X] 상품이 여러개인 경우, 상품명이 중복되지 않아야 한다. + - [X] 입력 형식 예: "[콜라,1500,20];[사이다,1000,10]" + + +- [X] 사용자가 투입한 금액으로 상품을 구매할 수 있다. + - [X] 투입 금액을 입력받고 투입 금액을 출력하고 구매할 상품명을 입력받는다. + + +- [X] 잔돈 반환 조건 + - [X] 남은 금액이 상품의 최저 가격보다 적은 경우 바로 잔돈을 돌려준다. + - [X] 모든 상품이 소진된 경우 바로 잔돈을 돌려준다. + + +- [ ] 잔돈 반환 + - [ ] 잔돈을 반환할 수 없는 경우 잔돈으로 반환할 수 있는 금액만 반환. 반환되지 않은 금액은 자판기에 남는다. + - [ ] 잔돈을 돌려줄 때 현재 보유한 최소 개수의 동전으로 잔돈을 돌려준다. + - [ ] 지폐를 잔돈으로 반환하는 경우는 없다. + + +- [ ] 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException 발생, "[ERROR]"로 시작하는 에러 메시지를 출력 후, 해당 부분부터 다시 입력을 받는다. + + +- [ ] 코드 컨벤션 적용 + - [ ] 메서드 10라인 이하 + - [ ] else X + - [ ] indent 2까지 + - [ ] 하드코딩 X (상수 처리) + + + +--- +- 전체 흐름 입출력 예시 +``` +자판기가 보유하고 있는 금액을 입력해 주세요. +450 + +자판기가 보유한 동전 +500원 - 0개 +100원 - 4개 +50원 - 1개 +10원 - 0개 + +상품명과 가격, 수량을 입력해 주세요. +[콜라,1500,20];[사이다,1000,10] + +투입 금액을 입력해 주세요. +3000 + +투입 금액: 3000원 +구매할 상품명을 입력해 주세요. +콜라 + +투입 금액: 1500원 +구매할 상품명을 입력해 주세요. +사이다 + +투입 금액: 500원 +잔돈 +100원 - 4개 +50원 - 1개 +``` \ No newline at end of file diff --git a/src/main/java/vendingmachine/Application.java b/src/main/java/vendingmachine/Application.java index 9d3be447b..3a536bc7f 100644 --- a/src/main/java/vendingmachine/Application.java +++ b/src/main/java/vendingmachine/Application.java @@ -1,7 +1,12 @@ package vendingmachine; +import vendingmachine.controller.MainController; +import vendingmachine.view.InputView; +import vendingmachine.view.OutputView; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + MainController mainController = new MainController(new InputView(), new OutputView()); + mainController.run(); } } diff --git a/src/main/java/vendingmachine/Coin.java b/src/main/java/vendingmachine/Coin.java deleted file mode 100644 index c76293fbc..000000000 --- a/src/main/java/vendingmachine/Coin.java +++ /dev/null @@ -1,16 +0,0 @@ -package vendingmachine; - -public enum Coin { - COIN_500(500), - COIN_100(100), - COIN_50(50), - COIN_10(10); - - private final int amount; - - Coin(final int amount) { - this.amount = amount; - } - - // 추가 기능 구현 -} diff --git a/src/main/java/vendingmachine/controller/MainController.java b/src/main/java/vendingmachine/controller/MainController.java new file mode 100644 index 000000000..0c6dc8ebb --- /dev/null +++ b/src/main/java/vendingmachine/controller/MainController.java @@ -0,0 +1,93 @@ +package vendingmachine.controller; + +import vendingmachine.domain.*; +import vendingmachine.dto.ExchangeCoinsDto; +import vendingmachine.dto.ItemDto; +import vendingmachine.dto.OrderDetailDto; +import vendingmachine.dto.VendingMachineCoinsDto; +import vendingmachine.view.InputView; +import vendingmachine.view.OutputView; + +import java.util.EnumMap; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class MainController { + private final InputView inputView; + private final OutputView outputView; + + public MainController(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public void run() { + VendingMachineCoins coins = createVendingMachineAmount(); + outputView.printVendingMachineAmount(VendingMachineCoinsDto.from(coins)); + Items items = createItems(); + InsertedAmount insertedAmount = createInsertedAmount(); + outputView.printInputAmount(insertedAmount.provideAmount()); + boolean canBuyMore = true; + while (canBuyMore) { + OrderDetailDto orderDetailDto = createOrderDetailDto(insertedAmount.provideAmount(), items); + insertedAmount.updateAmount(orderDetailDto.getUpdatedInputAmount()); + outputView.printInputAmount(insertedAmount.provideAmount()); + canBuyMore = canBuyMore(items, insertedAmount); + } + generateExchangeCoins(coins, insertedAmount); + } + + private void generateExchangeCoins(VendingMachineCoins coins, InsertedAmount insertedAmount) { + long amount = insertedAmount.provideAmount(); + EnumMap exchangeCoins = coins.generateExchangeCoins(amount); + ExchangeCoinsDto exchangeCoinsDto = ExchangeCoinsDto.from(exchangeCoins); + outputView.printExchangeCoins(exchangeCoinsDto); + } + + private boolean canBuyMore(Items items, InsertedAmount amount) { + return amount.isEqualOrLargerThan(items.findPurchasableMinimumPrice()) && !items.hasNoQuantity(); + } + + private VendingMachineCoins createVendingMachineAmount() { + return readUserInput(() -> { + long amount = inputView.readVendingMachineAmount(); + return VendingMachineCoins.from(amount); + }); + } + + private Items createItems() { + return readUserInput(() -> { + List items = inputView.readItems().stream() + .map(ItemDto::toItem) + .collect(Collectors.toList()); + return Items.from(items); + }); + } + + private InsertedAmount createInsertedAmount() { + return readUserInput(() -> { + long amount = inputView.readInputAmount(); + return InsertedAmount.from(amount); + }); + } + + private OrderDetailDto createOrderDetailDto(long inputAmount, Items items) { + return readUserInput(() -> { + String itemName = inputView.readOrderItemName(); + Item orderItem = items.buyItem(itemName, inputAmount); + long updatedInputAmount = inputAmount - orderItem.providePrice(); + return OrderDetailDto.of(itemName, updatedInputAmount); + }); + } + + private T readUserInput(Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + outputView.printError(e.getMessage()); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/vendingmachine/domain/Coin.java b/src/main/java/vendingmachine/domain/Coin.java new file mode 100644 index 000000000..43745973d --- /dev/null +++ b/src/main/java/vendingmachine/domain/Coin.java @@ -0,0 +1,35 @@ +package vendingmachine.domain; + +import java.util.Arrays; +import java.util.EnumMap; +import java.util.concurrent.atomic.AtomicLong; + +public enum Coin { + COIN_500(500), + COIN_100(100), + COIN_50(50), + COIN_10(10); + + private final int amount; + + Coin(final int amount) { + this.amount = amount; + } + + public static EnumMap generateMinimumCoins(long totalAmount) { + EnumMap coins = new EnumMap<>(Coin.class); + AtomicLong remainingAmount = new AtomicLong(totalAmount); + + Arrays.stream(Coin.values()).forEach(coin -> { + long coinCount = remainingAmount.get() / coin.getAmount(); + remainingAmount.addAndGet(-coinCount * coin.getAmount()); + coins.put(coin, coinCount); + }); + + return coins; + } + + public int getAmount() { + return amount; + } +} \ No newline at end of file diff --git a/src/main/java/vendingmachine/domain/InsertedAmount.java b/src/main/java/vendingmachine/domain/InsertedAmount.java new file mode 100644 index 000000000..6a0071ed2 --- /dev/null +++ b/src/main/java/vendingmachine/domain/InsertedAmount.java @@ -0,0 +1,26 @@ +package vendingmachine.domain; + +public class InsertedAmount { + private long amount; + + private InsertedAmount(long amount) { + this.amount = amount; + } + + public static InsertedAmount from(long amount) { + return new InsertedAmount(amount); + } + + public long provideAmount() { + return amount; + } + + public void updateAmount(long amount) { + this.amount = amount; + } + + + public boolean isEqualOrLargerThan(long amount) { + return this.amount >= amount; + } +} diff --git a/src/main/java/vendingmachine/domain/Item.java b/src/main/java/vendingmachine/domain/Item.java new file mode 100644 index 000000000..73476cd43 --- /dev/null +++ b/src/main/java/vendingmachine/domain/Item.java @@ -0,0 +1,55 @@ +package vendingmachine.domain; + +import vendingmachine.utils.ItemValidator; + +import static vendingmachine.exception.ErrorMessage.CANNOT_BUY_ORDER_ITEM; + +public class Item { + private final String name; + private final long price; + private long quantity; + + private Item(String name, long price, long quantity) { + this.name = name; + this.price = price; + this.quantity = quantity; + } + + public static Item of(String name, long price, long quantity) { + ItemValidator.validatePrice(price); + + return new Item(name, price, quantity); + } + + public void buyItem(long priceAmount) { + if (canBuy(priceAmount)) { + updateQuantity(); + return; + } + throw new IllegalArgumentException(CANNOT_BUY_ORDER_ITEM.getMessage()); + } + + private void updateQuantity() { + quantity--; + } + + public boolean canBuy(long priceAmount) { + return hasQuantity() && priceAmount >= price; + } + + public boolean hasQuantity() { + return quantity > 0; + } + + public String provideName() { + return name; + } + + public long providePrice() { + return price; + } + + public boolean hasNoQuantity() { + return quantity == 0; + } +} diff --git a/src/main/java/vendingmachine/domain/Items.java b/src/main/java/vendingmachine/domain/Items.java new file mode 100644 index 000000000..f4f347ad6 --- /dev/null +++ b/src/main/java/vendingmachine/domain/Items.java @@ -0,0 +1,57 @@ +package vendingmachine.domain; + +import vendingmachine.utils.ItemsValidator; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.OptionalLong; +import java.util.stream.Collectors; + +import static vendingmachine.exception.ErrorMessage.*; + +public class Items { + private final List items; + + private Items(List items) { + this.items = items; + } + + public static Items from(List items) { + validateUniqueName(items); + return new Items(items); + } + + private static void validateUniqueName(List items) { + List names = items.stream() + .map(Item::provideName) + .collect(Collectors.toList()); + ItemsValidator.validateUniqueValue(names); + } + + public Item buyItem(String itemName, long priceAmount) { + Item item = findItemByName(itemName); + item.buyItem(priceAmount); + return item; + } + + private Item findItemByName(String name) { + return items.stream() + .filter(item -> item.provideName().equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(INVALID_ORDER_ITEM_NAME.getMessage())); + } + + public long findPurchasableMinimumPrice() { + return items.stream() + .filter(Item::hasQuantity) + .mapToLong(Item::providePrice) + .min() + .orElseThrow(() -> new IllegalArgumentException(INVALID_ORDER_ITEM.getMessage())); + } + + public boolean hasNoQuantity() { + return items.stream() + .allMatch(Item::hasNoQuantity); + } + +} diff --git a/src/main/java/vendingmachine/domain/VendingMachineCoins.java b/src/main/java/vendingmachine/domain/VendingMachineCoins.java new file mode 100644 index 000000000..aeda83faf --- /dev/null +++ b/src/main/java/vendingmachine/domain/VendingMachineCoins.java @@ -0,0 +1,41 @@ +package vendingmachine.domain; + +import vendingmachine.utils.VendingMachineAmountValidator; + +import java.util.EnumMap; + +public class VendingMachineCoins { + private final long totalAmount; + private final EnumMap coins; + + private VendingMachineCoins(long totalAmount, EnumMap coins) { + this.totalAmount = totalAmount; + this.coins = coins; + } + + public static VendingMachineCoins from(long amount) { + VendingMachineAmountValidator.validateDividedByMinimumAmount(amount); + EnumMap coins = Coin.generateMinimumCoins(amount); + return new VendingMachineCoins(amount, coins); + } + + public EnumMap generateExchangeCoins(long amount) { + if (amount > totalAmount) { + return new EnumMap<>(coins); + } + + EnumMap exchangeCoins = new EnumMap<>(Coin.class); + + for (Coin coin : Coin.values()) { + long coinCount = Math.min(coins.get(coin), amount / coin.getAmount()); + amount -= coinCount * coin.getAmount(); + coins.put(coin, coins.get(coin) - coinCount); + exchangeCoins.put(coin, coinCount); + } + return exchangeCoins; + } + + public EnumMap provideCoins() { + return coins; + } +} diff --git a/src/main/java/vendingmachine/dto/ExchangeCoinsDto.java b/src/main/java/vendingmachine/dto/ExchangeCoinsDto.java new file mode 100644 index 000000000..510b5271f --- /dev/null +++ b/src/main/java/vendingmachine/dto/ExchangeCoinsDto.java @@ -0,0 +1,28 @@ +package vendingmachine.dto; + +import vendingmachine.domain.Coin; + +import java.util.EnumMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class ExchangeCoinsDto { + private final Map coins; + + private ExchangeCoinsDto(Map coins) { + this.coins = coins; + } + + public static ExchangeCoinsDto from(EnumMap exchangeCoins) { + Map coins = exchangeCoins.entrySet().stream() + .collect(Collectors.toMap( + entry -> entry.getKey().getAmount(), + Map.Entry::getValue + )); + return new ExchangeCoinsDto(coins); + } + + public Map getCoins() { + return coins; + } +} \ No newline at end of file diff --git a/src/main/java/vendingmachine/dto/ItemDto.java b/src/main/java/vendingmachine/dto/ItemDto.java new file mode 100644 index 000000000..bd01001c5 --- /dev/null +++ b/src/main/java/vendingmachine/dto/ItemDto.java @@ -0,0 +1,23 @@ +package vendingmachine.dto; + +import vendingmachine.domain.Item; + +public class ItemDto { + private final String name; + private final long price; + private final long quantity; + + private ItemDto(String name, long price, long quantity) { + this.name = name; + this.price = price; + this.quantity = quantity; + } + + public static ItemDto of(String name, long price, long quantity) { + return new ItemDto(name, price, quantity); + } + + public Item toItem() { + return Item.of(name, price, quantity); + } +} diff --git a/src/main/java/vendingmachine/dto/OrderDetailDto.java b/src/main/java/vendingmachine/dto/OrderDetailDto.java new file mode 100644 index 000000000..aee4eb568 --- /dev/null +++ b/src/main/java/vendingmachine/dto/OrderDetailDto.java @@ -0,0 +1,20 @@ +package vendingmachine.dto; + + +public class OrderDetailDto { + private final String itemName; + private final long updatedInputAmount; + + private OrderDetailDto(String itemName, long updatedInputAmount) { + this.itemName = itemName; + this.updatedInputAmount = updatedInputAmount; + } + + public static OrderDetailDto of(String itemName, long updatedInputAmount) { + return new OrderDetailDto(itemName, updatedInputAmount); + } + + public long getUpdatedInputAmount() { + return updatedInputAmount; + } +} diff --git a/src/main/java/vendingmachine/dto/VendingMachineCoinsDto.java b/src/main/java/vendingmachine/dto/VendingMachineCoinsDto.java new file mode 100644 index 000000000..bfadbbb84 --- /dev/null +++ b/src/main/java/vendingmachine/dto/VendingMachineCoinsDto.java @@ -0,0 +1,23 @@ +package vendingmachine.dto; + +import vendingmachine.domain.Coin; +import vendingmachine.domain.VendingMachineCoins; + +import java.util.EnumMap; + +public class VendingMachineCoinsDto { + private final EnumMap coins; + + private VendingMachineCoinsDto(EnumMap coins) { + this.coins = coins; + } + + public static VendingMachineCoinsDto from(VendingMachineCoins coins) { + EnumMap providedCoins = coins.provideCoins(); + return new VendingMachineCoinsDto(providedCoins); + } + + public EnumMap getCoins() { + return coins; + } +} diff --git a/src/main/java/vendingmachine/exception/ErrorMessage.java b/src/main/java/vendingmachine/exception/ErrorMessage.java new file mode 100644 index 000000000..2e87c67d5 --- /dev/null +++ b/src/main/java/vendingmachine/exception/ErrorMessage.java @@ -0,0 +1,24 @@ +package vendingmachine.exception; + +public enum ErrorMessage { + CAPTION("[ERROR] "), + NOT_NUMERIC_AMOUNT("금액은 숫자여야 합니다."), + INVALID_VENDING_MACHINE_AMOUNT("금액은 10으로 나눠떨어져야 합니다."), + INVALID_ITEMS_FORMAT("유효하지 않은 상품 입력 형식 입니다."), + INVALID_ITEM_DETAIL("유효하지 않은 상품 입력 값 입니다."), + INVALID_ITEM_PRICE("상품 가격은 100 이상 이어야 하고, 10 으로 나눠떨어지는 값이어야 합니다."), + DUPLICATE_ITEM_NAMES("중복된 상품 이름 입니다."), + INVALID_ORDER_ITEM_NAME("유효하지 않은 상품명 입니다."), + CANNOT_BUY_ORDER_ITEM("재고가 없거나 금액이 부족하여 해당 상품은 구매 불가합니다."), + INVALID_ORDER_ITEM("구매 가능한 아이템이 없습니다."); + + private final String message; + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return CAPTION.message + message; + } +} diff --git a/src/main/java/vendingmachine/utils/InputAmountValidator.java b/src/main/java/vendingmachine/utils/InputAmountValidator.java new file mode 100644 index 000000000..5862fff63 --- /dev/null +++ b/src/main/java/vendingmachine/utils/InputAmountValidator.java @@ -0,0 +1,21 @@ +package vendingmachine.utils; + +import static vendingmachine.exception.ErrorMessage.*; + +public class InputAmountValidator { + public static long safeParsePositiveLong(String input) { + try { + long value = Long.parseLong(input); + validatePositive(value); + return value; + } catch (NumberFormatException e) { + throw new IllegalArgumentException(INVALID_ITEMS_FORMAT.getMessage()); + } + } + + private static void validatePositive(long value) { + if (value <= 0) { + throw new IllegalArgumentException(INVALID_ITEM_DETAIL.getMessage()); + } + } +} diff --git a/src/main/java/vendingmachine/utils/ItemValidator.java b/src/main/java/vendingmachine/utils/ItemValidator.java new file mode 100644 index 000000000..14bb42425 --- /dev/null +++ b/src/main/java/vendingmachine/utils/ItemValidator.java @@ -0,0 +1,62 @@ +package vendingmachine.utils; + +import org.junit.platform.commons.util.StringUtils; + +import java.util.List; + +import static vendingmachine.exception.ErrorMessage.*; + +public class ItemValidator { + private static final int MINIMUM_PRICE = 100; + private static final int PRICE_UNIT = 10; + + public static List safeSplit(String input, String delimiter) { + validateEmpty(input); + validateStartsOrEndsWithDelimiter(input, delimiter); + return List.of(input.split(delimiter)); + } + + private static void validateEmpty(String input) { + if (StringUtils.isBlank(input)) { + throw new IllegalArgumentException(INVALID_ITEMS_FORMAT.getMessage()); + } + } + + private static void validateStartsOrEndsWithDelimiter(String input, String delimiter) { + if (input.startsWith(delimiter) || input.endsWith(delimiter)) { + throw new IllegalArgumentException(INVALID_ITEMS_FORMAT.getMessage()); + } + } + + public static String validateAndCleanPair(String input, String startDelimiter, String endDelimiter) { + if (!input.startsWith(startDelimiter) || !input.endsWith(endDelimiter)) { + throw new IllegalArgumentException(INVALID_ITEMS_FORMAT.getMessage()); + } + return input.substring(1, input.length() - 1); + } + + public static long safeParsePositiveLong(String input) { + try { + long value = Long.parseLong(input); + validatePositive(value); + return value; + } catch (NumberFormatException e) { + throw new IllegalArgumentException(INVALID_ITEMS_FORMAT.getMessage()); + } + } + + private static void validatePositive(long value) { + if (value <= 0) { + throw new IllegalArgumentException(INVALID_ITEM_DETAIL.getMessage()); + } + } + + public static void validatePrice(long price) { + if (price < MINIMUM_PRICE) { + throw new IllegalArgumentException(INVALID_ITEM_PRICE.getMessage()); + } + if (price % PRICE_UNIT != 0) { + throw new IllegalArgumentException(INVALID_ITEM_PRICE.getMessage()); + } + } +} diff --git a/src/main/java/vendingmachine/utils/ItemsValidator.java b/src/main/java/vendingmachine/utils/ItemsValidator.java new file mode 100644 index 000000000..9d15ffaaa --- /dev/null +++ b/src/main/java/vendingmachine/utils/ItemsValidator.java @@ -0,0 +1,16 @@ +package vendingmachine.utils; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static vendingmachine.exception.ErrorMessage.DUPLICATE_ITEM_NAMES; + +public class ItemsValidator { + public static void validateUniqueValue(List elements) { + Set uniqueElements = new HashSet<>(elements); + if (elements.size() != uniqueElements.size()) { + throw new IllegalArgumentException(DUPLICATE_ITEM_NAMES.getMessage()); + } + } +} diff --git a/src/main/java/vendingmachine/utils/VendingMachineAmountValidator.java b/src/main/java/vendingmachine/utils/VendingMachineAmountValidator.java new file mode 100644 index 000000000..6706d2b5b --- /dev/null +++ b/src/main/java/vendingmachine/utils/VendingMachineAmountValidator.java @@ -0,0 +1,22 @@ +package vendingmachine.utils; + +import static vendingmachine.exception.ErrorMessage.INVALID_VENDING_MACHINE_AMOUNT; +import static vendingmachine.exception.ErrorMessage.NOT_NUMERIC_AMOUNT; + +public class VendingMachineAmountValidator { + private static final int MINIMUM_AMOUNT = 10; + public static long safeParseLong(String input) { + try { + return Long.parseLong(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(NOT_NUMERIC_AMOUNT.getMessage()); + } + } + + public static void validateDividedByMinimumAmount(long amount) { + if (amount % MINIMUM_AMOUNT != 0) { + throw new IllegalArgumentException(INVALID_VENDING_MACHINE_AMOUNT.getMessage()); + } + } + +} diff --git a/src/main/java/vendingmachine/view/InputView.java b/src/main/java/vendingmachine/view/InputView.java new file mode 100644 index 000000000..8fe159d1d --- /dev/null +++ b/src/main/java/vendingmachine/view/InputView.java @@ -0,0 +1,77 @@ +package vendingmachine.view; + +import camp.nextstep.edu.missionutils.Console; +import vendingmachine.dto.ItemDto; +import vendingmachine.utils.InputAmountValidator; +import vendingmachine.utils.ItemValidator; +import vendingmachine.utils.VendingMachineAmountValidator; + +import java.util.List; +import java.util.stream.Collectors; + +import static vendingmachine.exception.ErrorMessage.INVALID_ITEMS_FORMAT; + +public class InputView { + private static final String ASK_VENDING_MACHINE_AMOUNT = "자판기가 보유하고 있는 금액을 입력해 주세요."; + private static final String ASK_ITEMS = "상품명과 가격, 수량을 입력해 주세요."; + private static final String ASK_INPUT_AMOUNT = "투입 금액을 입력해 주세요."; + private static final String ITEM_START_SYMBOL = "["; + private static final String ITEM_END_SYMBOL = "]"; + private static final String ITEM_DELIMITER = ";"; + private static final String ITEM_DETAIL_DELIMITER = ","; + private static final int REQUIRED_ITEM_COMPONENTS = 3; + private static final int ITEM_NAME_INDEX = 0; + private static final int ITEM_PRICE_INDEX = 1; + private static final int ITEM_QUANTITY_INDEX = 2; + private static final String ASK_ORDER_ITEM = "구매할 상품명을 입력해 주세요."; + + public long readVendingMachineAmount() { + System.out.println(ASK_VENDING_MACHINE_AMOUNT); + String input = Console.readLine(); + return VendingMachineAmountValidator.safeParseLong(input); + } + + public List readItems() { + printNewLine(); + System.out.println(ASK_ITEMS); + String input = Console.readLine(); + List pairs = ItemValidator.safeSplit(input, ITEM_DELIMITER); + return toItemDto(pairs); + } + + private void printNewLine() { + System.out.println(); + } + + private List toItemDto(List pairs) { + return pairs.stream() + .map(pair -> ItemValidator.validateAndCleanPair(pair, ITEM_START_SYMBOL, ITEM_END_SYMBOL)) + .map(this::createItemDto) + .collect(Collectors.toList()); + } + + private ItemDto createItemDto(String pair) { + List pairs = ItemValidator.safeSplit(pair, ITEM_DETAIL_DELIMITER); + + if (pairs.size() != REQUIRED_ITEM_COMPONENTS) { + throw new IllegalArgumentException(INVALID_ITEMS_FORMAT.getMessage()); + } + String name = pairs.get(ITEM_NAME_INDEX); + long price = ItemValidator.safeParsePositiveLong(pairs.get(ITEM_PRICE_INDEX)); + long quantity = ItemValidator.safeParsePositiveLong(pairs.get(ITEM_QUANTITY_INDEX)); + + return ItemDto.of(name, price, quantity); + } + + public long readInputAmount() { + printNewLine(); + System.out.println(ASK_INPUT_AMOUNT); + String input = Console.readLine(); + return InputAmountValidator.safeParsePositiveLong(input); + } + + public String readOrderItemName() { + System.out.println(ASK_ORDER_ITEM); + return Console.readLine(); + } +} \ No newline at end of file diff --git a/src/main/java/vendingmachine/view/OutputView.java b/src/main/java/vendingmachine/view/OutputView.java new file mode 100644 index 000000000..7e89d4ef8 --- /dev/null +++ b/src/main/java/vendingmachine/view/OutputView.java @@ -0,0 +1,37 @@ +package vendingmachine.view; + +import vendingmachine.dto.ExchangeCoinsDto; +import vendingmachine.dto.VendingMachineCoinsDto; + +public class OutputView { + private static final String INPUT_AMOUNT_FORMAT = "투입 금액: %d원"; + private static final String VENDING_MACHINE_AMOUNT_MESSAGE = "자판기가 보유한 동전"; + private static final String START_EXCHANGE = "잔돈"; + private static final String EXCHANGE_FORMAT = "%d원 - %d개"; + + public void printError(String message) { + System.out.println(message); + } + + public void printInputAmount(long amount) { + printNewLine(); + System.out.printf((INPUT_AMOUNT_FORMAT) + "%n", amount); + } + + private void printNewLine() { + System.out.println(); + } + + public void printVendingMachineAmount(VendingMachineCoinsDto dto) { + printNewLine(); + System.out.println(VENDING_MACHINE_AMOUNT_MESSAGE); + dto.getCoins().forEach((coin, quantity) -> + System.out.println(String.format(EXCHANGE_FORMAT, coin.getAmount(), quantity))); + } + + public void printExchangeCoins(ExchangeCoinsDto dto) { + System.out.println(START_EXCHANGE); + dto.getCoins().forEach((coinValue, quantity) -> + System.out.println(String.format(EXCHANGE_FORMAT, coinValue, quantity))); + } +}