diff --git a/src/main/java/vendingmachine/Application.java b/src/main/java/vendingmachine/Application.java index 9d3be447b..5513270f7 100644 --- a/src/main/java/vendingmachine/Application.java +++ b/src/main/java/vendingmachine/Application.java @@ -1,7 +1,10 @@ package vendingmachine; +import vendingmachine.controller.MainController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + MainController mainController = new MainController(); + 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..ad5977d55 --- /dev/null +++ b/src/main/java/vendingmachine/controller/MainController.java @@ -0,0 +1,91 @@ +package vendingmachine.controller; + +import vendingmachine.model.VendingMachine; +import vendingmachine.model.coin.Coins; +import vendingmachine.model.drink.Drinks; +import vendingmachine.model.user.UserMoney; +import vendingmachine.view.OutputView; + +import static vendingmachine.model.coin.RandomCoins.makeRandomCoins; +import static vendingmachine.view.InputView.readDrinks; +import static vendingmachine.view.InputView.readMoney; +import static vendingmachine.view.InputView.readPurchase; +import static vendingmachine.view.OutputView.askDrinkFromUsers; +import static vendingmachine.view.OutputView.askMachineTotalMoney; +import static vendingmachine.view.OutputView.askPurchaseDrinkType; +import static vendingmachine.view.OutputView.askUserInputMoney; +import static vendingmachine.view.OutputView.printVendingMachineCoins; +import static vendingmachine.view.OutputView.println; +import static vendingmachine.view.OutputView.showBalance; + +public class MainController { +// VendingMachineController vendingMachineController = new VendingMachineController(); + + public void run(){ + Coins coins = askTotalMoney(); + showCoins(coins); + Drinks drinks = askDrinks(); + VendingMachine vendingMachine = new VendingMachine(coins, drinks); + UserMoney userMoney = askInputAmount(); + + makePurchase(vendingMachine, userMoney); + } + + private Coins askTotalMoney(){ + while (true){ + try { + askMachineTotalMoney(); + return new Coins(makeRandomCoins(readMoney())); + } catch (IllegalArgumentException exception) { + OutputView.errorMessage(exception.getMessage()); + } + } + } + + private void showCoins(Coins coins) { + printVendingMachineCoins(coins.coinsCount()); + } + + private Drinks askDrinks() { + while (true) { + try { + askDrinkFromUsers(); + return new Drinks(readDrinks()); + } catch (IllegalArgumentException exception) { + OutputView.errorMessage(exception.getMessage()); + } + } + } + + private UserMoney askInputAmount() { + while (true) { + try { + askUserInputMoney(); + return new UserMoney(readMoney()); + } catch (IllegalArgumentException exception) { + OutputView.errorMessage(exception.getMessage()); + } + } + } + + private void makePurchase(VendingMachine vendingMachine, UserMoney userMoney) { + while (vendingMachine.hasMoneyMoreThenMinimumPrice(userMoney)) { + showBalance(userMoney.getBalance()); + String purchaseDrinkType = askPurchase(); + userMoney.purchaseDrink(vendingMachine.getPrice(purchaseDrinkType)); + println(); + } + showBalance(userMoney.getBalance()); + } + + private String askPurchase() { + while (true) { + try { + askPurchaseDrinkType(); + return readPurchase(); + } catch (IllegalArgumentException exception) { + OutputView.errorMessage(exception.getMessage()); + } + } + } +} diff --git a/src/main/java/vendingmachine/controller/handler/RetryHandler.java b/src/main/java/vendingmachine/controller/handler/RetryHandler.java new file mode 100644 index 000000000..c0fefcf19 --- /dev/null +++ b/src/main/java/vendingmachine/controller/handler/RetryHandler.java @@ -0,0 +1,17 @@ +package vendingmachine.controller.handler; + +import vendingmachine.view.OutputView; + +public abstract class RetryHandler implements RetryHandlerController { + @Override + public E process() { + try { + return doProcess(); + } catch (IllegalArgumentException exception) { + OutputView.errorMessage(exception.getMessage()); + return process(); + } + } + + protected abstract E doProcess(); +} diff --git a/src/main/java/vendingmachine/controller/handler/RetryHandlerController.java b/src/main/java/vendingmachine/controller/handler/RetryHandlerController.java new file mode 100644 index 000000000..1f07025f1 --- /dev/null +++ b/src/main/java/vendingmachine/controller/handler/RetryHandlerController.java @@ -0,0 +1,5 @@ +package vendingmachine.controller.handler; + +public interface RetryHandlerController { + E process(); +} diff --git a/src/main/java/vendingmachine/model/VendingMachine.java b/src/main/java/vendingmachine/model/VendingMachine.java new file mode 100644 index 000000000..d4e610a9c --- /dev/null +++ b/src/main/java/vendingmachine/model/VendingMachine.java @@ -0,0 +1,29 @@ +package vendingmachine.model; + +import vendingmachine.model.coin.Coins; +import vendingmachine.model.drink.Drinks; +import vendingmachine.model.user.UserMoney; + +import java.util.List; + +public class VendingMachine { + private Coins moneyBox; + private Drinks drinks; + + public VendingMachine(Coins moneyBox, Drinks drinks) { + this.moneyBox = moneyBox; + this.drinks = drinks; + } + + public List showCoinBox() { + return moneyBox.coinsCount(); + } + + public boolean hasMoneyMoreThenMinimumPrice(UserMoney userMoney) { + return userMoney.getBalance() >= drinks.cheapestDrink(); + } + + public int getPrice(String purchaseDrinkType) { + return drinks.getPriceFindByName(purchaseDrinkType); + } +} diff --git a/src/main/java/vendingmachine/model/coin/Coin.java b/src/main/java/vendingmachine/model/coin/Coin.java new file mode 100644 index 000000000..38811dda8 --- /dev/null +++ b/src/main/java/vendingmachine/model/coin/Coin.java @@ -0,0 +1,29 @@ +package vendingmachine.model.coin; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +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 List coinTypes(){ + List types = new ArrayList<>(); + Arrays.stream(Coin.values()) + .forEach(coin -> types.add(coin.amount)); + return types; + } + + public static Coin getCoinType(int amount){ + return Coin.valueOf("COIN_" + amount); + } +} diff --git a/src/main/java/vendingmachine/model/coin/Coins.java b/src/main/java/vendingmachine/model/coin/Coins.java new file mode 100644 index 000000000..8895388a1 --- /dev/null +++ b/src/main/java/vendingmachine/model/coin/Coins.java @@ -0,0 +1,30 @@ +package vendingmachine.model.coin; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class Coins { + private HashMap coins; + + public Coins(HashMap coins) { + this.coins = coins; + } + + public List coinsCount() { + List count = new ArrayList<>(); + count.add(getCoinCount("COIN_500")); + count.add(getCoinCount("COIN_100")); + count.add(getCoinCount("COIN_50")); + count.add(getCoinCount("COIN_10")); + return count; + } + + private Integer getCoinCount(String coinType) { + Integer coinCount = coins.get(Coin.valueOf(coinType)); + if (coinCount == null) { + return 0; + } + return coinCount; + } +} diff --git a/src/main/java/vendingmachine/model/coin/RandomCoins.java b/src/main/java/vendingmachine/model/coin/RandomCoins.java new file mode 100644 index 000000000..72bb096ae --- /dev/null +++ b/src/main/java/vendingmachine/model/coin/RandomCoins.java @@ -0,0 +1,25 @@ +package vendingmachine.model.coin; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.HashMap; + +public class RandomCoins { + public static HashMap makeRandomCoins(int totalMoney){ + HashMap moneyBox = new HashMap<>(); + while(totalMoney > 0){ + int coin = Randoms.pickNumberInList(Coin.coinTypes()); + Coin coinType = Coin.getCoinType(coin); + if (totalMoney >= coin) { + if (moneyBox.containsKey(Coin.getCoinType(coin))) { + moneyBox.put(coinType, moneyBox.get(coinType)+1); + } + if (!moneyBox.containsKey(Coin.getCoinType(coin))) { + moneyBox.put(coinType, 1); + } + totalMoney -= coin; + } + } + return moneyBox; + } +} diff --git a/src/main/java/vendingmachine/model/constants/Delimiter.java b/src/main/java/vendingmachine/model/constants/Delimiter.java new file mode 100644 index 000000000..76d02f72e --- /dev/null +++ b/src/main/java/vendingmachine/model/constants/Delimiter.java @@ -0,0 +1,6 @@ +package vendingmachine.model.constants; + +public class Delimiter { + public static final String DRINK_TYPE_DELIMITER_SEMICOLON = ";"; + public static final String DRINK_DETAIL_DELIMITER_COMMA = ","; +} diff --git a/src/main/java/vendingmachine/model/constants/Index.java b/src/main/java/vendingmachine/model/constants/Index.java new file mode 100644 index 000000000..5cc1153c9 --- /dev/null +++ b/src/main/java/vendingmachine/model/constants/Index.java @@ -0,0 +1,7 @@ +package vendingmachine.model.constants; + +public class Index { + public static Integer DRINK_TYPE_INDEX = 0; + public static Integer DRINK_PRICE_INDEX = 1; + public static Integer DRINK_COUNT_INDEX = 2; +} diff --git a/src/main/java/vendingmachine/model/drink/Drink.java b/src/main/java/vendingmachine/model/drink/Drink.java new file mode 100644 index 000000000..341873f6d --- /dev/null +++ b/src/main/java/vendingmachine/model/drink/Drink.java @@ -0,0 +1,21 @@ +package vendingmachine.model.drink; + +public class Drink { + private String title; + private Integer price; + private Integer count; + + public Drink(String title, Integer price, Integer count) { + this.title = title; + this.price = price; + this.count = count; + } + + public Integer getPrice() { + return price; + } + + public String getName() { + return title; + } +} diff --git a/src/main/java/vendingmachine/model/drink/Drinks.java b/src/main/java/vendingmachine/model/drink/Drinks.java new file mode 100644 index 000000000..08bdc2314 --- /dev/null +++ b/src/main/java/vendingmachine/model/drink/Drinks.java @@ -0,0 +1,47 @@ +package vendingmachine.model.drink; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; + +import static vendingmachine.model.constants.Delimiter.DRINK_DETAIL_DELIMITER_COMMA; +import static vendingmachine.model.constants.Delimiter.DRINK_TYPE_DELIMITER_SEMICOLON; +import static vendingmachine.model.constants.Index.DRINK_COUNT_INDEX; +import static vendingmachine.model.constants.Index.DRINK_PRICE_INDEX; +import static vendingmachine.model.constants.Index.DRINK_TYPE_INDEX; + +public class Drinks { + List drinks = new ArrayList<>(); + + public Drinks(String inputDrink) { + String[] drinkTypes = inputDrink.split(DRINK_TYPE_DELIMITER_SEMICOLON); + + for(int i=0; i comparatorByAge = Comparator.comparingInt(Drink::getPrice); + + Drink cheapestDrink = drinks.stream() + .min(comparatorByAge).orElseThrow(NoSuchElementException::new); + return cheapestDrink.getPrice(); + } + + public int getPriceFindByName(String purchaseDrinkType) { + for (Drink drink : drinks) { + if (Objects.equals(drink.getName(), purchaseDrinkType)) { + return drink.getPrice(); + } + } + return 0; + } +} diff --git a/src/main/java/vendingmachine/model/user/UserMoney.java b/src/main/java/vendingmachine/model/user/UserMoney.java new file mode 100644 index 000000000..4c064ac30 --- /dev/null +++ b/src/main/java/vendingmachine/model/user/UserMoney.java @@ -0,0 +1,17 @@ +package vendingmachine.model.user; + +public class UserMoney { + int money = 0; + + public UserMoney(int money) { + this.money = money; + } + + public int getBalance() { + return money; + } + + public void purchaseDrink(int price) { + money -= price; + } +} diff --git a/src/main/java/vendingmachine/view/InputView.java b/src/main/java/vendingmachine/view/InputView.java new file mode 100644 index 000000000..3d4cd82b6 --- /dev/null +++ b/src/main/java/vendingmachine/view/InputView.java @@ -0,0 +1,32 @@ +package vendingmachine.view; + +import camp.nextstep.edu.missionutils.Console; + +import static vendingmachine.view.constants.Regex.REGEX_SELECT_BRACKETS; +import static vendingmachine.view.validate.CoinValidator.checkNumericInput; +import static vendingmachine.view.validate.DrinkValidator.checkDrinkInput; +import static vendingmachine.view.validate.DrinkValidator.checkOnlyKoreanLetter; + +public class InputView { + public static String readLine(){ + return Console.readLine(); + } + + public static int readMoney(){ + String money = readLine(); + checkNumericInput(money); + return Integer.parseInt(money); + } + + public static String readDrinks() { + String drinks = readLine(); + checkDrinkInput(drinks); + return drinks.replaceAll(REGEX_SELECT_BRACKETS, ""); + } + + public static String readPurchase() { + String purchase = readLine(); + checkOnlyKoreanLetter(purchase); + return purchase; + } +} diff --git a/src/main/java/vendingmachine/view/OutputView.java b/src/main/java/vendingmachine/view/OutputView.java new file mode 100644 index 000000000..7505850e6 --- /dev/null +++ b/src/main/java/vendingmachine/view/OutputView.java @@ -0,0 +1,56 @@ +package vendingmachine.view; + +import java.util.List; + +import static vendingmachine.view.constants.ErrorMessage.ERROR_TAG; +import static vendingmachine.view.constants.OutputMessage.ASK_DRINKS; +import static vendingmachine.view.constants.OutputMessage.ASK_PURCHASE_DRINK_TYPE; +import static vendingmachine.view.constants.OutputMessage.ASK_TOTAL_MONEY_OF_VENDING_MACHINE; +import static vendingmachine.view.constants.OutputMessage.ASK_USER_INPUT_MONEY; +import static vendingmachine.view.constants.OutputMessage.SHOW_BALANCE; +import static vendingmachine.view.constants.OutputMessage.SHOW_VENDING_MACHINE_COINS; +import static vendingmachine.view.constants.OutputMessage.SHOW_VENDING_MACHINE_HOLD_COIN_START_TAG; + +public class OutputView { + public static void askMachineTotalMoney(){ + print(ASK_TOTAL_MONEY_OF_VENDING_MACHINE); + } + + public static void printVendingMachineCoins(List coinCounts){ + print(SHOW_VENDING_MACHINE_HOLD_COIN_START_TAG); + print(String.format(SHOW_VENDING_MACHINE_COINS, + coinCounts.get(0), + coinCounts.get(1), + coinCounts.get(2), + coinCounts.get(3) + )); + } + + public static void askDrinkFromUsers() { + print(ASK_DRINKS); + } + + public static void askUserInputMoney() { + print(ASK_USER_INPUT_MONEY); + } + + public static void showBalance(int balance) { + print(String.format(SHOW_BALANCE, balance)); + } + + public static void askPurchaseDrinkType() { + print(ASK_PURCHASE_DRINK_TYPE); + } + + private static void print(String message){ + System.out.println(message); + } + + public static void println() { + System.out.println(System.lineSeparator()); + } + + public static void errorMessage(String errorMessage) { + System.out.println(ERROR_TAG + errorMessage); + } +} diff --git a/src/main/java/vendingmachine/view/constants/ErrorMessage.java b/src/main/java/vendingmachine/view/constants/ErrorMessage.java new file mode 100644 index 000000000..809cbc93d --- /dev/null +++ b/src/main/java/vendingmachine/view/constants/ErrorMessage.java @@ -0,0 +1,11 @@ +package vendingmachine.view.constants; + +public class ErrorMessage { + public static final String ERROR_TAG = "[ERROR] "; + + public static final String INVALID_VENDING_MACHINE_TOTAL_MONEY = "금액은 10원 단위의 숫자여야 합니다."; + + public static final String INVALID_DRINKS_INPUT_FORMAT = "입력 포맷을 지켜 주세요. 입력 포맷: [음료 이름,가격,수량]"; + + public static final String ONLY_KOREAN_LETTERS = "입력 값은 한국어만 가능합니다."; +} diff --git a/src/main/java/vendingmachine/view/constants/OutputMessage.java b/src/main/java/vendingmachine/view/constants/OutputMessage.java new file mode 100644 index 000000000..5f9732c7b --- /dev/null +++ b/src/main/java/vendingmachine/view/constants/OutputMessage.java @@ -0,0 +1,15 @@ +package vendingmachine.view.constants; + +public class OutputMessage { + public static final String ASK_TOTAL_MONEY_OF_VENDING_MACHINE = "자판기가 보유하고 있는 금액을 입력해 주세요."; + public static final String SHOW_VENDING_MACHINE_HOLD_COIN_START_TAG = "자판기가 보유한 동전"; + public static final String SHOW_VENDING_MACHINE_COINS = "500원 - %d개\n100원 - %d개\n50원 - %d개\n10원 - %d개"; + + public static final String ASK_DRINKS = "상품명과 가격, 수량을 입력해 주세요."; + + public static final String ASK_USER_INPUT_MONEY = "투입 금액을 입력해 주세요."; + + public static final String SHOW_BALANCE = "투입 금액: %d원"; + + public static final String ASK_PURCHASE_DRINK_TYPE = "구매할 상품명을 입력해 주세요."; +} diff --git a/src/main/java/vendingmachine/view/constants/Regex.java b/src/main/java/vendingmachine/view/constants/Regex.java new file mode 100644 index 000000000..fceeeb9ee --- /dev/null +++ b/src/main/java/vendingmachine/view/constants/Regex.java @@ -0,0 +1,10 @@ +package vendingmachine.view.constants; + +public class Regex { + public static final String REGEX_MONEY_NUMERIC = "[1-9]\\d*0"; + + public static final String REGEX_DRINK_INPUT_FORMAT = "((\\[[ㄱ-ㅎ가-힣]{1,}),([1-9]\\d*0),([1-9]\\d*)\\](;)?){1,}"; + public static final String REGEX_SELECT_BRACKETS = "[\\[\\]]"; + + public static final String REGEX_ONLY_KOREAN_LETTER = "([ㄱ-ㅎ가-힣]){1,}"; +} diff --git a/src/main/java/vendingmachine/view/validate/CoinValidator.java b/src/main/java/vendingmachine/view/validate/CoinValidator.java new file mode 100644 index 000000000..abad3a7f9 --- /dev/null +++ b/src/main/java/vendingmachine/view/validate/CoinValidator.java @@ -0,0 +1,17 @@ +package vendingmachine.view.validate; + +import java.util.regex.Pattern; + +import static vendingmachine.view.constants.ErrorMessage.INVALID_VENDING_MACHINE_TOTAL_MONEY; +import static vendingmachine.view.constants.Regex.REGEX_MONEY_NUMERIC; + +public class CoinValidator { + private static final Pattern NUMERIC = Pattern.compile(REGEX_MONEY_NUMERIC); + + public static void checkNumericInput(String input) { + if (NUMERIC.matcher(input).matches()) { + return; + } + throw new IllegalArgumentException(INVALID_VENDING_MACHINE_TOTAL_MONEY); + } +} diff --git a/src/main/java/vendingmachine/view/validate/DrinkValidator.java b/src/main/java/vendingmachine/view/validate/DrinkValidator.java new file mode 100644 index 000000000..2af6d71d4 --- /dev/null +++ b/src/main/java/vendingmachine/view/validate/DrinkValidator.java @@ -0,0 +1,27 @@ +package vendingmachine.view.validate; + +import java.util.regex.Pattern; + +import static vendingmachine.view.constants.ErrorMessage.INVALID_DRINKS_INPUT_FORMAT; +import static vendingmachine.view.constants.ErrorMessage.ONLY_KOREAN_LETTERS; +import static vendingmachine.view.constants.Regex.REGEX_DRINK_INPUT_FORMAT; +import static vendingmachine.view.constants.Regex.REGEX_ONLY_KOREAN_LETTER; + +public class DrinkValidator { + private static final Pattern DRINK_INPUT_FORMAT = Pattern.compile(REGEX_DRINK_INPUT_FORMAT); + private static final Pattern ONLY_KOREAN_LETTER = Pattern.compile(REGEX_ONLY_KOREAN_LETTER); + + public static void checkDrinkInput(String input) { + if (DRINK_INPUT_FORMAT.matcher(input).matches()) { + return; + } + throw new IllegalArgumentException(INVALID_DRINKS_INPUT_FORMAT); + } + + public static void checkOnlyKoreanLetter(String input) { + if (ONLY_KOREAN_LETTER.matcher(input).matches()) { + return; + } + throw new IllegalArgumentException(ONLY_KOREAN_LETTERS); + } +}