From a40bde07fc079fe96c1c709ff4061fc9999f95f3 Mon Sep 17 00:00:00 2001 From: backFox Date: Sun, 11 Dec 2022 17:28:28 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B8=B0=EB=8A=A5=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84(=EA=B8=B0=EB=8A=A5=20=EB=82=98=EB=88=84?= =?UTF-8?q?=EA=B8=B0=EB=A5=BC=20=EB=AA=BB=ED=95=A8=20=E3=85=8B=20=E3=85=8B?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- src/main/java/pairmatching/Application.java | 5 +- .../controller/AbstractController.java | 31 +++++ .../pairmatching/controller/Controller.java | 7 ++ .../controller/FindingController.java | 19 +++ .../controller/MainController.java | 39 ++++++ .../controller/MatchingController.java | 58 +++++++++ .../controller/ResetingController.java | 17 +++ .../controller/SetupController.java | 22 ++++ .../controller/SetupCrewController.java | 34 ++++++ src/main/java/pairmatching/domain/Course.java | 35 ++++++ src/main/java/pairmatching/domain/Level.java | 38 ++++++ .../java/pairmatching/domain/Mission.java | 58 +++++++++ .../inputview/AbstractInputView.java | 9 ++ .../pairmatching/inputview/MainInputView.java | 9 ++ .../inputview/MatchingInputView.java | 46 ++++++++ .../ExceptionHandlingOutputView.java | 10 ++ .../outputview/MainOutputView.java | 21 ++++ .../outputview/MatchingOutputView.java | 111 ++++++++++++++++++ .../repository/CrewRepository.java | 25 ++++ .../repository/MissionRepository.java | 41 +++++++ .../repository/PairMatchingRepository.java | 85 ++++++++++++++ .../pairmatching/service/MatchingService.java | 47 ++++++++ .../pairmatching/system/ControllerHolder.java | 28 +++++ .../system/PairMatchingApplication.java | 20 ++++ .../PairMatchingAlreadyExistException.java | 4 + .../SamePairMatchedAtSameLevelException.java | 4 + .../java/pairmatching/vo/ControllerName.java | 5 + .../java/pairmatching/vo/MainCommand.java | 33 ++++++ .../java/pairmatching/vo/MatchingCommand.java | 38 ++++++ .../pairmatching/vo/PairMatchingInfo.java | 54 +++++++++ .../pairmatching/vo/RematchingCommand.java | 26 ++++ 32 files changed, 979 insertions(+), 2 deletions(-) create mode 100644 src/main/java/pairmatching/controller/AbstractController.java create mode 100644 src/main/java/pairmatching/controller/Controller.java create mode 100644 src/main/java/pairmatching/controller/FindingController.java create mode 100644 src/main/java/pairmatching/controller/MainController.java create mode 100644 src/main/java/pairmatching/controller/MatchingController.java create mode 100644 src/main/java/pairmatching/controller/ResetingController.java create mode 100644 src/main/java/pairmatching/controller/SetupController.java create mode 100644 src/main/java/pairmatching/controller/SetupCrewController.java create mode 100644 src/main/java/pairmatching/domain/Course.java create mode 100644 src/main/java/pairmatching/domain/Level.java create mode 100644 src/main/java/pairmatching/domain/Mission.java create mode 100644 src/main/java/pairmatching/inputview/AbstractInputView.java create mode 100644 src/main/java/pairmatching/inputview/MainInputView.java create mode 100644 src/main/java/pairmatching/inputview/MatchingInputView.java create mode 100644 src/main/java/pairmatching/outputview/ExceptionHandlingOutputView.java create mode 100644 src/main/java/pairmatching/outputview/MainOutputView.java create mode 100644 src/main/java/pairmatching/outputview/MatchingOutputView.java create mode 100644 src/main/java/pairmatching/repository/CrewRepository.java create mode 100644 src/main/java/pairmatching/repository/MissionRepository.java create mode 100644 src/main/java/pairmatching/repository/PairMatchingRepository.java create mode 100644 src/main/java/pairmatching/service/MatchingService.java create mode 100644 src/main/java/pairmatching/system/ControllerHolder.java create mode 100644 src/main/java/pairmatching/system/PairMatchingApplication.java create mode 100644 src/main/java/pairmatching/system/exception/PairMatchingAlreadyExistException.java create mode 100644 src/main/java/pairmatching/system/exception/SamePairMatchedAtSameLevelException.java create mode 100644 src/main/java/pairmatching/vo/ControllerName.java create mode 100644 src/main/java/pairmatching/vo/MainCommand.java create mode 100644 src/main/java/pairmatching/vo/MatchingCommand.java create mode 100644 src/main/java/pairmatching/vo/PairMatchingInfo.java create mode 100644 src/main/java/pairmatching/vo/RematchingCommand.java diff --git a/build.gradle b/build.gradle index 449e0350a..6d2928bfb 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ dependencies { java { toolchain { - languageVersion = JavaLanguageVersion.of(8) + languageVersion = JavaLanguageVersion.of(11) } } diff --git a/src/main/java/pairmatching/Application.java b/src/main/java/pairmatching/Application.java index 6f56e741c..124ef440f 100644 --- a/src/main/java/pairmatching/Application.java +++ b/src/main/java/pairmatching/Application.java @@ -1,7 +1,10 @@ package pairmatching; +import pairmatching.system.PairMatchingApplication; + public class Application { public static void main(String[] args) { - // TODO 구현 진행 + PairMatchingApplication pairMatchingApplication = new PairMatchingApplication(); + pairMatchingApplication.run(); } } diff --git a/src/main/java/pairmatching/controller/AbstractController.java b/src/main/java/pairmatching/controller/AbstractController.java new file mode 100644 index 000000000..990024568 --- /dev/null +++ b/src/main/java/pairmatching/controller/AbstractController.java @@ -0,0 +1,31 @@ +package pairmatching.controller; + +import pairmatching.domain.Level; +import pairmatching.inputview.MatchingInputView; +import pairmatching.outputview.ExceptionHandlingOutputView; +import pairmatching.outputview.MatchingOutputView; +import pairmatching.repository.MissionRepository; +import pairmatching.vo.MatchingCommand; + +import java.util.List; +import java.util.Map; + +public abstract class AbstractController implements Controller { + @Override + public void process(Map model) { + try { + doProcess(model); + } catch (IllegalArgumentException e) { + ExceptionHandlingOutputView.printExceptionMessage(e.getMessage()); + process(model); + } + } + + protected static MatchingCommand getMatchingCommand() { + Map> allCrewNamesByLevel = MissionRepository.findAllNamesByAllLevel(); + MatchingOutputView.printInformation(allCrewNamesByLevel); + return MatchingInputView.getCommand(); + } + + public abstract void doProcess(Map model); +} diff --git a/src/main/java/pairmatching/controller/Controller.java b/src/main/java/pairmatching/controller/Controller.java new file mode 100644 index 000000000..e6b5c4ece --- /dev/null +++ b/src/main/java/pairmatching/controller/Controller.java @@ -0,0 +1,7 @@ +package pairmatching.controller; + +import java.util.Map; + +public interface Controller { + void process(Map model); +} diff --git a/src/main/java/pairmatching/controller/FindingController.java b/src/main/java/pairmatching/controller/FindingController.java new file mode 100644 index 000000000..e7e686b51 --- /dev/null +++ b/src/main/java/pairmatching/controller/FindingController.java @@ -0,0 +1,19 @@ +package pairmatching.controller; + +import pairmatching.outputview.MatchingOutputView; +import pairmatching.service.MatchingService; +import pairmatching.vo.MatchingCommand; + +import java.util.List; +import java.util.Map; + +public class FindingController extends AbstractController { + @Override + public void doProcess(Map model) { + MatchingCommand matchingCommand = getMatchingCommand(); + + List> pairs + = MatchingService.findAllPairs(matchingCommand.getCourse(), matchingCommand.getMission()); + MatchingOutputView.printPairInformation(pairs); + } +} diff --git a/src/main/java/pairmatching/controller/MainController.java b/src/main/java/pairmatching/controller/MainController.java new file mode 100644 index 000000000..26bfd649f --- /dev/null +++ b/src/main/java/pairmatching/controller/MainController.java @@ -0,0 +1,39 @@ +package pairmatching.controller; + +import pairmatching.inputview.MainInputView; +import pairmatching.outputview.MainOutputView; +import pairmatching.system.ControllerHolder; +import pairmatching.vo.ControllerName; +import pairmatching.vo.MainCommand; + +import java.util.Map; + +public class MainController extends AbstractController { + @Override + public void doProcess(Map model) { + MainCommand command; + do { + MainOutputView.printCommands(); + command = MainInputView.getCommand(); + + doMatchingProcess(model, command); + doFindingProcess(model, command); + if (command == MainCommand.RESET) { + ControllerHolder.get(ControllerName.RESET).process(model); + } + + } while (command != MainCommand.QUIT); + } + + private static void doFindingProcess(Map model, MainCommand command) { + if (command == MainCommand.FIND) { + ControllerHolder.get(ControllerName.FIND).process(model); + } + } + + private static void doMatchingProcess(Map model, MainCommand command) { + if (command == MainCommand.MATCHING) { + ControllerHolder.get(ControllerName.MATCHING).process(model); + } + } +} diff --git a/src/main/java/pairmatching/controller/MatchingController.java b/src/main/java/pairmatching/controller/MatchingController.java new file mode 100644 index 000000000..4037d47ec --- /dev/null +++ b/src/main/java/pairmatching/controller/MatchingController.java @@ -0,0 +1,58 @@ +package pairmatching.controller; + +import pairmatching.inputview.MatchingInputView; +import pairmatching.outputview.ExceptionHandlingOutputView; +import pairmatching.outputview.MatchingOutputView; +import pairmatching.service.MatchingService; +import pairmatching.system.exception.PairMatchingAlreadyExistException; +import pairmatching.system.exception.SamePairMatchedAtSameLevelException; +import pairmatching.vo.MatchingCommand; +import pairmatching.vo.RematchingCommand; + +import java.util.List; +import java.util.Map; + +public class MatchingController extends AbstractController { + + public static final int DUPLICATING_LIMIT_COUNT = 3; + public static final String MATCHING_FAILED_MESSAGE = "페어 매칭에 실패했습니다."; + + @Override + public void doProcess(Map model) { + MatchingCommand matchingCommand = getMatchingCommand(); + try { + MatchingService.checkPairMatchingAlreadyExists(matchingCommand.getCourse(), matchingCommand.getMission()); + matchPairs(matchingCommand); + } catch (PairMatchingAlreadyExistException e) { + handlePairMatchingAlreadyExistingCondition(matchingCommand); + } + } + + private static void matchPairs(MatchingCommand matchingCommand) { + doMatch(matchingCommand, 0); + + List> pairs + = MatchingService.findAllPairs(matchingCommand.getCourse(), matchingCommand.getMission()); + MatchingOutputView.printPairInformation(pairs); + } + + private static void doMatch(MatchingCommand matchingCommand, int duplicatedCount) { + try { + MatchingService.doMatch(matchingCommand.getCourse(), matchingCommand.getMission()); + } catch (SamePairMatchedAtSameLevelException e) { + if (duplicatedCount == DUPLICATING_LIMIT_COUNT) { + ExceptionHandlingOutputView.printExceptionMessage(MATCHING_FAILED_MESSAGE); + throw e; + } + doMatch(matchingCommand, ++duplicatedCount); + } + } + + private static void handlePairMatchingAlreadyExistingCondition(MatchingCommand matchingCommand) { + MatchingOutputView.printAskingReMatchingCommand(); + RematchingCommand rematchingCommand = MatchingInputView.getReMatchingCommand(); + if (rematchingCommand == RematchingCommand.YES) { + matchPairs(matchingCommand); + } + } +} diff --git a/src/main/java/pairmatching/controller/ResetingController.java b/src/main/java/pairmatching/controller/ResetingController.java new file mode 100644 index 000000000..b4947d82d --- /dev/null +++ b/src/main/java/pairmatching/controller/ResetingController.java @@ -0,0 +1,17 @@ +package pairmatching.controller; + +import pairmatching.outputview.MatchingOutputView; +import pairmatching.service.MatchingService; +import pairmatching.vo.MatchingCommand; + +import java.util.Map; + +public class ResetingController extends AbstractController { + @Override + public void doProcess(Map model) { + MatchingCommand matchingCommand = getMatchingCommand(); + MatchingService.resetPairMatching(matchingCommand.getCourse(), matchingCommand.getMission()); + + MatchingOutputView.printResetResult(); + } +} diff --git a/src/main/java/pairmatching/controller/SetupController.java b/src/main/java/pairmatching/controller/SetupController.java new file mode 100644 index 000000000..c9159fc30 --- /dev/null +++ b/src/main/java/pairmatching/controller/SetupController.java @@ -0,0 +1,22 @@ +package pairmatching.controller; + +import pairmatching.domain.Level; +import pairmatching.repository.MissionRepository; + +import java.util.List; +import java.util.Map; + +public class SetupController extends AbstractController { + @Override + public void doProcess(Map model) { + saveMission(Level.LEVEL1, List.of("자동차경주", "로또", "숫자야구게임")); + saveMission(Level.LEVEL2, List.of("장바구니", "결제", "지하철노선도")); + saveMission(Level.LEVEL4, List.of("성능개선", "배포")); + } + + private void saveMission(Level level, List missionNames) { + for (String missionName : missionNames) { + MissionRepository.save(level, missionName); + } + } +} diff --git a/src/main/java/pairmatching/controller/SetupCrewController.java b/src/main/java/pairmatching/controller/SetupCrewController.java new file mode 100644 index 000000000..047c52a20 --- /dev/null +++ b/src/main/java/pairmatching/controller/SetupCrewController.java @@ -0,0 +1,34 @@ +package pairmatching.controller; + +import pairmatching.domain.Course; +import pairmatching.repository.CrewRepository; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Map; +import java.util.Scanner; + +public class SetupCrewController extends AbstractController { + + public static final String FILE_NOT_FOUND_MESSAGE = "파일을 읽어오지 못했습니다."; + public static final String BACKEND_CREW_MD = "src/main/resources/backend-crew.md"; + public static final String FRONTEND_CREW_MD = "src/main/resources/frontend-crew.md"; + + @Override + public void doProcess(Map model) { + saveCrewsFromFile(BACKEND_CREW_MD, Course.BACKEND); + saveCrewsFromFile(FRONTEND_CREW_MD, Course.FRONTEND); + } + + private static void saveCrewsFromFile(String crewFilePath, Course course) { + try { + Scanner scanner = new Scanner(new File(crewFilePath)); + while (scanner.hasNext()) { + String crewName = scanner.nextLine(); + CrewRepository.save(course, crewName); + } + } catch (FileNotFoundException e) { + throw new IllegalStateException(FILE_NOT_FOUND_MESSAGE); + } + } +} diff --git a/src/main/java/pairmatching/domain/Course.java b/src/main/java/pairmatching/domain/Course.java new file mode 100644 index 000000000..e22b7f75e --- /dev/null +++ b/src/main/java/pairmatching/domain/Course.java @@ -0,0 +1,35 @@ +package pairmatching.domain; + +import java.util.Arrays; + +public enum Course { + BACKEND("백엔드"), + FRONTEND("프론트엔드"); + + public static final String NOT_EXISTING_COURSE_NAME = "존재하지 않는 과정 이름"; + private final String name; + + Course(String name) { + this.name = name; + } + + public static Course findByName(String courseName) { + return Arrays.stream(Course.values()) + .filter(course -> course.name.equals(courseName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTING_COURSE_NAME)); + } + + // 추가 기능 구현 + + @Override + public String toString() { + return "Course{" + + "name='" + name + '\'' + + '}'; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/pairmatching/domain/Level.java b/src/main/java/pairmatching/domain/Level.java new file mode 100644 index 000000000..fdbb151f9 --- /dev/null +++ b/src/main/java/pairmatching/domain/Level.java @@ -0,0 +1,38 @@ +package pairmatching.domain; + +import java.util.Arrays; + +public enum Level { + LEVEL1("레벨1"), + LEVEL2("레벨2"), + LEVEL3("레벨3"), + LEVEL4("레벨4"), + LEVEL5("레벨5"); + + public static final String NOT_EXISTING_LEVEL_NAME = "존재하지 않는 레벨 이름"; + private String name; + + Level(String name) { + this.name = name; + } + + public static Level findByName(String levelName) { + return Arrays.stream(Level.values()) + .filter(level -> level.name.equals(levelName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTING_LEVEL_NAME)); + } + + // 추가 기능 구현 + + @Override + public String toString() { + return "Level{" + + "name='" + name + '\'' + + '}'; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/pairmatching/domain/Mission.java b/src/main/java/pairmatching/domain/Mission.java new file mode 100644 index 000000000..177922772 --- /dev/null +++ b/src/main/java/pairmatching/domain/Mission.java @@ -0,0 +1,58 @@ +package pairmatching.domain; + +import java.util.Objects; + +public class Mission { + private final Level level; + private final String name; + + public Mission(Level level, String name) { + this.level = level; + this.name = name; + } + + public static Mission of(Level level, String missionName) { + return new Mission(level, missionName); + } + + public Level getLevel() { + return level; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Mission mission = (Mission) o; + return level == mission.level && name.equals(mission.name); + } + + @Override + public int hashCode() { + return Objects.hash(level, name); + } + + @Override + public String toString() { + return "Mission{" + + "level=" + level + + ", name='" + name + '\'' + + '}'; + } + + public boolean isLevel(Level level) { + return this.level.equals(level); + } + + public boolean isName(String missionName) { + return this.name.equals(missionName); + } +} diff --git a/src/main/java/pairmatching/inputview/AbstractInputView.java b/src/main/java/pairmatching/inputview/AbstractInputView.java new file mode 100644 index 000000000..8d8d9dfed --- /dev/null +++ b/src/main/java/pairmatching/inputview/AbstractInputView.java @@ -0,0 +1,9 @@ +package pairmatching.inputview; + +import camp.nextstep.edu.missionutils.Console; + +public class AbstractInputView { + protected static String readInput() { + return Console.readLine(); + } +} diff --git a/src/main/java/pairmatching/inputview/MainInputView.java b/src/main/java/pairmatching/inputview/MainInputView.java new file mode 100644 index 000000000..760bf4bac --- /dev/null +++ b/src/main/java/pairmatching/inputview/MainInputView.java @@ -0,0 +1,9 @@ +package pairmatching.inputview; + +import pairmatching.vo.MainCommand; + +public class MainInputView extends AbstractInputView { + public static MainCommand getCommand() { + return MainCommand.findByCommand(readInput()); + } +} diff --git a/src/main/java/pairmatching/inputview/MatchingInputView.java b/src/main/java/pairmatching/inputview/MatchingInputView.java new file mode 100644 index 000000000..b42f88284 --- /dev/null +++ b/src/main/java/pairmatching/inputview/MatchingInputView.java @@ -0,0 +1,46 @@ +package pairmatching.inputview; + +import pairmatching.domain.Course; +import pairmatching.domain.Level; +import pairmatching.vo.MatchingCommand; +import pairmatching.vo.RematchingCommand; + +public class MatchingInputView extends AbstractInputView { + public static MatchingCommand getCommand() { + String input = readInput(); + String[] validatedInputArray = Validator.validate(input); + + Course course = Course.findByName(validatedInputArray[0]); + Level level = Level.findByName(validatedInputArray[1]); + String missionName = validatedInputArray[2]; + + return MatchingCommand.of(course, level, missionName); + } + + public static RematchingCommand getReMatchingCommand() { + return RematchingCommand.findByCommand(readInput()); + } + + private static class Validator { + + public static final String NOT_VALID_LENGTH_MESSAGE = ",로 구분된 3개의 값이 아님"; + public static final int VALUE_AMOUNT = 3; + public static final String COMMA = ","; + public static final String SPACE = " "; + public static final String BLANK = ""; + + public static String[] validate(String target) { + target = target.replaceAll(SPACE, BLANK); + String[] splitedTarget = target.split(COMMA); + hasValidSize(splitedTarget); + + return splitedTarget; + } + + private static void hasValidSize(String[] splitedTarget) { + if (splitedTarget.length != VALUE_AMOUNT) { + throw new IllegalArgumentException(NOT_VALID_LENGTH_MESSAGE); + } + } + } +} diff --git a/src/main/java/pairmatching/outputview/ExceptionHandlingOutputView.java b/src/main/java/pairmatching/outputview/ExceptionHandlingOutputView.java new file mode 100644 index 000000000..c3bc05a93 --- /dev/null +++ b/src/main/java/pairmatching/outputview/ExceptionHandlingOutputView.java @@ -0,0 +1,10 @@ +package pairmatching.outputview; + +public class ExceptionHandlingOutputView { + + public static final String ERROR_MESSAGE_FORMAT = "[ERROR] %s"; + + public static void printExceptionMessage(String message) { + System.out.printf(ERROR_MESSAGE_FORMAT, message); + } +} diff --git a/src/main/java/pairmatching/outputview/MainOutputView.java b/src/main/java/pairmatching/outputview/MainOutputView.java new file mode 100644 index 000000000..f36cf094f --- /dev/null +++ b/src/main/java/pairmatching/outputview/MainOutputView.java @@ -0,0 +1,21 @@ +package pairmatching.outputview; + +import pairmatching.vo.MainCommand; + +public class MainOutputView { + + public static final String MAIN_MESSAGE = "기능을 선택하세요."; + public static final String COMMAND_FORMAT = "%s. %s%n"; + + public static void printCommands() { + System.out.println(); + System.out.println(MAIN_MESSAGE); + printCommandInfo(); + } + + private static void printCommandInfo() { + for (MainCommand mainCommand : MainCommand.values()) { + System.out.printf(COMMAND_FORMAT, mainCommand.getCommand(), mainCommand.getDescription()); + } + } +} diff --git a/src/main/java/pairmatching/outputview/MatchingOutputView.java b/src/main/java/pairmatching/outputview/MatchingOutputView.java new file mode 100644 index 000000000..96303bb07 --- /dev/null +++ b/src/main/java/pairmatching/outputview/MatchingOutputView.java @@ -0,0 +1,111 @@ +package pairmatching.outputview; + +import pairmatching.domain.Course; +import pairmatching.domain.Level; +import pairmatching.vo.RematchingCommand; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class MatchingOutputView { + + public static final String LINE_SEPARATOR = "#############################################"; + public static final String MISSION_MESSAGE_FORMAT = " - %s: "; + public static final String VALUE_SEPARATOR_LINE = " | "; + public static final String COURSE_PREFIX = "과정: "; + public static final String MISSION_PREFIX = "미션:"; + public static final String ASKING_REMATCHING_COMMAND = "매칭 정보가 있습니다. 다시 매칭하시겠습니까?"; + public static final String PAIR_MATCHING_RESULT_MESSAGE = "페어 매칭 결과입니다."; + public static final String VALUE_SEPARATOR_COLON = " : "; + + public static void printInformation(Map> allCrewNamesByLevel) { + System.out.println(); + System.out.println(LINE_SEPARATOR); + + printCourse(); + printMissions(allCrewNamesByLevel); + System.out.println(LINE_SEPARATOR); + + printAskingInfoMessage(); + } + + public static void printAskingReMatchingCommand() { + System.out.println(); + System.out.println(ASKING_REMATCHING_COMMAND); + printRematchingCommands(); + System.out.println(); + } + + private static void printRematchingCommands() { + Iterator iterator = Arrays.stream(RematchingCommand.values()).iterator(); + while (iterator.hasNext()) { + System.out.print(iterator.next().getCommand()); + addSeparator(iterator, VALUE_SEPARATOR_LINE); + } + } + + private static void printAskingInfoMessage() { + System.out.println("과정, 레벨, 미션을 선택하세요."); + System.out.println("ex) 백엔드, 레벨1, 자동차경주"); + } + + private static void printMissions(Map> allCrewNamesByLevel) { + System.out.println(MISSION_PREFIX); + for (Level level : Level.values()) { + System.out.printf(MISSION_MESSAGE_FORMAT, level.getName()); + Iterator iterator = allCrewNamesByLevel.get(level).iterator(); + printMissionNames(iterator); + System.out.println(); + } + } + + private static void printCourse() { + System.out.print(COURSE_PREFIX); + Iterator iterator = Arrays.stream(Course.values()).iterator(); + printCourseNames(iterator); + System.out.println(); + } + + private static void printCourseNames(Iterator iterator) { + while (iterator.hasNext()) { + System.out.print(iterator.next().getName()); + addSeparator(iterator, VALUE_SEPARATOR_LINE); + } + } + + private static void printMissionNames(Iterator iterator) { + while (iterator.hasNext()) { + System.out.print(iterator.next()); + addSeparator(iterator, VALUE_SEPARATOR_LINE); + } + } + + private static void addSeparator(Iterator iterator, String separator) { + if (iterator.hasNext()) { + System.out.print(separator); + } + } + + public static void printPairInformation(List> pairs) { + System.out.println(PAIR_MATCHING_RESULT_MESSAGE); + for (List pair : pairs) { + printPair(pair); + } + } + + private static void printPair(List pair) { + Iterator iterator = pair.iterator(); + while (iterator.hasNext()) { + System.out.print(iterator.next()); + addSeparator(iterator, VALUE_SEPARATOR_COLON); + } + System.out.println(); + } + + public static void printResetResult() { + System.out.println(); + System.out.println("초기화 되었습니다."); + } +} diff --git a/src/main/java/pairmatching/repository/CrewRepository.java b/src/main/java/pairmatching/repository/CrewRepository.java new file mode 100644 index 000000000..e7c240c03 --- /dev/null +++ b/src/main/java/pairmatching/repository/CrewRepository.java @@ -0,0 +1,25 @@ +package pairmatching.repository; + +import pairmatching.domain.Course; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CrewRepository { + private static final Map> crews = new HashMap<>(); + + static { + crews.put(Course.BACKEND, new ArrayList<>()); + crews.put(Course.FRONTEND, new ArrayList<>()); + } + public static void save(Course course, String crewName) { + List crews = CrewRepository.crews.get(course); + crews.add(crewName); + } + + public static List findAllByCourse(Course course) { + return crews.get(course); + } +} diff --git a/src/main/java/pairmatching/repository/MissionRepository.java b/src/main/java/pairmatching/repository/MissionRepository.java new file mode 100644 index 000000000..980019718 --- /dev/null +++ b/src/main/java/pairmatching/repository/MissionRepository.java @@ -0,0 +1,41 @@ +package pairmatching.repository; + +import pairmatching.domain.Level; +import pairmatching.domain.Mission; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class MissionRepository { + private static final List missions = new ArrayList<>(); + + public static void save(Level level, String missionName) { + missions.add(Mission.of(level, missionName)); + } + + public static Map> findAllNamesByAllLevel() { + HashMap> model = new HashMap<>(); + for (Level level : Level.values()) { + List missionNames = findAllNamesByLevel(level); + model.put(level, missionNames); + } + return model; + } + + private static List findAllNamesByLevel(Level level) { + return missions.stream() + .filter(mission -> mission.isLevel(level)) + .map(Mission::getName) + .collect(Collectors.toList()); + } + + public static boolean isExistingWith(Level level, String missionName) { + return missions + .stream() + .filter(mission -> mission.isLevel(level)) + .anyMatch(mission -> mission.isName(missionName)); + } +} diff --git a/src/main/java/pairmatching/repository/PairMatchingRepository.java b/src/main/java/pairmatching/repository/PairMatchingRepository.java new file mode 100644 index 000000000..0bc38089b --- /dev/null +++ b/src/main/java/pairmatching/repository/PairMatchingRepository.java @@ -0,0 +1,85 @@ +package pairmatching.repository; + +import pairmatching.domain.Course; +import pairmatching.domain.Level; +import pairmatching.domain.Mission; +import pairmatching.system.exception.SamePairMatchedAtSameLevelException; +import pairmatching.vo.PairMatchingInfo; + +import java.util.*; +import java.util.stream.Collectors; + +public class PairMatchingRepository { + public static final int PAIR_DEFAULT_SIZE = 2; + private static final Map>> pairMatchings = new HashMap<>(); + + public static boolean existingWith(Course course, Mission mission) { + return pairMatchings.containsKey(PairMatchingInfo.of(course, mission)); + } + + public static void save(PairMatchingInfo pairMatchingInfo, List crewNames) { + ArrayList crewNamesArray = new ArrayList<>(crewNames); + List> pairedCrews = makePair(crewNamesArray); + Validator.checkIfPairAlreadyMatched(pairMatchingInfo, pairedCrews); + + pairMatchings.put(pairMatchingInfo, pairedCrews); + } + + private static List> makePair(ArrayList crewNamesArray) { + List> pairedCrews = new ArrayList<>(); + while (PAIR_DEFAULT_SIZE <= crewNamesArray.size()) { + List crewPair = new ArrayList<>(List.of(getCrewName(crewNamesArray), getCrewName(crewNamesArray))); + handleOddCrew(crewNamesArray, crewPair); + pairedCrews.add(crewPair); + } + return pairedCrews; + } + + private static void handleOddCrew(ArrayList crewNamesArray, List crewPair) { + if (crewNamesArray.size() == 1) { + crewPair.add(getCrewName(crewNamesArray)); + } + } + + private static String getCrewName(ArrayList crewNamesArray) { + return crewNamesArray.remove(0); + } + + public static List> findAllPairsBy(PairMatchingInfo pairMatchingInfo) { + return pairMatchings.get(pairMatchingInfo); + } + + public static void reset(PairMatchingInfo pairMatchingInfo) { + pairMatchings.remove(pairMatchingInfo); + } + + private static class Validator { + private static void checkIfPairAlreadyMatched(PairMatchingInfo pairMatchingInfo, List> pairedCrews) { + List> pairsAtSameLevelAndCourse + = findAllPairsByLevelAndCourse(pairMatchingInfo.getCourse(), pairMatchingInfo.getLevel()); + + for (List pair : pairedCrews) { + if (pairsAtSameLevelAndCourse.contains(pair)) { + throw new SamePairMatchedAtSameLevelException(); + } + } + } + + private static List> findAllPairsByLevelAndCourse(Course course, Level level) { + List> pairsAtSameLevel = new ArrayList<>(); + for (PairMatchingInfo pairMatchingInfo : getPairMatchingInfosAtSameLevelAndCourse(course, level)) { + pairsAtSameLevel.addAll(pairMatchings.get(pairMatchingInfo)); + } + pairsAtSameLevel.forEach(Collections::sort); + return pairsAtSameLevel; + } + + private static List getPairMatchingInfosAtSameLevelAndCourse(Course course, Level level) { + List infosAtSameLevel = pairMatchings.keySet().stream() + .filter(pairMatchingInfo -> pairMatchingInfo.isLevel(level)) + .filter(pairMatchingInfo -> pairMatchingInfo.isCourse(course)) + .collect(Collectors.toList()); + return infosAtSameLevel; + } + } +} diff --git a/src/main/java/pairmatching/service/MatchingService.java b/src/main/java/pairmatching/service/MatchingService.java new file mode 100644 index 000000000..8f8876114 --- /dev/null +++ b/src/main/java/pairmatching/service/MatchingService.java @@ -0,0 +1,47 @@ +package pairmatching.service; + +import camp.nextstep.edu.missionutils.Randoms; +import pairmatching.domain.Course; +import pairmatching.domain.Mission; +import pairmatching.repository.CrewRepository; +import pairmatching.repository.PairMatchingRepository; +import pairmatching.system.exception.PairMatchingAlreadyExistException; +import pairmatching.vo.PairMatchingInfo; + +import java.util.List; + +public class MatchingService { + + public static final String PAIR_MATCHING_NOT_EXISTING = "매칭 이력이 없습니다."; + public static final String NOT_ENOUGH_CREWS = "페어 매칭이 가능한 경우의 수가 없습니다."; + + public static void doMatch(Course course, Mission mission) { + List crewNames = CrewRepository.findAllByCourse(course); + if (crewNames.size() < 2) { + throw new IllegalStateException(NOT_ENOUGH_CREWS); + } + List shuffledCrewNames = Randoms.shuffle(crewNames); + PairMatchingInfo pairMatchingInfo = PairMatchingInfo.of(course, mission); + PairMatchingRepository.save(pairMatchingInfo, shuffledCrewNames); + } + + public static void checkPairMatchingAlreadyExists(Course course, Mission mission) { + if (PairMatchingRepository.existingWith(course, mission)) { + throw new PairMatchingAlreadyExistException(); + } + } + + public static List> findAllPairs(Course course, Mission mission) { + PairMatchingInfo pairMatchingInfo = PairMatchingInfo.of(course, mission); + List> pairs = PairMatchingRepository.findAllPairsBy(pairMatchingInfo); + if (pairs == null) { + throw new IllegalArgumentException(PAIR_MATCHING_NOT_EXISTING); + } + return pairs; + } + + public static void resetPairMatching(Course course, Mission mission) { + PairMatchingInfo pairMatchingInfo = PairMatchingInfo.of(course, mission); + PairMatchingRepository.reset(pairMatchingInfo); + } +} diff --git a/src/main/java/pairmatching/system/ControllerHolder.java b/src/main/java/pairmatching/system/ControllerHolder.java new file mode 100644 index 000000000..e6609b829 --- /dev/null +++ b/src/main/java/pairmatching/system/ControllerHolder.java @@ -0,0 +1,28 @@ +package pairmatching.system; + +import pairmatching.controller.*; +import pairmatching.vo.ControllerName; + +import java.util.HashMap; +import java.util.Map; + +public class ControllerHolder { + private final static Map controllers = new HashMap<>(); + + static { + initializeSetupControllers(); + controllers.put(ControllerName.MAIN, new MainController()); + controllers.put(ControllerName.MATCHING, new MatchingController()); + controllers.put(ControllerName.FIND, new FindingController()); + controllers.put(ControllerName.RESET, new ResetingController()); + } + + private static void initializeSetupControllers() { + controllers.put(ControllerName.SETUP_MISSION, new SetupController()); + controllers.put(ControllerName.SETUP_CREW, new SetupCrewController()); + } + + public static Controller get(ControllerName controllerName) { + return controllers.get(controllerName); + } +} diff --git a/src/main/java/pairmatching/system/PairMatchingApplication.java b/src/main/java/pairmatching/system/PairMatchingApplication.java new file mode 100644 index 000000000..4c07f6c9e --- /dev/null +++ b/src/main/java/pairmatching/system/PairMatchingApplication.java @@ -0,0 +1,20 @@ +package pairmatching.system; + +import pairmatching.vo.ControllerName; + +import java.util.HashMap; +import java.util.Map; + +public class PairMatchingApplication { + public void run() { + Map model = new HashMap<>(); + setupData(model); + + ControllerHolder.get(ControllerName.MAIN).process(model); + } + + private static void setupData(Map model) { + ControllerHolder.get(ControllerName.SETUP_MISSION).process(model); + ControllerHolder.get(ControllerName.SETUP_CREW).process(model); + } +} diff --git a/src/main/java/pairmatching/system/exception/PairMatchingAlreadyExistException.java b/src/main/java/pairmatching/system/exception/PairMatchingAlreadyExistException.java new file mode 100644 index 000000000..669692e9b --- /dev/null +++ b/src/main/java/pairmatching/system/exception/PairMatchingAlreadyExistException.java @@ -0,0 +1,4 @@ +package pairmatching.system.exception; + +public class PairMatchingAlreadyExistException extends RuntimeException { +} diff --git a/src/main/java/pairmatching/system/exception/SamePairMatchedAtSameLevelException.java b/src/main/java/pairmatching/system/exception/SamePairMatchedAtSameLevelException.java new file mode 100644 index 000000000..e9fa6d462 --- /dev/null +++ b/src/main/java/pairmatching/system/exception/SamePairMatchedAtSameLevelException.java @@ -0,0 +1,4 @@ +package pairmatching.system.exception; + +public class SamePairMatchedAtSameLevelException extends RuntimeException { +} diff --git a/src/main/java/pairmatching/vo/ControllerName.java b/src/main/java/pairmatching/vo/ControllerName.java new file mode 100644 index 000000000..a59c982d3 --- /dev/null +++ b/src/main/java/pairmatching/vo/ControllerName.java @@ -0,0 +1,5 @@ +package pairmatching.vo; + +public enum ControllerName { + SETUP_CREW, MAIN, MATCHING, FIND, RESET, SETUP_MISSION +} diff --git a/src/main/java/pairmatching/vo/MainCommand.java b/src/main/java/pairmatching/vo/MainCommand.java new file mode 100644 index 000000000..e6a52f224 --- /dev/null +++ b/src/main/java/pairmatching/vo/MainCommand.java @@ -0,0 +1,33 @@ +package pairmatching.vo; + +import java.util.Arrays; + +public enum MainCommand { + MATCHING("1", "페어 매칭"), + FIND("2", "페어 조회"), + RESET("3", "페어 초기화"), + QUIT("Q", "종료"); + + private final String command; + private final String description; + + MainCommand(String command, String description) { + this.command = command; + this.description = description; + } + + public static MainCommand findByCommand(String command) { + return Arrays.stream(values()) + .filter(mainCommand -> mainCommand.command.equals(command)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("1, 2, 3, Q 중 하나가 아닌 커멘드 입력")); + } + + public String getCommand() { + return command; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/pairmatching/vo/MatchingCommand.java b/src/main/java/pairmatching/vo/MatchingCommand.java new file mode 100644 index 000000000..f9ea61e72 --- /dev/null +++ b/src/main/java/pairmatching/vo/MatchingCommand.java @@ -0,0 +1,38 @@ +package pairmatching.vo; + +import pairmatching.domain.Course; +import pairmatching.domain.Level; +import pairmatching.domain.Mission; +import pairmatching.repository.MissionRepository; + +public class MatchingCommand { + public static final String NOT_EXISTING_MISSION = "해당 레벨과 미션 이름에 일치하는 미션이 없음"; + private final Course course; + private final Mission mission; + + public MatchingCommand(Course course, Mission mission) { + this.course = course; + this.mission = mission; + } + + public static MatchingCommand of(Course course, Level level, String missionName) { + Validator.validate(level, missionName); + return new MatchingCommand(course, Mission.of(level, missionName)); + } + + private static class Validator { + public static void validate(Level level, String missionName) { + if (!MissionRepository.isExistingWith(level, missionName)) { + throw new IllegalArgumentException(NOT_EXISTING_MISSION); + } + } + } + + public Course getCourse() { + return course; + } + + public Mission getMission() { + return mission; + } +} diff --git a/src/main/java/pairmatching/vo/PairMatchingInfo.java b/src/main/java/pairmatching/vo/PairMatchingInfo.java new file mode 100644 index 000000000..e93ea7442 --- /dev/null +++ b/src/main/java/pairmatching/vo/PairMatchingInfo.java @@ -0,0 +1,54 @@ +package pairmatching.vo; + +import pairmatching.domain.Course; +import pairmatching.domain.Level; +import pairmatching.domain.Mission; + +import java.util.Objects; + +public class PairMatchingInfo { + private final Course course; + private final Mission mission; + + private PairMatchingInfo(Course course, Mission mission) { + this.course = course; + this.mission = mission; + } + + public static PairMatchingInfo of(Course course, Mission mission) { + return new PairMatchingInfo(course, mission); + } + + public Course getCourse() { + return course; + } + + public Level getLevel() { + return mission.getLevel(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PairMatchingInfo that = (PairMatchingInfo) o; + return course == that.course && mission.equals(that.mission); + } + + @Override + public int hashCode() { + return Objects.hash(course, mission); + } + + public boolean isLevel(Level level) { + return this.getLevel().equals(level); + } + + public boolean isCourse(Course course) { + return this.getCourse().equals(course); + } +} diff --git a/src/main/java/pairmatching/vo/RematchingCommand.java b/src/main/java/pairmatching/vo/RematchingCommand.java new file mode 100644 index 000000000..d852cd313 --- /dev/null +++ b/src/main/java/pairmatching/vo/RematchingCommand.java @@ -0,0 +1,26 @@ +package pairmatching.vo; + +import java.util.Arrays; + +public enum RematchingCommand { + YES("네"), + NO("아니오"); + + public static final String INVALID_REMATCHING_COMMAND_VALUE = "네, 아니오가 아닌 유효하지 않은 입력"; + private final String command; + + RematchingCommand(String command) { + this.command = command; + } + + public static RematchingCommand findByCommand(String command) { + return Arrays.stream(values()) + .filter(rematchingCommand -> rematchingCommand.command.equals(command)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(INVALID_REMATCHING_COMMAND_VALUE)); + } + + public String getCommand() { + return command; + } +}