diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..b7009a40e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,70 @@ +# 기능 구현 목록 +- [X] : 페어 이름을 초기화 받는다. +- [X] : 기능 선택 목록을 출력한다. +- [X] : 기능을 선택 받는다. + - [X] `예외 처리` : 빈 값을 입력한 경우 + - [X] `예외 처리` : 1~3, Q 이외의 값을 입력한 경우. + +## 페어 매칭 기능 +- [X] : 페어 매칭 안내 문자열을 출력한다. + ``` + ############################################# + 과정: 백엔드 | 프론트엔드 + 미션: + - 레벨1: 자동차경주 | 로또 | 숫자야구게임 + - 레벨2: 장바구니 | 결제 | 지하철노선도 + - 레벨3: + - 레벨4: 성능개선 | 배포 + - 레벨5: + ############################################ + 과정, 레벨, 미션을 선택하세요. + ex) 백엔드, 레벨1, 자동차경주 + ``` +- [X] : 과정, 레벨, 미션을 `, `기준으로 구분되어 입력 받는다. (ex `백엔드, 레벨1, 자동차경주`) + - [X] `예외 처리` : 빈 값을 입력한 경우 + - [X] `예외 처리` : 구분자를 잘못 입력한 경우 + - [X] `예외 처리` : 일치하는 값이 없는 경우 + +- [X] 페어를 매칭한다. + - [X] : 두명씩 매칭한다. + - [X] : 매칭 대상이 홀수인 경우 한 페어는 3인으로 구성된다. + - [X] : 같은 레벨에서 이미 페어를 맺은 크루와는 다시 매칭되지 않는다. + +- [X] : 크루들 이름 목록을 List 형태로 준비한다. +- [X] : 크루 목록의 순서를 랜덤으로 섞는다. (shuffle 메서드를 활용해야 한다.) +- [X] : 홀수인 경우 마지막 남은 크루는 마지막 페어에 포함시킨다. + +- [X] : 같은 레벨에서 이미 페어로 만난적이 있는 크루끼리 다시 페어로 매칭 된다면 크루 목록의 순서를 다시 랜덤으로 섞어서 매칭을 시도한다. + - [X] : 안내 문구를 출력 후 사용자에게 네, 아니오를 입력받는다. + - [X] `예외 처리` : 빈 값을 입력한 경우 + - [X] `예외 처리` : 네, 아니오가 아닌 값을 입력 받은 경우 + ```text + 매칭 정보가 있습니다. 다시 매칭하시겠습니까? + 네 | 아니오 + ``` + - [X] : 아니오를 선택할 경우 코스, 레벨, 미션을 다시 선택한다. + - [X] : 3회 시도까지 매칭이 되지 않거나 매칭을 할 수 있는 경우의 수가 없으면 에러 메시지를 출력한다. + +- [X] : 매칭 결과를 출력한다. + ```text + 페어 매칭 결과입니다. + 용팔 : 대만 + 대협 : 덕규 + 치수 : 준호 + 태웅 : 백호 + 달재 : 태산 + 한나 : 수겸 + 태섭 : 대남 + 준섭 : 소연 + 현준 : 호열 + 구식 : 경태 + ``` + +## 페어 조회 기능 +- [X] : 과정, 레벨, 미션을 선택하면 해당 미션의 페어 정보를 출력한다. +- [X] : 매칭 이력이 없으면 매칭 이력이 없다고 안내한다. ( `[ERROR] 매칭 이력이 없습니다.` ) + + +## 페어 초기화 기능 +- [X] : 페어를 초기화한다. + diff --git a/src/main/java/pairmatching/Application.java b/src/main/java/pairmatching/Application.java index 6f56e741c..807030c2d 100644 --- a/src/main/java/pairmatching/Application.java +++ b/src/main/java/pairmatching/Application.java @@ -1,7 +1,9 @@ package pairmatching; +import pairmatching.domain.PairMatchingController; + public class Application { public static void main(String[] args) { - // TODO 구현 진행 + PairMatchingController.run(); } } diff --git a/src/main/java/pairmatching/Utils.java b/src/main/java/pairmatching/Utils.java new file mode 100644 index 000000000..8b21f8ee1 --- /dev/null +++ b/src/main/java/pairmatching/Utils.java @@ -0,0 +1,25 @@ +package pairmatching; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Utils { + public static int countDuplicateElements(List list1, List list2) { + Map countMap = new HashMap<>(); + + for (String str : list1) { + countMap.put(str, countMap.getOrDefault(str, 0) + 1); + } + + int duplicateCount = 0; + + for (String str : list2) { + if (countMap.containsKey(str) && countMap.get(str) > 0) { + duplicateCount++; + } + } + + return duplicateCount; + } +} diff --git a/src/main/java/pairmatching/domain/CrewRepository.java b/src/main/java/pairmatching/domain/CrewRepository.java new file mode 100644 index 000000000..4e9dade8d --- /dev/null +++ b/src/main/java/pairmatching/domain/CrewRepository.java @@ -0,0 +1,42 @@ +package pairmatching.domain; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import pairmatching.domain.option.Course; + +public class CrewRepository { + private final List backend = new ArrayList<>(); + private final List frontend = new ArrayList<>(); + + public void initBackend(String path) { + addCrewsByFile(path, backend); + } + + public void initFrontend(String path) { + addCrewsByFile(path, frontend); + } + + public void addCrewsByFile(String path, List repository) { + try (BufferedReader br = new BufferedReader(new FileReader(path))) { + String line; + while ((line = br.readLine()) != null) { + repository.add(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public List findByCourse(Course course) { + if (course == Course.FRONTEND) { + return new ArrayList<>(frontend); + } + if (course == Course.BACKEND) { + return new ArrayList<>(backend); + } + return null; + } +} diff --git a/src/main/java/pairmatching/domain/PairMatchingController.java b/src/main/java/pairmatching/domain/PairMatchingController.java new file mode 100644 index 000000000..580e4db7e --- /dev/null +++ b/src/main/java/pairmatching/domain/PairMatchingController.java @@ -0,0 +1,111 @@ +package pairmatching.domain; + +import java.util.List; +import pairmatching.domain.pair.PairOption; +import pairmatching.domain.pair.Pairs; +import pairmatching.message.ExceptionMessage; +import pairmatching.message.ProgramOption; +import pairmatching.view.InputView; +import pairmatching.view.OutputView; + +public class PairMatchingController { + + public static void run() { + PairMatchingService.initCrews(); + proceed(); + } + + private static void proceed() { + while (true) { + String validateMenu = getValidateMenu(); + if (validateMenu.equals(ProgramOption.QUIT)) { + break; + } + handleByMenu(validateMenu); + } + } + + private static String getValidateMenu() { + try { + return PairMatchingService.getValidateMenu(InputView.readMenu()); + } catch (IllegalArgumentException error) { + OutputView.printException(error); + return getValidateMenu(); + } + } + + private static void handleByMenu(String validateMenu) { + if (validateMenu.equals(ProgramOption.PAIR_MATCHING)) { + handlePairMatching(); + return; + } + if (validateMenu.equals(ProgramOption.PAIR_SEARCH)) { + handlePairSearch(); + return; + } + if (validateMenu.equals(ProgramOption.PAIR_RESET)) { + handlePairReset(); + return; + } + throw new IllegalArgumentException(ExceptionMessage.INVALID_MENU_INPUT); + } + + private static void handlePairMatching() { + try { + PairOption pairOption = InputView.readOption(); + pairMatching(pairOption); + } catch (IllegalArgumentException error) { + OutputView.printException(error); + handlePairMatching(); + } + } + + private static void pairMatching(PairOption pairOption) { + if (PairMatchingService.hasHistory(pairOption)) { + handleRematch(pairOption); + return; + } + Pairs matchingResult = PairMatchingService.createNewPairMatching(pairOption); + PairMatchingService.addPairsToHistory(pairOption, matchingResult); + OutputView.printMatchingResult(matchingResult); + } + + private static void handleRematch(PairOption pairOption) { + if (getValidateRematch().equals(ProgramOption.YES)) { + // 3번 리매칭 + List history = PairMatchingService.getHistory(pairOption.getCourse(), pairOption.getLevel()); + Pairs matchingResult = PairMatchingService.createNoDuplicatePairMatching(pairOption, history); + OutputView.printMatchingResult(matchingResult); + return; + } + // 아니오를 선택한 경우 다시 코스, 레벨, 미션을 선택한다. + handlePairMatching(); + } + + private static String getValidateRematch() { + try { + return PairMatchingService.getValidateRematch(InputView.readRematch()); + } catch (IllegalArgumentException error) { + OutputView.printException(error); + return getValidateRematch(); + } + } + + private static void handlePairSearch() { + try { + PairOption pairOption = InputView.readOption(); + if (!PairMatchingService.hasHistory(pairOption)) { + throw new IllegalArgumentException(ExceptionMessage.NO_MATCHING_HISTORY); + } + Pairs matchedPairs = PairMatchingService.findHistory(pairOption); + OutputView.printMatchingResult(matchedPairs); + } catch (IllegalArgumentException error) { + OutputView.printException(error); + handlePairSearch(); + } + } + + private static void handlePairReset() { + PairMatchingService.reset(); + } +} diff --git a/src/main/java/pairmatching/domain/PairMatchingService.java b/src/main/java/pairmatching/domain/PairMatchingService.java new file mode 100644 index 000000000..151524a8f --- /dev/null +++ b/src/main/java/pairmatching/domain/PairMatchingService.java @@ -0,0 +1,118 @@ +package pairmatching.domain; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import pairmatching.domain.option.Course; +import pairmatching.domain.option.Level; +import pairmatching.domain.option.Mission; +import pairmatching.domain.pair.Pair; +import pairmatching.domain.pair.PairOption; +import pairmatching.domain.pair.Pairs; +import pairmatching.message.ExceptionMessage; +import pairmatching.message.ProgramOption; + +public class PairMatchingService { + private static CrewRepository crewRepository = new CrewRepository(); + private static Map history = new HashMap<>(); + + public static void initCrews() { + crewRepository = new CrewRepository(); + history = new HashMap<>(); + crewRepository.initBackend(ProgramOption.BACKEND_FILE_PATH); + crewRepository.initFrontend(ProgramOption.FRONTEND_FILE_PATH); + } + + public static String getValidateMenu(String input) { + validateMenu(input); + return input; + } + + private static void validateMenu(String input) { + if (input.length() != 1) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_MENU_INPUT); + } + if (!ProgramOption.MENU_INPUT_OPTIONS.contains(input)) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_MENU_INPUT); + } + } + public static Pairs createNewPairMatching(PairOption option) { + Pairs pairs = createPairMatching(option); + addPairsToHistory(option, pairs); + return pairs; + } + + public static Pairs createNoDuplicatePairMatching(PairOption option, List history) { + for (int i = 0; i < 3; i++) { + Pairs rematch = createPairMatching(option); + if (hasSamePair(history, rematch)) { + continue; + } + addPairsToHistory(option, rematch); + return rematch; + } + throw new IllegalArgumentException(ExceptionMessage.REMATCHING_FAIL); + } + + private static Pairs createPairMatching(PairOption option) { + List shuffleCrew = getShuffleCrew(option.getCourse()); + return Pairs.createByNameList(shuffleCrew); + } + + public static void addPairsToHistory(PairOption option, Pairs pairs) { + history.put(option, pairs); + } + + private static List getShuffleCrew(Course course) { + List crewNames = crewRepository.findByCourse(course); + if (crewNames == null) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_MENU_INPUT); + } + return Randoms.shuffle(crewNames); + } + + public static boolean hasHistory(PairOption option) { + return history.containsKey(option); + } + + public static List getHistory(Course course, Level level) { + List result = new ArrayList<>(); + Mission.findByLevel(level).forEach(mission -> { + PairOption option = new PairOption(course, level, mission); + if (hasHistory(option)) { + result.add(history.get(option)); + } + }); + return result; + } + + public static String getValidateRematch(String input) { + validateRematch(input); + return input; + } + + private static void validateRematch(String input) { + if (!input.equals(ProgramOption.YES) && !input.equals(ProgramOption.NO)) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_REMATCH_INPUT); + } + } + + public static boolean hasSamePair(List history, Pairs rematch) { + for (Pairs pairs : history) { + if (pairs.hasSamePair(rematch)) { + return true; + } + } + return false; + } + + public static Pairs findHistory(PairOption option) { + return history.get(option); + } + + public static void reset() { + history = new HashMap<>(); + } +} diff --git a/src/main/java/pairmatching/domain/option/Course.java b/src/main/java/pairmatching/domain/option/Course.java new file mode 100644 index 000000000..39555853a --- /dev/null +++ b/src/main/java/pairmatching/domain/option/Course.java @@ -0,0 +1,25 @@ +package pairmatching.domain.option; + +import pairmatching.message.ExceptionMessage; + +public enum Course { + BACKEND("백엔드"), + FRONTEND("프론트엔드"); + + private String name; + + Course(String name) { + this.name = name; + } + + + + public static Course findByName(String name) { + for (Course course : Course.values()) { + if (course.name.equals(name)) { + return course; + } + } + throw new IllegalArgumentException(ExceptionMessage.INVALID_INPUT); + } +} diff --git a/src/main/java/pairmatching/domain/option/Level.java b/src/main/java/pairmatching/domain/option/Level.java new file mode 100644 index 000000000..65d82d9a1 --- /dev/null +++ b/src/main/java/pairmatching/domain/option/Level.java @@ -0,0 +1,26 @@ +package pairmatching.domain.option; + +import pairmatching.message.ExceptionMessage; + +public enum Level { + LEVEL1("레벨1"), + LEVEL2("레벨2"), + LEVEL3("레벨3"), + LEVEL4("레벨4"), + LEVEL5("레벨5"); + + private String name; + + Level(String name) { + this.name = name; + } + + public static Level findByName(String name) { + for (Level level : Level.values()) { + if (level.name.equals(name)) { + return level; + } + } + throw new IllegalArgumentException(ExceptionMessage.INVALID_INPUT); + } +} diff --git a/src/main/java/pairmatching/domain/option/Mission.java b/src/main/java/pairmatching/domain/option/Mission.java new file mode 100644 index 000000000..a3586ef39 --- /dev/null +++ b/src/main/java/pairmatching/domain/option/Mission.java @@ -0,0 +1,45 @@ +package pairmatching.domain.option; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import pairmatching.message.ExceptionMessage; + +public enum Mission { + RACING_CAR("자동차경주", Level.LEVEL1), + LOTTO("로또", Level.LEVEL1), + BASEBALL("숫자야구게임", Level.LEVEL1), + SHOPPING_CART("장바구니", Level.LEVEL2), + PAYMENT("결제", Level.LEVEL2), + SUBWAY("지하철노선도", Level.LEVEL2), + PERFORMANCE_IMPROVEMENT("성능개선", Level.LEVEL4), + DEPLOYMENT("배포", Level.LEVEL4); + + private String name; + private Level level; + + Mission(String name, Level level) { + this.name = name; + this.level = level; + } + + public static Mission findByName(String name) { + for (Mission mission : Mission.values()) { + if (mission.name.equals(name)) { + return mission; + } + } + throw new IllegalArgumentException(ExceptionMessage.INVALID_INPUT); + } + + public static List findByLevel(Level level) { + return Arrays.stream(Mission.values()) + .filter(mission -> mission.level.equals(level)) + .collect(Collectors.toList()); + } + + public Level getLevel() { + return level; + } +} diff --git a/src/main/java/pairmatching/domain/pair/Pair.java b/src/main/java/pairmatching/domain/pair/Pair.java new file mode 100644 index 000000000..d1ab1c773 --- /dev/null +++ b/src/main/java/pairmatching/domain/pair/Pair.java @@ -0,0 +1,42 @@ +package pairmatching.domain.pair; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import pairmatching.Utils; + +public class Pair { + private final List crews; + + public Pair(String firstCrew, String secondCrew) { + crews = new ArrayList<>(); + crews.add(firstCrew); + crews.add(secondCrew); + } + + public void addCrew(String crew) { + crews.add(crew); + } + + public List getCrews() { + return new ArrayList<>(crews); + } + + + public boolean hasSamePair(Pair pair) { + if (crews.size() == 2 && pair.crews.size() == 2) { + Set set = new HashSet<>(); + set.addAll(crews); + set.addAll(pair.crews); + return set.size() == 2; + } + if (crews.size() == 3 && pair.crews.size() == 3) { + Set set = new HashSet<>(); + set.addAll(crews); + set.addAll(pair.crews); + return set.size() == 3; + } + return Utils.countDuplicateElements(crews, pair.crews) > 2; + } +} diff --git a/src/main/java/pairmatching/domain/pair/PairOption.java b/src/main/java/pairmatching/domain/pair/PairOption.java new file mode 100644 index 000000000..32fe049a3 --- /dev/null +++ b/src/main/java/pairmatching/domain/pair/PairOption.java @@ -0,0 +1,77 @@ +package pairmatching.domain.pair; + +import java.util.Objects; +import java.util.regex.Pattern; +import pairmatching.domain.option.Course; +import pairmatching.domain.option.Level; +import pairmatching.domain.option.Mission; +import pairmatching.message.ExceptionMessage; + +public class PairOption { + public static final int COURSE_INDEX = 0; + public static final int LEVEL_INDEX = 1; + public static final int MISSION_INDEX = 2; + public static final String OPTION_DELIMITER = ", "; + + private final Course course; + private final Level level; + private final Mission mission; + + public PairOption(Course course, Level level, Mission mission) { + validateMatch(level, mission); + this.course = course; + this.level = level; + this.mission = mission; + } + + private void validateMatch(Level level, Mission mission) { + if (!mission.getLevel().equals(level)) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_INPUT); + } + } + + public static PairOption createByString(String value) { + validateRegex(value); + String[] split = value.split(OPTION_DELIMITER); + Course course = Course.findByName(split[COURSE_INDEX]); + Level level = Level.findByName(split[LEVEL_INDEX]); + Mission mission = Mission.findByName(split[MISSION_INDEX]); + return new PairOption(course, level, mission); + } + + private static void validateRegex(String value) { + String regexPattern = "^[가-힣]+,\\s[가-힣]+\\d,\\s[가-힣]+$"; + + // 정규표현식 검사 + boolean isMatch = Pattern.matches(regexPattern, value); + + if (!isMatch) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_INPUT); + } + } + + public Course getCourse() { + return course; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PairOption that = (PairOption) o; + return course == that.course && level == that.level && mission == that.mission; + } + + @Override + public int hashCode() { + return Objects.hash(course, level, mission); + } + + public Level getLevel() { + return level; + } +} diff --git a/src/main/java/pairmatching/domain/pair/Pairs.java b/src/main/java/pairmatching/domain/pair/Pairs.java new file mode 100644 index 000000000..f7f07643f --- /dev/null +++ b/src/main/java/pairmatching/domain/pair/Pairs.java @@ -0,0 +1,49 @@ +package pairmatching.domain.pair; + +import java.util.ArrayList; +import java.util.List; + +public class Pairs { + private final List pairs; + + public Pairs(List pairs) { + this.pairs = pairs; + } + + public static Pairs createByNameList(List names) { + List pairs = new ArrayList<>(); + for (int i = 0; i < names.size(); i += 2) { + if (isThirdPair(names, i)) { + handleThirdPair(names, pairs, i); + break; + } + Pair newPair = new Pair(names.get(i), names.get(i + 1)); + pairs.add(newPair); + } + return new Pairs(pairs); + } + + private static void handleThirdPair(List names, List pairs, int i) { + Pair lastPair = pairs.get(pairs.size() - 1); + lastPair.addCrew(names.get(i)); + } + + private static boolean isThirdPair(List names, int i) { + return i + 1 == names.size(); + } + + public boolean hasSamePair(Pairs source) { + for (Pair pair : source.pairs) { + for (Pair thisPair : this.pairs) { + if (thisPair.hasSamePair(pair)) { + return true; + } + } + } + return false; + } + + public List getPairs() { + return new ArrayList<>(pairs); + } +} diff --git a/src/main/java/pairmatching/message/ExceptionMessage.java b/src/main/java/pairmatching/message/ExceptionMessage.java new file mode 100644 index 000000000..af6f9b92b --- /dev/null +++ b/src/main/java/pairmatching/message/ExceptionMessage.java @@ -0,0 +1,12 @@ +package pairmatching.message; + +public class ExceptionMessage { + public static final String EXCEPTION_PREFIX = "[ERROR] : "; + + public static final String INVALID_INPUT = "잘못된 입력입니다."; + public static final String INVALID_MENU_INPUT = "잘못된 메뉴 입력입니다."; + public static final String INVALID_REMATCH_INPUT = "잘못된 리매칭 입력입니다."; + + public static final String REMATCHING_FAIL = "3번의 리매칭을 모두 실패했습니다."; + public static final String NO_MATCHING_HISTORY = "매칭 기록이 없습니다."; +} diff --git a/src/main/java/pairmatching/message/ProgramOption.java b/src/main/java/pairmatching/message/ProgramOption.java new file mode 100644 index 000000000..966fadb69 --- /dev/null +++ b/src/main/java/pairmatching/message/ProgramOption.java @@ -0,0 +1,15 @@ +package pairmatching.message; + +public class ProgramOption { + public static final String BACKEND_FILE_PATH = "src/main/resources/backend-crew.md"; + public static final String FRONTEND_FILE_PATH = "src/main/resources/frontend-crew.md"; + + public static final String PAIR_MATCHING = "1"; + public static final String PAIR_SEARCH = "2"; + public static final String PAIR_RESET = "3"; + public static final String QUIT = "Q"; + public static final String MENU_INPUT_OPTIONS = "123Q"; + + public static final String YES = "네"; + public static final String NO = "아니오"; +} diff --git a/src/main/java/pairmatching/message/ViewMessage.java b/src/main/java/pairmatching/message/ViewMessage.java new file mode 100644 index 000000000..5d2db24e6 --- /dev/null +++ b/src/main/java/pairmatching/message/ViewMessage.java @@ -0,0 +1,20 @@ +package pairmatching.message; + +public class ViewMessage { + public static final String INPUT_MENU_MESSAGE = "기능을 선택하세요.\n1. 페어 매칭\n2. 페어 조회\n3. 페어 초기화\nQ. 종료"; + public static final String INPUT_REMATCH_MESSAGE = "매칭 정보가 있습니다. 다시 매칭하시겠습니까?\n네 | 아니오"; + public static final String INPUT_OPTION_PRE_MESSAGE = + "#############################################\n" + + "과정: 백엔드 | 프론트엔드\n" + + "미션:\n" + + " - 레벨1: 자동차경주 | 로또 | 숫자야구게임\n" + + " - 레벨2: 장바구니 | 결제 | 지하철노선도\n" + + " - 레벨3: \n" + + " - 레벨4: 성능개선 | 배포\n" + + " - 레벨5: \n" + + "############################################"; + public static final String INPUT_OPTION_MESSAGE = "과정, 레벨, 미션을 선택하세요.\nex) 백엔드, 레벨1, 자동차경주"; + + public static final String OUTPUT_PAIR_RESULT_PRE_MESSAGE = "페어 매칭 결과입니다."; + public static final String OUTPUT_PAIR_DELIMITER = " : "; +} diff --git a/src/main/java/pairmatching/view/InputView.java b/src/main/java/pairmatching/view/InputView.java new file mode 100644 index 000000000..c7ea6e398 --- /dev/null +++ b/src/main/java/pairmatching/view/InputView.java @@ -0,0 +1,38 @@ +package pairmatching.view; + +import camp.nextstep.edu.missionutils.Console; +import pairmatching.domain.pair.PairOption; +import pairmatching.message.ExceptionMessage; +import pairmatching.message.ViewMessage; + +public class InputView { + public static String readMenu() { + System.out.println(ViewMessage.INPUT_MENU_MESSAGE); + String input = readString(); + return input; + } + + private static String readString() { + String input = Console.readLine().trim(); + validateBlank(input); + return input; + } + + private static void validateBlank(String input) { + if (input == null || input.isEmpty()) { + throw new IllegalArgumentException(ExceptionMessage.INVALID_INPUT); + } + } + + public static String readRematch() { + System.out.println(ViewMessage.INPUT_REMATCH_MESSAGE); + return readString(); + } + + public static PairOption readOption() { + System.out.println(ViewMessage.INPUT_OPTION_PRE_MESSAGE); + System.out.println(ViewMessage.INPUT_OPTION_MESSAGE); + String input = readString(); + return PairOption.createByString(input); + } +} diff --git a/src/main/java/pairmatching/view/OutputView.java b/src/main/java/pairmatching/view/OutputView.java new file mode 100644 index 000000000..7215a78dd --- /dev/null +++ b/src/main/java/pairmatching/view/OutputView.java @@ -0,0 +1,20 @@ +package pairmatching.view; + +import pairmatching.domain.pair.Pair; +import pairmatching.domain.pair.Pairs; +import pairmatching.message.ExceptionMessage; +import pairmatching.message.ViewMessage; + +public class OutputView { + public static void printException(Exception error) { + System.out.println(ExceptionMessage.EXCEPTION_PREFIX + error.getMessage()); + } + + public static void printMatchingResult(Pairs matchingResult) { + System.out.println(ViewMessage.OUTPUT_PAIR_RESULT_PRE_MESSAGE); + for (Pair pair : matchingResult.getPairs()) { + String pairString = String.join(ViewMessage.OUTPUT_PAIR_DELIMITER, pair.getCrews()); + System.out.println(pairString); + } + } +} diff --git a/src/test/java/pairmatching/ApplicationTest.java b/src/test/java/pairmatching/ApplicationTest.java index 2dff1e6d3..271810999 100644 --- a/src/test/java/pairmatching/ApplicationTest.java +++ b/src/test/java/pairmatching/ApplicationTest.java @@ -24,6 +24,32 @@ class ApplicationTest extends NsTest { ); } + @Test + void 홀수_인원_페어_매칭() { + assertShuffleTest( + () -> { + run("1", "프론트엔드, 레벨1, 자동차경주", "Q"); + assertThat(output()).contains("보노 : 시저", "쉐리 : 신디 : 다비"); + }, + Arrays.asList("보노", "시저", "쉐리", "신디", "다비") + ); + } + + @Test + void 재매칭_실패() { + assertShuffleTest( + () -> { + runException("1", "백엔드, 레벨1, 자동차경주", "1", "백엔드, 레벨1, 자동차경주", "네", "프론트엔드, 레벨1, 자동차경주"); + assertThat(output()).contains("3번의 리매칭을 모두 실패했습니다."); + }, + Arrays.asList("태웅", "백호", "치수", "태섭"), + Arrays.asList("태웅", "백호", "치수", "태섭"), + Arrays.asList("태웅", "백호", "치수", "태섭"), + Arrays.asList("태웅", "백호", "치수", "태섭"), + Arrays.asList("보노", "시저", "쉐리", "신디", "다비") + ); + } + @Test void 없는_미션에_대한_예외_처리() { assertSimpleTest( @@ -34,6 +60,36 @@ class ApplicationTest extends NsTest { ); } + @Test + void 없는_코스에_대한_예외_처리() { + assertSimpleTest( + () -> { + runException("1", "안드로이드, 레벨1, 자동차경주"); + assertThat(output()).contains(ERROR_MESSAGE); + } + ); + } + + @Test + void 없는_레벨에_대한_예외_처리() { + assertSimpleTest( + () -> { + runException("1", "백엔드, 레벨6, 자동차경주"); + assertThat(output()).contains(ERROR_MESSAGE); + } + ); + } + + @Test + void 매칭되지_않는_미션에_대한_예외_처리() { + assertSimpleTest( + () -> { + runException("1", "백엔드, 레벨2, 숫자야구게임"); + assertThat(output()).contains(ERROR_MESSAGE); + } + ); + } + @Override public void runMain() { Application.main(new String[]{}); diff --git a/src/test/java/pairmatching/PairMatchingServiceTest.java b/src/test/java/pairmatching/PairMatchingServiceTest.java new file mode 100644 index 000000000..4c82643db --- /dev/null +++ b/src/test/java/pairmatching/PairMatchingServiceTest.java @@ -0,0 +1,25 @@ +package pairmatching; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import pairmatching.domain.PairMatchingService; + +class PairMatchingServiceTest { + + @ParameterizedTest + @ValueSource(strings = {"1", "2", "3", "Q"}) + void getValidateMenuSuccess(String value) { + Assertions.assertThatNoException().isThrownBy(() -> { + PairMatchingService.getValidateMenu(value); + }); + } + + @ParameterizedTest + @ValueSource(strings = {"", " ", "", "B", "q", "12", "2b", "4", "a"}) + void getValidateMenuFail(String value) { + Assertions.assertThatThrownBy(() -> { + PairMatchingService.getValidateMenu(value); + }); + } +} \ No newline at end of file