Skip to content
Open
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
# java-calculator-precourse
# 🐣문자열 덧셈 계산기
- 입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다.
- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다.
- 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.

## ✅ 기능 목록
**사용자에게 문자열 입력**
- [X] 가이드 문구 출력한다.
- [X] `camp.nextstep.edu.missionutils.Console`의 `readLine()` 사용

**구분자로 숫자 추출 후 계산**
- [X] 기본 구분자(쉼표`,` , 콜론`:`)으로 분리한다.
- [X] "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다.
- [X] 구분자를 사용해 문자열을 나눈다.
- [X] 나눈 문자열을 숫자로 변환 후 합을 출력한다.

**입력 예외처리 (`IllegalArgumentException`)**
- [X] 입력한 숫자가 음수를 포함할 때 예외로 처리한다.
- [X] 구분자가 두 개 연속으로 나오면 예외로 처리한다.
- [X] 구분자가 숫자면 예외로 처리한다.
6 changes: 4 additions & 2 deletions src/main/java/calculator/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package calculator;
import controller.CalculatorController;


public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
new CalculatorController().calculate();
}
}
}
34 changes: 34 additions & 0 deletions src/main/java/controller/CalculatorController.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
20 changes: 20 additions & 0 deletions src/main/java/exception/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
19 changes: 19 additions & 0 deletions src/main/java/model/Calculator.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
60 changes: 60 additions & 0 deletions src/main/java/model/Delimiter.java
Original file line number Diff line number Diff line change
@@ -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<Delimiter> getDefaultDelimiter() {
List<Delimiter> 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<Delimiter> delimiters) {
return delimiters.stream()
.map(delim -> Pattern.quote(delim.input)) // 구분자 값을 안전하게 사용하기 위해 quote 처리
.reduce((d1, d2) -> d1 + "|" + d2)
.orElse(DEFAULT_DELIMITER); // 기본 구분자를 기본값으로 사용
}
}
77 changes: 77 additions & 0 deletions src/main/java/model/NumberSeparator.java
Original file line number Diff line number Diff line change
@@ -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<Delimiter> 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()
);
}
}
59 changes: 59 additions & 0 deletions src/main/java/model/Numbers.java
Original file line number Diff line number Diff line change
@@ -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<Long> numbers;


public Numbers(List<Long> numbers) {
validatePositive(numbers);
this.numbers = numbers;
}

public static Numbers parseNumbers(List<String> stringNumbers) {
validateNumber(stringNumbers);

try {
List<Long> 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<Long> numbers) {
numbers.stream()
.filter(number -> number < MIN)
.findAny()
.ifPresent(number -> {
throw new IllegalArgumentException(ErrorMessage.NEGATIVE_NUMBER_NOT_ALLOWED.getMessage());
});
}

private static void validateNumber(List<String> 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);
}
}
14 changes: 14 additions & 0 deletions src/main/java/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package view;

import camp.nextstep.edu.missionutils.Console;

public class InputView {

/**
* 덧셈할 문자열 입력받는 함수
* **/
public String input() {
System.out.println("덧셈할 문자열을 입력해 주세요. ");
return Console.readLine();
}
}
12 changes: 12 additions & 0 deletions src/main/java/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package view;

public class OutputView {


/**
* 결과 출력 함수
* **/
public void output(int sum) {
System.out.printf("%s %d", "결과 :", sum);
}
}