diff --git a/src/main/java/WebMain.java b/src/main/java/WebMain.java new file mode 100644 index 00000000..606b8fdd --- /dev/null +++ b/src/main/java/WebMain.java @@ -0,0 +1,83 @@ +import dto.LotteryRequestDto; +import handlebars.CustomHandlebarsTemplateEngine; +import lotto.*; +import spark.ModelAndView; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static spark.Spark.*; +import static view.InputView.COMMA; + +public class WebMain { + private static final String LOTTERIES = "lotteries"; + private static final String INPUT_MONEY = "inputMoney"; + public static final String WINNING_NUMBER = "winningNumber"; + public static final String BONUS_NUMBER = "bonusNumber"; + public static final String YIELD = "yield"; + public static final String RESULTS = "results"; + + public static void main(String[] args) { + port(8080); + + get("/", (req, res) -> { + req.session(true); + return render("/index.html"); + }); + post("/buyLotto", (req, res) -> { + final LotteryRequestDto requestDto = new LotteryRequestDto(req.queryParams("inputMoney"), req.queryParams("manualNumber")); + + final List lotteries = LottoShop.sell(requestDto.getInputMoney(), requestDto.getManualLottoInputs()); + + req.session().attribute(LOTTERIES, lotteries); + req.session().attribute(INPUT_MONEY, requestDto.getInputMoney()); + + final Map responseParams = new HashMap<>(); + responseParams.put(LOTTERIES, lotteries.stream() + .map(Lottery::toString) + .collect(Collectors.toList())); + return render(responseParams, "/show.html"); + }); + + post("/matchLotto", (req, res) -> { + final List lotteries = req.session().attribute(LOTTERIES); + final Money inputMoney = req.session().attribute(INPUT_MONEY); + + req.session().attribute(LOTTERIES, lotteries); + + final String[] winningNumber = req.queryParams(WINNING_NUMBER) + .trim() + .split(COMMA); + final String inputBonusNumber = req.queryParams(BONUS_NUMBER).trim(); + + final WinningNumbers winningNumbers = WinningNumbers.of( + Arrays.stream(winningNumber) + .map(String::trim) + .map(Integer::parseInt) + .collect(Collectors.toList()) + , Integer.parseInt(inputBonusNumber) + ); + + final Results results = ResultsFactory.create(lotteries, winningNumbers, inputMoney); + final Map responseParams = new HashMap<>(); + responseParams.put(YIELD, results.getYield()); + for (Prize prize : Prize.values()) { + responseParams.put(prize.name(), results.getCountOfPrize().getOrDefault(prize, 0)); + } + + req.session().invalidate(); + return render(responseParams, "/result.html"); + }); + } + + private static String render(String templatePath) { + return render(null, templatePath); + } + + public static String render(Map model, String templatePath) { + return new CustomHandlebarsTemplateEngine().render(new ModelAndView(model, templatePath)); + } +} diff --git a/src/main/java/dto/LotteryRequestDto.java b/src/main/java/dto/LotteryRequestDto.java new file mode 100644 index 00000000..914537a8 --- /dev/null +++ b/src/main/java/dto/LotteryRequestDto.java @@ -0,0 +1,40 @@ +package dto; + +import lotto.Money; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static view.InputView.COMMA; + +public class LotteryRequestDto { + + public static final String LINE_BREAK_REGEX = "\\r?\\n"; + private Money inputMoney; + private List> manualLottoInputs; + + public LotteryRequestDto(String inputMoney, String manualLottoInputs) { + this.inputMoney = Money.of(Long.parseLong(inputMoney)); + this.manualLottoInputs = Arrays.stream(manualLottoInputs.split(LINE_BREAK_REGEX)) + .map(LotteryRequestDto::parseLine) + .collect(Collectors.toList()); + } + + private static List parseLine(String eachLine) { + return Arrays.stream(eachLine.split(COMMA)) + .map(String::trim) + .mapToInt(Integer::parseInt) + .boxed() + .collect(Collectors.toList()); + } + + public Money getInputMoney() { + return inputMoney; + } + + public List> getManualLottoInputs() { + return Collections.unmodifiableList(manualLottoInputs); + } +} diff --git a/src/main/java/handlebars/CustomHandlebarsTemplateEngine.java b/src/main/java/handlebars/CustomHandlebarsTemplateEngine.java new file mode 100644 index 00000000..6e149a53 --- /dev/null +++ b/src/main/java/handlebars/CustomHandlebarsTemplateEngine.java @@ -0,0 +1,52 @@ +package handlebars; + +import com.github.jknack.handlebars.Handlebars; +import com.github.jknack.handlebars.Template; +import com.github.jknack.handlebars.cache.GuavaTemplateCache; +import com.github.jknack.handlebars.io.ClassPathTemplateLoader; +import com.github.jknack.handlebars.io.TemplateLoader; +import com.github.jknack.handlebars.io.TemplateSource; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import org.eclipse.jetty.io.RuntimeIOException; +import spark.ModelAndView; +import spark.TemplateEngine; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class CustomHandlebarsTemplateEngine extends TemplateEngine { + + protected Handlebars handlebars; + + public CustomHandlebarsTemplateEngine() { + this("/templates"); + } + + public CustomHandlebarsTemplateEngine(String resourceRoot) { + final TemplateLoader templateLoader = new ClassPathTemplateLoader(); + templateLoader.setPrefix(resourceRoot); + templateLoader.setSuffix(null); + + this.handlebars = new Handlebars(templateLoader); + this.handlebars.registerHelper("length", new LengthHelper()); + this.handlebars.registerHelper("plusOne", new PlusOneHelper()); + + // Set Guava cache. + Cache cache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES) + .maximumSize(1000).build(); + + handlebars = handlebars.with(new GuavaTemplateCache(cache)); + } + + @Override + public String render(ModelAndView modelAndView) { + try { + String viewName = modelAndView.getViewName(); + Template template = handlebars.compile(viewName); + return template.apply(modelAndView.getModel()); + } catch (IOException e) { + throw new RuntimeIOException(e); + } + } +} diff --git a/src/main/java/handlebars/LengthHelper.java b/src/main/java/handlebars/LengthHelper.java new file mode 100644 index 00000000..9db7b5d4 --- /dev/null +++ b/src/main/java/handlebars/LengthHelper.java @@ -0,0 +1,17 @@ +package handlebars; + +import com.github.jknack.handlebars.Helper; +import com.github.jknack.handlebars.Options; + +import java.util.List; + +public class LengthHelper implements Helper> { + + @Override + public Object apply(List context, Options options) { + if (context == null || context.isEmpty()) { + return 0; + } + return context.size(); + } +} diff --git a/src/main/java/handlebars/PlusOneHelper.java b/src/main/java/handlebars/PlusOneHelper.java new file mode 100644 index 00000000..1e810349 --- /dev/null +++ b/src/main/java/handlebars/PlusOneHelper.java @@ -0,0 +1,13 @@ +package handlebars; + +import com.github.jknack.handlebars.Helper; +import com.github.jknack.handlebars.Options; + +public class PlusOneHelper implements Helper { + + @Override + public CharSequence apply(Integer context, Options options) { + final int plusOne = context + 1; + return String.valueOf(plusOne); + } +} diff --git a/src/main/java/lotto/Lottery.java b/src/main/java/lotto/Lottery.java index 669a029d..e2d39b8e 100644 --- a/src/main/java/lotto/Lottery.java +++ b/src/main/java/lotto/Lottery.java @@ -6,15 +6,17 @@ import java.util.TreeSet; import java.util.stream.Collectors; +import static view.InputView.COMMA; + public class Lottery { public static final int LOTTO_NUMBER_COUNT = 6; public static final long LOTTO_PRICE = 1000; - private Set numbers; + protected Set numbers; - private Lottery(Set lottoNumbers) { + protected Lottery(Set lottoNumbers) { validateNumbers(lottoNumbers); this.numbers = lottoNumbers.stream() .map(LottoNo::of) @@ -36,6 +38,9 @@ public Result checkResult(WinningNumbers winningNumbers) { } private void validateNumbers(Set lottoNumbers) { + if (lottoNumbers == null) { + throw new IllegalArgumentException("입력값이 NULL입니다."); + } if (lottoNumbers.size() != LOTTO_NUMBER_COUNT) { throw new IllegalArgumentException("로또의 갯수가 잘못되었거나 중복된 숫자가 있습니다."); } @@ -52,4 +57,12 @@ private int correctCount(WinningNumbers winningNumbers) { return LOTTO_NUMBER_COUNT + (LOTTO_NUMBER_COUNT - combinedSize); } + @Override + public String toString() { + return "[" + + numbers.stream() + .map(String::valueOf) + .collect(Collectors.joining(COMMA)) + + "]"; + } } diff --git a/src/main/java/lotto/RandomLottoGenerator.java b/src/main/java/lotto/RandomLottoGenerator.java index ff10b7cb..bf13d6b5 100644 --- a/src/main/java/lotto/RandomLottoGenerator.java +++ b/src/main/java/lotto/RandomLottoGenerator.java @@ -22,4 +22,7 @@ public Lottery generate() { return Lottery.of(lottoNumbers); } + + + } diff --git a/src/main/java/lotto/WinningNumbers.java b/src/main/java/lotto/WinningNumbers.java index e10313a6..e2e2039c 100644 --- a/src/main/java/lotto/WinningNumbers.java +++ b/src/main/java/lotto/WinningNumbers.java @@ -1,62 +1,33 @@ package lotto; -import spark.utils.CollectionUtils; - -import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; +import java.util.TreeSet; -public class WinningNumbers { +public class WinningNumbers extends Lottery { - private List winningNumbers; private LottoNo bonusNumber; - private WinningNumbers(List numbers, Integer bonusNumber) { - validateParams(numbers, bonusNumber); + protected WinningNumbers(List numbers, Integer bonusNumber) { + super(new TreeSet<>(numbers)); + validateBonusNumber(bonusNumber); this.bonusNumber = LottoNo.of(bonusNumber); - this.winningNumbers = numbers.stream() - .map(LottoNo::of) - .collect(Collectors.toList()); - } - - private void validateParams(List numbers, Integer bonusNumber) { - validateNumbers(numbers); - validateBonusNumber(bonusNumber, numbers); } public static WinningNumbers of(List numbers, Integer bonusNumber) { return new WinningNumbers(numbers, bonusNumber); } - private void validateNumbers(List numbers) { - if (CollectionUtils.isEmpty(numbers)) { - throw new IllegalArgumentException("winning numbers cannot be empty"); - } - - if (numbers.size() != Lottery.LOTTO_NUMBER_COUNT) { - throw new IllegalArgumentException("winning numbers size should be LOTTO_NUMBER_COUNT"); - } - - if (numbers.stream().distinct().count() != numbers.size()) { - throw new IllegalArgumentException("winning numbers can not have duplicate numbers"); - } - } - - private void validateBonusNumber(Integer bonusNumber, List winningNumbers) { + private void validateBonusNumber(Integer bonusNumber) { if (Objects.isNull(bonusNumber)) { throw new IllegalArgumentException("bonus number can not be null or empty"); } - if (winningNumbers.contains(bonusNumber)) { + if (this.numbers.contains(LottoNo.of(bonusNumber))) { throw new IllegalArgumentException("bonus number is already in"); } } - public List getNumbers() { - return new ArrayList<>(winningNumbers); - } - public LottoNo getBonusNumber() { return bonusNumber; } diff --git a/src/main/java/view/ResultView.java b/src/main/java/view/ResultView.java index 26121c73..88e68539 100644 --- a/src/main/java/view/ResultView.java +++ b/src/main/java/view/ResultView.java @@ -31,9 +31,8 @@ public void printLottoResults(ResultsDto resultsDto) { printHeader(); final Map countOfPrize = resultsDto.getCountOfPrize(); - final Set prizes = countOfPrize.keySet(); - printCountOfPrize(countOfPrize, prizes); + printCountOfPrize(countOfPrize); printYield(resultsDto); } @@ -43,8 +42,8 @@ private static void printHeader() { System.out.println("============"); } - private void printCountOfPrize(Map countOfPrize, Set prizes) { - prizes.stream() + private void printCountOfPrize(Map countOfPrize) { + countOfPrize.keySet().stream() .filter(prize -> prize != Prize.NONE) .forEach(prize -> printEach(countOfPrize.get(prize), prize)); } diff --git a/src/main/resources/templates/result.html b/src/main/resources/templates/result.html index 7d1700b9..0308ab41 100644 --- a/src/main/resources/templates/result.html +++ b/src/main/resources/templates/result.html @@ -73,23 +73,23 @@ - 3개 일치 (5000원)- 3개 + 3개 일치 (5000원)- {{FIFTH_PRIZE}}개 - 4개 일치 (50000원)- 1개 + 4개 일치 (50000원)- {{SECOND_PRIZE}}개 - 5개 일치 (1500000원)- 0개 + 5개 일치 (1500000원)- {{THIRD_PRIZE}}개 - 5개 일치, 보너스 볼 일치(30000000원)- 0개 + 5개 일치, 보너스 볼 일치(30000000원)- {{SECOND_PRIZE}}개 - 6개 일치 (2000000000원)- 0개 + 6개 일치 (2000000000원)- {{FIRST_PRIZE}}개 -

총 수익률은 20%입니다.

+

총 수익률은 {{yield}}%입니다.

diff --git a/src/main/resources/templates/show.html b/src/main/resources/templates/show.html index b360e39e..0671940c 100644 --- a/src/main/resources/templates/show.html +++ b/src/main/resources/templates/show.html @@ -78,33 +78,19 @@
-	

<5개를 구매 하셨습니다>

+

<{{length lotteries}}개를 구매 하셨습니다>

+ {{#each lotteries}} - - - - - - - - - - - - - - - - - - + + + {{/each}}
로또 번호
1[8, 21, 23, 41, 42, 43]
2[3, 5, 11, 16, 32, 38]
3[7, 11, 16, 35, 36, 44]
4[1, 8, 11, 31, 41, 42]
5[13, 14, 16, 38, 42, 45]{{plusOne @index}}{{this}}
diff --git a/src/test/java/lotto/WinningNumbersTest.java b/src/test/java/lotto/WinningNumbersTest.java index b7358a51..31ea822f 100644 --- a/src/test/java/lotto/WinningNumbersTest.java +++ b/src/test/java/lotto/WinningNumbersTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.EmptySource; import org.junit.jupiter.params.provider.ValueSource; import java.util.List; @@ -14,8 +14,8 @@ class WinningNumbersTest { @ParameterizedTest - @NullAndEmptySource - @DisplayName("로또 번호 갯수가 NULL 또는 빈값이 들어왔을 떄 Exception을 던진다") + @EmptySource + @DisplayName("로또 번호 갯수가 빈값이 들어왔을 떄 Exception을 던진다") void testValidate(List numbers) { assertThrows(IllegalArgumentException.class, () -> WinningNumbers.of(numbers, 5)); }