diff --git a/build.gradle b/build.gradle index 40aaf325a..0a7d2e2c5 100644 --- a/build.gradle +++ b/build.gradle @@ -5,13 +5,14 @@ plugins { group = 'co.programmers' version = '1.0-SNAPSHOT' -repositories { - mavenCentral() +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } } -dependencies { - testImplementation platform('org.junit:junit-bom:5.9.1') - testImplementation 'org.junit.jupiter:junit-jupiter' +repositories { + mavenCentral() } test { diff --git a/src/main/java/co/programmers/App.java b/src/main/java/co/programmers/App.java new file mode 100644 index 000000000..516cad909 --- /dev/null +++ b/src/main/java/co/programmers/App.java @@ -0,0 +1,20 @@ +package co.programmers; + +import co.programmers.domain.CalculatorApp; +import co.programmers.repository.CalculatorRepository; +import co.programmers.repository.Repository; +import co.programmers.view.CalculatorInputView; +import co.programmers.view.CalculatorOutputView; +import co.programmers.view.InputView; +import co.programmers.view.OutputView; + +public class App { + + public static void main(String[] args) { + InputView inputView = new CalculatorInputView(); + OutputView outputView = new CalculatorOutputView(); + Repository repository = new CalculatorRepository(); + CalculatorApp calculatorApp = new CalculatorApp(inputView, outputView, repository); + calculatorApp.run(); + } +} diff --git a/src/main/java/co/programmers/domain/Calculation.java b/src/main/java/co/programmers/domain/Calculation.java new file mode 100644 index 000000000..e1889f246 --- /dev/null +++ b/src/main/java/co/programmers/domain/Calculation.java @@ -0,0 +1,59 @@ +package co.programmers.domain; + +import java.util.ArrayList; +import java.util.List; + +public class Calculation { + + private List parsedExpression; + private List operators; + private Expression expression; + + public Calculation(Expression expression) { + this.expression = expression; + parsedExpression = new ArrayList<>(); + operators = new ArrayList<>(); + } + + public Double calculate() throws ArithmeticException { + parsedExpression = expression.split(); + operators = extractOperators(); + Operator.decideCalculationOrder(operators); + for (String operator : operators) { + int operatorPosition = parsedExpression.indexOf(operator); + Double[] operands = extractOperands(operator); + Double calculationRes = Operator.calculate(operator, operands[0], operands[1]); + storeIntermediateResult(operatorPosition, calculationRes); + removeCompletedExpression(operatorPosition, operands.length); + } + return calcFinalResult(); + } + + private void removeCompletedExpression(int operatorPosition, int count) { + for (int cnt = 0; cnt <= count; cnt++) { + parsedExpression.remove(operatorPosition); + } + } + + private void storeIntermediateResult(int operatorPosition, Double calculationRes) { + parsedExpression.add(operatorPosition - 1, String.valueOf(calculationRes)); + } + + private Double[] extractOperands(String operator) { + int operatorIdx = parsedExpression.indexOf(operator); + Double operand1 = Double.parseDouble(parsedExpression.get(operatorIdx - 1)); + Double operand2 = Double.parseDouble(parsedExpression.get(operatorIdx + 1)); + return new Double[] {operand1, operand2}; + } + + private List extractOperators() { + expression.eliminateWhiteSpace(); + List parsed = expression.split("\\d+"); + parsed.removeIf(String::isEmpty); + return parsed; + } + + private Double calcFinalResult() { + return Double.parseDouble(parsedExpression.get(0)); + } +} diff --git a/src/main/java/co/programmers/domain/CalculatorApp.java b/src/main/java/co/programmers/domain/CalculatorApp.java new file mode 100644 index 000000000..0df1b66a0 --- /dev/null +++ b/src/main/java/co/programmers/domain/CalculatorApp.java @@ -0,0 +1,62 @@ +package co.programmers.domain; + +import co.programmers.exception.ExceptionMessage; +import co.programmers.repository.Repository; +import co.programmers.view.CalculatorOutputView; +import co.programmers.view.InputView; +import co.programmers.view.OutputView; + +public class CalculatorApp { + + private final InputView inputView; + private final OutputView outputView; + private final Repository repository; + + public CalculatorApp(InputView inputView, OutputView outputView, Repository repository) { + this.inputView = inputView; + this.outputView = outputView; + this.repository = repository; + } + + public void run() { + UserMenu userMenu; + do { + CalculatorOutputView.printMenuChoiceGuide(); + userMenu = UserMenu.get(inputView.inputUserMenu()); + executeSelectedMenu(userMenu); + } while (userMenu != UserMenu.TERMINATE); + } + + private void executeSelectedMenu(UserMenu userMenu) { + switch (userMenu) { + case INQUIRY: + inquiry(); + break; + case CALCULATE: + calculate(); + break; + case TERMINATE: + break; + default: + outputView.printMessage(ExceptionMessage.INVALID_INPUT); + break; + } + } + + public void inquiry() { + outputView.printCalculationHistory(repository.read()); + } + + public void calculate() { + try { + CalculatorOutputView.printCalculationGuide(); + Expression expression = inputView.inputExpression(); + Calculation calculator = new Calculation(expression); + Double output = calculator.calculate(); + outputView.printCalculationResult(output); + repository.save(expression.getExpression(), output); + } catch (ArithmeticException arithmeticException) { + System.out.println(arithmeticException.getMessage()); + } + } +} diff --git a/src/main/java/co/programmers/domain/Expression.java b/src/main/java/co/programmers/domain/Expression.java new file mode 100644 index 000000000..96fbc3c42 --- /dev/null +++ b/src/main/java/co/programmers/domain/Expression.java @@ -0,0 +1,47 @@ +package co.programmers.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import co.programmers.exception.ExceptionMessage; + +public class Expression { + + private static final Pattern EXPRESSION_FORMAT = Pattern.compile("(\\d+\\s[\\+\\-\\*\\/]\\s)+\\d+"); + private static final String DELIMITER = " "; + private String expression; + + public Expression(String expression) { + this.expression = expression; + if (expression == null || expression.isEmpty()) { + throw new IllegalArgumentException(ExceptionMessage.EMPTY_INPUT); + } + if (!validate()) { + throw new ArithmeticException(ExceptionMessage.INVALID_EXPRESSION); + } + } + + private boolean validate() { + + Matcher matcher = EXPRESSION_FORMAT.matcher(expression); + return matcher.matches(); + } + + public List split() { + return new ArrayList<>(List.of(expression.split(DELIMITER))); + } + + public List split(String delimiter) { + return new ArrayList<>(List.of(expression.split(delimiter))); + } + + public void eliminateWhiteSpace() { + expression = expression.replaceAll("\\s", ""); + } + + public String getExpression() { + return expression; + } +} diff --git a/src/main/java/co/programmers/domain/Operator.java b/src/main/java/co/programmers/domain/Operator.java new file mode 100644 index 000000000..e26205020 --- /dev/null +++ b/src/main/java/co/programmers/domain/Operator.java @@ -0,0 +1,51 @@ +package co.programmers.domain; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.BiFunction; + +import co.programmers.exception.ExceptionMessage; + +public enum Operator { + ADDITION("+", 2, (operand1, operand2) -> operand1 + operand2), + SUBTRACTION("-", 2, (operand1, operand2) -> operand1 - operand2), + MULTIPLICATION("*", 1, (operand1, operand2) -> operand1 * operand2), + DIVISION("/", 1, (operand1, operand2) -> { + if (operand2 == 0) { + throw new ArithmeticException(ExceptionMessage.DIVIDED_BY_ZERO); + } + return operand1 / operand2; + }); + + private final String symbol; + private final int priority; + private final BiFunction operation; + + private Operator(String symbol, int priority, BiFunction operation) { + this.symbol = symbol; + this.priority = priority; + this.operation = operation; + } + + public static Double calculate(String operator, Double operand1, Double operand2) { + return getSymbol(operator).operation.apply(operand1, operand2); + } + + public static Operator getSymbol(Object operator) { + return Arrays.stream(values()) + .filter(o -> o.symbol.equals(operator)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ExceptionMessage.INVALID_INPUT)); + } + + public static void decideCalculationOrder(List operators) { + Collections.sort(operators, (operator1, operator2) -> + Integer.compare(getSymbol(operator1).getPriority(), getSymbol(operator2).getPriority()) + ); + } + + public int getPriority() { + return priority; + } +} diff --git a/src/main/java/co/programmers/domain/UserMenu.java b/src/main/java/co/programmers/domain/UserMenu.java new file mode 100644 index 000000000..93e719a3a --- /dev/null +++ b/src/main/java/co/programmers/domain/UserMenu.java @@ -0,0 +1,20 @@ +package co.programmers.domain; + +import java.util.Arrays; + +public enum UserMenu { + INQUIRY(1), CALCULATE(2), TERMINATE(3), ERROR(-9999); + + private final Integer menu; + + private UserMenu(Integer value) { + this.menu = value; + } + + public static UserMenu get(Integer input) { + return Arrays.stream(values()) + .filter(menuNum -> menuNum.menu.equals(input)) + .findFirst() + .orElse(ERROR); + } +} diff --git a/src/main/java/co/programmers/exception/ExceptionMessage.java b/src/main/java/co/programmers/exception/ExceptionMessage.java new file mode 100644 index 000000000..08cb0c899 --- /dev/null +++ b/src/main/java/co/programmers/exception/ExceptionMessage.java @@ -0,0 +1,12 @@ +package co.programmers.exception; + +public class ExceptionMessage { + + public static final String DIVIDED_BY_ZERO = "0으로 나눌 수 없습니다."; + public static final String INVALID_INPUT = "잘못된 입력입니다"; + public static final String INVALID_EXPRESSION = "잘못된 수식입니다"; + public static final String EMPTY_INPUT = "입력이 비어있습니다"; + + private ExceptionMessage() { + } +} diff --git a/src/main/java/co/programmers/repository/CalculatorRepository.java b/src/main/java/co/programmers/repository/CalculatorRepository.java new file mode 100644 index 000000000..2f6f880d6 --- /dev/null +++ b/src/main/java/co/programmers/repository/CalculatorRepository.java @@ -0,0 +1,18 @@ +package co.programmers.repository; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class CalculatorRepository implements Repository { + private static final Map storage = new LinkedHashMap<>(); + + @Override + public void save(String expression, Double result) { + storage.put(expression, result); + } + + @Override + public Map read() { + return storage; + } +} diff --git a/src/main/java/co/programmers/repository/Repository.java b/src/main/java/co/programmers/repository/Repository.java new file mode 100644 index 000000000..c9fbf9123 --- /dev/null +++ b/src/main/java/co/programmers/repository/Repository.java @@ -0,0 +1,9 @@ +package co.programmers.repository; + +import java.util.Map; + +public interface Repository { + void save(String expression, Double result); + + Map read(); +} diff --git a/src/main/java/co/programmers/view/CalculatorInputView.java b/src/main/java/co/programmers/view/CalculatorInputView.java new file mode 100644 index 000000000..8001d8d3f --- /dev/null +++ b/src/main/java/co/programmers/view/CalculatorInputView.java @@ -0,0 +1,22 @@ +package co.programmers.view; + +import java.util.Scanner; + +import co.programmers.domain.Expression; + +public class CalculatorInputView implements InputView { + + private static final Scanner SCANNER = new Scanner(System.in); + + @Override + public Integer inputUserMenu() { + Integer userInput = SCANNER.nextInt(); + SCANNER.nextLine(); + return userInput; + } + + @Override + public Expression inputExpression() throws ArithmeticException { + return new Expression(SCANNER.nextLine()); + } +} diff --git a/src/main/java/co/programmers/view/CalculatorOutputView.java b/src/main/java/co/programmers/view/CalculatorOutputView.java new file mode 100644 index 000000000..2e4f73a70 --- /dev/null +++ b/src/main/java/co/programmers/view/CalculatorOutputView.java @@ -0,0 +1,36 @@ +package co.programmers.view; + +import java.util.Map; + +public class CalculatorOutputView implements OutputView { + public static void printCalculationGuide() { + System.out.println("1 + 2 * 3와 같은 형식으로 계산하고자 하는 식을 입력하세요."); + System.out.print("> "); + } + + public static void printMenuChoiceGuide() { + System.out.println("\n[다음 중 원하시는 항목을 숫자로 입력하세요]"); + System.out.println("1. 조회"); + System.out.println("2. 계산"); + System.out.println("3. 종료"); + System.out.print("> 선택 : "); + } + + @Override + public void printCalculationResult(Double result) { + System.out.println(">> 결과 : " + result); + } + + @Override + public void printMessage(String message) { + System.out.println(message); + } + + @Override + public void printCalculationHistory(Map history) { + System.out.println(">> 계산 기록 조회"); + for (Map.Entry oneExpression : history.entrySet()) { + System.out.println(oneExpression.getKey() + " = " + oneExpression.getValue()); + } + } +} diff --git a/src/main/java/co/programmers/view/InputView.java b/src/main/java/co/programmers/view/InputView.java new file mode 100644 index 000000000..6bd3f37dd --- /dev/null +++ b/src/main/java/co/programmers/view/InputView.java @@ -0,0 +1,10 @@ +package co.programmers.view; + +import co.programmers.domain.Expression; + +public interface InputView { + + Integer inputUserMenu(); + + Expression inputExpression(); +} diff --git a/src/main/java/co/programmers/view/OutputView.java b/src/main/java/co/programmers/view/OutputView.java new file mode 100644 index 000000000..a216ddfff --- /dev/null +++ b/src/main/java/co/programmers/view/OutputView.java @@ -0,0 +1,12 @@ +package co.programmers.view; + +import java.util.Map; + +public interface OutputView { + + void printCalculationResult(Double result); + + void printMessage(String message); + + void printCalculationHistory(Map read); +}