diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..42903af20 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,29 @@ +- 사전 제공 정보 +* [x] 크루 목록이 담긴 파일을 읽어 리스트에 저장한다. +* [x] 미션 목록을 리스트에 저장한다. +* [x] 페어 정보를 관리하는 저장소를 설계한다. + +- 페어 매칭 +* [x] 기능의 종류를 출력한다. +* [x] 기능 중 하나의 입력을 받는다. +* [x] 과정과 미션을 출력한다. +* [x] 매칭하고자 하는 과정, 레벨, 미션을 입력 받는다. + +* [x] 크루 목록의 순서를 랜덤으로 섞는다. +* [x] 랜덤으로 섞인 페어 목록에서 페어 매칭을 할 때 앞에서부터 순서대로 두명씩 페어를 맺는다. +* [x] 홀수인 경우 마지막 남은 크루는 마지막 페어에 포함시킨다. +* [x] 같은 레벨에서 이미 페어로 만난적이 있는 크루끼리 다시 페어로 매칭 된다면 크루 목록의 순서를 다시 랜덤으로 섞어서 매칭을 시도한다. +* [x] 3회 시도까지 매칭이 되지 않거나 매칭을 할 수 있는 경우의 수가 없으면 에러 메시지를 출력한다. + +* [x] 페어 매칭 결과를 출력한다. + +- 페어 조회 +* [x] 과정과 미션을 출력한다. +* [x] 조회하고자 하는 과정, 레벨, 미션을 입력 받는다. + +- 페어 초기화 +* [x] 커맨드를 호출하면 저장된 페어 매칭을 제거한다. +* [x] 초기화 완료 메시지를 출력한다. + +- 종료 +* [x] 종료 커맨드를 입력하면 애플리케이션이 종료된다. \ No newline at end of file diff --git a/src/main/java/pairmatching/Application.java b/src/main/java/pairmatching/Application.java index 6f56e741c..fc3679032 100644 --- a/src/main/java/pairmatching/Application.java +++ b/src/main/java/pairmatching/Application.java @@ -1,7 +1,11 @@ package pairmatching; +import pairmatching.system.PairApplication; + public class Application { public static void main(String[] args) { // TODO 구현 진행 + PairApplication pairApplication = new PairApplication(); + pairApplication.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..19db19479 --- /dev/null +++ b/src/main/java/pairmatching/controller/AbstractController.java @@ -0,0 +1,24 @@ +package pairmatching.controller; + +import pairmatching.outputview.ErrorMessageOutputView; +import pairmatching.outputview.OutputView; + +import java.util.Map; + +public abstract class AbstractController implements Controller { + private final OutputView outputView = new ErrorMessageOutputView(); + + @Override + public void process(Map model) { + try { + doProcess(model); + } catch (IllegalArgumentException e) { + model.put("errorMessage", e.getMessage()); + outputView.print(model); + + doProcess(model); + } + } + + 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/FindPairController.java b/src/main/java/pairmatching/controller/FindPairController.java new file mode 100644 index 000000000..a970bb8cd --- /dev/null +++ b/src/main/java/pairmatching/controller/FindPairController.java @@ -0,0 +1,24 @@ +package pairmatching.controller; + +import pairmatching.outputview.OutputView; +import pairmatching.repository.PairMatchingRepository; +import pairmatching.vo.PairMatchingInfo; + +import java.util.Map; + +public class FindPairController extends AbstractController { + private final OutputView outputView; + private final PairMatchingRepository pairMatchingRepository; + + public FindPairController(OutputView outputView, PairMatchingRepository pairMatchingRepository) { + this.outputView = outputView; + this.pairMatchingRepository = pairMatchingRepository; + } + + @Override + public void doProcess(Map model) { + PairMatchingInfo pairMatchingInfo = (PairMatchingInfo) model.get("pairMatchingInfo"); + model.put("matchedPairNames", pairMatchingRepository.findAllNamesByPairMatchingInfo(pairMatchingInfo)); + outputView.print(model); + } +} diff --git a/src/main/java/pairmatching/controller/MatchingPairController.java b/src/main/java/pairmatching/controller/MatchingPairController.java new file mode 100644 index 000000000..95da16241 --- /dev/null +++ b/src/main/java/pairmatching/controller/MatchingPairController.java @@ -0,0 +1,62 @@ +package pairmatching.controller; + +import camp.nextstep.edu.missionutils.Randoms; +import pairmatching.model.Crew; +import pairmatching.model.Pair; +import pairmatching.outputview.OutputView; +import pairmatching.repository.CrewRepository; +import pairmatching.repository.PairMatchingRepository; +import pairmatching.system.util.PairsMaker; +import pairmatching.vo.PairMatchingInfo; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class MatchingPairController extends AbstractController { + public static final String CREW_MATCHING_FAILED_THREE_TIMES_MESSAGE = "크루 매칭이 3회 이상 실패하였습니다."; + private final CrewRepository crewRepository; + private final PairMatchingRepository pairMatchingRepository; + private final PairsMaker pairsMaker; + private final OutputView outputView; + + public MatchingPairController(CrewRepository crewRepository, PairMatchingRepository pairMatchingRepository, PairsMaker pairsMaker, OutputView outputView) { + this.crewRepository = crewRepository; + this.pairMatchingRepository = pairMatchingRepository; + this.pairsMaker = pairsMaker; + this.outputView = outputView; + } + + @Override + public void doProcess(Map model) { + PairMatchingInfo pairMatchingInfo = (PairMatchingInfo) model.get("pairMatchingInfo"); + List crews = crewRepository.findByCourse(pairMatchingInfo.getCourse()); + List pairs; + + pairs = makePairs(pairMatchingInfo, crews); + pairMatchingRepository.save(pairMatchingInfo, pairs); + + model.put("matchedPairNames", pairMatchingRepository.findAllNamesByPairMatchingInfo(pairMatchingInfo)); + outputView.print(model); + } + + private List makePairs(PairMatchingInfo pairMatchingInfo, List crews) { + List pairs; + int notMatchedCount = 0; + do { + pairs = pairsMaker.makePairs(getShuffledCrewNames(crews)); + notMatchedCount++; + if (notMatchedCount == 3) { + throw new IllegalStateException(CREW_MATCHING_FAILED_THREE_TIMES_MESSAGE); + } + } while (pairMatchingRepository.hasDuplicatingAtSameLevel(pairMatchingInfo, pairs)); + return pairs; + } + + private List getShuffledCrewNames(List crews) { + List crewNames = crews.stream() + .map(Crew::getName) + .collect(Collectors.toList()); + return Randoms.shuffle(crewNames); + } +} diff --git a/src/main/java/pairmatching/controller/ReadingCrewsFileController.java b/src/main/java/pairmatching/controller/ReadingCrewsFileController.java new file mode 100644 index 000000000..922b7f9b9 --- /dev/null +++ b/src/main/java/pairmatching/controller/ReadingCrewsFileController.java @@ -0,0 +1,66 @@ +package pairmatching.controller; + +import pairmatching.model.Course; +import pairmatching.model.Crew; +import pairmatching.repository.CrewRepository; +import pairmatching.system.convertion.NamesToCrewConverter; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ReadingCrewsFileController extends AbstractController { + + public static final String BACKEND_CREWS_FILE_DIRECTORY = "src/main/resources/backend-crew.md"; + public static final String FRONTEND_CREWS_FILE_DIRECTORY = "src/main/resources/frontend-crew.md"; + + private final CrewRepository crewRepository; + + public ReadingCrewsFileController(CrewRepository crewRepository) { + this.crewRepository = crewRepository; + } + + @Override + public void doProcess(Map model) { + try { + List backEndCrews = NamesToCrewConverter.convert(readBackEndCrews(), Course.BACKEND); + crewRepository.saveAll(backEndCrews); + + List frontEndCrews = NamesToCrewConverter.convert(readFrontEndCrews(), Course.FRONTEND); + crewRepository.saveAll(frontEndCrews); + } catch (IOException e) { + throw new IllegalStateException("크루 파일 읽기에 문제가 발생했습니다."); + } + } + + private static List readBackEndCrews() throws IOException { + List crewNames = new ArrayList<>(); + readFile(crewNames, BACKEND_CREWS_FILE_DIRECTORY); + return crewNames; + } + + private static List readFrontEndCrews() throws IOException { + List crewNames = new ArrayList<>(); + readFile(crewNames, FRONTEND_CREWS_FILE_DIRECTORY); + return crewNames; + } + + private static void readFile(List crewNames, String fileDirectory) throws IOException { + File file = new File(fileDirectory); + FileReader fileReader = new FileReader(file); + BufferedReader bufferedReader = new BufferedReader(fileReader); + readLine(crewNames, bufferedReader); + bufferedReader.close(); + } + + private static void readLine(List crewNames, BufferedReader bufferedReader) throws IOException { + String line = ""; + while ((line = bufferedReader.readLine()) != null) { + crewNames.add(line); + } + } +} diff --git a/src/main/java/pairmatching/controller/ResetPairController.java b/src/main/java/pairmatching/controller/ResetPairController.java new file mode 100644 index 000000000..330e6d458 --- /dev/null +++ b/src/main/java/pairmatching/controller/ResetPairController.java @@ -0,0 +1,22 @@ +package pairmatching.controller; + +import pairmatching.outputview.OutputView; +import pairmatching.repository.PairMatchingRepository; + +import java.util.Map; + +public class ResetPairController extends AbstractController { + private final PairMatchingRepository pairMatchingRepository; + private final OutputView outputView; + + public ResetPairController(PairMatchingRepository pairMatchingRepository, OutputView outputView) { + this.pairMatchingRepository = pairMatchingRepository; + this.outputView = outputView; + } + + @Override + public void doProcess(Map model) { + pairMatchingRepository.resetAll(); + outputView.print(model); + } +} diff --git a/src/main/java/pairmatching/controller/SavingMissionsController.java b/src/main/java/pairmatching/controller/SavingMissionsController.java new file mode 100644 index 000000000..5b37ac357 --- /dev/null +++ b/src/main/java/pairmatching/controller/SavingMissionsController.java @@ -0,0 +1,39 @@ +package pairmatching.controller; + +import pairmatching.model.Level; +import pairmatching.repository.MissionRepository; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class SavingMissionsController extends AbstractController { + private final MissionRepository missionRepository; + + public SavingMissionsController(MissionRepository missionRepository) { + this.missionRepository = missionRepository; + } + + @Override + public void doProcess(Map model) { + saveMissions(getLevelOneMissionNames(), Level.LEVEL1); + saveMissions(getLevelTwoMissionNames(), Level.LEVEL2); + saveMissions(getLevelFiveMissionNames(), Level.LEVEL4); + } + + private static List getLevelFiveMissionNames() { + return Arrays.asList("성능개선", "배포"); + } + + private static List getLevelTwoMissionNames() { + return Arrays.asList("장바구니", "결제", "지하철노선도"); + } + + private static List getLevelOneMissionNames() { + return Arrays.asList("자동차경주", "로또", "숫자야구게임"); + } + + private void saveMissions(List missionNames, Level level) { + missionRepository.saveAllNamesWithLevel(missionNames, level); + } +} diff --git a/src/main/java/pairmatching/controller/SelectingFeatureController.java b/src/main/java/pairmatching/controller/SelectingFeatureController.java new file mode 100644 index 000000000..62767afc1 --- /dev/null +++ b/src/main/java/pairmatching/controller/SelectingFeatureController.java @@ -0,0 +1,23 @@ +package pairmatching.controller; + +import pairmatching.inputview.InputView; +import pairmatching.outputview.OutputView; +import pairmatching.vo.FeatureCommand; + +import java.util.Map; + +public class SelectingFeatureController extends AbstractController { + private final OutputView outputView; + private final InputView inputView; + + public SelectingFeatureController(OutputView outputView, InputView inputView) { + this.outputView = outputView; + this.inputView = inputView; + } + + @Override + public void doProcess(Map model) { + outputView.print(model); + model.put("featureCommand", inputView.getInput(model)); + } +} diff --git a/src/main/java/pairmatching/controller/SelectingMissionController.java b/src/main/java/pairmatching/controller/SelectingMissionController.java new file mode 100644 index 000000000..449d4acc2 --- /dev/null +++ b/src/main/java/pairmatching/controller/SelectingMissionController.java @@ -0,0 +1,57 @@ +package pairmatching.controller; + +import pairmatching.inputview.InputView; +import pairmatching.model.Course; +import pairmatching.model.Level; +import pairmatching.outputview.OutputView; +import pairmatching.repository.MissionRepository; +import pairmatching.vo.PairMatchingInfo; + +import java.util.Map; + +public class SelectingMissionController extends AbstractController { + private final OutputView outputView; + private final InputView inputView; + private final MissionRepository missionRepository; + + public SelectingMissionController(OutputView outputView, InputView inputView, MissionRepository missionRepository) { + this.outputView = outputView; + this.inputView = inputView; + this.missionRepository = missionRepository; + } + + @Override + public void doProcess(Map model) { + printMessage(model); + readInputIntoModel(model); + } + + private void readInputIntoModel(Map model) { + putAllMissionsToModel(model); + model.put("pairMatchingInfo", inputView.getInput(model)); + } + + private void printMessage(Map model) { + putCoursesNameToModel(model); + putMissionNamesToModel(model); + outputView.print(model); + } + + private Object putAllMissionsToModel(Map model) { + return model.put("missions", missionRepository.findAll()); + } + + private static Object putCoursesNameToModel(Map model) { + return model.put("courses", Course.getCourseNames()); + } + + private void putMissionNamesToModel(Map model) { + model.put("level1Missions", missionRepository.findAllNamesByLevel(Level.LEVEL1)); + model.put("level2Missions", missionRepository.findAllNamesByLevel(Level.LEVEL2)); + model.put("level3Missions", missionRepository.findAllNamesByLevel(Level.LEVEL3)); + model.put("level4Missions", missionRepository.findAllNamesByLevel(Level.LEVEL4)); + model.put("level5Missions", missionRepository.findAllNamesByLevel(Level.LEVEL5)); + } + + +} diff --git a/src/main/java/pairmatching/inputview/GettingFeatureCommandInputView.java b/src/main/java/pairmatching/inputview/GettingFeatureCommandInputView.java new file mode 100644 index 000000000..7f1b9b01f --- /dev/null +++ b/src/main/java/pairmatching/inputview/GettingFeatureCommandInputView.java @@ -0,0 +1,19 @@ +package pairmatching.inputview; + +import camp.nextstep.edu.missionutils.Console; +import pairmatching.system.convertion.StringToFeatureCommandConverter; +import pairmatching.vo.FeatureCommand; + +import java.util.Map; + +public class GettingFeatureCommandInputView implements InputView { + @Override + public FeatureCommand getInput(Map model) { + String input = readInput(); + return StringToFeatureCommandConverter.convert(input); + } + + protected static String readInput() { + return Console.readLine(); + } +} diff --git a/src/main/java/pairmatching/inputview/InputView.java b/src/main/java/pairmatching/inputview/InputView.java new file mode 100644 index 000000000..4390ec9ab --- /dev/null +++ b/src/main/java/pairmatching/inputview/InputView.java @@ -0,0 +1,8 @@ +package pairmatching.inputview; + +import java.util.Map; + +@FunctionalInterface +public interface InputView { + E getInput(Map model); +} diff --git a/src/main/java/pairmatching/inputview/SelectingMissionInputView.java b/src/main/java/pairmatching/inputview/SelectingMissionInputView.java new file mode 100644 index 000000000..0f794ceb2 --- /dev/null +++ b/src/main/java/pairmatching/inputview/SelectingMissionInputView.java @@ -0,0 +1,21 @@ +package pairmatching.inputview; + +import camp.nextstep.edu.missionutils.Console; +import pairmatching.model.Mission; +import pairmatching.system.convertion.StringToPairMatchingInfoConverter; +import pairmatching.vo.PairMatchingInfo; + +import java.util.List; +import java.util.Map; + +public class SelectingMissionInputView implements InputView { + @Override + public PairMatchingInfo getInput(Map model) { + String input = readInput(); + return StringToPairMatchingInfoConverter.convert(input, (List) model.get("missions")); + } + + protected static String readInput() { + return Console.readLine(); + } +} diff --git a/src/main/java/pairmatching/model/Course.java b/src/main/java/pairmatching/model/Course.java new file mode 100644 index 000000000..ea16a530a --- /dev/null +++ b/src/main/java/pairmatching/model/Course.java @@ -0,0 +1,36 @@ +package pairmatching.model; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public enum Course { + BACKEND("백엔드"), + FRONTEND("프론트엔드"); + + public static final String INVALID_COURSE_NAME_MESSAGE = "주어진 이름과 일치하는 코스가 없습니다."; + private final String name; + + Course(String name) { + this.name = name; + } + + public static Course findByName(String courseName) { + return Arrays.stream(values()) + .filter(course -> course.name.equals(courseName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(INVALID_COURSE_NAME_MESSAGE)); + } + + // 추가 기능 구현 + + public String getName() { + return name; + } + + public static List getCourseNames() { + return Arrays.stream(Course.values()) + .map(Course::getName) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/pairmatching/model/Crew.java b/src/main/java/pairmatching/model/Crew.java new file mode 100644 index 000000000..be2bd4815 --- /dev/null +++ b/src/main/java/pairmatching/model/Crew.java @@ -0,0 +1,19 @@ +package pairmatching.model; + +public class Crew { + private final Course course; + private final String name; + + public Crew(Course course, String name) { + this.course = course; + this.name = name; + } + + public Course getCourse() { + return course; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/pairmatching/model/Level.java b/src/main/java/pairmatching/model/Level.java new file mode 100644 index 000000000..9c58ec02e --- /dev/null +++ b/src/main/java/pairmatching/model/Level.java @@ -0,0 +1,21 @@ +package pairmatching.model; + +public enum Level { + LEVEL1("레벨1"), + LEVEL2("레벨2"), + LEVEL3("레벨3"), + LEVEL4("레벨4"), + LEVEL5("레벨5"); + + private final String name; + + Level(String name) { + this.name = name; + } + + // 추가 기능 구현 + + public String getName() { + return name; + } +} diff --git a/src/main/java/pairmatching/model/Mission.java b/src/main/java/pairmatching/model/Mission.java new file mode 100644 index 000000000..72f7f22b6 --- /dev/null +++ b/src/main/java/pairmatching/model/Mission.java @@ -0,0 +1,38 @@ +package pairmatching.model; + +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 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); + } +} diff --git a/src/main/java/pairmatching/model/Pair.java b/src/main/java/pairmatching/model/Pair.java new file mode 100644 index 000000000..4a05106ba --- /dev/null +++ b/src/main/java/pairmatching/model/Pair.java @@ -0,0 +1,16 @@ +package pairmatching.model; + +import java.util.Collections; +import java.util.List; + +public class Pair { + private final List crews; + + public Pair(List crews) { + this.crews = crews; + } + + public List getCrewNames() { + return Collections.unmodifiableList(crews); + } +} diff --git a/src/main/java/pairmatching/outputview/ErrorMessageOutputView.java b/src/main/java/pairmatching/outputview/ErrorMessageOutputView.java new file mode 100644 index 000000000..f1158b2d3 --- /dev/null +++ b/src/main/java/pairmatching/outputview/ErrorMessageOutputView.java @@ -0,0 +1,11 @@ +package pairmatching.outputview; + +import java.util.Map; + +public class ErrorMessageOutputView implements OutputView { + @Override + public void print(Map model) { + String errorMessage = (String) model.get("errorMessage"); + System.out.printf("%s %s", "[ERROR]", errorMessage); + } +} diff --git a/src/main/java/pairmatching/outputview/MatchingResultOutputView.java b/src/main/java/pairmatching/outputview/MatchingResultOutputView.java new file mode 100644 index 000000000..a6417a02d --- /dev/null +++ b/src/main/java/pairmatching/outputview/MatchingResultOutputView.java @@ -0,0 +1,33 @@ +package pairmatching.outputview; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class MatchingResultOutputView implements OutputView { + public static final String SEPARATOR_FORMAT = " %s "; + public static final String VALUE_SEPARATOR = ":"; + + @Override + public void print(Map model) { + System.out.println("페어 매칭 결과입니다."); + for (List matchedPairNames : (List>) model.get("matchedPairNames")) { + printValues(matchedPairNames); + } + } + + private void printValues(List values) { + Iterator iterator = values.iterator(); + while (iterator.hasNext()) { + System.out.print(iterator.next()); + if (iterator.hasNext()) { + addSeparator(); + } + } + System.out.println(); + } + + private void addSeparator() { + System.out.printf(SEPARATOR_FORMAT, VALUE_SEPARATOR); + } +} diff --git a/src/main/java/pairmatching/outputview/OutputView.java b/src/main/java/pairmatching/outputview/OutputView.java new file mode 100644 index 000000000..c2cf8b91f --- /dev/null +++ b/src/main/java/pairmatching/outputview/OutputView.java @@ -0,0 +1,8 @@ +package pairmatching.outputview; + +import java.util.Map; + +@FunctionalInterface +public interface OutputView { + void print(Map model); +} diff --git a/src/main/java/pairmatching/outputview/ResetOutputView.java b/src/main/java/pairmatching/outputview/ResetOutputView.java new file mode 100644 index 000000000..13a373eca --- /dev/null +++ b/src/main/java/pairmatching/outputview/ResetOutputView.java @@ -0,0 +1,13 @@ +package pairmatching.outputview; + +import java.util.Map; + +public class ResetOutputView implements OutputView { + + public static final String RESET_COMPLETED_MESSAGE = "초기화 되었습니다."; + + @Override + public void print(Map model) { + System.out.println(RESET_COMPLETED_MESSAGE); + } +} diff --git a/src/main/java/pairmatching/outputview/SelectingFeatureOutputView.java b/src/main/java/pairmatching/outputview/SelectingFeatureOutputView.java new file mode 100644 index 000000000..48423f7e4 --- /dev/null +++ b/src/main/java/pairmatching/outputview/SelectingFeatureOutputView.java @@ -0,0 +1,19 @@ +package pairmatching.outputview; + +import pairmatching.vo.FeatureCommand; + +import java.util.Map; + +public class SelectingFeatureOutputView implements OutputView { + + public static final String SELECT_FEATURE_MESSAGE = "기능을 선택하세요."; + public static final String FEATURE_FORMAT = "%s. %s%n"; + + @Override + public void print(Map model) { + System.out.println(SELECT_FEATURE_MESSAGE); + for (FeatureCommand featureCommand : FeatureCommand.values()) { + System.out.printf(FEATURE_FORMAT, featureCommand.getCommand(), featureCommand.getDescription()); + } + } +} diff --git a/src/main/java/pairmatching/outputview/SelectingMissionOutputView.java b/src/main/java/pairmatching/outputview/SelectingMissionOutputView.java new file mode 100644 index 000000000..514b9992d --- /dev/null +++ b/src/main/java/pairmatching/outputview/SelectingMissionOutputView.java @@ -0,0 +1,71 @@ +package pairmatching.outputview; + +import pairmatching.model.Level; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class SelectingMissionOutputView implements OutputView { + + public static final String SELECT_COURSE_LEVEL_MISSION_MESSAGE = "과정, 레벨, 미션을 선택하세요."; + public static final String SELECTION_EXAMPLE_MESSAGE = "ex) 백엔드, 레벨1, 자동차경주"; + public static final String PRINTING_MISSION_MESSAGE_FORMAT = " - %s: "; + public static final String PRINTING_COURSE_PREFIX = "과정: "; + public static final String SEPARATOR_FORMAT = " %s "; + public static final String MISSION_MESSAGE_PREFIX = "미션:"; + public static final String LINE_SEPARATOR = "#############################################"; + public static final String VALUE_SEPARATOR = "|"; + + @Override + public void print(Map model) { + printLineSeparator(); + printCourses((List) model.get("courses")); + printMissions(model); + printLineSeparator(); + printSelectCourseLevelMissionSelectionMessage(); + } + + private static void printSelectCourseLevelMissionSelectionMessage() { + System.out.println(SELECT_COURSE_LEVEL_MISSION_MESSAGE); + System.out.println(SELECTION_EXAMPLE_MESSAGE); + } + + private static void printLineSeparator() { + System.out.println(LINE_SEPARATOR); + } + + private void printMissions(Map model) { + System.out.println(MISSION_MESSAGE_PREFIX); + printMissionsOfEachLevel(Level.LEVEL1, (List) model.get("level1Missions")); + printMissionsOfEachLevel(Level.LEVEL2, (List) model.get("level2Missions")); + printMissionsOfEachLevel(Level.LEVEL3, (List) model.get("level3Missions")); + printMissionsOfEachLevel(Level.LEVEL4, (List) model.get("level4Missions")); + printMissionsOfEachLevel(Level.LEVEL5, (List) model.get("level5Missions")); + } + + private void printMissionsOfEachLevel(Level level, List missions) { + System.out.printf(PRINTING_MISSION_MESSAGE_FORMAT, level.getName()); + printValues(missions); + } + + private void printCourses(List courses) { + System.out.print(PRINTING_COURSE_PREFIX); + printValues(courses); + } + + private void printValues(List values) { + Iterator iterator = values.iterator(); + while (iterator.hasNext()) { + System.out.print(iterator.next()); + if (iterator.hasNext()) { + addSeparator(VALUE_SEPARATOR); + } + } + System.out.println(); + } + + private void addSeparator(String separator) { + System.out.printf(SEPARATOR_FORMAT, separator); + } +} diff --git a/src/main/java/pairmatching/repository/CrewRepository.java b/src/main/java/pairmatching/repository/CrewRepository.java new file mode 100644 index 000000000..7dbbb467b --- /dev/null +++ b/src/main/java/pairmatching/repository/CrewRepository.java @@ -0,0 +1,22 @@ +package pairmatching.repository; + +import pairmatching.model.Course; +import pairmatching.model.Crew; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class CrewRepository { + private final List crews = new ArrayList<>(); + + public void saveAll(List crews) { + this.crews.addAll(crews); + } + + public List findByCourse(Course course) { + return crews.stream() + .filter(crew -> crew.getCourse() == course) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/pairmatching/repository/MissionRepository.java b/src/main/java/pairmatching/repository/MissionRepository.java new file mode 100644 index 000000000..b5e195442 --- /dev/null +++ b/src/main/java/pairmatching/repository/MissionRepository.java @@ -0,0 +1,30 @@ +package pairmatching.repository; + +import pairmatching.model.Level; +import pairmatching.model.Mission; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class MissionRepository { + private final List missions = new ArrayList<>(); + + public void saveAllNamesWithLevel(List missionNames, Level level) { + missions.addAll(missionNames.stream() + .map(missionName -> new Mission(level, missionName)) + .collect(Collectors.toList())); + } + + public List findAllNamesByLevel(Level level) { + return missions.stream() + .filter(mission -> mission.getLevel() == level) + .map(Mission::getName) + .collect(Collectors.toList()); + } + + public List findAll() { + return Collections.unmodifiableList(missions); + } +} diff --git a/src/main/java/pairmatching/repository/PairMatchingRepository.java b/src/main/java/pairmatching/repository/PairMatchingRepository.java new file mode 100644 index 000000000..feb4f12f3 --- /dev/null +++ b/src/main/java/pairmatching/repository/PairMatchingRepository.java @@ -0,0 +1,93 @@ +package pairmatching.repository; + +import pairmatching.model.Level; +import pairmatching.model.Pair; +import pairmatching.system.exception.DuplicationPairsException; +import pairmatching.system.exception.EmptyPairMatchingInfoException; +import pairmatching.vo.PairMatchingInfo; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PairMatchingRepository { + private final Map> matchedPairs = new HashMap<>(); + + public void save(PairMatchingInfo pairMatchingInfo, List pairs) { + matchedPairs.put(pairMatchingInfo, pairs); + } + + public boolean hasDuplicatingAtSameLevel(PairMatchingInfo pairMatchingInfo, List pairs) { + List> matchedPairsAtSameLevel = findAllByLevel(pairMatchingInfo.getLevel()); + + try { + checkHavingDuplicatingPairs(pairs, matchedPairsAtSameLevel); + } catch (DuplicationPairsException e) { + return true; + } + + return false; + } + + private static void checkHavingDuplicatingPairs(List pairs, List> matchedPairsAtSameLevel) throws DuplicationPairsException { + for (List pairsAtSameLevel : matchedPairsAtSameLevel) { + checkDuplicationByEachPairMatchingInfo(pairs, pairsAtSameLevel); + } + } + + private static void checkDuplicationByEachPairMatchingInfo(List pairs, List pairsAtSameLevel) throws DuplicationPairsException { + for (Pair pair : pairsAtSameLevel) { + checkDuplication(pairs, pair); + } + } + + private static void checkDuplication(List pairs, Pair pair) throws DuplicationPairsException { + if (pairs.contains(pair)) { + throw new DuplicationPairsException(); + } + } + + private List> findAllByLevel(Level level) { + List> matchedPairsAtSameLevel = new ArrayList<>(); + getPairsAtSameLevel(level, matchedPairsAtSameLevel); + return matchedPairsAtSameLevel; + } + + private void getPairsAtSameLevel(Level level, List> matchedPairsAtSameLevel) { + matchedPairs.forEach((pairMatchingInfo, pairs) -> { + if (pairMatchingInfo.getLevel() == level) { + matchedPairsAtSameLevel.add(pairs); + } + }); + } + + public List> findAllNamesByPairMatchingInfo(PairMatchingInfo pairMatchingInfo) { + List pairs = findByPairMatchingInfo(pairMatchingInfo); + if (pairs == null) { + throw new EmptyPairMatchingInfoException(); + } + + return findAllNamesFromPairs(pairs); + } + + private static List> findAllNamesFromPairs(List pairs) { + List> pairNamesArray = new ArrayList<>(); + findAllNamesFromPair(pairs, pairNamesArray); + return pairNamesArray; + } + + private static void findAllNamesFromPair(List pairs, List> pairNamesArray) { + for (Pair pair : pairs) { + pairNamesArray.add(pair.getCrewNames()); + } + } + + private List findByPairMatchingInfo(PairMatchingInfo pairMatchingInfo) { + return matchedPairs.get(pairMatchingInfo); + } + + public void resetAll() { + matchedPairs.clear(); + } +} diff --git a/src/main/java/pairmatching/system/PairApplication.java b/src/main/java/pairmatching/system/PairApplication.java new file mode 100644 index 000000000..d4edbab2a --- /dev/null +++ b/src/main/java/pairmatching/system/PairApplication.java @@ -0,0 +1,110 @@ +package pairmatching.system; + +import pairmatching.controller.*; +import pairmatching.inputview.GettingFeatureCommandInputView; +import pairmatching.inputview.SelectingMissionInputView; +import pairmatching.outputview.MatchingResultOutputView; +import pairmatching.outputview.ResetOutputView; +import pairmatching.outputview.SelectingFeatureOutputView; +import pairmatching.outputview.SelectingMissionOutputView; +import pairmatching.repository.CrewRepository; +import pairmatching.repository.MissionRepository; +import pairmatching.repository.PairMatchingRepository; +import pairmatching.system.util.PairsMaker; +import pairmatching.vo.FeatureCommand; + +import java.util.HashMap; +import java.util.Map; + +public class PairApplication { + public static final String READING_CREW_FILE_CONTROLLER_PATH = "readCrewFile"; + public static final String SAVE_MISSIONS_CONTROLLER_PATH = "saveMissions"; + public static final String SELECT_FEATURE_CONTROLLER_PATH = "selectFeature"; + public static final String SELECTING_MISSION_PATH = "selectMission"; + public static final String MATCHING_PAIR = "matchPair"; + public static final String FIND_PAIR_PATH = "findPair"; + public static final String RESET_PAIR_CONTROLLER_PATH = "resetPair"; + private final Map controllers = new HashMap<>(); + + public PairApplication() { + MissionRepository missionRepository = new MissionRepository(); + CrewRepository crewRepository = new CrewRepository(); + PairMatchingRepository pairMatchingRepository = new PairMatchingRepository(); + + MatchingResultOutputView matchingResultOutputView = new MatchingResultOutputView(); + + controllers.put(READING_CREW_FILE_CONTROLLER_PATH, new ReadingCrewsFileController(crewRepository)); + controllers.put(SAVE_MISSIONS_CONTROLLER_PATH, new SavingMissionsController(missionRepository)); + controllers.put(SELECT_FEATURE_CONTROLLER_PATH, new SelectingFeatureController( + new SelectingFeatureOutputView(), + new GettingFeatureCommandInputView() + )); + controllers.put(SELECTING_MISSION_PATH, new SelectingMissionController( + new SelectingMissionOutputView(), + new SelectingMissionInputView(), + missionRepository + )); + controllers.put(MATCHING_PAIR, new MatchingPairController( + crewRepository, + pairMatchingRepository, + new PairsMaker(), + matchingResultOutputView) + ); + controllers.put(FIND_PAIR_PATH, new FindPairController( + matchingResultOutputView, pairMatchingRepository + )); + controllers.put(RESET_PAIR_CONTROLLER_PATH, new ResetPairController( + pairMatchingRepository, new ResetOutputView())); + } + + public void run() { + HashMap model = new HashMap<>(); + readFileAndSaveCrews(model); + saveMissions(model); + + FeatureCommand featureCommand; + do { + readFeatureCommand(model); + featureCommand = (FeatureCommand) model.get("featureCommand"); + doFeature(featureCommand, model); + } while (featureCommand != FeatureCommand.QUIT); + } + + private void doFeature(FeatureCommand featureCommand, HashMap model) { + if (featureCommand == FeatureCommand.MATCHING) { + doMatchingProcess(model); + } + if (featureCommand == FeatureCommand.FIND) { + doFindingProcess(model); + } + if (featureCommand == FeatureCommand.RESET) { + doResetProcess(model); + } + } + + private void doResetProcess(HashMap model) { + controllers.get(RESET_PAIR_CONTROLLER_PATH).process(model); + } + + private void doFindingProcess(HashMap model) { + controllers.get(SELECTING_MISSION_PATH).process(model); + controllers.get(FIND_PAIR_PATH).process(model); + } + + private void doMatchingProcess(HashMap model) { + controllers.get(SELECTING_MISSION_PATH).process(model); + controllers.get(MATCHING_PAIR).process(model); + } + + private void readFeatureCommand(HashMap model) { + controllers.get(SELECT_FEATURE_CONTROLLER_PATH).process(model); + } + + private void saveMissions(HashMap model) { + controllers.get(SAVE_MISSIONS_CONTROLLER_PATH).process(model); + } + + private void readFileAndSaveCrews(HashMap model) { + controllers.get(READING_CREW_FILE_CONTROLLER_PATH).process(model); + } +} diff --git a/src/main/java/pairmatching/system/convertion/NamesToCrewConverter.java b/src/main/java/pairmatching/system/convertion/NamesToCrewConverter.java new file mode 100644 index 000000000..ba2d5db48 --- /dev/null +++ b/src/main/java/pairmatching/system/convertion/NamesToCrewConverter.java @@ -0,0 +1,17 @@ +package pairmatching.system.convertion; + +import pairmatching.model.Course; +import pairmatching.model.Crew; + +import java.util.List; +import java.util.stream.Collectors; + +public class NamesToCrewConverter { + + public static List convert(List crewNames, Course course) { + // TODO: 변환 검증 필요하다 + return crewNames.stream() + .map(crewName -> new Crew(course, crewName)) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/pairmatching/system/convertion/StringToFeatureCommandConverter.java b/src/main/java/pairmatching/system/convertion/StringToFeatureCommandConverter.java new file mode 100644 index 000000000..b796b4846 --- /dev/null +++ b/src/main/java/pairmatching/system/convertion/StringToFeatureCommandConverter.java @@ -0,0 +1,18 @@ +package pairmatching.system.convertion; + +import pairmatching.vo.FeatureCommand; + +import java.util.Arrays; + +public class StringToFeatureCommandConverter { + + public static final String WRONG_COMMAND_MESSAGE = "잘못된 커멘드를 입력하였습니다."; + + public static FeatureCommand convert(String target) { + // TODO: 값 검증 + return Arrays.stream(FeatureCommand.values()) + .filter(featureCommand -> featureCommand.getCommand().equals(target)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(WRONG_COMMAND_MESSAGE)); + } +} diff --git a/src/main/java/pairmatching/system/convertion/StringToPairMatchingInfoConverter.java b/src/main/java/pairmatching/system/convertion/StringToPairMatchingInfoConverter.java new file mode 100644 index 000000000..550646862 --- /dev/null +++ b/src/main/java/pairmatching/system/convertion/StringToPairMatchingInfoConverter.java @@ -0,0 +1,36 @@ +package pairmatching.system.convertion; + +import pairmatching.model.Course; +import pairmatching.model.Mission; +import pairmatching.vo.PairMatchingInfo; + +import java.util.List; + +public class StringToPairMatchingInfoConverter { + + public static final String INVALID_MISSION_INPUT_MESSAGE = "입력한 값과 일치하는 미션이 없습니다."; + + public static PairMatchingInfo convert(String input, List missions) { + // TODO: 검증 필요하다. + String[] splitedInput = input + .replaceAll(" ", "") + .split(","); + return new PairMatchingInfo( + getCourse(splitedInput), + getMission(missions, splitedInput) + ); + } + + private static Course getCourse(String[] splitedInput) { + return Course.findByName(splitedInput[0]); + } + + private static Mission getMission(List missions, String[] splitedInput) { + return missions.stream() + .filter(mission -> + mission.getLevel().getName().equals(splitedInput[1]) && + mission.getName().equals(splitedInput[2])) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(INVALID_MISSION_INPUT_MESSAGE)); + } +} diff --git a/src/main/java/pairmatching/system/exception/DuplicationPairsException.java b/src/main/java/pairmatching/system/exception/DuplicationPairsException.java new file mode 100644 index 000000000..ad547d7ac --- /dev/null +++ b/src/main/java/pairmatching/system/exception/DuplicationPairsException.java @@ -0,0 +1,7 @@ +package pairmatching.system.exception; + +public class DuplicationPairsException extends IllegalStateException { + public DuplicationPairsException() { + super(); + } +} diff --git a/src/main/java/pairmatching/system/exception/EmptyPairMatchingInfoException.java b/src/main/java/pairmatching/system/exception/EmptyPairMatchingInfoException.java new file mode 100644 index 000000000..92f3c8fb4 --- /dev/null +++ b/src/main/java/pairmatching/system/exception/EmptyPairMatchingInfoException.java @@ -0,0 +1,6 @@ +package pairmatching.system.exception; + +public class EmptyPairMatchingInfoException extends IllegalStateException { + public EmptyPairMatchingInfoException() { + } +} diff --git a/src/main/java/pairmatching/system/util/PairsMaker.java b/src/main/java/pairmatching/system/util/PairsMaker.java new file mode 100644 index 000000000..0c7a256f1 --- /dev/null +++ b/src/main/java/pairmatching/system/util/PairsMaker.java @@ -0,0 +1,34 @@ +package pairmatching.system.util; + +import pairmatching.model.Pair; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class PairsMaker { + public List makePairs(List crewNames) { + ArrayList pairs = new ArrayList<>(); + List modifiableCrews = new ArrayList<>(crewNames); + while (2 <= modifiableCrews.size()) { + List matchedCrews = getMatchedCrews(modifiableCrews); + pairs.add(new Pair(matchedCrews)); + } + return pairs; + } + + private static List getMatchedCrews(List crewNames) { + List matchedCrews = Arrays.asList( + crewNames.remove(0), crewNames.remove(0) + ); + + handleOddCrewSizeCase(crewNames, matchedCrews); + return matchedCrews; + } + + private static void handleOddCrewSizeCase(List crews, List matchedCrews) { + if (crews.size() == 1) { + matchedCrews.add(crews.remove(0)); + } + } +} diff --git a/src/main/java/pairmatching/vo/FeatureCommand.java b/src/main/java/pairmatching/vo/FeatureCommand.java new file mode 100644 index 000000000..05baa7955 --- /dev/null +++ b/src/main/java/pairmatching/vo/FeatureCommand.java @@ -0,0 +1,24 @@ +package pairmatching.vo; + +public enum FeatureCommand { + MATCHING("1", "페어 매칭"), + FIND("2", "페어 조회"), + RESET("3", "페어 초기화"), + QUIT("Q", "종료"); + + private final String command; + private final String description; + + FeatureCommand(String command, String description) { + this.command = command; + this.description = description; + } + + public String getCommand() { + return command; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/pairmatching/vo/PairMatchingInfo.java b/src/main/java/pairmatching/vo/PairMatchingInfo.java new file mode 100644 index 000000000..4fb0be153 --- /dev/null +++ b/src/main/java/pairmatching/vo/PairMatchingInfo.java @@ -0,0 +1,42 @@ +package pairmatching.vo; + +import pairmatching.model.Course; +import pairmatching.model.Level; +import pairmatching.model.Mission; + +import java.util.Objects; + +public class PairMatchingInfo { + private final Course course; + private final Mission mission; + + public PairMatchingInfo(Course course, Mission mission) { + this.course = course; + this.mission = 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); + } +}