Skip to content
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apply plugin: 'eclipse'

group = 'camp.nextstep'
version = '1.0.0'
sourceCompatibility = '1.8'
sourceCompatibility = '11'

repositories {
mavenCentral()
Expand All @@ -16,4 +16,4 @@ dependencies {

test {
useJUnitPlatform()
}
}
16 changes: 16 additions & 0 deletions src/main/java/calculator/StringCalculatorApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package calculator;

import calculator.model.StringCalculator;
import calculator.model.StringParser;
import calculator.utils.StringExpression;
import calculator.view.InputView;
import calculator.view.OutputView;

public class StringCalculatorApplication {
public static void main(String[] args) {
String[] expressionTokens = new StringParser(new InputView().getInputString()).parse();
StringExpression stringExpression = new StringExpression(expressionTokens).validate();
int calculationResult = new StringCalculator(stringExpression).calculate();
new OutputView().printCalculationResult(calculationResult);
}
}
50 changes: 50 additions & 0 deletions src/main/java/calculator/model/Operator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package calculator.model;

import calculator.utils.StringException;
import java.util.Arrays;

public enum Operator {
PLUS("+") {
@Override
public int calculate(int operand1, int operand2) {
return operand1 + operand2;
}
},
MINUS("-") {
@Override
public int calculate(int operand1, int operand2) {
return operand1 - operand2;
}
},
DIVIDE("/") {
@Override
public int calculate(int operand1, int operand2) {
try {
return operand1 / operand2;
} catch (ArithmeticException e) {
throw new StringException(StringException.INVALID_DIVIDE_VALUE);
}
}
},
MULTIPLY("*") {
@Override
public int calculate(int operand1, int operand2) {
return operand1 * operand2;
}
};

private final String operator;

Operator(String operator) {
this.operator = operator;
}

public static Operator findOperator(String operator) {
return Arrays.stream(values())
.filter(value -> value.operator.equals(operator))
.findAny()
.orElseThrow(() -> new StringException(StringException.INVALID_OPERATOR));
}

public abstract int calculate(int operand1, int operand2);
}
22 changes: 22 additions & 0 deletions src/main/java/calculator/model/StringCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package calculator.model;

import calculator.utils.StringExpression;

public class StringCalculator {
private final StringExpression stringExpression;

public StringCalculator(StringExpression stringExpression) {
this.stringExpression = stringExpression;
}

public int calculate() {
int expressionLength = stringExpression.getExpressionLength();
int preNumber = stringExpression.getNumberByIndex(0);
for (int i = 1; i < expressionLength; i += 2) {
Operator operator = stringExpression.getOperatorByIndex(i);
int afterNumber = stringExpression.getNumberByIndex(i + 1);
preNumber = operator.calculate(preNumber, afterNumber);
}
return preNumber;
}
}
24 changes: 24 additions & 0 deletions src/main/java/calculator/model/StringParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package calculator.model;

import calculator.utils.StringException;

public class StringParser {
private static final String DELIMITER = " ";
private final String expression;

public StringParser(String inputString) {
this.expression = inputString;
}

public String[] parse() {
if (isNullOrBlank(expression)) {
throw new StringException(StringException.NULL_STRING_EXCEPTION);
}
return expression.split(DELIMITER);
}

private boolean isNullOrBlank(String expression) {
return expression == null || expression.isBlank();
}
}

14 changes: 14 additions & 0 deletions src/main/java/calculator/utils/StringException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package calculator.utils;

public class StringException extends RuntimeException {
public static final String NULL_STRING_EXCEPTION = "문자열이 null 또는 비어있습니다.";
public static final String INVALID_STRING_TOKEN_COUNT = "계산식의 토큰 개수가 올바르지 않습니다.";
public static final String INVALID_OPERATOR = "연산자가 올바르지 않습니다.";
public static final String INVALID_LOCATION = "문자의 위치가 올바르지 않습니다.";
public static final String INVALID_DIVIDE_VALUE = "나누는 수는 0이 될 수 없습니다.";
public static final String INDEX_OUT_OF_BOUND = "문자열 토큰의 인덱스를 벗어났습니다.";

public StringException(String message) {
super(message);
}
}
50 changes: 50 additions & 0 deletions src/main/java/calculator/utils/StringExpression.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package calculator.utils;

import calculator.model.Operator;

public class StringExpression {
private static final int MINIMUM_TOKEN_COUNT = 3;
private final String[] expressionTokens;

public StringExpression(String[] expressionTokens) {
this.expressionTokens = expressionTokens;
}

public StringExpression validate() {
validateExpressionTokenCount(expressionTokens);
return new StringExpression(expressionTokens);
}

public int getExpressionLength() {
return expressionTokens.length;
}

public int getNumberByIndex(int index) {
try {
return Integer.parseInt(expressionTokens[index]);
} catch (NumberFormatException e) {
throw new StringException(StringException.INVALID_LOCATION);
} catch (IndexOutOfBoundsException e) {
throw new StringException(StringException.INDEX_OUT_OF_BOUND);
}
}

public Operator getOperatorByIndex(int index) {
try {
return Operator.findOperator(expressionTokens[index]);
} catch (IndexOutOfBoundsException e) {
throw new StringException(StringException.INDEX_OUT_OF_BOUND);
}
}

private void validateExpressionTokenCount(String[] expressionTokens) {
int tokenCount = expressionTokens.length;
if (tokenCount < MINIMUM_TOKEN_COUNT || isEven(tokenCount)) {
throw new StringException(StringException.INVALID_STRING_TOKEN_COUNT);
}
}

private boolean isEven(int tokenCount) {
return tokenCount % 2 == 0;
}
}
16 changes: 16 additions & 0 deletions src/main/java/calculator/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package calculator.view;

import java.util.Scanner;

public class InputView {
private static final Scanner scanner = new Scanner(System.in);
private static final String INPUT_STRING_MESSAGE = "계산하고 싶은 수식을 입력하세요: ";

public InputView() {
}

public String getInputString() {
System.out.print(INPUT_STRING_MESSAGE);
return scanner.nextLine();
}
}
12 changes: 12 additions & 0 deletions src/main/java/calculator/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package calculator.view;

public class OutputView {
private static final String OUTPUT_STRING_MESSAGE = "계산결과 입니다: ";

public OutputView() {
}

public void printCalculationResult(int calculationResult) {
System.out.println(OUTPUT_STRING_MESSAGE + calculationResult);
}
}
77 changes: 77 additions & 0 deletions src/test/java/study/calculator/OperatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package study.calculator;

import calculator.model.Operator;
import calculator.utils.StringException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;

public class OperatorTest {
@DisplayName("덧셈 연산자 계산 테스트")
@Test
void plusCalculateTest() {
//given
int operand1 = 2, operand2 = 34;

//when
int result = Operator.PLUS.calculate(operand1, operand2);

//then
assertThat(result).isEqualTo(36);
}

@DisplayName("뺄셈 연산자 계산 테스트")
@Test
void minusCalculationTest() {
//given
int operand1 = 2, operand2 = 34;

//when
int result = Operator.MINUS.calculate(operand1, operand2);

//then
assertThat(result).isEqualTo(-32);
}

@DisplayName("곱셈 연산자 계산 테스트")
@Test
void multiplyCalculationTest() {
//given
int operand1 = 2, operand2 = 34;

//when
int result = Operator.MULTIPLY.calculate(operand1, operand2);

//then
assertThat(result).isEqualTo(68);
}

@DisplayName("나눗셈 연산자 계산 테스트")
@Test
void divideCalculationTest() {
//given
int operand1 = 100, operand2 = 5;

//when
int result = Operator.DIVIDE.calculate(operand1, operand2);

//then
assertThat(result).isEqualTo(20);
}

@DisplayName("나누는 숫자가 0일때 예외처리")
@Test
void invalidDivideCalculationTest() {
//given
int operand1 = 100, operand2 = 0;

//when
assertThatThrownBy(() ->
Operator.DIVIDE.calculate(operand1, operand2))
//then
.isInstanceOf(StringException.class)
.hasMessageContaining(StringException.INVALID_DIVIDE_VALUE);
}
}
70 changes: 70 additions & 0 deletions src/test/java/study/calculator/StringCalculatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package study.calculator;

import calculator.model.StringCalculator;
import calculator.utils.StringException;
import calculator.utils.StringExpression;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;

import java.util.stream.Stream;

public class StringCalculatorTest {
static Stream<Arguments> generateData() {
return Stream.of(
Arguments.of(new String[]{"2", "*", "4", "-", "45", "+", "3", "/", "7"}, -4),
Arguments.of(new String[]{"10", "*", "2", "-", "1", "+", "55", "/", "10"}, 7),
Arguments.of(new String[]{"5", "*", "7", "-", "10", "+", "346", "/", "5"}, 74)
);
}

@DisplayName("모든 4가지 연산자에 대해 계산 테스트")
@ParameterizedTest
@MethodSource("generateData")
void calculateAllOperatorTest(String[] tokens, int answer) {
//given
StringExpression stringExpression = new StringExpression(tokens);
StringCalculator stringCalculator = new StringCalculator(stringExpression);

//when
int result = stringCalculator.calculate();

//then
assertThat(result).isEqualTo(answer);
}

@DisplayName("문자열 토큰 숫자자리에 문자가 온 경우 예외처리")
@Test
void invalidTokenLocationTest() {
//given
String[] expressionTokens = {"2", "*", "+", "/", "8"};
StringExpression stringExpression = new StringExpression(expressionTokens);
StringCalculator stringCalculator = new StringCalculator(stringExpression);

//when
assertThatThrownBy(stringCalculator::calculate)
//then
.isInstanceOf(StringException.class)
.hasMessageContaining(StringException.INVALID_LOCATION);
}

@DisplayName("연산자 자리에 잘못된 연산자가 온 경우 예외처리")
@Test
void invalidTokenOperatorTest() {
//given
String[] expressionTokens = {"2", "*", "20", "$", "8"};
StringExpression stringExpression = new StringExpression(expressionTokens);
StringCalculator stringCalculator = new StringCalculator(stringExpression);

//when
assertThatThrownBy(stringCalculator::calculate)
//then
.isInstanceOf(StringException.class)
.hasMessageContaining(StringException.INVALID_OPERATOR);
}
}
Loading