diff --git a/README.md b/README.md index bd90ef0247..8eacf2522c 100644 --- a/README.md +++ b/README.md @@ -1 +1,163 @@ -# java-calculator-precourse \ No newline at end of file +# java-calculator-precourse + +About 1st Pre-Course Project : Calculator + +--- + +## ๐Ÿ“ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ +``` +calculator +โ”œโ”€โ”€ controller +โ”‚ โ””โ”€โ”€ CalculatorController.java +โ”œโ”€โ”€ dto +โ”‚ โ”œโ”€โ”€ InputRequest.java +โ”‚ โ””โ”€โ”€ OutputResponse.java +โ”œโ”€โ”€ error +โ”‚ โ””โ”€โ”€ CustomException.java +โ”œโ”€โ”€ message +โ”‚ โ”œโ”€โ”€ ErrorMessage.java +โ”‚ โ”œโ”€โ”€ InputMessage.java +โ”‚ โ”œโ”€โ”€ MessageProvider.java +โ”‚ โ””โ”€โ”€ OutputMessage.java +โ”œโ”€โ”€ model +โ”‚ โ”œโ”€โ”€ Calculator.java +โ”‚ โ”œโ”€โ”€ Operator.java +โ”‚ โ”œโ”€โ”€ PlusCalculator.java +โ”‚ โ””โ”€โ”€ PlusOperator.java +โ”œโ”€โ”€ parser +โ”‚ โ”œโ”€โ”€ CalculatorParser.java +โ”‚ โ”œโ”€โ”€ DelimiterPattern.java +โ”‚ โ””โ”€โ”€ InputParser.java +โ”œโ”€โ”€ service +โ”‚ โ””โ”€โ”€ CalculatorService.java +โ”œโ”€โ”€ validation +โ”‚ โ”œโ”€โ”€ CalculateValidator.java +โ”‚ โ”œโ”€โ”€ InputValidator.java +โ”‚ โ””โ”€โ”€ ValidationPattern.java +โ””โ”€โ”€ view +โ”‚ โ”œโ”€โ”€ InputView.java +โ”‚ โ””โ”€โ”€ OutputView.java +โ””โ”€โ”€ Application +``` + +--- + +## ๐ŸŽฏ ํ•™์Šต ๋ชฉํ‘œ + +- Git, GitHub, IDE ๋“ฑ ์‹ค์ œ ๊ฐœ๋ฐœ์„ ์œ„ํ•œ ํ™˜๊ฒฝ์— ์ต์ˆ™ํ•ด์ง„๋‹ค. +- ๊ต์œก ๋ถ„์•ผ์— ๋งž๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ๋‹ค. + +--- + +## ๐Ÿ› ๏ธ ํ”„๋ฆฌ์ฝ”์Šค ์ง„ํ–‰ ๋ฐฉ์‹ + +### ๐Ÿ” ์ง„ํ–‰ ๋ฐฉ์‹ +- ๋ฏธ์…˜์€ **๊ณผ์ œ ์ง„ํ–‰ ์š”๊ตฌ ์‚ฌํ•ญ**, **๊ธฐ๋Šฅ ์š”๊ตฌ ์‚ฌํ•ญ**, **ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ** ์„ธ ๊ฐ€์ง€๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค. +- ์„ธ ๊ฐœ์˜ ์š”๊ตฌ ์‚ฌํ•ญ์„ ๋งŒ์กฑํ•˜๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅํ•œ๋‹ค. ํŠนํžˆ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „์— ๊ธฐ๋Šฅ ๋ชฉ๋ก์„ ๋งŒ๋“ค๊ณ , ๊ธฐ๋Šฅ ๋‹จ์œ„๋กœ ์ปค๋ฐ‹ ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ•œ๋‹ค. +- **๊ธฐ๋Šฅ ์š”๊ตฌ ์‚ฌํ•ญ์— ๊ธฐ์žฌ๋˜์ง€ ์•Š์€ ๋‚ด์šฉ**์€ ์Šค์Šค๋กœ ํŒ๋‹จํ•˜์—ฌ ๊ตฌํ˜„ํ•œ๋‹ค. +- ๋งค์ฃผ ์ง„ํ–‰ํ•  ๋ฏธ์…˜์€ **ํ™”์š”์ผ ์˜คํ›„ 3์‹œ**๋ถ€ํ„ฐ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, **๋‹ค์Œ ์ฃผ ์›”์š”์ผ๊นŒ์ง€** ๊ตฌํ˜„์„ ์™„๋ฃŒํ•˜์—ฌ ์ œ์ถœํ•ด์•ผ ํ•œ๋‹ค. ์ œ์ถœ์€ **์ผ์š”์ผ ์˜คํ›„ 3์‹œ**๋ถ€ํ„ฐ ๊ฐ€๋Šฅํ•˜๋‹ค. + - ์ •ํ•ด์ง„ ์‹œ๊ฐ„์„ ์ง€ํ‚ค์ง€ ์•Š์„ ๊ฒฝ์šฐ ๋ฏธ์…˜์„ ์ œ์ถœํ•˜์ง€ ์•Š์€ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผํ•œ๋‹ค. + - ์ข…๋ฃŒ ์ผ์‹œ ์ดํ›„์—๋Š” ์ถ”๊ฐ€ ๋ฌด์ œ๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. + +--- + +## ๐Ÿ“ฎ ๋ฏธ์…˜ ์ œ์ถœ ๋ฐฉ๋ฒ• + +- ๋ฏธ์…˜ ๊ตฌํ˜„์„ ์™„๋ฃŒํ•œ ํ›„ **GitHub**์„ ํ†ตํ•ด ์ œ์ถœํ•ด์•ผ ํ•œ๋‹ค. + - GitHub๋ฅผ ํ™œ์šฉํ•œ ์ œ์ถœ ๋ฐฉ๋ฒ•์€ [ํ”„๋ฆฌ์ฝ”์Šค ๊ณผ์ œ ์ œ์ถœ](https://github.com/woowacourse/woowacourse-docs/tree/main/precourse) ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•ด ์ œ์ถœํ•œ๋‹ค. + - GitHub์— ๋ฏธ์…˜์„ ์ œ์ถœํ•œ ํ›„ [์šฐ์•„ํ•œํ…Œํฌ์ฝ”์Šค ์ง€์› ํ”Œ๋žซํผ](https://apply.techcourse.co.kr/)์— PR ๋งํฌ๋ฅผ ํฌํ•จํ•˜์—ฌ ์ตœ์ข… ์ œ์ถœํ•œ๋‹ค. + - ์ž์„ธํ•œ ๋‚ด์šฉ์€ [์ œ์ถœ ๊ฐ€์ด๋“œ](https://github.com/woowacourse/woowacourse-docs/tree/main/precourse#%EC%A0%9C%EC%B6%9C-%EA%B0%80%EC%9D%B4%EB%93%9C)๋ฅผ ์ฐธ๊ณ ํ•œ๋‹ค. +- ๊ณผ์ œ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด์„œ ๋А๋‚€ ์ , ๋งŽ์€ ์‹œ๊ฐ„์„ ํˆฌ์žํ•œ ๋ถ€๋ถ„ ๋“ฑ ์ž์œ ๋กญ๊ฒŒ ์ž‘์„ฑํ•œ๋‹ค. + +--- + +## โœ… ๊ณผ์ œ ์ œ์ถœ ์ „ ์ฒดํฌ ๋ฆฌ์ŠคํŠธ + +- ๊ธฐ๋Šฅ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„ํ–ˆ๋”๋ผ๋„ ์š”๊ตฌ ์‚ฌํ•ญ์— ๋ช…์‹œ๋œ **์ถœ๋ ฅ ํ˜•์‹**์„ ๋”ฐ๋ฅด์ง€ ์•Š์œผ๋ฉด **0์ **์„ ๋ฐ›๊ฒŒ ๋œ๋‹ค. +- ๊ธฐ๋Šฅ ๊ตฌํ˜„์„ ์™„๋ฃŒํ•œ ํ›„ ์—ฌ๋Ÿฌ ๊ฐ€์ด๋“œ์— ๋”ฐ๋ผ **๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์‹คํ–‰๋˜๋Š”์ง€** ํ™•์ธํ•œ๋‹ค. +- **ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜๋ฉด ์ ์ˆ˜๊ฐ€ 0์ **์ด ๋˜๋ฏ€๋กœ ์ œ์ถœํ•˜๊ธฐ ์ „์— ๋ฐ˜๋“œ์‹œ ํ™•์ธํ•œ๋‹ค. + +--- + +## ๐Ÿงช ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๊ฐ€์ด๋“œ + +- **ํ„ฐ๋ฏธ๋„**์—์„œ `java -version`์„ ์‹คํ–‰ํ•˜์—ฌ Java ๋ฒ„์ „์ด **21 ์ด์ƒ**์ธ์ง€ ํ™•์ธํ•œ๋‹ค. + Eclipse ๋˜๋Š” IntelliJ IDEA์™€ ๊ฐ™์€ IDE์—์„œ Java 21๋กœ ์‹คํ–‰๋˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. + +- ํ„ฐ๋ฏธ๋„์—์„œ **Mac ๋˜๋Š” Linux ์‚ฌ์šฉ์ž์˜ ๊ฒฝ์šฐ**: + ```bash + ./gradlew clean test + ``` + +- **Windows ์‚ฌ์šฉ์ž์˜ ๊ฒฝ์šฐ**: + ```bash + gradlew.bat clean test + ``` + + ์œ„ ๋ช…๋ น์„ ์‹คํ–‰ํ•œ ํ›„ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ์•„๋ž˜์™€ ๊ฐ™์ด ํ†ต๊ณผํ•˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค: + ``` + BUILD SUCCESSFUL in 0s + ``` + +--- + +## ๐Ÿš€ ๊ณผ์ œ ์ง„ํ–‰ ์š”๊ตฌ ์‚ฌํ•ญ + +- ๋ฏธ์…˜์€ [๋ฌธ์ž์—ด ๋ง์…ˆ ๊ณ„์‚ฐ๊ธฐ](https://github.com/woowacourse-precourse/java-calculator-7) ์ €์žฅ์†Œ๋ฅผ ํฌํฌํ•˜๊ณ  ํด๋ก ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์‹œ์ž‘ํ•œ๋‹ค. +- ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „ **README.md**์— ๊ตฌํ˜„ํ•  ๊ธฐ๋Šฅ ๋ชฉ๋ก์„ ์ •๋ฆฌํ•ด ์ถ”๊ฐ€ํ•œ๋‹ค. +- Git์˜ ์ปค๋ฐ‹ ๋‹จ์œ„๋Š” ๊ฐ ๋‹จ๊ณ„์—์„œ **README.md**์— ์ •๋ฆฌํ•œ ๊ธฐ๋Šฅ ๋ชฉ๋ก ๋‹จ์œ„๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. + - [AngularJS Git Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153)๋ฅผ ์ฐธ๊ณ ํ•ด ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€๋ฅผ ์ž‘์„ฑํ•œ๋‹ค. +- ์ž์„ธํ•œ ๊ณผ์ œ ์ง„ํ–‰ ๋ฐฉ๋ฒ•์€ ํ”„๋ฆฌ์ฝ”์Šค ์ง„ํ–‰ ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ๊ณ ํ•œ๋‹ค. + +--- + +## โœจ ๊ธฐ๋Šฅ ์š”๊ตฌ ์‚ฌํ•ญ + +- ์ž…๋ ฅํ•œ **๋ฌธ์ž์—ด์—์„œ ์ˆซ์ž๋ฅผ ์ถ”์ถœ**ํ•˜์—ฌ ๋”ํ•˜๋Š” ๊ณ„์‚ฐ๊ธฐ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. +- ์‰ผํ‘œ(,) ๋˜๋Š” ์ฝœ๋ก (:)์„ ๊ตฌ๋ถ„์ž๋กœ ๊ฐ€์ง€๋Š” ๋ฌธ์ž์—ด์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒฝ์šฐ, ๊ฐ ์ˆซ์ž์˜ ํ•ฉ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. + - ์˜ˆ: `""` => 0, `"1,2,3"` => 6, `"1:2:3"` => 6 +- ์•ž์— ๊ธฐ๋ณธ ๊ตฌ๋ถ„์ž(์‰ผํ‘œ, ์ฝœ๋ก ) ์™ธ์— **์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž**๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. + ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž๋Š” ๋ฌธ์ž์—ด ์•ž๋ถ€๋ถ„์˜ `"//"`์™€ `"\n"` ์‚ฌ์ด์— ์œ„์น˜ํ•˜๋Š” ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„์ž๋กœ ์‚ฌ์šฉํ•œ๋‹ค. + - ์˜ˆ: `"//;\n1;2;3"`๊ณผ ๊ฐ™์ด ์ž…๋ ฅํ•  ๊ฒฝ์šฐ ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž๋Š” ์„ธ๋ฏธ์ฝœ๋ก (;)์ด๋ฉฐ, ๊ฒฐ๊ณผ ๊ฐ’์€ 6์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค. +- ์‚ฌ์šฉ์ž๊ฐ€ ์ž˜๋ชป๋œ ๊ฐ’์„ ์ž…๋ ฅํ•  ๊ฒฝ์šฐ **`IllegalArgumentException`**์„ ๋ฐœ์ƒ์‹œํ‚จ ํ›„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ข…๋ฃŒ๋˜์–ด์•ผ ํ•œ๋‹ค. + +--- + +## ๐Ÿ“ฅ ์ž…๋ ฅ๊ณผ ์ถœ๋ ฅ ์š”๊ตฌ ์‚ฌํ•ญ + +### ๐Ÿ“Œ ์ž…๋ ฅ +- ๊ตฌ๋ถ„์ž์™€ ์–‘์ˆ˜๋กœ ๊ตฌ์„ฑ๋œ ๋ฌธ์ž์—ด + +### ๐Ÿ“Œ ์ถœ๋ ฅ +- ๋ง์…ˆ ๊ฒฐ๊ณผ +```text +๊ฒฐ๊ณผ: 6 +``` + +### ๐Ÿ“Œ ์‹คํ–‰ ๊ฒฐ๊ณผ ์˜ˆ์‹œ +```text +๋ง์…ˆํ•  ๋ฌธ์ž์—ด์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. +1,2:3 +๊ฒฐ๊ณผ: 6 +``` + +--- + +## ๐Ÿ“ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ + +- **JDK 21** ๋ฒ„์ „์—์„œ ์‹คํ–‰ ๊ฐ€๋Šฅํ•ด์•ผ ํ•œ๋‹ค. +- ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰์˜ ์‹œ์ž‘์ ์€ `Application`์˜ **`main()`** ์ด๋‹ค. +- **`build.gradle`** ํŒŒ์ผ์€ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์œผ๋ฉฐ, **์ œ๊ณต๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ด์™ธ์˜ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.** +- ํ”„๋กœ๊ทธ๋žจ ์ข…๋ฃŒ ์‹œ `System.exit()`๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค. +- ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ์—์„œ ๋‹ฌ๋ฆฌ ๋ช…์‹œํ•˜์ง€ ์•Š์€ ํ•œ **ํŒŒ์ผ๋ช…๊ณผ ํŒจํ‚ค์ง€๋ช…**์„ ๋ฐ”๊พธ๊ฑฐ๋‚˜ ์ด๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค. +- ์ž๋ฐ” ์ฝ”๋“œ ์ปจ๋ฒค์…˜์„ ์ง€ํ‚ค๋ฉด์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•œ๋‹ค. + - ๊ธฐ๋ณธ์ ์œผ๋กœ [Java Style Guide](https://google.github.io/styleguide/javaguide.html)๋ฅผ ์›์น™์œผ๋กœ ํ•œ๋‹ค. + +--- + +## ๐Ÿ“š ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ + +- `camp.nextstep.edu.missionutils`์—์„œ ์ œ๊ณตํ•˜๋Š” `Console` API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค. + - ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•˜๋Š” ๊ฐ’์€ `camp.nextstep.edu.missionutils.Console`์˜ `readLine()` ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•œ๋‹ค. + +--- diff --git a/src/main/java/calculator/Application.java b/src/main/java/calculator/Application.java index 573580fb40..05ff4cc2c3 100644 --- a/src/main/java/calculator/Application.java +++ b/src/main/java/calculator/Application.java @@ -1,7 +1,9 @@ package calculator; +import calculator.controller.CalculatorController; + public class Application { public static void main(String[] args) { - // TODO: ํ”„๋กœ๊ทธ๋žจ ๊ตฌํ˜„ + new CalculatorController().run(); } -} +} \ No newline at end of file diff --git a/src/main/java/calculator/controller/CalculatorController.java b/src/main/java/calculator/controller/CalculatorController.java new file mode 100644 index 0000000000..64ba3ed3d8 --- /dev/null +++ b/src/main/java/calculator/controller/CalculatorController.java @@ -0,0 +1,22 @@ +package calculator.controller; + +import calculator.dto.InputRequest; +import calculator.dto.OutputResponse; +import calculator.service.CalculatorService; +import calculator.view.InputView; +import calculator.view.OutputView; + +public class CalculatorController { + + private final CalculatorService calculatorService; + + public CalculatorController() { + this.calculatorService = new CalculatorService(); + } + + public void run() { + InputRequest input = InputView.getInputRequest(); // ์ž…๋ ฅ + OutputResponse result = calculatorService.calculate(input); // ๊ณ„์‚ฐ + OutputView.outputMessage(result); // ์ถœ๋ ฅ + } +} diff --git a/src/main/java/calculator/dto/InputRequest.java b/src/main/java/calculator/dto/InputRequest.java new file mode 100644 index 0000000000..67e701cff1 --- /dev/null +++ b/src/main/java/calculator/dto/InputRequest.java @@ -0,0 +1,8 @@ +package calculator.dto; + +public record InputRequest(String delimiters, String targetInput) { + + public static InputRequest of(String delimiters, String targetInput) { + return new InputRequest(delimiters, targetInput); + } +} diff --git a/src/main/java/calculator/dto/OutputResponse.java b/src/main/java/calculator/dto/OutputResponse.java new file mode 100644 index 0000000000..d492048c70 --- /dev/null +++ b/src/main/java/calculator/dto/OutputResponse.java @@ -0,0 +1,9 @@ +package calculator.dto; + +public record OutputResponse(int result) { + + public static OutputResponse of(int result) { + return new OutputResponse(result); + } +} + diff --git a/src/main/java/calculator/error/CustomException.java b/src/main/java/calculator/error/CustomException.java new file mode 100644 index 0000000000..bf72ab5512 --- /dev/null +++ b/src/main/java/calculator/error/CustomException.java @@ -0,0 +1,13 @@ +package calculator.error; + +import calculator.message.MessageProvider; + +public class CustomException extends IllegalArgumentException { + private CustomException(MessageProvider messageProvider) { + super(messageProvider.getMessage()); + } + + public static CustomException from(MessageProvider messageProvider) { + return new CustomException(messageProvider); + } +} diff --git a/src/main/java/calculator/message/ErrorMessage.java b/src/main/java/calculator/message/ErrorMessage.java new file mode 100644 index 0000000000..61431241b3 --- /dev/null +++ b/src/main/java/calculator/message/ErrorMessage.java @@ -0,0 +1,23 @@ +package calculator.message; + +public enum ErrorMessage implements MessageProvider{ + + EMPTY_INPUT("์ž…๋ ฅ๊ฐ’์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค."), + INVALID_INPUT("์œ ํšจํ•˜์ง€ ์•Š์€ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค."), + INVALID_WHITESPACE("๊ณต๋ฐฑ์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."), + INVALID_NUMBER_FORMAT("์ž˜๋ชป๋œ ์ˆซ์ž ํ˜•์‹์ž…๋‹ˆ๋‹ค."), + INVALID_CHARACTER("๊ตฌ๋ถ„์ž ์™ธ์˜ ๋ฌธ์ž๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค."), + NOT_ALLOWED_NEGATIVE_NUMBER("์Œ์ˆ˜๋Š” ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."), + INTEGER_OUT_OF_RANGE("ํ‘œํ˜„ ๊ฐ€๋Šฅ ๋ฒ”์œ„๋ฅผ ์ดˆ๊ณผํ•˜์˜€์Šต๋‹ˆ๋‹ค."), + MISSING_CUSTOM_DELIMITER_END("์ž˜๋ชป๋œ ์ปค์Šคํ…€ ๋ฌธ์ž์ž…๋‹ˆ๋‹ค."); + + private final String message; + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/calculator/message/InputMessage.java b/src/main/java/calculator/message/InputMessage.java new file mode 100644 index 0000000000..a8a9077731 --- /dev/null +++ b/src/main/java/calculator/message/InputMessage.java @@ -0,0 +1,19 @@ +package calculator.message; + + +public enum InputMessage implements MessageProvider { + + REQUEST_MESSAGE("๋ง์…ˆํ•  ๋ฌธ์ž์—ด์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”."); + + private final String message; + + InputMessage(String message) { + this.message = message; + } + + @Override + public String getMessage() { + return message; + } +} + diff --git a/src/main/java/calculator/message/MessageProvider.java b/src/main/java/calculator/message/MessageProvider.java new file mode 100644 index 0000000000..7a72a1bc55 --- /dev/null +++ b/src/main/java/calculator/message/MessageProvider.java @@ -0,0 +1,5 @@ +package calculator.message; + +public interface MessageProvider { + String getMessage(); +} diff --git a/src/main/java/calculator/message/OutputMessage.java b/src/main/java/calculator/message/OutputMessage.java new file mode 100644 index 0000000000..b4324222a4 --- /dev/null +++ b/src/main/java/calculator/message/OutputMessage.java @@ -0,0 +1,19 @@ +package calculator.message; + + +public enum OutputMessage implements MessageProvider { + + RESPONSE_MESSAGE("๊ฒฐ๊ณผ : "); + + private final String message; + + OutputMessage(String message) { + this.message = message; + } + + @Override + public String getMessage() { + return message; + } +} + diff --git a/src/main/java/calculator/model/Calculator.java b/src/main/java/calculator/model/Calculator.java new file mode 100644 index 0000000000..97dbf05e75 --- /dev/null +++ b/src/main/java/calculator/model/Calculator.java @@ -0,0 +1,7 @@ +package calculator.model; + +import java.util.List; + +public abstract class Calculator { + public abstract int calculate(List numbers); +} diff --git a/src/main/java/calculator/model/Operator.java b/src/main/java/calculator/model/Operator.java new file mode 100644 index 0000000000..81e980168f --- /dev/null +++ b/src/main/java/calculator/model/Operator.java @@ -0,0 +1,7 @@ +package calculator.model; + +import java.util.List; + +public interface Operator { + int operate(List numbers); +} diff --git a/src/main/java/calculator/model/PlusCalculator.java b/src/main/java/calculator/model/PlusCalculator.java new file mode 100644 index 0000000000..b1eafbb54e --- /dev/null +++ b/src/main/java/calculator/model/PlusCalculator.java @@ -0,0 +1,18 @@ +package calculator.model; + +import java.util.List; + +public class PlusCalculator extends Calculator { + + private final Operator operator; + + public PlusCalculator(Operator operator) { + this.operator = operator; + } + + @Override + public int calculate(List numbers) { + return operator.operate(numbers); + } +} + diff --git a/src/main/java/calculator/model/PlusOperator.java b/src/main/java/calculator/model/PlusOperator.java new file mode 100644 index 0000000000..a21d5a54cc --- /dev/null +++ b/src/main/java/calculator/model/PlusOperator.java @@ -0,0 +1,19 @@ +package calculator.model; + + +import calculator.error.CustomException; +import calculator.message.ErrorMessage; +import java.util.List; +import java.util.Optional; + +public class PlusOperator implements Operator { + + @Override + public int operate(List numbers) { + return Optional.of(numbers.stream() + .mapToInt(Integer::intValue) + .sum()) + .filter(sum -> sum >= 0) // ๋ง์…ˆ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜ + .orElseThrow(() -> CustomException.from(ErrorMessage.INTEGER_OUT_OF_RANGE)); + } +} diff --git a/src/main/java/calculator/parser/CalculatorParser.java b/src/main/java/calculator/parser/CalculatorParser.java new file mode 100644 index 0000000000..eeb07a6c3e --- /dev/null +++ b/src/main/java/calculator/parser/CalculatorParser.java @@ -0,0 +1,40 @@ +package calculator.parser; + +import calculator.dto.InputRequest; +import calculator.error.CustomException; +import calculator.message.ErrorMessage; +import calculator.validation.CalculateValidator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class CalculatorParser { + + public CalculatorParser() { + } + + public static List parseForDelimiters(InputRequest request) { + String delimiters = request.delimiters(); + String targetInput = request.targetInput(); + + return Optional.of(targetInput) + .filter(input -> !input.isEmpty()) + .map(input -> input.split(delimiters, -1)) + .stream() + .flatMap(Arrays::stream) + .map(CalculatorParser::validateInput) // ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ + .map(CalculateValidator::parseToInt) // ๋ฌธ์ž์—ด ์ •์ˆ˜ ๋ณ€ํ™˜ + .collect(Collectors.toCollection(ArrayList::new)); + } + + + private static String validateInput(String data) { + return Optional.of(data) + .filter(dataValue -> !dataValue.isEmpty()) + .orElseThrow(() -> CustomException.from(ErrorMessage.INVALID_INPUT)); + } +} + diff --git a/src/main/java/calculator/parser/DelimiterPattern.java b/src/main/java/calculator/parser/DelimiterPattern.java new file mode 100644 index 0000000000..f44afc47a0 --- /dev/null +++ b/src/main/java/calculator/parser/DelimiterPattern.java @@ -0,0 +1,16 @@ +package calculator.parser; + +public enum DelimiterPattern { + DEFAULT_DELIMITER(",|:"), + CUSTOM_DELIMITER_PREFIX("//"); + + private final String value; + + DelimiterPattern(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/calculator/parser/InputParser.java b/src/main/java/calculator/parser/InputParser.java new file mode 100644 index 0000000000..6e4aad4b5f --- /dev/null +++ b/src/main/java/calculator/parser/InputParser.java @@ -0,0 +1,41 @@ +package calculator.parser; + +import calculator.dto.InputRequest; +import calculator.error.CustomException; +import calculator.message.ErrorMessage; +import calculator.validation.InputValidator; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class InputParser { + + // ์ž…๋ ฅ ๋ฌธ์ž์—ด์—์„œ ๊ตฌ๋ถ„์ž์™€ ์ˆซ์ž ๋ถ„๋ฆฌ + public static InputRequest parseDelimiterAndInput(String input) { + InputValidator.checkForEmptyInput(input); + + return Optional.of(input) + .filter(s -> s.startsWith(DelimiterPattern.CUSTOM_DELIMITER_PREFIX.getValue())) + .map(InputParser::parseCustomDelimiter) + .orElseGet(() -> parseDefaultDelimiter(input)); + } + + // ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž ํŒŒ์‹ฑ ํ›„ ์ž…๋ ฅ ๊ฐ์ฒด ์ƒ์„ฑ + private static InputRequest parseCustomDelimiter(String input) { + Matcher matcher = InputValidator.validateCustomDelimiter(input); + String customDelimiter = matcher.group(1); + String targetInput = input.substring(matcher.end()); + + InputValidator.validateDelimiter(customDelimiter); + return InputRequest.of(DelimiterPattern.DEFAULT_DELIMITER.getValue() + "|" + Pattern.quote(customDelimiter), targetInput); + } + + // ๊ธฐ๋ณธ ๊ตฌ๋ถ„์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅ ๊ฐ์ฒด ์ƒ์„ฑ + private static InputRequest parseDefaultDelimiter(String input) { + return Optional.of(input) + .filter(s -> !s.isEmpty() && Character.isDigit(s.charAt(0))) + .map(s -> InputRequest.of(DelimiterPattern.DEFAULT_DELIMITER.getValue(), s)) + .orElseThrow(() -> CustomException.from(ErrorMessage.INVALID_INPUT)); + } +} diff --git a/src/main/java/calculator/service/CalculatorService.java b/src/main/java/calculator/service/CalculatorService.java new file mode 100644 index 0000000000..a7bf31562a --- /dev/null +++ b/src/main/java/calculator/service/CalculatorService.java @@ -0,0 +1,23 @@ +package calculator.service; + +import calculator.dto.InputRequest; +import calculator.dto.OutputResponse; +import calculator.model.PlusCalculator; +import calculator.model.PlusOperator; +import calculator.parser.CalculatorParser; + +import java.util.List; + +public class CalculatorService { + + private final PlusCalculator plusCalculator; + + public CalculatorService() { + this.plusCalculator = new PlusCalculator(new PlusOperator()); + } + + public OutputResponse calculate(InputRequest request) { + List numbers = CalculatorParser.parseForDelimiters(request); + return OutputResponse.of(plusCalculator.calculate(numbers)); + } +} diff --git a/src/main/java/calculator/validation/CalculateValidator.java b/src/main/java/calculator/validation/CalculateValidator.java new file mode 100644 index 0000000000..e953b28970 --- /dev/null +++ b/src/main/java/calculator/validation/CalculateValidator.java @@ -0,0 +1,34 @@ +package calculator.validation; + +import calculator.error.CustomException; +import calculator.message.ErrorMessage; + +import java.util.Optional; + +public class CalculateValidator { + private CalculateValidator() { + } + + // ๋ฌธ์ž์—ด ์ •์ˆ˜๋กœ ๋ณ€ํ™˜ํ•˜๊ณ , ์–‘์ˆ˜์ธ์ง€ ๊ฒ€์ฆ + public static int parseToInt(String number) { + return Optional.of(number) + .map(String::trim) + .map(CalculateValidator::validateAndParse) + .filter(CalculateValidator::isPositive) + .orElseThrow(() -> CustomException.from(ErrorMessage.NOT_ALLOWED_NEGATIVE_NUMBER)); // 0 ์ดํ•˜์ผ ๊ฒฝ์šฐ ์˜ˆ์™ธ ๋ฐœ์ƒ + } + + // ์ˆซ์ž ํ˜•์‹์ธ์ง€ ๊ฒ€์ฆํ•˜๊ณ  ์ •์ˆ˜๋กœ ๋ณ€ํ™˜ + private static int validateAndParse(String s) { + return Optional.of(s) + .filter(str -> ValidationPattern.INTEGER_PATTERN.getPattern().matcher(str).matches()) // ์ •๊ทœ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ์ˆซ์ž ํ˜•์‹ ๊ฒ€์ฆ + .map(Integer::parseInt) + .orElseThrow(() -> CustomException.from(ErrorMessage.INVALID_NUMBER_FORMAT)); // ๋ณ€ํ™˜ ์‹คํŒจ ์‹œ ์˜ˆ์™ธ ๋ฐœ์ƒ + } + + // ์–‘์ˆ˜์ธ์ง€ ๊ฒ€์ฆ + private static boolean isPositive(int num) { + return num > 0; + } +} + diff --git a/src/main/java/calculator/validation/InputValidator.java b/src/main/java/calculator/validation/InputValidator.java new file mode 100644 index 0000000000..502c40bbd6 --- /dev/null +++ b/src/main/java/calculator/validation/InputValidator.java @@ -0,0 +1,34 @@ +package calculator.validation; + +import calculator.error.CustomException; +import calculator.message.ErrorMessage; + +import java.util.Optional; +import java.util.regex.Matcher; + +public class InputValidator { + + // ์ž…๋ ฅ ๋ฌธ์ž์—ด์ด null์ด๊ฑฐ๋‚˜ ๋น„์–ด ์žˆ๋Š”์ง€ ํ™•์ธ + public static void checkForEmptyInput(String input) { + Optional.ofNullable(input) + .filter(s -> !s.isBlank()) + .orElseThrow(() -> CustomException.from(ErrorMessage.INVALID_WHITESPACE)); + } + + // ์‚ฌ์šฉ์ž ์ •์˜ ๊ตฌ๋ถ„์ž๊ฐ€ ํฌํ•จ๋œ ์ž…๋ ฅ์„ ๊ฒ€์ฆ + public static Matcher validateCustomDelimiter(String input) { + return Optional.of(input) + .map(ValidationPattern.CUSTOM_DELIMITER_PATTERN.getPattern()::matcher) + .filter(Matcher::find) + .orElseThrow(() -> CustomException.from(ErrorMessage.MISSING_CUSTOM_DELIMITER_END)); + } + + // ๊ตฌ๋ถ„์ž์— ์ˆซ์ž๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ + public static void validateDelimiter(String customDelimiter) { + Optional.of(customDelimiter) + .filter(delimiter -> ValidationPattern.INTEGER_PATTERN.getPattern().matcher(delimiter).find()) // ์ˆซ์ž๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ + .ifPresent(delimiter -> { + throw CustomException.from(ErrorMessage.MISSING_CUSTOM_DELIMITER_END); + }); + } +} diff --git a/src/main/java/calculator/validation/ValidationPattern.java b/src/main/java/calculator/validation/ValidationPattern.java new file mode 100644 index 0000000000..bd4ca039d3 --- /dev/null +++ b/src/main/java/calculator/validation/ValidationPattern.java @@ -0,0 +1,20 @@ +package calculator.validation; + +import java.util.regex.Pattern; + +public enum ValidationPattern { + CUSTOM_DELIMITER_PATTERN("^//(.{1,5})\\\\n"), + INTEGER_PATTERN("\\d+"); + + private final Pattern pattern; + + ValidationPattern(String regex) { + this.pattern = Pattern.compile(regex); + } + + public Pattern getPattern() { + return pattern; + } +} + + diff --git a/src/main/java/calculator/view/InputView.java b/src/main/java/calculator/view/InputView.java new file mode 100644 index 0000000000..3b0e9f5a74 --- /dev/null +++ b/src/main/java/calculator/view/InputView.java @@ -0,0 +1,15 @@ +package calculator.view; + +import calculator.dto.InputRequest; +import calculator.parser.InputParser; +import camp.nextstep.edu.missionutils.Console; + +import static calculator.message.InputMessage.REQUEST_MESSAGE; + +public class InputView { + + public static InputRequest getInputRequest() { + System.out.println(REQUEST_MESSAGE.getMessage()); + return InputParser.parseDelimiterAndInput(Console.readLine()); + } +} diff --git a/src/main/java/calculator/view/OutputView.java b/src/main/java/calculator/view/OutputView.java new file mode 100644 index 0000000000..70acaa6feb --- /dev/null +++ b/src/main/java/calculator/view/OutputView.java @@ -0,0 +1,12 @@ +package calculator.view; + +import calculator.dto.OutputResponse; + +import static calculator.message.OutputMessage.RESPONSE_MESSAGE; + +public class OutputView { + public static void outputMessage(OutputResponse response) { + System.out.print(RESPONSE_MESSAGE.getMessage() + response.result()); + } +} + diff --git a/src/test/java/calculator/ApplicationTest.java b/src/test/java/calculator/ApplicationTest.java index 93771fb011..9810f6a6c9 100644 --- a/src/test/java/calculator/ApplicationTest.java +++ b/src/test/java/calculator/ApplicationTest.java @@ -8,6 +8,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; class ApplicationTest extends NsTest { + @Test void ์ปค์Šคํ…€_๊ตฌ๋ถ„์ž_์‚ฌ์šฉ() { assertSimpleTest(() -> { @@ -16,11 +17,59 @@ class ApplicationTest extends NsTest { }); } + @Test + void ์ปค์Šคํ…€_๊ตฌ๋ถ„์ž_์‚ฌ์šฉ2() { + assertSimpleTest(() -> { + run("//x\\n1x2x3"); + assertThat(output()).contains("๊ฒฐ๊ณผ : 6"); + }); + } + @Test void ์˜ˆ์™ธ_ํ…Œ์ŠคํŠธ() { assertSimpleTest(() -> - assertThatThrownBy(() -> runException("-1,2,3")) - .isInstanceOf(IllegalArgumentException.class) + assertThatThrownBy(() -> runException("-1,2,3")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void ๊ธฐ๋ณธ_๊ตฌ๋ถ„์ž_ํ˜ผํ•ฉ_์‚ฌ์šฉ_ํ…Œ์ŠคํŠธ() { + assertSimpleTest(() -> { + run("1,2:3,4"); + assertThat(output()).contains("๊ฒฐ๊ณผ : 10"); + }); + } + + @Test + void ์ปค์Šคํ…€_๋ฌธ์ž์—ด_์‹œ์ž‘_์˜ค๋ฅ˜_์˜ˆ์™ธ_ํ…Œ์ŠคํŠธ() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("/;\\n1;2,3")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void ์ž˜๋ชป๋œ_์ž…๋ ฅ๊ฐ’_ํฌํ•จ์‹œ_์˜ˆ์™ธ_ํ…Œ์ŠคํŠธ() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("//;\\n1;a;3")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void ์ปค์Šคํ…€_๊ตฌ๋ถ„์ž_์ˆซ์ž์ž…๋ ฅ_์˜ˆ์™ธ_ํ…Œ์ŠคํŠธ() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("//3\\n132333")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void ๋‹ค์ค‘_์ปค์Šคํ…€_๊ตฌ๋ถ„์ž_์œ„์น˜_์ค‘๊ฐ„_์˜ˆ์™ธ_ํ…Œ์ŠคํŠธ() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("//x\\n2x//x\\n3x2x3")) + .isInstanceOf(IllegalArgumentException.class) ); }