diff --git a/README.md b/README.md index bd90ef0247..e4a91dfab8 100644 --- a/README.md +++ b/README.md @@ -1 +1,21 @@ -# java-calculator-precourse \ No newline at end of file +# ๐Ÿฃ๋ฌธ์ž์—ด ๋ง์…ˆ ๊ณ„์‚ฐ๊ธฐ +- ์ž…๋ ฅํ•œ ๋ฌธ์ž์—ด์—์„œ ์ˆซ์ž๋ฅผ ์ถ”์ถœํ•˜์—ฌ ๋”ํ•˜๋Š” ๊ณ„์‚ฐ๊ธฐ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. +- ์‰ผํ‘œ(,) ๋˜๋Š” ์ฝœ๋ก (:)์„ ๊ตฌ๋ถ„์ž๋กœ ๊ฐ€์ง€๋Š” ๋ฌธ์ž์—ด์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒฝ์šฐ ๊ตฌ๋ถ„์ž๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ถ„๋ฆฌํ•œ ๊ฐ ์ˆซ์ž์˜ ํ•ฉ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. +- ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž๋Š” ๋ฌธ์ž์—ด ์•ž๋ถ€๋ถ„์˜ "//"์™€ "\n" ์‚ฌ์ด์— ์œ„์น˜ํ•˜๋Š” ๋ฌธ์ž๋ฅผ ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž๋กœ ์‚ฌ์šฉํ•œ๋‹ค. +- ์‚ฌ์šฉ์ž๊ฐ€ ์ž˜๋ชป๋œ ๊ฐ’์„ ์ž…๋ ฅํ•  ๊ฒฝ์šฐ IllegalArgumentException์„ ๋ฐœ์ƒ์‹œํ‚จ ํ›„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ข…๋ฃŒ๋˜์–ด์•ผ ํ•œ๋‹ค. + +## โœ… ๊ธฐ๋Šฅ ๋ชฉ๋ก +**์‚ฌ์šฉ์ž์—๊ฒŒ ๋ฌธ์ž์—ด ์ž…๋ ฅ** + - [X] ๊ฐ€์ด๋“œ ๋ฌธ๊ตฌ ์ถœ๋ ฅํ•œ๋‹ค. + - [X] `camp.nextstep.edu.missionutils.Console`์˜ `readLine()` ์‚ฌ์šฉ + +**๊ตฌ๋ถ„์ž๋กœ ์ˆซ์ž ์ถ”์ถœ ํ›„ ๊ณ„์‚ฐ** + - [X] ๊ธฐ๋ณธ ๊ตฌ๋ถ„์ž(์‰ผํ‘œ`,` , ์ฝœ๋ก `:`)์œผ๋กœ ๋ถ„๋ฆฌํ•œ๋‹ค. + - [X] "//"์™€ "\n" ์‚ฌ์ด์— ์œ„์น˜ํ•˜๋Š” ๋ฌธ์ž๋ฅผ ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž๋กœ ์‚ฌ์šฉํ•œ๋‹ค. + - [X] ๊ตฌ๋ถ„์ž๋ฅผ ์‚ฌ์šฉํ•ด ๋ฌธ์ž์—ด์„ ๋‚˜๋ˆˆ๋‹ค. + - [X] ๋‚˜๋ˆˆ ๋ฌธ์ž์—ด์„ ์ˆซ์ž๋กœ ๋ณ€ํ™˜ ํ›„ ํ•ฉ์„ ์ถœ๋ ฅํ•œ๋‹ค. + +**์ž…๋ ฅ ์˜ˆ์™ธ์ฒ˜๋ฆฌ (`IllegalArgumentException`)** + - [X] ์ž…๋ ฅํ•œ ์ˆซ์ž๊ฐ€ ์Œ์ˆ˜๋ฅผ ํฌํ•จํ•  ๋•Œ ์˜ˆ์™ธ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค. + - [X] ๊ตฌ๋ถ„์ž๊ฐ€ ๋‘ ๊ฐœ ์—ฐ์†์œผ๋กœ ๋‚˜์˜ค๋ฉด ์˜ˆ์™ธ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค. + - [X] ๊ตฌ๋ถ„์ž๊ฐ€ ์ˆซ์ž๋ฉด ์˜ˆ์™ธ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค. \ No newline at end of file diff --git a/src/main/java/calculator/Application.java b/src/main/java/calculator/Application.java index 573580fb40..15999cfa06 100644 --- a/src/main/java/calculator/Application.java +++ b/src/main/java/calculator/Application.java @@ -1,7 +1,9 @@ package calculator; +import controller.CalculatorController; + public class Application { public static void main(String[] args) { - // TODO: ํ”„๋กœ๊ทธ๋žจ ๊ตฌํ˜„ + new CalculatorController().calculate(); } -} +} \ No newline at end of file diff --git a/src/main/java/controller/CalculatorController.java b/src/main/java/controller/CalculatorController.java new file mode 100644 index 0000000000..e7a886aa66 --- /dev/null +++ b/src/main/java/controller/CalculatorController.java @@ -0,0 +1,34 @@ +package controller; + +import model.Calculator; +import view.InputView; +import view.OutputView; + +public class CalculatorController { + //์˜์กด์„ฑ ์ถ”๊ฐ€ + + + private final InputView inputView; + private final OutputView outputView; + + public CalculatorController() { + this.inputView = new InputView(); + this.outputView = new OutputView(); + } + + + /** + * ๋ฌธ์ž์—ด ๊ณ„์‚ฐ๊ธฐ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋ฉ”์„œ๋“œ + **/ + public void calculate() { + // ๋ฌธ์ž์—ด ์ž…๋ ฅ ๋ฐ›๊ธฐ + String input = inputView.input(); + + // Calculator๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅ ๋ฌธ์ž์—ด์˜ ํ•ฉ์„ ๊ณ„์‚ฐ + Calculator calculator = Calculator.from(input); // ์ˆ˜์ •๋œ ๋ถ€๋ถ„ + Long sum = calculator.sum(); + + // ๊ฒฐ๊ณผ ์ถœ๋ ฅ + outputView.output(sum.intValue()); + } +} diff --git a/src/main/java/exception/ErrorMessage.java b/src/main/java/exception/ErrorMessage.java new file mode 100644 index 0000000000..16f4101b54 --- /dev/null +++ b/src/main/java/exception/ErrorMessage.java @@ -0,0 +1,20 @@ +package exception; + +public enum ErrorMessage { + NEGATIVE_NUMBER_NOT_ALLOWED("์–‘์ˆ˜๋งŒ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."), + INVALID_NUMBERS_RANGE("์ •์ˆ˜์˜ ๋ฒ”์œ„๊ฐ€ ์ •์ƒ์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค."), + INPUT_MUST_BE_NUMERIC("์ˆซ์ž๋งŒ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."), + DELIMITER_LENGTH_EXCEEDS_ONE("๊ตฌ๋ถ„์ž์˜ ๊ธธ์ด๋Š” 1์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."), + INVALID_DELIMITER_TYPE("๋ฌธ์ž๊ฐ€ ์•„๋‹Œ ๊ตฌ๋ถ„์ž๊ฐ€ ์ž…๋ ฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."), + DUPLICATE_WITH_DEFAULT_DELIMITER("๊ธฐ๋ณธ ๊ตฌ๋ถ„์ž(์‰ผํ‘œ, ์ฝœ๋ก )์™€ ์ค‘๋ณต๋˜๋Š” ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž์ž…๋‹ˆ๋‹ค."); + + private final String message; + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return this.message; + } +} diff --git a/src/main/java/model/Calculator.java b/src/main/java/model/Calculator.java new file mode 100644 index 0000000000..cb57ab034f --- /dev/null +++ b/src/main/java/model/Calculator.java @@ -0,0 +1,19 @@ +package model; + +import java.util.ArrayList; + +public class Calculator { + private final Numbers numbers; + + private Calculator(String input) { + numbers = NumberSeparator.from(input).separate(); + } + + public static Calculator from(String input) { + return new Calculator(input); + } + + public Long sum() { + return numbers.sum(); + } +} diff --git a/src/main/java/model/Delimiter.java b/src/main/java/model/Delimiter.java new file mode 100644 index 0000000000..0273d26d25 --- /dev/null +++ b/src/main/java/model/Delimiter.java @@ -0,0 +1,60 @@ +package model; + +import exception.ErrorMessage; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +public class Delimiter { + private static final int DELIMITER_LENGTH = 1; + private static final String DEFAULT_DELIMITER = ",|:"; + private static final Pattern SINGLE_NUMERIC_PATTERN = Pattern.compile("\\d"); + + private final String input; + + private Delimiter(String input) { + validateLength(input); + validateDelimiterCharacter(input); + + this.input = input; + } + + // ๊ตฌ๋ถ„์ž์˜ ๊ธธ์ด๋ฅผ ํ™•์ธํ•˜๋Š” ํ•จ์ˆ˜ + private void validateLength(String input) { + if (input.isBlank() || input.length() > DELIMITER_LENGTH) { + throw new IllegalArgumentException(ErrorMessage.DELIMITER_LENGTH_EXCEEDS_ONE.getMessage()); + } + } + + private void validateDelimiterCharacter(String input) { + if (SINGLE_NUMERIC_PATTERN.matcher(input).matches()) { + throw new IllegalArgumentException(ErrorMessage.INVALID_DELIMITER_TYPE.getMessage()); + } + } + + /** + * ๊ธฐ๋ณธ ๊ตฌ๋ถ„์ž๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ (Delimiter ๊ฐ์ฒด ๋ฆฌ์ŠคํŠธ๋กœ) + **/ + public static List getDefaultDelimiter() { + List delimiters = new ArrayList<>(); + // ๊ธฐ๋ณธ ๊ตฌ๋ถ„์ž (",", ":")๋ฅผ Delimiter ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ + Arrays.asList(DEFAULT_DELIMITER.split("\\|")).forEach(delim -> delimiters.add(new Delimiter(delim))); + return delimiters; + } + + /** + * ๋ฌธ์ž์—ด์„ Delimiter ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์ •์  ๋ฉ”์„œ๋“œ + **/ + public static Delimiter from(String delimiter) { + return new Delimiter(delimiter); + } + + public static String toRegex(List delimiters) { + return delimiters.stream() + .map(delim -> Pattern.quote(delim.input)) // ๊ตฌ๋ถ„์ž ๊ฐ’์„ ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด quote ์ฒ˜๋ฆฌ + .reduce((d1, d2) -> d1 + "|" + d2) + .orElse(DEFAULT_DELIMITER); // ๊ธฐ๋ณธ ๊ตฌ๋ถ„์ž๋ฅผ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ + } +} diff --git a/src/main/java/model/NumberSeparator.java b/src/main/java/model/NumberSeparator.java new file mode 100644 index 0000000000..2744452f9e --- /dev/null +++ b/src/main/java/model/NumberSeparator.java @@ -0,0 +1,77 @@ +package model; + +import exception.ErrorMessage; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NumberSeparator { + private static final String NUMBER_PATTERN = "^-?[0-9]+$"; + private static final Pattern CUSTOM_DELIMITER_PATTERN = Pattern.compile("^//(.*?)\\\\n(.*?)$"); + private static final String DEFAULT_OPERATION = ",|:"; + private final List delimiters = new ArrayList<>(); + private String equation; + + /** + * ๊ตฌ๋ถ„์ž์— ๋”ฐ๋ผ ๋ฌธ์ž์—ด์„ ๋ถ„๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜ + **/ + private NumberSeparator(String input) { + delimiters.addAll(Delimiter.getDefaultDelimiter()); + + Matcher matcher = getMatcher(input); + if (hasCustomDelimiter(matcher)) { + delimiters.add(extractCustomDelimiter(matcher)); + equation = extractEquation(matcher); + return; + } + + equation = input; + } + + public static NumberSeparator from(String input) { + return new NumberSeparator(input); + } + + private void validateNotDefaultDelimiter(Delimiter delimiter) { + Delimiter.getDefaultDelimiter().stream() + .filter(defaultDelimiter -> defaultDelimiter.equals(delimiter)) + .findAny() + .ifPresent(matched -> { + throw new IllegalArgumentException( + ErrorMessage.DUPLICATE_WITH_DEFAULT_DELIMITER.getMessage()); + }); + } + + private Matcher getMatcher(String input) { + return CUSTOM_DELIMITER_PATTERN.matcher(input); + } + + private boolean hasCustomDelimiter(Matcher matcher) { + return matcher.find(); + } + + private Delimiter extractCustomDelimiter(Matcher matcher) { + Delimiter customDelimiter = Delimiter.from(matcher.group(1)); + validateNotDefaultDelimiter(customDelimiter); + return customDelimiter; + } + + private String extractEquation(Matcher matcher) { + return matcher.group(2).trim(); + } + + public Numbers separate() { + if (equation == null ||equation.isEmpty()) { + return Numbers.parseNumbers(new ArrayList<>()); + } + + return Numbers.parseNumbers( + Arrays.stream( + equation.split(Delimiter.toRegex(delimiters)) + ).map(String::trim).toList() + ); + } +} diff --git a/src/main/java/model/Numbers.java b/src/main/java/model/Numbers.java new file mode 100644 index 0000000000..95bc1549d2 --- /dev/null +++ b/src/main/java/model/Numbers.java @@ -0,0 +1,59 @@ +package model; + +import exception.ErrorMessage; + +import java.util.List; +import java.util.regex.Pattern; + +public class Numbers { + private final static long MIN = 1L; + private final static long SUM_BASE = 0L; + private static final Pattern NUMBER_PATTERN = Pattern.compile("^0|[1-9]+[0-9]*$"); + + private final List numbers; + + + public Numbers(List numbers) { + validatePositive(numbers); + this.numbers = numbers; + } + + public static Numbers parseNumbers(List stringNumbers) { + validateNumber(stringNumbers); + + try { + List numbers = stringNumbers.stream() + .map(Long::parseLong) + .toList(); + + return new Numbers(numbers); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ErrorMessage.INVALID_NUMBERS_RANGE.getMessage()); + } + } + + private void validatePositive(List numbers) { + numbers.stream() + .filter(number -> number < MIN) + .findAny() + .ifPresent(number -> { + throw new IllegalArgumentException(ErrorMessage.NEGATIVE_NUMBER_NOT_ALLOWED.getMessage()); + }); + } + + private static void validateNumber(List stringNumbers) { + // ๋ฆฌ์ŠคํŠธ์˜ ๋‚ด์šฉ ์ถœ๋ ฅ (๋””๋ฒ„๊น…์šฉ) + System.out.println("Checking numbers: " + stringNumbers); + + if (!stringNumbers.stream().allMatch( + stringNumber -> NUMBER_PATTERN.matcher(stringNumber).matches() + )) { + throw new IllegalArgumentException(ErrorMessage.INPUT_MUST_BE_NUMERIC.getMessage()); + } + } + + Long sum() { + return numbers.stream() + .reduce(SUM_BASE, Long::sum); + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 0000000000..bf5db8c930 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,14 @@ +package view; + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + + /** + * ๋ง์…ˆํ•  ๋ฌธ์ž์—ด ์ž…๋ ฅ๋ฐ›๋Š” ํ•จ์ˆ˜ + * **/ + public String input() { + System.out.println("๋ง์…ˆํ•  ๋ฌธ์ž์—ด์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. "); + return Console.readLine(); + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 0000000000..21bd73ab71 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,12 @@ +package view; + +public class OutputView { + + + /** + * ๊ฒฐ๊ณผ ์ถœ๋ ฅ ํ•จ์ˆ˜ + * **/ + public void output(int sum) { + System.out.printf("%s %d", "๊ฒฐ๊ณผ :", sum); + } +}