diff --git a/README.md b/README.md index bd90ef0247..8e5de1ae78 100644 --- a/README.md +++ b/README.md @@ -1 +1,58 @@ -# java-calculator-precourse \ No newline at end of file +# **문자열 덧셈 계산기** + +## **과제 진행 요구 사항** + +- 미션은 [문자열 덧셈 계산기](https://github.com/woowacourse-precourse/java-calculator-8) 저장소를 포크하고 클론하는 것으로 시작한다. +- **기능을 구현하기 전 `README.md`에 구현할 기능 목록을 정리**해 추가한다. +- Git의 커밋 단위는 앞 단계에서 `README.md`에 정리한 기능 목록 단위로 추가한다. + - [AngularJS Git Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153)을 참고해 커밋 메시지를 작성한다. +- 자세한 과제 진행 방법은 프리코스 진행 가이드 문서를 참고한다. + +## **기능 요구 사항** + +입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다. + +- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다. + - 예: "" => 0, "1,2" => 3, "1,2,3" => 6, "1,2:3" => 6 +- 앞의 기본 구분자(쉼표, 콜론) 외에 커스텀 구분자를 지정할 수 있다. 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다. + - 예를 들어 "//;\n1;2;3"과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다. +- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다. + +### **입출력 요구 사항** + +### **입력** + +- 구분자와 양수로 구성된 문자열 + +### **출력** + +- 덧셈 결과 + +``` +결과 : 6 + +``` + +### **실행 결과 예시** + +``` +덧셈할 문자열을 입력해 주세요. +1,2:3 +결과 : 6 + +``` + +## **프로그래밍 요구 사항** + +- JDK 21 버전에서 실행 가능해야 한다. +- 프로그램 실행의 시작점은 `Application`의 `main()`이다. +- `build.gradle` 파일은 변경할 수 없으며, **제공된 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.** +- 프로그램 종료 시 `System.exit()`를 호출하지 않는다. +- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다. +- 자바 코드 컨벤션을 지키면서 프로그래밍한다. + - 기본적으로 [Java Style Guide](https://github.com/woowacourse/woowacourse-docs/blob/main/styleguide/java)를 원칙으로 한다. + +### **라이브러리** + +- `camp.nextstep.edu.missionutils`에서 제공하는 `Console` API를 사용하여 구현해야 한다. + - 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다. \ No newline at end of file diff --git a/src/main/java/calculator/AppLogger.java b/src/main/java/calculator/AppLogger.java new file mode 100644 index 0000000000..cd753c1a3f --- /dev/null +++ b/src/main/java/calculator/AppLogger.java @@ -0,0 +1,25 @@ +package calculator; + +import java.util.logging.*; + +// 테스트에 적합하지 않아 사용하지 않음 +public class AppLogger { + public static final Logger log = Logger.getLogger(AppLogger.class.getName()); + + public static void formatLogger() { + Logger rootHandler = Logger.getLogger(""); + for(Handler handler: rootHandler.getHandlers()) { + rootHandler.removeHandler(handler); + } + ConsoleHandler consoleHandler = new ConsoleHandler(); + consoleHandler.setLevel(Level.ALL); + consoleHandler.setFormatter(new Formatter() { + @Override + public String format(LogRecord record) { + return record.getMessage() + "\n"; + } + }); + log.addHandler(consoleHandler); + log.setLevel(Level.ALL); + } +} diff --git a/src/main/java/calculator/Application.java b/src/main/java/calculator/Application.java index 573580fb40..a8abe3e79e 100644 --- a/src/main/java/calculator/Application.java +++ b/src/main/java/calculator/Application.java @@ -1,7 +1,89 @@ package calculator; +import camp.nextstep.edu.missionutils.Console; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Scanner; + +import static calculator.CalculatorFactory.BIG_DECIMAL; +import static calculator.CalculatorFactory.INTEGER; + public class Application { + public static final String DEFAULT = "default"; + public static void main(String[] args) { - // TODO: 프로그램 구현 + System.out.println("덧셈할 문자열을 입력해 주세요."); + String input = Console.readLine(); + // "" 값이 입력된 경우 + if (input.isEmpty()) { + System.out.println("결과 : 0"); + return; + } + + if (PatternMatcher.hasCustomDelimiter(input)) { + handleCustomDelimiter(input); + } else { + handleDefaultDelimiter(input); + } + } + + // custom + static void handleCustomDelimiter(String input) { + DelimiterParser delimiterParser = new DelimiterParser(); + String delimiter = delimiterParser.concatDelimiter(input); + // substring 문자열 자르기 + String concat = delimiterParser.concatInput(input, delimiter); + // 구분자 값이 "." + if (delimiter.equals(".")) { + calculateAndPrint(concat, delimiter, INTEGER); + return; + } + // 나머지 구분자 처리 + if (PatternMatcher.hasDecimalPoint(concat)) { + calculateAndPrint(concat, delimiter, BIG_DECIMAL); + } else { + calculateAndPrint(concat, delimiter, INTEGER); + } + } + + // default + private static void handleDefaultDelimiter(String input) { + // 실수 계산 + if (PatternMatcher.hasDecimalPoint(input)) { + calculateAndPrint(input, DEFAULT, BIG_DECIMAL); + } + // 정수 계산 + else { + calculateAndPrint(input, DEFAULT, INTEGER); + } + } + + // 계산 출력 + private static void calculateAndPrint(String input, String delimiter, String type) { + PatternMatcher.checkInvalidCharacter(input); + + Calculator calculator = CalculatorFactory.calculate(type); + NumberScanner scanner = new NumberScanner(new Scanner(input)); + // 기본 구분자 + if (DEFAULT.equals(delimiter)) { + scanner.setDelimiter(); + } + // custom 구분자 + else { + scanner.setDelimiter(delimiter); + } + // 실수 + if (BIG_DECIMAL.equals(type)) { + List list = scanner.parseList(BigDecimal::new); + BigDecimal result = ((BigDecimalCalculator) calculator).add(list); + System.out.println("결과 : " + result); + } + // 정수 + else { + List list = scanner.parseList(Integer::parseInt); + Integer result = ((IntegerCalculator) calculator).add(list); + System.out.println("결과 : " + result); + } } } diff --git a/src/main/java/calculator/BigDecimalCalculator.java b/src/main/java/calculator/BigDecimalCalculator.java new file mode 100644 index 0000000000..467da31613 --- /dev/null +++ b/src/main/java/calculator/BigDecimalCalculator.java @@ -0,0 +1,11 @@ +package calculator; + +import java.math.BigDecimal; +import java.util.List; + +public class BigDecimalCalculator implements Calculator { + @Override + public BigDecimal add(List numbers) { + return numbers.stream().reduce(BigDecimal.ZERO, BigDecimal::add); + } +} diff --git a/src/main/java/calculator/Calculator.java b/src/main/java/calculator/Calculator.java new file mode 100644 index 0000000000..6d43e5107b --- /dev/null +++ b/src/main/java/calculator/Calculator.java @@ -0,0 +1,7 @@ +package calculator; + +import java.util.List; + +public interface Calculator { + T add(List numbers); +} diff --git a/src/main/java/calculator/CalculatorFactory.java b/src/main/java/calculator/CalculatorFactory.java new file mode 100644 index 0000000000..0bb37390d6 --- /dev/null +++ b/src/main/java/calculator/CalculatorFactory.java @@ -0,0 +1,14 @@ +package calculator; + +public class CalculatorFactory { + public final static String INTEGER = "Integer"; + public final static String BIG_DECIMAL = "BigDecimal"; + + public static Calculator calculate(String type) { + return switch (type) { + case INTEGER -> new IntegerCalculator(); + case BIG_DECIMAL -> new BigDecimalCalculator(); + default -> throw new IllegalArgumentException("Invalid Type"); + }; + } +} diff --git a/src/main/java/calculator/DelimiterParser.java b/src/main/java/calculator/DelimiterParser.java new file mode 100644 index 0000000000..01a830f05b --- /dev/null +++ b/src/main/java/calculator/DelimiterParser.java @@ -0,0 +1,21 @@ +package calculator; + +public class DelimiterParser { + // custom 구분자 구하기 + public String concatDelimiter(String input) { + // 구분자 마지막 인덱스 + int end = input.indexOf("\\n") - 1; + // 구분자의 글자 수가 1 일때 + if (end == 3) { + char delimiter = input.charAt(2); + return Character.toString(delimiter); + } + return input.substring(2, end + 1); + } + + // custom 구분자 제외 문자열 구하기 + public String concatInput(String input, String delimiter) { + String replace = "//" + delimiter + "\\n"; + return input.replace(replace, ""); + } +} diff --git a/src/main/java/calculator/IntegerCalculator.java b/src/main/java/calculator/IntegerCalculator.java new file mode 100644 index 0000000000..c9f101ede3 --- /dev/null +++ b/src/main/java/calculator/IntegerCalculator.java @@ -0,0 +1,10 @@ +package calculator; + +import java.util.List; + +public class IntegerCalculator implements Calculator { + @Override + public Integer add(List numbers) { + return numbers.stream().reduce(0, Integer::sum); + } +} diff --git a/src/main/java/calculator/NumberScanner.java b/src/main/java/calculator/NumberScanner.java new file mode 100644 index 0000000000..70a1666401 --- /dev/null +++ b/src/main/java/calculator/NumberScanner.java @@ -0,0 +1,41 @@ +package calculator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.function.Function; + +public class NumberScanner { + private final Scanner scanner; + + NumberScanner(Scanner scanner) { + this.scanner = scanner; + } + + public void setDelimiter(String delimiter) { + // delimiter 변경(, 또는 : 또는 custom) + if (delimiter.equals(".")) { + this.scanner.useDelimiter("[,.:]"); + return; + } + this.scanner.useDelimiter(",|:|" + delimiter); + } + + public void setDelimiter() { + // delimiter 변경(, 또는 :) + this.scanner.useDelimiter("[,:]"); + } + + public List parseList(Function mapper) { + ArrayList list = new ArrayList<>(); + try { + while (this.scanner.hasNext()) { + String num = this.scanner.next(); + list.add(mapper.apply(num)); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid Input"); + } + return list; + } +} diff --git a/src/main/java/calculator/PatternMatcher.java b/src/main/java/calculator/PatternMatcher.java new file mode 100644 index 0000000000..dcad1baa13 --- /dev/null +++ b/src/main/java/calculator/PatternMatcher.java @@ -0,0 +1,28 @@ +package calculator; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PatternMatcher { + private PatternMatcher() { + } + + public static boolean hasCustomDelimiter(String input) { + return (input.contains("//") && input.contains("\\n")); + } + + public static boolean hasDecimalPoint(String input) { + String regex = "[0-9]*\\.[0-9]*"; + Matcher matcher = Pattern.compile(regex).matcher(input); + return matcher.find(); + } + + // ",", ":", 숫자 외에 다른 문자열이 있는가 + public static void checkInvalidCharacter(String input) { + String regex = "[^,:0-9]"; + Matcher matcher = Pattern.compile(regex).matcher(input); + if (matcher.find()) { + throw new IllegalArgumentException("Invalid Delimiter"); + } + } +} diff --git a/src/test/java/calculator/ApplicationTest.java b/src/test/java/calculator/ApplicationTest.java index 93771fb011..d95c798bbf 100644 --- a/src/test/java/calculator/ApplicationTest.java +++ b/src/test/java/calculator/ApplicationTest.java @@ -19,8 +19,8 @@ class ApplicationTest extends NsTest { @Test void 예외_테스트() { assertSimpleTest(() -> - assertThatThrownBy(() -> runException("-1,2,3")) - .isInstanceOf(IllegalArgumentException.class) + assertThatThrownBy(() -> runException("-1,2,3")) + .isInstanceOf(IllegalArgumentException.class) ); }