diff --git a/README.md b/README.md index 9b4867cc0f..cc8c2e7511 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,138 @@ -# 볼링 게임 점수판 -## 진행 방법 -* 볼링 게임 점수판 요구사항을 파악한다. -* 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다. -* 코드 리뷰 피드백에 대한 개선 작업을 하고 다시 PUSH한다. -* 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다. - -## 온라인 코드 리뷰 과정 -* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview) \ No newline at end of file +``` +플레이어 이름은 (3 english letters)?: aaa +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | | | | | | | | | | | +1프레임 투구: 1 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1 | | | | | | | | | | +1프레임 투구: 2 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | | | | | | | | | | +2프레임 투구: 2 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2 | | | | | | | | | +2프레임 투구: 8 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2|/ | | | | | | | | | +3프레임 투구: 2 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2|/ | 2 | | | | | | | | +3프레임 투구: 3 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2|/ | 2|3 | | | | | | | | +4프레임 투구: 10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2|/ | 2|3 | X | | | | | | | +5프레임 투구: 10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2|/ | 2|3 | X | X | | | | | | +6프레임 투구: 10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2|/ | 2|3 | X | X | X | | | | | +7프레임 투구: 10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2|/ | 2|3 | X | X | X | X | | | | +8프레임 투구: 10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2|/ | 2|3 | X | X | X | X | X | | | +9프레임 투구: 10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2|/ | 2|3 | X | X | X | X | X | X | | +10프레임 투구: 10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2|/ | 2|3 | X | X | X | X | X | X | X | +10프레임 투구: 10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2|/ | 2|3 | X | X | X | X | X | X | X|X | +10프레임 투구: 10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|2 | 2|/ | 2|3 | X | X | X | X | X | X | X|X|X| +``` + +---- +``` +플레이어 이름은(3 english letters)?:aaa +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | | | | | | | | | | | +| | | | | | | | | | | | + +1프레임 투구 :1 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1 | | | | | | | | | | +| | | | | | | | | | | | + +1프레임 투구 :9 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | | | | | | | | | | +| | | | | | | | | | | | + +2프레임 투구 :10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | | | | | | | | | +| | 20 | | | | | | | | | | + +3프레임 투구 :10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | | | | | | | | +| | 20 | | | | | | | | | | + +4프레임 투구 :10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | x | | | | | | | +| | 20 | 50 | | | | | | | | | + +5프레임 투구 :2 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | x | 2 | | | | | | +| | 20 | 50 | 72 | | | | | | | | + +5프레임 투구 :8 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | x | 2|/ | | | | | | +| | 20 | 50 | 72 | 92 | | | | | | | + +6프레임 투구 :10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | x | 2|/ | x | | | | | +| | 20 | 50 | 72 | 92 | 112 | | | | | | + +7프레임 투구 :10 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | x | 2|/ | x | x | | | | +| | 20 | 50 | 72 | 92 | 112 | | | | | | + +8프레임 투구 :2 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | x | 2|/ | x | x | 2 | | | +| | 20 | 50 | 72 | 92 | 112 | 134 | | | | | + +8프레임 투구 :8 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | x | 2|/ | x | x | 2|/ | | | +| | 20 | 50 | 72 | 92 | 112 | 134 | 154 | | | | + +9프레임 투구 :1 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | x | 2|/ | x | x | 2|/ | 1 | | +| | 20 | 50 | 72 | 92 | 112 | 134 | 154 | 165 | | | + +9프레임 투구 :9 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | x | 2|/ | x | x | 2|/ | 1|/ | | +| | 20 | 50 | 72 | 92 | 112 | 134 | 154 | 165 | | | + +10프레임 투구 :2 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | x | 2|/ | x | x | 2|/ | 1|/ | 2 | +| | 20 | 50 | 72 | 92 | 112 | 134 | 154 | 165 | 177 | | + +10프레임 투구 :8 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | x | 2|/ | x | x | 2|/ | 1|/ | 2|8 | +| | 20 | 50 | 72 | 92 | 112 | 134 | 154 | 165 | 177 | | + +10프레임 투구 :2 +| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | +| aaa | 1|/ | x | x | x | 2|/ | x | x | 2|/ | 1|/ | 2|8|2| +| | 20 | 50 | 72 | 92 | 112 | 134 | 154 | 165 | 177 | 189 | +``` \ No newline at end of file diff --git a/build.gradle b/build.gradle index 81bb93e739..9d087b142e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,24 +1,22 @@ plugins { - id 'org.springframework.boot' version '2.6.4' - id 'io.spring.dependency-management' version '1.0.11.RELEASE' - id 'java' + id 'java' } -group = 'camp.nextstep' -version = '1.0.0' -sourceCompatibility = '11' - repositories { mavenCentral() + maven { url 'https://jitpack.io' } } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - runtimeOnly 'com.h2database:h2' - testImplementation 'org.assertj:assertj-core:3.22.0' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'com.github.woowacourse-projects:mission-utils:1.0.0' +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } } -tasks.named('test') { +test { useJUnitPlatform() } diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..d05fe12706 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,66 @@ +# 📌 핵심 기능: + +# 👩‍💻프로그램 동작 흐름 +- [x] 플레이어의 이름을 입력받는다. +- 프레임투구 입력, 출력을 9번 반복한다. (1~9 프레임까지) +- 10번 프레임투구를 입력받고 출력한다.. + + + +# 기능 구현 목록 +## 🖨️ 입출력 기능 + +### 입력 기능 + +- 플레이어 이름 입력받는 기능 +- ⚠️예외 검증: 플레이어 이름 입력 형식 + - [x] 공백이 아닌지 + - [x] 한글이 아닌지 + +- ⚠️예외 검증: 플레이어 이름 객체 + - [x] 3글자인지 + + +- 프레임투구 입력받는 기능 +- ⚠️예외 검증: 프레임투구 입력 형식 + - [ ] 공백이 아닌지 + - [ ] 숫자인지 + - [ ] integer 범위 내의 숫자인지 + +- ⚠️예외 검증: 프레임투구 객체 + - [ ] 0~10 범위의 숫자인지 + + +### 출력 기능 + +- [ ] 투구별 점수판을 출력하는 기능 + +## 🏃‍♀️ 프로그램 진행 기능 +- [ ] 플레이어 이름을 입력받아 생성한다. +- [ ] 점수판을 초기화 한다. +- [ ] + + +# ✅ 체크할 점 +-[ ] +-[ ] + +# 🛠 리팩토링 목록 +## 요구사항 +- [ ] indent (2) +- [ ] 메서드 길이 (15) +- [ ] 단위 테스트 모두 됐는지 확인 +## 그 외 +- [ ] 컨트롤러 메소드 네이밍 정리 및 분리 개선 +- [ ] 숫자 리터럴 상수화 +- [ ] 일급 컬렉션 필요 부분 확인 +- [ ] 원시값 포장 확인 +--- +- [ ] 부생성자가 있는 경우 생성자의 접근제어자가 private인지 +- [ ] 객체 생성이 불필요한 경우 private 생성자가 있는지 +- [ ] 각 클래스의 책임 범위 위반한 것 없는지 점검하기 +- [ ] 각 클래스 메소드 순서 점검하기 +- [ ] 이해를 위한 코드 전반의 공백 추가 +- [ ] 테스트 코드 리팩토링 +- [ ] getter 리스트 레퍼런스에 unmodifaible 추가 +---- \ No newline at end of file diff --git a/src/main/java/bowling/Application.java b/src/main/java/bowling/Application.java new file mode 100644 index 0000000000..62e3de1f9b --- /dev/null +++ b/src/main/java/bowling/Application.java @@ -0,0 +1,15 @@ +package bowling; + +import bowling.view.InputView; +import bowling.view.printer.ConsolePrinter; +import bowling.view.printer.Printer; +import bowling.view.reader.ConsoleReader; +import bowling.view.reader.Reader; + +public class Application { + public static void main(String[] args) { + Reader reader = new ConsoleReader(); + Printer printer = new ConsolePrinter(); + InputView inputView = InputView.of(reader, printer); + } +} diff --git a/src/main/java/bowling/common/Symbol.java b/src/main/java/bowling/common/Symbol.java new file mode 100644 index 0000000000..d087c4ae12 --- /dev/null +++ b/src/main/java/bowling/common/Symbol.java @@ -0,0 +1,10 @@ +package bowling.common; + +public class Symbol { + private Symbol() { + } + + public static final String HYPHEN = "-"; + + public static final String COMMA = ","; +} diff --git a/src/main/java/bowling/controller/BowlingController.java b/src/main/java/bowling/controller/BowlingController.java new file mode 100644 index 0000000000..596daf893d --- /dev/null +++ b/src/main/java/bowling/controller/BowlingController.java @@ -0,0 +1,70 @@ +package bowling.controller; + +import bowling.domain.Ball; +import bowling.domain.FrameBoard; +import bowling.domain.PlayerName; +import bowling.view.InputView; +import bowling.view.OutputView; +import java.util.function.Supplier; + +public class BowlingController { + private final InputView inputView; + private final OutputView outputView; + + public BowlingController(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public void run() { + PlayerName playerName = inputView.inputPlayerName(); + FrameBoard frameBoard = FrameBoard.init(playerName); + outputView.printBoard(frameBoard); + + playFrames(frameBoard); + } + + private void playFrames(FrameBoard frameBoard) { + int frameIndex = 0; + + while (!frameBoard.hasEmptyFrame()) { + playFrame(frameBoard, frameIndex); + frameIndex++; + } + + playBonusBall(frameBoard); + } + + private void playBonusBall(FrameBoard frameBoard) { + playFrame(frameBoard, 9); + if (frameBoard.needLastFrameBonus()) { + Ball bonusBall = inputView.inputBall(9); + frameBoard.applyBonusBall(bonusBall); + outputView.printBoard(frameBoard); + } + } + + private void playFrame(FrameBoard frameBoard, int frameIndex) { + frameIndex = frameBoard.getNextFrameIndes(); + Ball firstBall = inputView.inputBall(frameIndex); + frameBoard.applyFirstBallOf(frameIndex, firstBall); + outputView.printBoard(frameBoard); + + if (!firstBall.isStrike()) { + Ball secondBall = inputView.inputBall(frameIndex); + frameBoard.applySecondBallOf(frameIndex, secondBall); + outputView.printBoard(frameBoard); + } + + + } + + private T readWithRetry(Supplier supplier) { + try { + return supplier.get(); + } catch (IllegalArgumentException e) { + outputView.printExceptionMessage(e.getMessage()); + return readWithRetry(supplier); + } + } +} diff --git a/src/main/java/bowling/domain/Ball.java b/src/main/java/bowling/domain/Ball.java new file mode 100644 index 0000000000..4e8871ca25 --- /dev/null +++ b/src/main/java/bowling/domain/Ball.java @@ -0,0 +1,62 @@ +package bowling.domain; + +public class Ball { + private final int ball; + private BallStatus ballStatus; + + public Ball(int ball, BallStatus ballStatus) { + this.ball = ball; + this.ballStatus = ballStatus; + } + + public static Ball from(int ball) { + BallStatus ballStatus = BallStatus.from(ball); + return new Ball(ball, ballStatus); + } + + public static Ball emptySecondBall() { + return new Ball(0, BallStatus.NOTHING); + } + + public static Ball of(int ball, BallStatus ballStatus) { + return new Ball(ball, ballStatus); + } + + public static Ball init() { + return new Ball(0, BallStatus.NOTHING); + } + + public int getBall() { + return ball; + } + + public BallStatus getBallStatus() { + return ballStatus; + } + + public boolean isStrike() { + return ballStatus.isStrike(); + } + + public void applySpareCase(Ball firstBall) { + if (firstBall.isTenWith(ball)) { + ballStatus = BallStatus.SPARE; + } + } + + private boolean isTenWith(int ball) { + return this.ball + ball == 10; + } + + public boolean isSpare() { + return ballStatus == BallStatus.SPARE; + } + + public boolean isOpen() { + return ballStatus == BallStatus.OPEN; + } + + public boolean isEmpty() { + return ballStatus == BallStatus.NOTHING; + } +} diff --git a/src/main/java/bowling/domain/BallStatus.java b/src/main/java/bowling/domain/BallStatus.java new file mode 100644 index 0000000000..8df170a120 --- /dev/null +++ b/src/main/java/bowling/domain/BallStatus.java @@ -0,0 +1,29 @@ +package bowling.domain; + +public enum BallStatus { + STIKE("X"), GUTTER("-"), SPARE("/"), OPEN(""), NOTHING(" "); + + private final String mark; + + BallStatus(String mark) { + this.mark = mark; + } + + public static BallStatus from(int ball) { + if (ball == 10) { + return STIKE; + } + if (ball == 0) { + return GUTTER; + } + return OPEN; + } + + public boolean isStrike() { + return this == STIKE; + } + + public String getMark() { + return mark; + } +} diff --git a/src/main/java/bowling/domain/Frame.java b/src/main/java/bowling/domain/Frame.java new file mode 100644 index 0000000000..26f2d586b5 --- /dev/null +++ b/src/main/java/bowling/domain/Frame.java @@ -0,0 +1,52 @@ +package bowling.domain; + +public class Frame { + private Ball firstBall; + private Ball secondBall; + + public Frame(Ball firstBall, Ball secondBall) { + this.firstBall = firstBall; + this.secondBall = secondBall; + } + + public static Frame fromFirstBall(Ball firstBall) { + return new Frame(firstBall, Ball.emptySecondBall()); + } + + public static Frame of(Ball firstBall, Ball secondBall) { + return new Frame(firstBall, secondBall); + } + + public static Frame init() { + return new Frame(Ball.init(), Ball.init()); + } + + public void applyFirstBall(Ball ball) { + firstBall = ball; + } + + public void applySecondBall(Ball ball) { + secondBall = ball; + secondBall.applySpareCase(firstBall); + } + + public boolean isStrike() { + return firstBall.isStrike(); + } + + public boolean isSpare() { + return secondBall.isSpare(); + } + + public Ball getFirstBall() { + return firstBall; + } + + public Ball getSecondBall() { + return secondBall; + } + + public boolean isEmpty() { + return firstBall.isEmpty() && secondBall.isEmpty(); + } +} diff --git a/src/main/java/bowling/domain/FrameBoard.java b/src/main/java/bowling/domain/FrameBoard.java new file mode 100644 index 0000000000..d6feb86fa6 --- /dev/null +++ b/src/main/java/bowling/domain/FrameBoard.java @@ -0,0 +1,71 @@ +package bowling.domain; + +import java.util.ArrayList; +import java.util.List; + +public class FrameBoard { + private final PlayerName playerName; + private final List frames; + private Ball bonusBall; + + public FrameBoard(PlayerName playerName, List frames, Ball bonusBall) { + this.playerName = playerName; + this.frames = frames; + this.bonusBall = bonusBall; + } + + public static FrameBoard init(PlayerName playerName) { + List frames = initFrames(); + return new FrameBoard(playerName, frames, Ball.init()); + } + + private static List initFrames() { + List frames = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + frames.add(Frame.init()); + } + return frames; + } + + public PlayerName getPlayerName() { + return playerName; + } + + public List getFrames() { + return frames; + } + + public void applyFirstBallOf(int frameIndex, Ball firstBall) { + Frame frame = frames.get(frameIndex); + frame.applyFirstBall(firstBall); + } + + public void applySecondBallOf(int frameIndex, Ball secondBall) { + Frame frame = frames.get(frameIndex); + frame.applySecondBall(secondBall); + } + + public boolean needLastFrameBonus() { + Frame lastFrame = frames.get(frames.size() - 1); + return lastFrame.isStrike() || lastFrame.isSpare(); + } + + public void applyBonusBall(Ball bonusBall) { + this.bonusBall = bonusBall; + } + + public Ball getBonusBall() { + return bonusBall; + } + + public boolean hasEmptyFrame() { + return frames.stream().anyMatch(Frame::isEmpty); + } + + public int getNextFrameIndes() { + frames.stream() + .filter(Frame::isEmpty) + .findFirst() + .map(frame -> frame.) + } +} diff --git a/src/main/java/bowling/domain/PlayerName.java b/src/main/java/bowling/domain/PlayerName.java new file mode 100644 index 0000000000..8a27d9c9d5 --- /dev/null +++ b/src/main/java/bowling/domain/PlayerName.java @@ -0,0 +1,13 @@ +package bowling.domain; + +public class PlayerName { + private final String playerName; + + public PlayerName(String playerName) { + this.playerName = playerName; + } + + public String getPlayerName() { + return playerName; + } +} diff --git a/src/main/java/bowling/util/converter/Converter.java b/src/main/java/bowling/util/converter/Converter.java new file mode 100644 index 0000000000..41dfad5bec --- /dev/null +++ b/src/main/java/bowling/util/converter/Converter.java @@ -0,0 +1,21 @@ +package bowling.util.converter; + +import java.util.Arrays; +import java.util.List; + +public class Converter { + private Converter() { + } + + public static List splitToList(String separator, String value) { + return Arrays.asList(value.split(separator)); + } + + public static int convertToInt(String value) { + return Integer.parseInt(value); + } + + public static String splitValue(String separator, int index, String value) { + return splitToList(separator, value).get(index); + } +} diff --git a/src/main/java/bowling/util/validator/GeneralValidator.java b/src/main/java/bowling/util/validator/GeneralValidator.java new file mode 100644 index 0000000000..df4a67e871 --- /dev/null +++ b/src/main/java/bowling/util/validator/GeneralValidator.java @@ -0,0 +1,29 @@ +package bowling.util.validator; + +public class GeneralValidator { + private GeneralValidator() { + } + + public static void validateDuplicateSubstring(String substring, String value, String target) { + if (containsDuplicateSubstring(substring, value)) { + throw new IllegalArgumentException(String.format("%s에 구분자는 하나만 입력해주세요", target)); + } + } + + public static void validateStartSubstring(String substring, String value, String target) { + if (value.startsWith(substring)) { + throw new IllegalArgumentException(String.format("%s은(는) 구분자로 시작할 수 없습니다.", target)); + } + } + + public static void validateEndSubstring(String substring, String value, String target) { + if (value.endsWith(substring)) { + throw new IllegalArgumentException(String.format("%s은(는) 구분자로 끝날 수 없습니다.", target)); + } + } + + private static boolean containsDuplicateSubstring(String substring, String value) { + String doubleSubstring = substring.repeat(2); + return value.contains(doubleSubstring); + } +} diff --git a/src/main/java/bowling/util/validator/NumberValidator.java b/src/main/java/bowling/util/validator/NumberValidator.java new file mode 100644 index 0000000000..8650796894 --- /dev/null +++ b/src/main/java/bowling/util/validator/NumberValidator.java @@ -0,0 +1,16 @@ +package bowling.util.validator; + +public class NumberValidator { + private NumberValidator() { + } + + public static void validatePositiveNumber(int number, String target) { + if (!isPositiveNumber(number)) { + throw new IllegalArgumentException(String.format("%s은(는) 0보다 큰 수여야 합니다..", target)); + } + } + + private static boolean isPositiveNumber(int number) { + return number > 0; + } +} diff --git a/src/main/java/bowling/util/validator/StringValidator.java b/src/main/java/bowling/util/validator/StringValidator.java new file mode 100644 index 0000000000..0c0c984054 --- /dev/null +++ b/src/main/java/bowling/util/validator/StringValidator.java @@ -0,0 +1,38 @@ +package bowling.util.validator; + +import java.util.Objects; +import java.util.regex.Pattern; + +public class StringValidator { + private static final Pattern NUMBER_PATTERN = Pattern.compile("-?\\d+"); + + private StringValidator() { + } + + public static void validateBlank(String value, String message) { + if (Objects.isNull(value)) { + throw new IllegalArgumentException(message); + } + if (value.isBlank()) { + throw new IllegalArgumentException(message); + } + } + + public static void validateNumeric(String value, String message) { + if (!isNumber(value)) { + throw new IllegalArgumentException(message); + } + } + + public static void validateIntegerRange(String value, String message) { + try { + Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(message); + } + } + + public static boolean isNumber(String value) { + return NUMBER_PATTERN.matcher(value).matches(); + } +} diff --git a/src/main/java/bowling/util/validatorFixErrorMessage/GeneralValidator.java b/src/main/java/bowling/util/validatorFixErrorMessage/GeneralValidator.java new file mode 100644 index 0000000000..d3719332a8 --- /dev/null +++ b/src/main/java/bowling/util/validatorFixErrorMessage/GeneralValidator.java @@ -0,0 +1,30 @@ +package bowling.util.validatorFixErrorMessage; + +public class GeneralValidator { + private GeneralValidator() { + } + + public static void validateDuplicateSubstring(String substring, String value, String message) { + if (containsDuplicateSubstring(substring, value)) { + throw new IllegalArgumentException(message); + } + } + + public static void validateStartSubstring(String substring, String value, String message) { + if (value.startsWith(substring)) { + throw new IllegalArgumentException(message); + } + } + + public static void validateEndSubstring(String substring, String value, String message) { + if (value.endsWith(substring)) { + throw new IllegalArgumentException(message); + } + } + + private static boolean containsDuplicateSubstring(String substring, String value) { + String doubleSubstring = substring.repeat(2); + return value.contains(doubleSubstring); + } + +} diff --git a/src/main/java/bowling/util/validatorFixErrorMessage/NumberValidator.java b/src/main/java/bowling/util/validatorFixErrorMessage/NumberValidator.java new file mode 100644 index 0000000000..8f7f43c49d --- /dev/null +++ b/src/main/java/bowling/util/validatorFixErrorMessage/NumberValidator.java @@ -0,0 +1,16 @@ +package bowling.util.validatorFixErrorMessage; + +public class NumberValidator { + private NumberValidator() { + } + + public static void validatePositiveNumber(int number, String target) { + if (!isPositiveNumber(number)) { + throw new IllegalArgumentException(String.format("%s은(는) 0보다 큰 수여야 합니다..", target)); + } + } + + private static boolean isPositiveNumber(int number) { + return number > 0; + } +} diff --git a/src/main/java/bowling/util/validatorFixErrorMessage/StringValidator.java b/src/main/java/bowling/util/validatorFixErrorMessage/StringValidator.java new file mode 100644 index 0000000000..d769eb27fa --- /dev/null +++ b/src/main/java/bowling/util/validatorFixErrorMessage/StringValidator.java @@ -0,0 +1,38 @@ +package bowling.util.validatorFixErrorMessage; + +import java.util.Objects; +import java.util.regex.Pattern; + +public class StringValidator { + private static final Pattern NUMBER_PATTERN = Pattern.compile("-?\\d+"); + + private StringValidator() { + } + + public static void validateBlank(String value, String message) { + if (Objects.isNull(value)) { + throw new IllegalArgumentException(message); + } + if (value.isBlank()) { + throw new IllegalArgumentException(message); + } + } + + public static void validateNumeric(String value, String message) { + if (!isNumber(value)) { + throw new IllegalArgumentException(message); + } + } + + public static void validateIntegerRange(String value, String message) { + try { + Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(message); + } + } + + public static boolean isNumber(String value) { + return NUMBER_PATTERN.matcher(value).matches(); + } +} diff --git a/src/main/java/bowling/view/InputView.java b/src/main/java/bowling/view/InputView.java new file mode 100644 index 0000000000..39467f4047 --- /dev/null +++ b/src/main/java/bowling/view/InputView.java @@ -0,0 +1,38 @@ +package bowling.view; + +import bowling.domain.Ball; +import bowling.domain.PlayerName; +import bowling.util.converter.Converter; +import bowling.view.printer.Printer; +import bowling.view.reader.Reader; +import bowling.view.validator.InputValidator; + +public class InputView { + private final Reader reader; + private final Printer printer; + private final InputValidator validator; + + private InputView(Reader reader, Printer printer, InputValidator validator) { + this.reader = reader; + this.printer = printer; + this.validator = validator; + } + + public static InputView of(Reader reader, Printer printer) { + return new InputView(reader, printer, InputValidator.getInstance()); + } + + public PlayerName inputPlayerName() { + printer.printLine("플레이어 이름은 (3 english letters)?:"); + String playerName = reader.readLine(); + validator.validatePlayerName(playerName, "플레이어 이름"); + return new PlayerName(playerName); + } + + public Ball inputBall(int frameIndex) { + printer.printLine(frameIndex + 1 + "프레임 투구: "); + String ball = reader.readLine(); + validator.validateBall(ball, "투구"); + return Ball.from(Converter.convertToInt(ball)); + } +} diff --git a/src/main/java/bowling/view/OutputView.java b/src/main/java/bowling/view/OutputView.java new file mode 100644 index 0000000000..7d1b7440d4 --- /dev/null +++ b/src/main/java/bowling/view/OutputView.java @@ -0,0 +1,28 @@ +package bowling.view; + +import bowling.domain.FrameBoard; +import bowling.view.formatter.OutputFomatter; +import bowling.view.printer.Printer; + +public class OutputView { + private static final String ERROR_MESSAGE_FORMAT = "[ERROR] "; + private final Printer printer; + private final OutputFomatter formatter; + + public OutputView(Printer printer, OutputFomatter formatter) { + this.printer = printer; + this.formatter = formatter; + } + + public void printExceptionMessage(String message) { + printer.printLine(ERROR_MESSAGE_FORMAT + message); + printer.printEmptyLine(); + } + + public void printBoard(FrameBoard frameBoard) { + printer.printLine("| NAME | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |"); + String name = formatter.toName(frameBoard); + String board = formatter.toBoard(frameBoard); + printer.printLine("| %s | %s|", name, board); + } +} diff --git a/src/main/java/bowling/view/formatter/OutputFomatter.java b/src/main/java/bowling/view/formatter/OutputFomatter.java new file mode 100644 index 0000000000..c27e4dcf6b --- /dev/null +++ b/src/main/java/bowling/view/formatter/OutputFomatter.java @@ -0,0 +1,50 @@ +package bowling.view.formatter; + +import bowling.domain.Ball; +import bowling.domain.Frame; +import bowling.domain.FrameBoard; +import java.util.List; +import java.util.stream.Collectors; + +public class OutputFomatter { + public String toName(FrameBoard frameBoard) { + return frameBoard.getPlayerName().getPlayerName(); + } + + public String toBoard(FrameBoard frameBoard) { + List frames = frameBoard.getFrames().stream() + .map(this::toFrame) + .collect(Collectors.toList()); + String board = String.join(" | ", frames); + if (frameBoard.needLastFrameBonus()) { + String bonusBall = getBallMark(frameBoard.getBonusBall()); + return board + "|" + bonusBall; + } + return board + " "; + } + + public String toFrame(Frame rawFrame) { + Ball firstBall = rawFrame.getFirstBall(); + + String firstBallMark = getBallMark(firstBall); + String secondBallMark = getBallMark(rawFrame.getSecondBall()); + + if (firstBall.isStrike()) { + return firstBallMark + " "; + } + if (rawFrame.isEmpty()) { + return firstBallMark + " " + secondBallMark; + } + return firstBallMark + "|" + secondBallMark; + } + + private String getBallMark(Ball firstBall) { + if (firstBall.isOpen()) { + return String.valueOf(firstBall.getBall()); + } + if (!firstBall.isOpen()) { + return firstBall.getBallStatus().getMark(); + } + return ""; + } +} diff --git a/src/main/java/bowling/view/printer/ConsolePrinter.java b/src/main/java/bowling/view/printer/ConsolePrinter.java new file mode 100644 index 0000000000..58d8e6b311 --- /dev/null +++ b/src/main/java/bowling/view/printer/ConsolePrinter.java @@ -0,0 +1,19 @@ +package bowling.view.printer; + +public class ConsolePrinter implements Printer { + @Override + public void printLine(String message) { + System.out.println(message); + } + + @Override + public void printLine(String format, Object... args) { + System.out.printf(format, args); + printEmptyLine(); + } + + @Override + public void printEmptyLine() { + System.out.println(); + } +} diff --git a/src/main/java/bowling/view/printer/Printer.java b/src/main/java/bowling/view/printer/Printer.java new file mode 100644 index 0000000000..e8c2f8a30f --- /dev/null +++ b/src/main/java/bowling/view/printer/Printer.java @@ -0,0 +1,9 @@ +package bowling.view.printer; + +public interface Printer { + void printLine(String message); + + void printLine(String format, Object... args); + + void printEmptyLine(); +} diff --git a/src/main/java/bowling/view/reader/ConsoleReader.java b/src/main/java/bowling/view/reader/ConsoleReader.java new file mode 100644 index 0000000000..c6eed37896 --- /dev/null +++ b/src/main/java/bowling/view/reader/ConsoleReader.java @@ -0,0 +1,10 @@ +package bowling.view.reader; + +import camp.nextstep.edu.missionutils.Console; + +public class ConsoleReader implements Reader { + @Override + public String readLine() { + return Console.readLine().trim(); + } +} diff --git a/src/main/java/bowling/view/reader/Reader.java b/src/main/java/bowling/view/reader/Reader.java new file mode 100644 index 0000000000..eb974b1d01 --- /dev/null +++ b/src/main/java/bowling/view/reader/Reader.java @@ -0,0 +1,5 @@ +package bowling.view.reader; + +public interface Reader { + String readLine(); +} diff --git a/src/main/java/bowling/view/validator/InputValidator.java b/src/main/java/bowling/view/validator/InputValidator.java new file mode 100644 index 0000000000..e4009f9dc6 --- /dev/null +++ b/src/main/java/bowling/view/validator/InputValidator.java @@ -0,0 +1,28 @@ +package bowling.view.validator; + +import bowling.util.validator.StringValidator; + +public class InputValidator { + private static InputValidator inputValidator; + + private InputValidator() { + } + + public static InputValidator getInstance() { + if (inputValidator == null) { + return new InputValidator(); + } + return inputValidator; + } + + public void validatePlayerName(String playerName, String target) { + StringValidator.validateBlank(playerName, target); + validateKorean(playerName, target); + } + + private void validateKorean(String playerName, String target) { + } + + public void validateBall(String ball, String target) { + } +} diff --git a/src/main/java/qna/CannotDeleteException.java b/src/main/java/qna/CannotDeleteException.java deleted file mode 100644 index 12ea9bc148..0000000000 --- a/src/main/java/qna/CannotDeleteException.java +++ /dev/null @@ -1,9 +0,0 @@ -package qna; - -public class CannotDeleteException extends Exception { - private static final long serialVersionUID = 1L; - - public CannotDeleteException(String message) { - super(message); - } -} \ No newline at end of file diff --git a/src/main/java/qna/ForbiddenException.java b/src/main/java/qna/ForbiddenException.java deleted file mode 100644 index e9540d5e8e..0000000000 --- a/src/main/java/qna/ForbiddenException.java +++ /dev/null @@ -1,10 +0,0 @@ -package qna; - -public class ForbiddenException extends RuntimeException{ - public ForbiddenException() { - } - - public ForbiddenException(String message) { - super(message); - } -} diff --git a/src/main/java/qna/NotFoundException.java b/src/main/java/qna/NotFoundException.java deleted file mode 100644 index 5fd0ff0d5c..0000000000 --- a/src/main/java/qna/NotFoundException.java +++ /dev/null @@ -1,4 +0,0 @@ -package qna; - -public class NotFoundException extends RuntimeException { -} diff --git a/src/main/java/qna/UnAuthenticationException.java b/src/main/java/qna/UnAuthenticationException.java deleted file mode 100644 index a75ff499c7..0000000000 --- a/src/main/java/qna/UnAuthenticationException.java +++ /dev/null @@ -1,26 +0,0 @@ -package qna; - -public class UnAuthenticationException extends Exception { - private static final long serialVersionUID = 1L; - - public UnAuthenticationException() { - super(); - } - - public UnAuthenticationException(String message, Throwable cause, boolean enableSuppression, - boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } - - public UnAuthenticationException(String message, Throwable cause) { - super(message, cause); - } - - public UnAuthenticationException(String message) { - super(message); - } - - public UnAuthenticationException(Throwable cause) { - super(cause); - } -} diff --git a/src/main/java/qna/UnAuthorizedException.java b/src/main/java/qna/UnAuthorizedException.java deleted file mode 100644 index 5acd14ee9e..0000000000 --- a/src/main/java/qna/UnAuthorizedException.java +++ /dev/null @@ -1,26 +0,0 @@ -package qna; - -public class UnAuthorizedException extends RuntimeException { - private static final long serialVersionUID = 1L; - - public UnAuthorizedException() { - super(); - } - - public UnAuthorizedException(String message, Throwable cause, boolean enableSuppression, - boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } - - public UnAuthorizedException(String message, Throwable cause) { - super(message, cause); - } - - public UnAuthorizedException(String message) { - super(message); - } - - public UnAuthorizedException(Throwable cause) { - super(cause); - } -} diff --git a/src/main/java/qna/domain/AbstractEntity.java b/src/main/java/qna/domain/AbstractEntity.java deleted file mode 100644 index 117ce7c45a..0000000000 --- a/src/main/java/qna/domain/AbstractEntity.java +++ /dev/null @@ -1,70 +0,0 @@ -package qna.domain; - -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import javax.persistence.*; -import java.time.LocalDateTime; - -@MappedSuperclass -@EntityListeners(AuditingEntityListener.class) -public class AbstractEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false, updatable = false) - @CreatedDate - private LocalDateTime createdAt; - - @LastModifiedDate - private LocalDateTime updatedAt; - - public AbstractEntity() { - } - - public AbstractEntity(Long id) { - this.id = id; - } - - public Long getId() { - return id; - } - - public AbstractEntity setId(Long id) { - this.id = id; - return this; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (int) (id ^ (id >>> 32)); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - AbstractEntity other = (AbstractEntity) obj; - if (id != other.id) - return false; - return true; - } - - @Override - public String toString() { - return "AbstractEntity{" + - "id=" + id + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + - '}'; - } -} diff --git a/src/main/java/qna/domain/Answer.java b/src/main/java/qna/domain/Answer.java deleted file mode 100644 index 548b71ed71..0000000000 --- a/src/main/java/qna/domain/Answer.java +++ /dev/null @@ -1,75 +0,0 @@ -package qna.domain; - -import qna.NotFoundException; -import qna.UnAuthorizedException; - -import javax.persistence.*; - -@Entity -public class Answer extends AbstractEntity { - @ManyToOne(optional = false) - @JoinColumn(foreignKey = @ForeignKey(name = "fk_answer_writer")) - private User writer; - - @ManyToOne(optional = false) - @JoinColumn(foreignKey = @ForeignKey(name = "fk_answer_to_question")) - private Question question; - - @Lob - private String contents; - - private boolean deleted = false; - - public Answer() { - } - - public Answer(User writer, Question question, String contents) { - this(null, writer, question, contents); - } - - public Answer(Long id, User writer, Question question, String contents) { - super(id); - - if(writer == null) { - throw new UnAuthorizedException(); - } - - if(question == null) { - throw new NotFoundException(); - } - - this.writer = writer; - this.question = question; - this.contents = contents; - } - - public Answer setDeleted(boolean deleted) { - this.deleted = deleted; - return this; - } - - public boolean isDeleted() { - return deleted; - } - - public boolean isOwner(User writer) { - return this.writer.equals(writer); - } - - public User getWriter() { - return writer; - } - - public String getContents() { - return contents; - } - - public void toQuestion(Question question) { - this.question = question; - } - - @Override - public String toString() { - return "Answer [id=" + getId() + ", writer=" + writer + ", contents=" + contents + "]"; - } -} diff --git a/src/main/java/qna/domain/AnswerRepository.java b/src/main/java/qna/domain/AnswerRepository.java deleted file mode 100644 index 872e1cd9bb..0000000000 --- a/src/main/java/qna/domain/AnswerRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package qna.domain; - -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; -import java.util.Optional; - -public interface AnswerRepository extends JpaRepository { - List findByQuestionAndDeletedFalse(Question question); - - Optional findByIdAndDeletedFalse(Long id); -} diff --git a/src/main/java/qna/domain/ContentType.java b/src/main/java/qna/domain/ContentType.java deleted file mode 100644 index 55bc6a28b0..0000000000 --- a/src/main/java/qna/domain/ContentType.java +++ /dev/null @@ -1,5 +0,0 @@ -package qna.domain; - -public enum ContentType { - QUESTION, ANSWER; -} diff --git a/src/main/java/qna/domain/DeleteHistory.java b/src/main/java/qna/domain/DeleteHistory.java deleted file mode 100644 index db03c60f8b..0000000000 --- a/src/main/java/qna/domain/DeleteHistory.java +++ /dev/null @@ -1,55 +0,0 @@ -package qna.domain; - -import javax.persistence.*; -import java.time.LocalDateTime; -import java.util.Objects; - -@Entity -public class DeleteHistory { - @Id - @GeneratedValue - private Long id; - - @Enumerated(EnumType.STRING) - private ContentType contentType; - - private Long contentId; - - @ManyToOne - @JoinColumn(foreignKey = @ForeignKey(name = "fk_deletehistory_to_user")) - private User deletedBy; - - private LocalDateTime createDate = LocalDateTime.now(); - - public DeleteHistory() { - } - - public DeleteHistory(ContentType contentType, Long contentId, User deletedBy, LocalDateTime createDate) { - this.contentType = contentType; - this.contentId = contentId; - this.deletedBy = deletedBy; - this.createDate = createDate; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - DeleteHistory that = (DeleteHistory) o; - return Objects.equals(id, that.id) && - contentType == that.contentType && - Objects.equals(contentId, that.contentId) && - Objects.equals(deletedBy, that.deletedBy); - } - - @Override - public int hashCode() { - return Objects.hash(id, contentType, contentId, deletedBy); - } - - @Override - public String toString() { - return "DeleteHistory [id=" + id + ", contentType=" + contentType + ", contentId=" + contentId + ", deletedBy=" - + deletedBy + ", createDate=" + createDate + "]"; - } -} diff --git a/src/main/java/qna/domain/DeleteHistoryRepository.java b/src/main/java/qna/domain/DeleteHistoryRepository.java deleted file mode 100644 index 28ad228f4c..0000000000 --- a/src/main/java/qna/domain/DeleteHistoryRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package qna.domain; - -import org.springframework.data.repository.CrudRepository; - -public interface DeleteHistoryRepository extends CrudRepository { - -} diff --git a/src/main/java/qna/domain/Question.java b/src/main/java/qna/domain/Question.java deleted file mode 100644 index 1e8bb11251..0000000000 --- a/src/main/java/qna/domain/Question.java +++ /dev/null @@ -1,95 +0,0 @@ -package qna.domain; - -import org.hibernate.annotations.Where; - -import javax.persistence.*; -import java.util.ArrayList; -import java.util.List; - -@Entity -public class Question extends AbstractEntity { - @Column(length = 100, nullable = false) - private String title; - - @Lob - private String contents; - - @ManyToOne - @JoinColumn(foreignKey = @ForeignKey(name = "fk_question_writer")) - private User writer; - - @OneToMany(mappedBy = "question", cascade = CascadeType.ALL) - @Where(clause = "deleted = false") - @OrderBy("id ASC") - private List answers = new ArrayList<>(); - - private boolean deleted = false; - - public Question() { - } - - public Question(String title, String contents) { - this.title = title; - this.contents = contents; - } - - public Question(long id, String title, String contents) { - super(id); - this.title = title; - this.contents = contents; - } - - public String getTitle() { - return title; - } - - public Question setTitle(String title) { - this.title = title; - return this; - } - - public String getContents() { - return contents; - } - - public Question setContents(String contents) { - this.contents = contents; - return this; - } - - public User getWriter() { - return writer; - } - - public Question writeBy(User loginUser) { - this.writer = loginUser; - return this; - } - - public void addAnswer(Answer answer) { - answer.toQuestion(this); - answers.add(answer); - } - - public boolean isOwner(User loginUser) { - return writer.equals(loginUser); - } - - public Question setDeleted(boolean deleted) { - this.deleted = deleted; - return this; - } - - public boolean isDeleted() { - return deleted; - } - - public List getAnswers() { - return answers; - } - - @Override - public String toString() { - return "Question [id=" + getId() + ", title=" + title + ", contents=" + contents + ", writer=" + writer + "]"; - } -} diff --git a/src/main/java/qna/domain/QuestionRepository.java b/src/main/java/qna/domain/QuestionRepository.java deleted file mode 100644 index 8a9dceda40..0000000000 --- a/src/main/java/qna/domain/QuestionRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package qna.domain; - -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; -import java.util.Optional; - -public interface QuestionRepository extends JpaRepository { - List findByDeletedFalse(); - - Optional findByIdAndDeletedFalse(Long id); -} diff --git a/src/main/java/qna/domain/User.java b/src/main/java/qna/domain/User.java deleted file mode 100755 index bc0eb530c9..0000000000 --- a/src/main/java/qna/domain/User.java +++ /dev/null @@ -1,120 +0,0 @@ -package qna.domain; - -import qna.UnAuthorizedException; - -import javax.persistence.Column; -import javax.persistence.Entity; -import java.util.Objects; - -@Entity -public class User extends AbstractEntity { - public static final GuestUser GUEST_USER = new GuestUser(); - - @Column(unique = true, nullable = false) - private String userId; - - @Column(nullable = false) - private String password; - - @Column(nullable = false) - private String name; - - private String email; - - public User() { - } - - public User(String userId, String password, String name, String email) { - this(null, userId, password, name, email); - } - - public User(Long id, String userId, String password, String name, String email) { - super(id); - this.userId = userId; - this.password = password; - this.name = name; - this.email = email; - } - - public String getUserId() { - return userId; - } - - public User setUserId(String userId) { - this.userId = userId; - return this; - } - - public String getPassword() { - return password; - } - - public User setPassword(String password) { - this.password = password; - return this; - } - - public String getName() { - return name; - } - - public User setName(String name) { - this.name = name; - return this; - } - - public String getEmail() { - return email; - } - - public User setEmail(String email) { - this.email = email; - return this; - } - - public void update(User loginUser, User target) { - if (!matchUserId(loginUser.getUserId())) { - throw new UnAuthorizedException(); - } - - if (!matchPassword(target.getPassword())) { - throw new UnAuthorizedException(); - } - - this.name = target.name; - this.email = target.email; - } - - private boolean matchUserId(String userId) { - return this.userId.equals(userId); - } - - public boolean matchPassword(String targetPassword) { - return password.equals(targetPassword); - } - - public boolean equalsNameAndEmail(User target) { - if (Objects.isNull(target)) { - return false; - } - - return name.equals(target.name) && - email.equals(target.email); - } - - public boolean isGuestUser() { - return false; - } - - private static class GuestUser extends User { - @Override - public boolean isGuestUser() { - return true; - } - } - - @Override - public String toString() { - return "User [userId=" + userId + ", password=" + password + ", name=" + name + ", email=" + email + "]"; - } -} diff --git a/src/main/java/qna/domain/UserRepository.java b/src/main/java/qna/domain/UserRepository.java deleted file mode 100755 index 14b3d677e0..0000000000 --- a/src/main/java/qna/domain/UserRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package qna.domain; - -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface UserRepository extends JpaRepository { - Optional findByUserId(String userId); -} diff --git a/src/main/java/qna/service/DeleteHistoryService.java b/src/main/java/qna/service/DeleteHistoryService.java deleted file mode 100644 index f9620367e3..0000000000 --- a/src/main/java/qna/service/DeleteHistoryService.java +++ /dev/null @@ -1,26 +0,0 @@ -package qna.service; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import qna.domain.DeleteHistory; -import qna.domain.DeleteHistoryRepository; - -import javax.annotation.Resource; -import java.util.List; - -@Service("deleteHistoryService") -public class DeleteHistoryService { - @Resource(name = "deleteHistoryRepository") - private DeleteHistoryRepository deleteHistoryRepository; - - @Transactional(propagation = Propagation.REQUIRES_NEW) - public void saveAll(List deleteHistories) { - deleteHistoryRepository.saveAll(deleteHistories); - } - - @Transactional(propagation = Propagation.REQUIRES_NEW) - public void save(DeleteHistory deleteHistory) { - deleteHistoryRepository.save(deleteHistory); - } -} \ No newline at end of file diff --git a/src/main/java/qna/service/QnAService.java b/src/main/java/qna/service/QnAService.java deleted file mode 100644 index 66821cd9c2..0000000000 --- a/src/main/java/qna/service/QnAService.java +++ /dev/null @@ -1,58 +0,0 @@ -package qna.service; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import qna.CannotDeleteException; -import qna.NotFoundException; -import qna.domain.*; - -import javax.annotation.Resource; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -@Service("qnaService") -public class QnAService { - private static final Logger log = LoggerFactory.getLogger(QnAService.class); - - @Resource(name = "questionRepository") - private QuestionRepository questionRepository; - - @Resource(name = "answerRepository") - private AnswerRepository answerRepository; - - @Resource(name = "deleteHistoryService") - private DeleteHistoryService deleteHistoryService; - - @Transactional(readOnly = true) - public Question findQuestionById(Long id) { - return questionRepository.findByIdAndDeletedFalse(id) - .orElseThrow(NotFoundException::new); - } - - @Transactional - public void deleteQuestion(User loginUser, long questionId) throws CannotDeleteException { - Question question = findQuestionById(questionId); - if (!question.isOwner(loginUser)) { - throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); - } - - List answers = question.getAnswers(); - for (Answer answer : answers) { - if (!answer.isOwner(loginUser)) { - throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); - } - } - - List deleteHistories = new ArrayList<>(); - question.setDeleted(true); - deleteHistories.add(new DeleteHistory(ContentType.QUESTION, questionId, question.getWriter(), LocalDateTime.now())); - for (Answer answer : answers) { - answer.setDeleted(true); - deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); - } - deleteHistoryService.saveAll(deleteHistories); - } -} diff --git a/src/test/java/bowling/domain/BallTest.java b/src/test/java/bowling/domain/BallTest.java new file mode 100644 index 0000000000..7b5c9efadb --- /dev/null +++ b/src/test/java/bowling/domain/BallTest.java @@ -0,0 +1,18 @@ +package bowling.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class BallTest { + @Test + void from_10() { + Ball ball = Ball.from(10); + assertThat(ball.getBallStatus()).isEqualTo(BallStatus.STIKE); + } + @Test + void from_0() { + Ball ball = Ball.from(0); + assertThat(ball.getBallStatus()).isEqualTo(BallStatus.GUTTER); + } +} diff --git a/src/test/java/bowling/domain/FrameTest.java b/src/test/java/bowling/domain/FrameTest.java new file mode 100644 index 0000000000..f88b0f6e33 --- /dev/null +++ b/src/test/java/bowling/domain/FrameTest.java @@ -0,0 +1,36 @@ +package bowling.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class FrameTest { + @Test + void _1투구_반영_스트라이크() { + Ball firstBall = Ball.from(10); + Frame frame = Frame.fromFirstBall(firstBall); + assertThat(frame.isStrike()).isTrue(); + } + + @Test + void _2투구_반영_스페어O() { + Ball firstBall = Ball.from(1); + Frame frame = Frame.fromFirstBall(firstBall); + + Ball secondBall = Ball.from(9); + frame.applySecondBall(secondBall); + + assertThat(frame.isSpare()).isTrue(); + } + + @Test + void _2투구_반영_스페어X() { + Ball firstBall = Ball.from(1); + Frame frame = Frame.fromFirstBall(firstBall); + + Ball secondBall = Ball.from(3); + frame.applySecondBall(secondBall); + + assertThat(frame.isSpare()).isFalse(); + } +} diff --git a/src/test/java/bowling/domain/OutputFormatterTest.java b/src/test/java/bowling/domain/OutputFormatterTest.java new file mode 100644 index 0000000000..07c29f648d --- /dev/null +++ b/src/test/java/bowling/domain/OutputFormatterTest.java @@ -0,0 +1,77 @@ +package bowling.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import bowling.view.formatter.OutputFomatter; +import org.junit.jupiter.api.Test; + +public class OutputFormatterTest { + @Test + void 빈프레임_출력() { + OutputFomatter outputFormatter = new OutputFomatter(); + Ball ball1 = Ball.init(); + Ball ball2 = Ball.init(); + Frame rawFrame = Frame.of(ball1, ball2); + + String frame = outputFormatter.toFrame(rawFrame); + + assertThat(frame).isEqualTo(" "); + } + @Test + void 스페어_출력() { + OutputFomatter outputFormatter = new OutputFomatter(); + Ball ball1 = Ball.of(1, BallStatus.OPEN); + Ball ball_spare = Ball.of(9, BallStatus.SPARE); + Frame rawFrame = Frame.of(ball1, ball_spare); + + String frame = outputFormatter.toFrame(rawFrame); + + assertThat(frame).isEqualTo("1|/"); + } + + @Test + void 스트라이크_출력() { + OutputFomatter outputFormatter = new OutputFomatter(); + Ball ball_strike = Ball.from(10); + Frame rawFrame = Frame.fromFirstBall(ball_strike); + + String frame = outputFormatter.toFrame(rawFrame); + + assertThat(frame).isEqualTo("X "); + } + + @Test + void 거터_출력1() { + OutputFomatter outputFormatter = new OutputFomatter(); + Ball ball1 = Ball.of(1, BallStatus.OPEN); + Ball ball_gutter = Ball.of(0, BallStatus.GUTTER); + Frame rawFrame = Frame.of(ball1, ball_gutter); + + String frame = outputFormatter.toFrame(rawFrame); + + assertThat(frame).isEqualTo("1|-"); + } + @Test + void 거터_출력2() { + OutputFomatter outputFormatter = new OutputFomatter(); + Ball ball_gutter = Ball.of(0, BallStatus.GUTTER); + Ball ball1 = Ball.of(1, BallStatus.OPEN); + Frame rawFrame = Frame.of(ball_gutter, ball1); + + String frame = outputFormatter.toFrame(rawFrame); + + assertThat(frame).isEqualTo("-|1"); + } + + @Test + void 오픈_출력() { + OutputFomatter outputFormatter = new OutputFomatter(); + Ball ball1 = Ball.of(1, BallStatus.OPEN); + Ball ball2 = Ball.of(2, BallStatus.OPEN); + Frame rawFrame = Frame.of(ball1, ball2); + + String frame = outputFormatter.toFrame(rawFrame); + + assertThat(frame).isEqualTo("1|2"); + } +} diff --git a/src/test/java/bowling/mock/FakePrinter.java b/src/test/java/bowling/mock/FakePrinter.java new file mode 100644 index 0000000000..223b6d0d9c --- /dev/null +++ b/src/test/java/bowling/mock/FakePrinter.java @@ -0,0 +1,20 @@ +package bowling.mock; + +import bowling.view.printer.Printer; + +public class FakePrinter implements Printer { + @Override + public void printLine(String message) { + + } + + @Override + public void printLine(String format, Object... args) { + + } + + @Override + public void printEmptyLine() { + + } +} diff --git a/src/test/java/bowling/mock/FakeReader.java b/src/test/java/bowling/mock/FakeReader.java new file mode 100644 index 0000000000..61dc88d85f --- /dev/null +++ b/src/test/java/bowling/mock/FakeReader.java @@ -0,0 +1,16 @@ +package bowling.mock; + +import bowling.view.reader.Reader; + +public class FakeReader implements Reader { + private final String input; + + public FakeReader(String input) { + this.input = input; + } + + @Override + public String readLine() { + return input; + } +} diff --git a/src/test/java/bowling/view/InputViewTest.java b/src/test/java/bowling/view/InputViewTest.java new file mode 100644 index 0000000000..3c8cd38771 --- /dev/null +++ b/src/test/java/bowling/view/InputViewTest.java @@ -0,0 +1,24 @@ +package bowling.view; + +import bowling.mock.FakePrinter; +import bowling.mock.FakeReader; +import bowling.view.printer.Printer; +import bowling.view.reader.Reader; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class InputViewTest { + private Printer printer = new FakePrinter(); + + @DisplayName("정상적이지 않은 에 대해서 예외를 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"", " ", "2two", "2147483648", "-2147483649"}) + void inputDay(String input) { + // given + Reader reader = new FakeReader(input); + InputView inputView = InputView.of(reader, printer); + + // when & then + } +} diff --git a/src/test/java/bowling/view/OutputFormatterTest.java b/src/test/java/bowling/view/OutputFormatterTest.java new file mode 100644 index 0000000000..a7d2188236 --- /dev/null +++ b/src/test/java/bowling/view/OutputFormatterTest.java @@ -0,0 +1,25 @@ +package bowling.view; + +import bowling.view.formatter.OutputFomatter; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class OutputFormatterTest { + OutputFomatter fomatter = new OutputFomatter(); + + @DisplayName(" 출력 포맷팅") + @Test + void format() { + // given + String input = ""; + + // when +// String result = fomatter.to(input); + + // then +// assertThat(result).isEqualTo( +// "[ O | X ]" +// + System.lineSeparator() +// ); + } +} diff --git a/src/test/java/qna/domain/AnswerTest.java b/src/test/java/qna/domain/AnswerTest.java deleted file mode 100644 index d858181e31..0000000000 --- a/src/test/java/qna/domain/AnswerTest.java +++ /dev/null @@ -1,6 +0,0 @@ -package qna.domain; - -public class AnswerTest { - public static final Answer A1 = new Answer(UserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); - public static final Answer A2 = new Answer(UserTest.SANJIGI, QuestionTest.Q1, "Answers Contents2"); -} diff --git a/src/test/java/qna/domain/QuestionTest.java b/src/test/java/qna/domain/QuestionTest.java deleted file mode 100644 index b48c9a2209..0000000000 --- a/src/test/java/qna/domain/QuestionTest.java +++ /dev/null @@ -1,6 +0,0 @@ -package qna.domain; - -public class QuestionTest { - public static final Question Q1 = new Question("title1", "contents1").writeBy(UserTest.JAVAJIGI); - public static final Question Q2 = new Question("title2", "contents2").writeBy(UserTest.SANJIGI); -} diff --git a/src/test/java/qna/domain/UserTest.java b/src/test/java/qna/domain/UserTest.java deleted file mode 100644 index 4f0936e93a..0000000000 --- a/src/test/java/qna/domain/UserTest.java +++ /dev/null @@ -1,6 +0,0 @@ -package qna.domain; - -public class UserTest { - public static final User JAVAJIGI = new User(1L, "javajigi", "password", "name", "javajigi@slipp.net"); - public static final User SANJIGI = new User(2L, "sanjigi", "password", "name", "sanjigi@slipp.net"); -} diff --git a/src/test/java/qna/service/QnaServiceTest.java b/src/test/java/qna/service/QnaServiceTest.java deleted file mode 100644 index 3504343caa..0000000000 --- a/src/test/java/qna/service/QnaServiceTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package qna.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import qna.CannotDeleteException; -import qna.domain.*; - -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class QnaServiceTest { - @Mock - private QuestionRepository questionRepository; - - @Mock - private DeleteHistoryService deleteHistoryService; - - @InjectMocks - private QnAService qnAService; - - private Question question; - private Answer answer; - - @BeforeEach - public void setUp() throws Exception { - question = new Question(1L, "title1", "contents1").writeBy(UserTest.JAVAJIGI); - answer = new Answer(11L, UserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); - question.addAnswer(answer); - } - - @Test - public void delete_성공() throws Exception { - when(questionRepository.findByIdAndDeletedFalse(question.getId())).thenReturn(Optional.of(question)); - - assertThat(question.isDeleted()).isFalse(); - qnAService.deleteQuestion(UserTest.JAVAJIGI, question.getId()); - - assertThat(question.isDeleted()).isTrue(); - verifyDeleteHistories(); - } - - @Test - public void delete_다른_사람이_쓴_글() throws Exception { - when(questionRepository.findByIdAndDeletedFalse(question.getId())).thenReturn(Optional.of(question)); - - assertThatThrownBy(() -> { - qnAService.deleteQuestion(UserTest.SANJIGI, question.getId()); - }).isInstanceOf(CannotDeleteException.class); - } - - @Test - public void delete_성공_질문자_답변자_같음() throws Exception { - when(questionRepository.findByIdAndDeletedFalse(question.getId())).thenReturn(Optional.of(question)); - - qnAService.deleteQuestion(UserTest.JAVAJIGI, question.getId()); - - assertThat(question.isDeleted()).isTrue(); - assertThat(answer.isDeleted()).isTrue(); - verifyDeleteHistories(); - } - - @Test - public void delete_답변_중_다른_사람이_쓴_글() throws Exception { - when(questionRepository.findByIdAndDeletedFalse(question.getId())).thenReturn(Optional.of(question)); - - assertThatThrownBy(() -> { - qnAService.deleteQuestion(UserTest.SANJIGI, question.getId()); - }).isInstanceOf(CannotDeleteException.class); - } - - private void verifyDeleteHistories() { - List deleteHistories = Arrays.asList( - new DeleteHistory(ContentType.QUESTION, question.getId(), question.getWriter(), LocalDateTime.now()), - new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); - verify(deleteHistoryService).saveAll(deleteHistories); - } -}