Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
# java-calculator-precourse
# java-calculator-precourse

# 구현 기능 목록
1. 입력 처리
- camp.nextstep.edu.missionutils.Console.readLine()을 사용해 문자열 입력을 받는다.
- 입력 문구: "덧셈할 문자열을 입력해 주세요."

2. 빈 문자열 처리
- 입력값이 빈 문자열("")일 경우 결과는 0을 반환한다.

3. 기본 구분자 처리
- 쉼표(,) 또는 콜론(:)을 구분자로 처리한다.

4. 커스텀 구분자 처리
- "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 처리한다.

5. 예외처리
- 다음과 같은 경우 `IllegalArgumentException`을 발생시키고 프로그램은 종료된다.
- 음수 입력 (`-1,2,3`)
- 숫자가 아닌 입력 (`a,1,2`)
- 잘못된 구분자 형식 (`//;12;3`, `//\n1,2`)
- 빈 숫자 토큰이 존재하는 경우 (`1,,2`)
- 프로그램 종료 시 `System.exit()`을 호출하지 않는다.

6. 출력
- 계산된 결과를 "결과 : " 형식으로 출력한다.
12 changes: 11 additions & 1 deletion src/main/java/calculator/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
package calculator;

import calculator.controller.CalculatorController;
import calculator.service.StringCalculatorService;
import calculator.view.InputView;
import calculator.view.OutputView;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
InputView in = new InputView();
OutputView out = new OutputView();
StringCalculatorService svc = new StringCalculatorService();
CalculatorController controller = new CalculatorController(in, out, svc);

controller.run();
}
}
25 changes: 25 additions & 0 deletions src/main/java/calculator/controller/CalculatorController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package calculator.controller;

import calculator.service.StringCalculatorService;
import calculator.view.InputView;
import calculator.view.OutputView;

public class CalculatorController {
private final InputView inputView;
private final OutputView outputView;
private final StringCalculatorService service;

public CalculatorController(InputView inputView, OutputView outputView, StringCalculatorService service) {
this.inputView = inputView;
this.outputView = outputView;
this.service = service;
}

public void run() {
outputView.printPrompt();
String input = inputView.readLine();
input = input.replace("\\n", "\n");
int sum = service.add(input);
outputView.printResult(sum);
}
}
85 changes: 85 additions & 0 deletions src/main/java/calculator/service/StringCalculatorService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package calculator.service;

import java.util.regex.Pattern;

public class StringCalculatorService {
private static final String DEFAULT_DELIMS = "[,:]";

public int add(String input) {
if (isEmpty(input)) {
return 0;
}

Parsed parsed = parse(input);
String[] tokens = tokenize(parsed.numbers, parsed.delimRegex);
validate(tokens);
return sum(tokens);
}

private boolean isEmpty(String s) {
return s == null || s.isEmpty();
}

private Parsed parse(String input) {
if (!input.startsWith("//")) {
return new Parsed(DEFAULT_DELIMS, input);
}

int nl = input.indexOf('\n');
if (nl < 0) {
throw new IllegalArgumentException("잘못된 커스텀 구분자 형식입니다.");
}

String custom = input.substring(2, nl);
if (custom.isEmpty()) {
throw new IllegalArgumentException("구분자가 비어 있습니다.");
}

String delimRegex = Pattern.quote(custom);
String numbers = input.substring(nl + 1);
return new Parsed(delimRegex, numbers);
}

private String[] tokenize(String numbers, String delimRegex) {
if (numbers.isEmpty()) {
return new String[0];
}
if (DEFAULT_DELIMS.equals(delimRegex)) {
return numbers.split(DEFAULT_DELIMS, -1);
}
return numbers.split(delimRegex, -1);
}

private void validate(String[] tokens) {
for (String t : tokens) {
if (t == null || t.isEmpty()) {
throw new IllegalArgumentException("빈 숫자 토큰이 포함되어 있습니다.");
}
if (!t.chars().allMatch(Character::isDigit)) {
throw new IllegalArgumentException("숫자가 아닌 값이 포함되었습니다.");
}
int v = Integer.parseInt(t);
if (v < 0) {
throw new IllegalArgumentException("음수는 허용되지 않습니다.");
}
}
}

private int sum(String[] tokens) {
int total = 0;
for (String t : tokens) {
total += Integer.parseInt(t);
}
return total;
}

private static final class Parsed {
final String delimRegex;
final String numbers;

Parsed(String d, String n) {
this.delimRegex = d;
this.numbers = n;
}
}
}
9 changes: 9 additions & 0 deletions src/main/java/calculator/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package calculator.view;

import camp.nextstep.edu.missionutils.Console;

public class InputView {
public String readLine() {
return Console.readLine();
}
}
11 changes: 11 additions & 0 deletions src/main/java/calculator/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package calculator.view;

public class OutputView {
public void printPrompt() {
System.out.println("덧셈할 문자열을 입력해 주세요.");
}

public void printResult(int sum) {
System.out.println("결과 : " + sum);
}
}
41 changes: 37 additions & 4 deletions src/test/java/calculator/ApplicationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,57 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class ApplicationTest extends NsTest {

@Test
void 커스텀_구분자_사용() {
assertSimpleTest(() -> {
run("//;\\n1");
run("//;\\n1\n");
assertThat(output()).contains("결과 : 1");
});
}

@Test
void 예외_테스트() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("-1,2,3"))
.isInstanceOf(IllegalArgumentException.class)
assertThatThrownBy(() -> runException("-1,2,3\n"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void 기본_구분자_쉼표와_콜론_테스트() {
assertSimpleTest(() -> {
run("1,2:3\n");
assertThat(output()).contains("결과 : 6");
});
}

@Test
void 빈_문자열_입력시_0_반환() {
assertSimpleTest(() -> {
run("\n");
assertThat(output()).contains("결과 : 0");
});
}

@Test
void 잘못된_커스텀_형식_예외() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("//;12;3\n"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void 비어있는_커스텀_구분자_예외() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("//\n1,2\n"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Override
public void runMain() {
Application.main(new String[]{});
Application.main(new String[] {});
}
}