diff --git a/README.md b/README.md index e6ed1056f..62bce37c6 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,18 @@ # 지하철 노선도 미션 + - 지하철 역과 노선을 관리하는 지하철 노선도 기능을 구현한다.
## 🚀 기능 요구사항 -### 초기 설정 +### 초기 설정 + - 프로그램 시작 시 역, 노선 등 필요한 정보를 미리 셋팅할 수 있다. > 아래의 사전 등록 정보로 반드시 초기 설정을 하기 > + ``` 1. 지하철역으로 교대역, 강남역, 역삼역, 남부터미널역, 양재역, 양재시민의숲역, 매봉역이 등록되어 있다. 2. 지하철 노선으로 2호선, 3호선, 신분당선이 등록되어 있다. @@ -20,23 +23,26 @@ ``` - + ### 지하철 역 관련 기능 + - 지하철 역을 등록하고 삭제할 수 있다. (단, 노선에 등록된 역은 삭제할 수 없다) - 중복된 지하철 역 이름이 등록될 수 없다. - 지하철 역 이름은 2글자 이상이어야 한다. - 지하철 역의 목록을 조회할 수 있다. ### 지하철 노선 관련 기능 + - 지하철 노선을 등록하고 삭제할 수 있다. - 중복된 지하철 노선 이름이 등록될 수 없다. - 지하철 노선 이름은 2글자 이상이어야 한다. -- 노선 등록 시 상행 종점역과 하행 종점역을 입력받는다. +- 노선 등록 시 상행 종점역과 하행 종점역을 입력받는다. - 지하철 노선의 목록을 조회할 수 있다. ### 지하철 구간 추가 기능 + - 지하철 노선에 구간을 추가하는 기능은 노선에 역을 추가하는 기능이라고도 할 수 있다. - - 역과 역사이를 구간이라 하고 이 구간들의 모음이 노선이다. + - 역과 역사이를 구간이라 하고 이 구간들의 모음이 노선이다. - 하나의 역은 여러개의 노선에 추가될 수 있다. - 역과 역 사이에 새로운 역이 추가 될 수 있다. - 노선에서 갈래길은 생길 수 없다. @@ -44,6 +50,7 @@ ### 지하철 구간 삭제 기능 + - 노선에 등록된 역을 제거할 수 있다. - 종점을 제거할 경우 다음 역이 종점이 된다. - 노선에 포함된 역이 두개 이하일 때는 역을 제거할 수 없다. @@ -51,17 +58,21 @@ ### 지하철 노선에 등록된 역 조회 기능 + - 노선의 상행 종점부터 하행 종점까지 연결된 순서대로 역 목록을 조회할 수 있다.
## ✍🏻 입출력 요구사항 + - `프로그래밍 실행 결과 예시`를 참고하여 입출력을 구현한다. - 기대하는 출력 결과는 `[INFO]`를 붙여서 출력한다. 출력값의 형식은 예시와 동일하게 한다. - 에러 발생 시 `[ERROR]`를 붙여서 출력한다. (에러의 문구는 자유롭게 작성한다.) -### 💻 프로그래밍 실행 결과 +### 💻 프로그래밍 실행 결과 + #### 역 관리 + ``` ## 메인 화면 1. 역 관리 @@ -233,6 +244,7 @@ B. 돌아가기 ``` ### 구간 관리 + - 순서는 1부터 시작한다. ``` @@ -379,25 +391,28 @@ B. 돌아가기
## 🎱 프로그래밍 요구사항 + - 자바 코드 컨벤션을 지키면서 프로그래밍한다. - - 기본적으로 [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html)을 원칙으로 한다. - - 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다. + - 기본적으로 [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html)을 원칙으로 한다. + - 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다. - indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. - - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다. + - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. + - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다. - 3항 연산자를 쓰지 않는다. - 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다. - - 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다. + - 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다. - else 예약어를 쓰지 않는다. - - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. - - else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. + - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. + - else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. - 프로그래밍 요구사항에서 별도로 변경 불가 안내가 없는 경우 파일 수정과 패키지 이동을 자유롭게 할 수 있다. - 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 `[ERROR]` 로 시작해야 한다. ### 프로그래밍 요구사항 - Application + - Application 클래스를 활용해 구현해야 한다. - Application의 패키지 구조는 변경하지 않는다. -- 주석을 참고하여 구현할 수 있으며 주석대로 구현하지 않아도 되고 삭제해도 무관하다. +- 주석을 참고하여 구현할 수 있으며 주석대로 구현하지 않아도 되고 삭제해도 무관하다. + ```java public class Application { public static void main(String[] args) { @@ -407,11 +422,12 @@ public class Application { ``` ### 프로그래밍 요구사항 - Station, Line + - Station, Line 클래스를 활용하여 지하철역과 노선을 구현해야 한다. - 제공하는 각 클래스의 기본 생성자를 추가할 수 없다. - 필드(인스턴스 변수)인 name의 접근 제어자 private을 변경할 수 없다. - 가능하면 setter 메소드(ex. setXXX)를 추가하지 않고 구현한다. - + ```java public class Station { private String name; @@ -430,12 +446,13 @@ public class Station { ``` ### 프로그래밍 요구사항 - StationRepository, LineRepository + - Station과 Line의 상태를 저장할 수 있는 StationRepository, LineRepository를 제공한다. - 필요 시 StationRepository, LineRepository 이 외 추가로 Repository를 만들 수 있다. - 추가로 생성되는 객체에 대해서 XXXRepository 네이밍으로 저장 클래스를 추가할 수 있다. - 객체들의 상태를 관리하기 위해서 XXXRepository 클래스를 활용해 저장 로직을 구현해야 한다. - 필요에 따라 자유롭게 수정이 가능하다. - + ```java public class StationRepository { private static final List stations = new ArrayList<>(); @@ -457,14 +474,61 @@ public class StationRepository {
## 📈 진행 요구사항 + - 미션은 [java-subway-map-precourse 저장소](https://github.com/woowacourse/java-subway-map-precourse) 를 fork/clone해 시작한다. - 기능을 구현하기 전에 java-subway-map-precourse/docs/README.md 파일에 구현할 기능 목록을 정리해 추가한다. - git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다. - - [AngularJS Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 참고해 commit log를 남긴다. + - [AngularJS Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 참고해 commit log를 + 남긴다. - [프리코스 과제 제출 문서](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 절차를 따라 미션을 제출한다. - - [프리코스 과제 FAQ](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse/faq) 문서를 참고하여 진행할 수 있다. -
+ - [프리코스 과제 FAQ](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse/faq) 문서를 참고하여 진행할 수 있다. +
## 📝 License This project is [MIT](https://github.com/woowacourse/java-subway-map-precourse/blob/master/LICENSE.md) licensed. + +
+ +## ☑ 구현할 기능 목록 + +- [x] 메인 + - [x] 선택지에 없는 입력일 시 에러메세지를 출력한다. + - [x] 사용자 입력에 따라 하위 기능을 실행한다. + - [x] 사용자 입력이 Q인 경우 프로그램을 완전 종료한다. + - [x] 사전등록정보에 따라 지하철 노선도를 초기화한다. +- [x] 역 관리 + - [x] 선택지에 없는 입력일 시 에러메세지를 출력한다. + - [x] 사용자 입력에 따라 하위 기능을 실행한다. + - [x] 역 조회 시 모든 역의 이름을 중복 없이 출력한다. + - [x] 역 등록 시 이름 중복을 확인한다. + - [x] 역 등록 시 이름이 2글자 이상인지 확인한다. + - [x] 역 삭제 시 존재하는 역인지 확인한다. + - [x] 역 삭제 시 노선에 등록된 역인지 확인한다. +- [x] 노선 관리 + - [x] 하위 기능을 선택한다. (역 관리와 동일) + - [x] 노선 조회 시 모든 노선의 이름을 중복 없이 출력한다. + - [x] 노선 등록 시 이름 중복을 확인한다. + - [x] 노선 등록 시 이름이 2글자 이상인지 확인한다. + - [x] 노선 등록 시 상행 종점역이 존재하는 역인지 확인한다. + - [x] 노선 등록 시 하행 종점역이 존재하는 역인지 확인한다. + - [x] 노선 등록 시 상하행 종점역을 역 리스트에 추가한다. + - [x] 노선 삭제 시 존재하는 노선인지 확인한다. +- [x] 구간 관리 + - [x] 선택지에 없는 입력일 시 에러메세지를 출력한다. + - [x] 사용자 입력에 따라 하위 기능을 실행한다. + - [x] 구간 등록 시 존재하는 노선인지 확인한다. + - [x] 구간 등록 시 존재하는 역인지 확인한다. + - [x] 구간 등록 시 순서 입력이 숫자가 아니면 에러메세지를 출력한다. + - [x] 구간 등록 시 순서 입력이 범위 내 숫자가 아니면 에러메세지를 출력한다. + - [x] 구간 등록 시 상하행 종점역을 갱신한다. + - [x] 구간 삭제 시 존재하는 노선인지 확인한다. + - [x] 구간 삭제 시 존재하는 역인지 확인한다. + - [x] 구간 삭제 시 노선에 남은 역이 2개 초과인지 확인한다. + - [x] 구간 삭제 시 상하행 종점역을 갱신한다. +- [x] 지하철 노선도 + - [x] 모든 노선과 역을 순서대로 출력한다. +- [x] 리팩토링 + - [x] 비즈니스 로직과 UI 로직을 분리한다. + - [x] InputView를 통해 사용자 입력을 받는다. + - [x] OutputView를 통해 안내 문구와 결과를 출력한다. \ No newline at end of file diff --git a/src/main/java/subway/Application.java b/src/main/java/subway/Application.java index 0bcf786cc..bf14ff136 100644 --- a/src/main/java/subway/Application.java +++ b/src/main/java/subway/Application.java @@ -1,10 +1,14 @@ package subway; +import subway.service.MainService; + import java.util.Scanner; public class Application { + public static void main(String[] args) { final Scanner scanner = new Scanner(System.in); - // TODO: 프로그램 구현 + MainService mainService = new MainService(scanner); + mainService.run(); } } diff --git a/src/main/java/subway/constant/Constant.java b/src/main/java/subway/constant/Constant.java new file mode 100644 index 000000000..9930e8aba --- /dev/null +++ b/src/main/java/subway/constant/Constant.java @@ -0,0 +1,9 @@ +package subway.constant; + +public class Constant { + + public static final int MIN_NAME_LENGTH = 2; + public static final int MIN_ORDER = 0; + public static final int MIN_STATIONS_IN_LINE = 3; + public static final String DIVIDER = "---"; +} diff --git a/src/main/java/subway/constant/Function.java b/src/main/java/subway/constant/Function.java new file mode 100644 index 000000000..cbb64da50 --- /dev/null +++ b/src/main/java/subway/constant/Function.java @@ -0,0 +1,40 @@ +package subway.constant; + +import subway.exception.InvalidInputException; + +import java.util.ArrayList; +import java.util.List; + +public enum Function { + + ADD("1"), + DELETE("2"), + SHOW("3"), + QUIT("B"); + + private static List functionCodes = new ArrayList<>(); + private String code; + + Function(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public static void validate(String input) throws InvalidInputException { + if (!isAvailable(input)) + throw new InvalidInputException(InvalidInputException.ExceptionCode.INVALID_FUNCTION_CODE); + } + + private static boolean isAvailable(String input) { + initFunctionCodes(); + return functionCodes.contains(input); + } + + private static void initFunctionCodes() { + for (Function function : Function.values()) + functionCodes.add(function.code); + } +} diff --git a/src/main/java/subway/constant/Information.java b/src/main/java/subway/constant/Information.java new file mode 100644 index 000000000..44005634d --- /dev/null +++ b/src/main/java/subway/constant/Information.java @@ -0,0 +1,36 @@ +package subway.constant; + +public class Information { + + public static final String INFO_HEADER = "[INFO] "; + + public static final String CHOOSE_FUNCTION = "\n## 원하는 기능을 선택하세요."; + public static final String MAIN_INFO = "\n## 메인 화면\n1. 역 관리\n2. 노선 관리\n3. 구간 관리\n4. 지하철 노선도 출력\nQ. 종료\n" + CHOOSE_FUNCTION; + + public static final String STATION_INFO = "\n## 역 관리 화면\n1. 역 등록\n2. 역 삭제\n3. 역 조회\nB. 돌아가기\n" + CHOOSE_FUNCTION; + public static final String ADD_STATION_INFO = "\n## 등록할 역 이름을 입력하세요."; + public static final String ADD_STATION_SUCCESS = INFO_HEADER + "지하철 역이 등록되었습니다."; + public static final String DELETE_STATION_INFO = "\n## 삭제할 역 이름을 입력하세요."; + public static final String DELETE_STATION_SUCCESS = INFO_HEADER + "지하철 역이 삭제되었습니다."; + public static final String SHOW_STATION_INFO = "\n## 역 목록"; + + public static final String LINE_INFO = "\n## 노선 관리 화면\n1. 노선 등록\n2. 노선 삭제\n3. 노선 조회\nB. 돌아가기\n" + CHOOSE_FUNCTION; + public static final String ADD_LINE_INFO = "\n## 등록할 노선 이름을 입력하세요."; + public static final String ADD_LINE_INFO_UP_END = "\n## 등록할 노선의 상행 종점역 이름을 입력하세요."; + public static final String ADD_LINE_INFO_DOWN_END = "\n## 등록할 노선의 하행 종점역 이름을 입력하세요."; + public static final String ADD_LINE_SUCCESS = INFO_HEADER + "지하철 노선이 등록되었습니다."; + public static final String DELETE_LINE_INFO = "\n## 삭제할 노선 이름을 입력하세요."; + public static final String DELETE_LINE_SUCCESS = INFO_HEADER + "지하철 노선이 삭제되었습니다."; + public static final String SHOW_LINE_INFO = "\n## 노선 목록"; + + public static final String LINK_INFO = "\n## 구간 관리 화면\n1. 구간 등록\n2. 구간 삭제\nB. 돌아가기\n" + CHOOSE_FUNCTION; + public static final String ADD_LINK_INFO_LINE = "\n## 노선 이름을 입력하세요."; + public static final String ADD_LINK_INFO_STATION = "\n## 역 이름을 입력하세요."; + public static final String ADD_LINK_INFO_ORDER = "\n## 순서를 입력하세요."; + public static final String ADD_LINK_SUCCESS = INFO_HEADER + "구간이 등록되었습니다."; + public static final String DELETE_LINK_INFO_LINE = "\n## 삭제할 구간의 노선 이름을 입력하세요."; + public static final String DELETE_LINK_INFO_STATION = "\n## 삭제할 구간의 역 이름을 입력하세요."; + public static final String DELETE_LINK_SUCCESS = INFO_HEADER + "구간이 삭제되었습니다."; + + public static final String PRINT_INFO = "\n## 지하철 노선도"; +} diff --git a/src/main/java/subway/constant/InitialData.java b/src/main/java/subway/constant/InitialData.java new file mode 100644 index 000000000..15991cc21 --- /dev/null +++ b/src/main/java/subway/constant/InitialData.java @@ -0,0 +1,47 @@ +package subway.constant; + +import subway.domain.Line; +import subway.domain.Station; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class InitialData { + + public static final List stations = new ArrayList<>(Arrays.asList( + new Station("교대역"), + new Station("강남역"), + new Station("역삼역"), + new Station("남부터미널역"), + new Station("양재역"), + new Station("양재시민의숲역"), + new Station("매봉역") + )); + + + private static final List lineTwoStations = new ArrayList<>(Arrays.asList( + new Station("교대역"), + new Station("강남역"), + new Station("역삼역") + )); + + private static final List lineThreeStations = new ArrayList<>(Arrays.asList( + new Station("교대역"), + new Station("남부터미널역"), + new Station("양재역"), + new Station("매봉역") + )); + + private static final List lineSinbundangStations = new ArrayList<>(Arrays.asList( + new Station("강남역"), + new Station("양재역"), + new Station("양재시민의숲역") + )); + + public static final List lines = new ArrayList<>(Arrays.asList( + new Line("2호선", lineTwoStations), + new Line("3호선", lineThreeStations), + new Line("신분당선", lineSinbundangStations) + )); +} diff --git a/src/main/java/subway/constant/Service.java b/src/main/java/subway/constant/Service.java new file mode 100644 index 000000000..d0162f949 --- /dev/null +++ b/src/main/java/subway/constant/Service.java @@ -0,0 +1,41 @@ +package subway.constant; + +import subway.exception.InvalidInputException; + +import java.util.ArrayList; +import java.util.List; + +public enum Service { + + STATION("1"), + LINE("2"), + LINK("3"), + PRINT("4"), + QUIT("Q"); + + private static List serviceCodes = new ArrayList<>(); + private String code; + + Service(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public static void validate(String input) throws InvalidInputException { + if (!isAvailable(input)) + throw new InvalidInputException(InvalidInputException.ExceptionCode.INVALID_SERVICE_CODE); + } + + private static boolean isAvailable(String input) { + initServiceCodes(); + return serviceCodes.contains(input); + } + + private static void initServiceCodes() { + for (Service service : Service.values()) + serviceCodes.add(service.code); + } +} diff --git a/src/main/java/subway/domain/Line.java b/src/main/java/subway/domain/Line.java index f4d738d5a..837aea7cf 100644 --- a/src/main/java/subway/domain/Line.java +++ b/src/main/java/subway/domain/Line.java @@ -1,15 +1,73 @@ package subway.domain; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + public class Line { + + private static final int UP_END_INDEX = 0; + private String name; + private Station upEnd; + private Station downEnd; + private List stations = new ArrayList<>(); public Line(String name) { this.name = name; } + public Line(String name, List stations) { + this.name = name; + this.stations = stations; + setUpAndDownEndStations(); + } + + private void setUpAndDownEndStations() { + this.upEnd = stations.get(UP_END_INDEX); + this.downEnd = stations.get(stations.size() - 1); + } + public String getName() { return name; } - // 추가 기능 구현 + public Station getUpEnd() { + return upEnd; + } + + public Station getDownEnd() { + return downEnd; + } + + public List getStations() { + return stations; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Line)) return false; + Line line = (Line) o; + return Objects.equals(name, line.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + + public void addNewLink(Link newLink) { + Station station = new Station(newLink.getStationName()); + int targetIndex = newLink.getOrder(); + stations.add(targetIndex, station); + setUpAndDownEndStations(); + } + + public void deleteTargetLink(Link targetLink) { + Station station = new Station(targetLink.getStationName()); + stations.remove(station); + setUpAndDownEndStations(); + } } diff --git a/src/main/java/subway/domain/Link.java b/src/main/java/subway/domain/Link.java new file mode 100644 index 000000000..76e05f11c --- /dev/null +++ b/src/main/java/subway/domain/Link.java @@ -0,0 +1,31 @@ +package subway.domain; + +public class Link { + + private String lineName; + private String stationName; + private int order; + + public Link(String lineName, String stationName, int order) { + this.lineName = lineName; + this.stationName = stationName; + this.order = order; + } + + public Link(String targetLineName, String targetStationName) { + this.lineName = targetLineName; + this.stationName = targetStationName; + } + + public String getLineName() { + return lineName; + } + + public String getStationName() { + return stationName; + } + + public int getOrder() { + return order; + } +} diff --git a/src/main/java/subway/domain/Station.java b/src/main/java/subway/domain/Station.java index bdb142590..a572ebe5c 100644 --- a/src/main/java/subway/domain/Station.java +++ b/src/main/java/subway/domain/Station.java @@ -1,6 +1,9 @@ package subway.domain; +import java.util.Objects; + public class Station { + private String name; public Station(String name) { @@ -11,5 +14,16 @@ public String getName() { return name; } - // 추가 기능 구현 + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Station)) return false; + Station station = (Station) o; + return Objects.equals(name, station.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } } diff --git a/src/main/java/subway/exception/InvalidInputException.java b/src/main/java/subway/exception/InvalidInputException.java new file mode 100644 index 000000000..ddba858f8 --- /dev/null +++ b/src/main/java/subway/exception/InvalidInputException.java @@ -0,0 +1,49 @@ +package subway.exception; + +public class InvalidInputException extends RuntimeException { + + private final String ERROR_HEADER = "\n[ERROR] "; + private ExceptionCode exceptionCode; + + public InvalidInputException(ExceptionCode exceptionCode) { + this.exceptionCode = exceptionCode; + } + + public enum ExceptionCode { + INVALID_SERVICE_CODE, + INVALID_FUNCTION_CODE, + DUPLICATE_STATION, + DUPLICATE_LINE, + NO_SUCH_STATION, + NO_SUCH_LINE, + INVALID_NAME_LENGTH, + NON_NUMBER_INPUT, + OUT_OF_LINE_RANGE, + NO_LINK_AVAILABLE, + STATION_LINKED; + } + + public String getMessage() { + if (exceptionCode.equals(ExceptionCode.INVALID_SERVICE_CODE) || exceptionCode.equals(ExceptionCode.INVALID_FUNCTION_CODE)) + return ERROR_HEADER + "선택할 수 없는 기능입니다."; + if (exceptionCode.equals(ExceptionCode.DUPLICATE_STATION)) + return ERROR_HEADER + "이미 등록된 역 이름입니다."; + if (exceptionCode.equals(ExceptionCode.DUPLICATE_LINE)) + return ERROR_HEADER + "이미 등록된 노선 이름입니다."; + if (exceptionCode.equals(ExceptionCode.NO_SUCH_STATION)) + return ERROR_HEADER + "존재하지 않는 역 이름입니다."; + if (exceptionCode.equals(ExceptionCode.NO_SUCH_LINE)) + return ERROR_HEADER + "존재하지 않는 노선 이름입니다."; + if (exceptionCode.equals(ExceptionCode.INVALID_NAME_LENGTH)) + return ERROR_HEADER + "이름은 두 글자 이상이어야 합니다."; + if (exceptionCode.equals(ExceptionCode.NON_NUMBER_INPUT)) + return ERROR_HEADER + "순서는 숫자로 입력해야 합니다."; + if (exceptionCode.equals(ExceptionCode.OUT_OF_LINE_RANGE)) + return ERROR_HEADER + "순서는 해당 노선의 범위 안에서 입력해야 합니다."; + if (exceptionCode.equals(ExceptionCode.NO_LINK_AVAILABLE)) + return ERROR_HEADER + "역이 두 개 이하인 노선은 삭제할 수 없습니다"; + if (exceptionCode.equals(ExceptionCode.STATION_LINKED)) + return ERROR_HEADER + "노선에 연결된 역은 삭제할 수 없습니다"; + return ""; + } +} diff --git a/src/main/java/subway/domain/LineRepository.java b/src/main/java/subway/repository/LineRepository.java similarity index 62% rename from src/main/java/subway/domain/LineRepository.java rename to src/main/java/subway/repository/LineRepository.java index 49132ddb6..9b4fb40d0 100644 --- a/src/main/java/subway/domain/LineRepository.java +++ b/src/main/java/subway/repository/LineRepository.java @@ -1,4 +1,6 @@ -package subway.domain; +package subway.repository; + +import subway.domain.Line; import java.util.ArrayList; import java.util.Collections; @@ -6,6 +8,7 @@ import java.util.Objects; public class LineRepository { + private static final List lines = new ArrayList<>(); public static List lines() { @@ -16,7 +19,17 @@ public static void addLine(Line line) { lines.add(line); } + public static void addLine(List lines) { + for (Line line : lines) + addLine(line); + } + public static boolean deleteLineByName(String name) { return lines.removeIf(line -> Objects.equals(line.getName(), name)); } + + public static Line getLineByName(String name) { + int index = lines.indexOf(new Line(name)); + return lines.get(index); + } } diff --git a/src/main/java/subway/domain/StationRepository.java b/src/main/java/subway/repository/StationRepository.java similarity index 65% rename from src/main/java/subway/domain/StationRepository.java rename to src/main/java/subway/repository/StationRepository.java index b7245c0f3..720e98012 100644 --- a/src/main/java/subway/domain/StationRepository.java +++ b/src/main/java/subway/repository/StationRepository.java @@ -1,4 +1,6 @@ -package subway.domain; +package subway.repository; + +import subway.domain.Station; import java.util.ArrayList; import java.util.Collections; @@ -6,6 +8,7 @@ import java.util.Objects; public class StationRepository { + private static final List stations = new ArrayList<>(); public static List stations() { @@ -16,7 +19,12 @@ public static void addStation(Station station) { stations.add(station); } - public static boolean deleteStation(String name) { + public static void addStation(List stations) { + for (Station station : stations) + addStation(station); + } + + public static boolean deleteStationByName(String name) { return stations.removeIf(station -> Objects.equals(station.getName(), name)); } } diff --git a/src/main/java/subway/service/CrudService.java b/src/main/java/subway/service/CrudService.java new file mode 100644 index 000000000..c10a01631 --- /dev/null +++ b/src/main/java/subway/service/CrudService.java @@ -0,0 +1,58 @@ +package subway.service; + +import subway.constant.Function; +import subway.exception.InvalidInputException; +import subway.view.InputView; +import subway.view.OutputView; + +public abstract class CrudService implements MapService { + + private InputView inputView; + private OutputView outputView; + private String functionInformation; + + public CrudService(InputView inputView, OutputView outputView, String functionInformation) { + this.inputView = inputView; + this.outputView = outputView; + this.functionInformation = functionInformation; + } + + public InputView getInputView() { + return inputView; + } + + public OutputView getOutputView() { + return outputView; + } + + @Override + public void run() { + try { + String selectedFunction = getSelectedFunction(); + Function.validate(selectedFunction); + runSelectedFunction(selectedFunction); + } catch (InvalidInputException e) { + outputView.printErrorMessage(e.getMessage()); + } + } + + private String getSelectedFunction() { + outputView.printInformation(functionInformation); + return inputView.getSelectedFunctionInput(); + } + + private void runSelectedFunction(String selectedFunction) { + if (selectedFunction.equals(Function.ADD.getCode())) + add(); + if (selectedFunction.equals(Function.DELETE.getCode())) + delete(); + if (selectedFunction.equals(Function.SHOW.getCode())) + show(); + } + + public abstract void add(); + + public abstract void delete(); + + public abstract void show(); +} diff --git a/src/main/java/subway/service/LineService.java b/src/main/java/subway/service/LineService.java new file mode 100644 index 000000000..0cac4bd38 --- /dev/null +++ b/src/main/java/subway/service/LineService.java @@ -0,0 +1,113 @@ +package subway.service; + +import subway.constant.Constant; +import subway.constant.InitialData; +import subway.domain.Line; +import subway.domain.Station; +import subway.exception.InvalidInputException; +import subway.repository.LineRepository; +import subway.repository.StationRepository; +import subway.view.InputView; +import subway.view.OutputView; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static subway.constant.Information.*; + +public class LineService extends CrudService { + + public LineService(InputView inputView, OutputView outputView) { + super(inputView, outputView, LINE_INFO); + initLines(); + } + + private void initLines() { + LineRepository.addLine(InitialData.lines); + } + + + @Override + public void add() { + Line newLine = getNewLine(); + validateNewLine(newLine); + LineRepository.addLine(newLine); + getOutputView().printInformation(ADD_LINE_SUCCESS); + } + + private Line getNewLine() { + String name = getNewLineName(); + List endStations = getNewLineEndStations(); + return new Line(name, endStations); + } + + private String getNewLineName() { + getOutputView().printInformation(ADD_LINE_INFO); + return getInputView().getLineName(); + } + + private List getNewLineEndStations() { + Station upEnd = getNewLineUpEnd(); + Station downEnd = getNewLineDownEnd(); + return new ArrayList<>(Arrays.asList(upEnd, downEnd)); + } + + private Station getNewLineUpEnd() { + getOutputView().printInformation(ADD_LINE_INFO_UP_END); + return new Station(getInputView().getStationName()); + } + + private Station getNewLineDownEnd() { + getOutputView().printInformation(ADD_LINE_INFO_DOWN_END); + return new Station(getInputView().getStationName()); + } + + private void validateNewLine(Line newLine) { + validateNameLength(newLine); + validateDuplicateLineExists(newLine); + validateEndStationsExist(newLine); + } + + private void validateNameLength(Line newLine) { + if (newLine.getName().length() < Constant.MIN_NAME_LENGTH) + throw new InvalidInputException(InvalidInputException.ExceptionCode.INVALID_NAME_LENGTH); + } + + private void validateDuplicateLineExists(Line newLine) { + if (LineRepository.lines().contains(newLine)) + throw new InvalidInputException(InvalidInputException.ExceptionCode.DUPLICATE_LINE); + } + + private void validateEndStationsExist(Line newLine) { + if (!StationRepository.stations().contains(newLine.getUpEnd()) + || !StationRepository.stations().contains(newLine.getDownEnd())) + throw new InvalidInputException(InvalidInputException.ExceptionCode.NO_SUCH_STATION); + } + + + @Override + public void delete() { + String targetLineName = getTargetLineName(); + validateTargetLine(targetLineName); + LineRepository.deleteLineByName(targetLineName); + getOutputView().printInformation(DELETE_LINE_SUCCESS); + } + + private String getTargetLineName() { + getOutputView().printInformation(DELETE_STATION_INFO); + return getInputView().getLineName(); + } + + private void validateTargetLine(String targetLineName) { + Line targetLine = new Line(targetLineName); + if (!LineRepository.lines().contains(targetLine)) + throw new InvalidInputException(InvalidInputException.ExceptionCode.NO_SUCH_LINE); + } + + + @Override + public void show() { + getOutputView().printLineList(); + } +} diff --git a/src/main/java/subway/service/LinkService.java b/src/main/java/subway/service/LinkService.java new file mode 100644 index 000000000..978d8b786 --- /dev/null +++ b/src/main/java/subway/service/LinkService.java @@ -0,0 +1,136 @@ +package subway.service; + +import subway.domain.Line; +import subway.domain.Link; +import subway.domain.Station; +import subway.exception.InvalidInputException; +import subway.repository.LineRepository; +import subway.repository.StationRepository; +import subway.view.InputView; +import subway.view.OutputView; + +import static subway.constant.Constant.MIN_ORDER; +import static subway.constant.Constant.MIN_STATIONS_IN_LINE; +import static subway.constant.Information.*; + +public class LinkService extends CrudService { + + public LinkService(InputView inputView, OutputView outputView) { + super(inputView, outputView, LINK_INFO); + } + + @Override + public void add() { + try { + Link newLink = getNewLink(); + validateNewLink(newLink); + addNewLink(newLink); + getOutputView().printInformation(ADD_LINK_SUCCESS); + } catch (NumberFormatException e) { + throw new InvalidInputException(InvalidInputException.ExceptionCode.NON_NUMBER_INPUT); + } + } + + private Link getNewLink() throws NumberFormatException { + String lineName = getLineNameOnAdd(); + String stationName = getStationNameOnAdd(); + int order = getOrderOnAdd(); + return new Link(lineName, stationName, order); + } + + private String getLineNameOnAdd() { + getOutputView().printInformation(ADD_LINK_INFO_LINE); + return getInputView().getLineName(); + } + + private String getStationNameOnAdd() { + getOutputView().printInformation(ADD_LINK_INFO_STATION); + return getInputView().getStationName(); + } + + private int getOrderOnAdd() throws NumberFormatException { + getOutputView().printInformation(ADD_LINK_INFO_ORDER); + return getInputView().getOrder(); + } + + private void validateNewLink(Link newLink) { + validateLineExists(newLink.getLineName()); + validateStationExists(newLink.getStationName()); + validateOrderInRange(newLink); + } + + private void validateLineExists(String lineName) { + Line line = new Line(lineName); + if (!LineRepository.lines().contains(line)) + throw new InvalidInputException(InvalidInputException.ExceptionCode.NO_SUCH_LINE); + } + + private void validateStationExists(String stationName) { + Station station = new Station(stationName); + if (!StationRepository.stations().contains(station)) + throw new InvalidInputException(InvalidInputException.ExceptionCode.NO_SUCH_STATION); + } + + private void validateOrderInRange(Link newLink) { + Line line = getTargetLineByName(newLink.getLineName()); + if (newLink.getOrder() < MIN_ORDER || newLink.getOrder() > line.getStations().size()) + throw new InvalidInputException(InvalidInputException.ExceptionCode.OUT_OF_LINE_RANGE); + } + + private Line getTargetLineByName(String lineName) { + return LineRepository.getLineByName(lineName); + } + + private void addNewLink(Link newLink) { + Line line = getTargetLineByName(newLink.getLineName()); + line.addNewLink(newLink); + } + + + @Override + public void delete() { + Link targetLink = getTargetLink(); + validateTargetLink(targetLink); + deleteTargetLink(targetLink); + getOutputView().printInformation(DELETE_LINK_SUCCESS); + } + + private Link getTargetLink() { + String targetLineName = getTargetLineNameOnDelete(); + String targetStationName = getTargetStationNameOnDelete(); + return new Link(targetLineName, targetStationName); + } + + private String getTargetLineNameOnDelete() { + getOutputView().printInformation(DELETE_LINK_INFO_LINE); + return getInputView().getLineName(); + } + + private String getTargetStationNameOnDelete() { + getOutputView().printInformation(DELETE_LINK_INFO_STATION); + return getInputView().getStationName(); + } + + private void validateTargetLink(Link targetLink) { + validateLineExists(targetLink.getLineName()); + validateStationExists(targetLink.getStationName()); + validateLineLength(targetLink.getLineName()); + } + + private void validateLineLength(String lineName) { + Line targetLine = getTargetLineByName(lineName); + if (targetLine.getStations().size() < MIN_STATIONS_IN_LINE) + throw new InvalidInputException(InvalidInputException.ExceptionCode.NO_LINK_AVAILABLE); + } + + private void deleteTargetLink(Link targetLink) { + Line targetLine = getTargetLineByName(targetLink.getLineName()); + targetLine.deleteTargetLink(targetLink); + } + + + @Override + public void show() { + throw new InvalidInputException(InvalidInputException.ExceptionCode.INVALID_FUNCTION_CODE); + } +} diff --git a/src/main/java/subway/service/MainService.java b/src/main/java/subway/service/MainService.java new file mode 100644 index 000000000..149121d64 --- /dev/null +++ b/src/main/java/subway/service/MainService.java @@ -0,0 +1,75 @@ +package subway.service; + +import subway.constant.Service; +import subway.exception.InvalidInputException; +import subway.view.InputView; +import subway.view.OutputView; + +import java.util.Scanner; + +import static subway.constant.Information.MAIN_INFO; + +public class MainService implements MapService { + + private boolean isContinue = true; + + private InputView inputView; + private OutputView outputView; + + private StationService stationService; + private LineService lineService; + private LinkService linkService; + private PrintService printService; + + public MainService(Scanner scanner) { + initViews(scanner); + initServices(); + } + + private void initServices() { + stationService = new StationService(inputView, outputView); + lineService = new LineService(inputView, outputView); + linkService = new LinkService(inputView, outputView); + printService = new PrintService(outputView); + } + + private void initViews(Scanner scanner) { + this.inputView = new InputView(scanner); + this.outputView = new OutputView(); + } + + + @Override + public void run() { + while (isContinue) + runService(); + } + + private void runService() { + try { + String selectedService = getSelectedService(); + Service.validate(selectedService); + runSelectedService(selectedService); + } catch (InvalidInputException e) { + outputView.printErrorMessage(e.getMessage()); + } + } + + private String getSelectedService() { + outputView.printInformation(MAIN_INFO); + return inputView.getSelectedServiceInput(); + } + + private void runSelectedService(String selectedService) { + if (selectedService.equals(Service.STATION.getCode())) + stationService.run(); + if (selectedService.equals(Service.LINE.getCode())) + lineService.run(); + if (selectedService.equals(Service.LINK.getCode())) + linkService.run(); + if (selectedService.equals(Service.PRINT.getCode())) + printService.run(); + if (selectedService.equals(Service.QUIT.getCode())) + isContinue = false; + } +} diff --git a/src/main/java/subway/service/MapService.java b/src/main/java/subway/service/MapService.java new file mode 100644 index 000000000..f9266c8ae --- /dev/null +++ b/src/main/java/subway/service/MapService.java @@ -0,0 +1,7 @@ +package subway.service; + +public interface MapService { + + void run(); + +} diff --git a/src/main/java/subway/service/PrintService.java b/src/main/java/subway/service/PrintService.java new file mode 100644 index 000000000..365565e93 --- /dev/null +++ b/src/main/java/subway/service/PrintService.java @@ -0,0 +1,17 @@ +package subway.service; + +import subway.view.OutputView; + +public class PrintService implements MapService { + + private OutputView outputView; + + public PrintService(OutputView outputView) { + this.outputView = outputView; + } + + @Override + public void run() { + outputView.printMap(); + } +} diff --git a/src/main/java/subway/service/StationService.java b/src/main/java/subway/service/StationService.java new file mode 100644 index 000000000..01380b5c3 --- /dev/null +++ b/src/main/java/subway/service/StationService.java @@ -0,0 +1,95 @@ +package subway.service; + +import subway.constant.Constant; +import subway.constant.Information; +import subway.constant.InitialData; +import subway.domain.Line; +import subway.domain.Station; +import subway.exception.InvalidInputException; +import subway.repository.LineRepository; +import subway.repository.StationRepository; +import subway.view.InputView; +import subway.view.OutputView; + +import static subway.constant.Information.*; + +public class StationService extends CrudService { + + public StationService(InputView inputView, OutputView outputView) { + super(inputView, outputView, Information.STATION_INFO); + initStations(); + } + + private void initStations() { + StationRepository.addStation(InitialData.stations); + } + + + @Override + public void add() { + Station newStation = getNewStation(); + validateNewStation(newStation); + StationRepository.addStation(newStation); + getOutputView().printInformation(ADD_STATION_SUCCESS); + } + + private Station getNewStation() { + getOutputView().printInformation(ADD_STATION_INFO); + return new Station(getInputView().getStationName()); + } + + private void validateNewStation(Station newStation) { + validateNameLength(newStation); + validateDuplicateStationExists(newStation); + } + + private void validateNameLength(Station newStation) { + if (newStation.getName().length() < Constant.MIN_NAME_LENGTH) + throw new InvalidInputException(InvalidInputException.ExceptionCode.INVALID_NAME_LENGTH); + } + + private void validateDuplicateStationExists(Station newStation) { + if (StationRepository.stations().contains(newStation)) + throw new InvalidInputException(InvalidInputException.ExceptionCode.DUPLICATE_STATION); + } + + + @Override + public void delete() { + Station targetStation = getTargetStation(); + validateTargetStation(targetStation); + StationRepository.deleteStationByName(targetStation.getName()); + getOutputView().printInformation(DELETE_STATION_SUCCESS); + } + + private Station getTargetStation() { + getOutputView().printInformation(DELETE_STATION_INFO); + return new Station(getInputView().getStationName()); + } + + private void validateTargetStation(Station targetStation) { + validateTargetStationExists(targetStation); + validateTargetStationIsNotLinked(targetStation); + } + + private void validateTargetStationExists(Station targetStation) { + if (!StationRepository.stations().contains(targetStation)) + throw new InvalidInputException(InvalidInputException.ExceptionCode.NO_SUCH_STATION); + } + + private void validateTargetStationIsNotLinked(Station targetStation) { + for (Line line : LineRepository.lines()) + validateTargetStationIsNotInLine(line, targetStation); + } + + private void validateTargetStationIsNotInLine(Line line, Station targetStation) { + if (line.getStations().contains(targetStation)) + throw new InvalidInputException(InvalidInputException.ExceptionCode.STATION_LINKED); + } + + + @Override + public void show() { + getOutputView().printStationList(); + } +} diff --git a/src/main/java/subway/view/InputView.java b/src/main/java/subway/view/InputView.java new file mode 100644 index 000000000..847d7a2ec --- /dev/null +++ b/src/main/java/subway/view/InputView.java @@ -0,0 +1,37 @@ +package subway.view; + +import java.util.Scanner; + +public class InputView { + + private final Scanner scanner; + + public InputView(Scanner scanner) { + this.scanner = scanner; + } + + + private String userInputString() { + return scanner.nextLine(); + } + + public String getSelectedServiceInput() { + return userInputString(); + } + + public String getSelectedFunctionInput() { + return userInputString(); + } + + public String getLineName() { + return userInputString(); + } + + public String getStationName() { + return userInputString(); + } + + public int getOrder() throws NumberFormatException { + return Integer.parseInt(scanner.nextLine()) - 1; + } +} diff --git a/src/main/java/subway/view/OutputView.java b/src/main/java/subway/view/OutputView.java new file mode 100644 index 000000000..3345bb2ff --- /dev/null +++ b/src/main/java/subway/view/OutputView.java @@ -0,0 +1,57 @@ +package subway.view; + +import subway.domain.Line; +import subway.domain.Station; +import subway.repository.LineRepository; +import subway.repository.StationRepository; + +import static subway.constant.Constant.DIVIDER; +import static subway.constant.Information.*; + +public class OutputView { + + public void printErrorMessage(String message) { + print(message); + } + + public void printInformation(String information) { + printEmptyLine(); + print(information); + } + + private void printEmptyLine() { + print(""); + } + + private void print(String message) { + System.out.println(message); + } + + public void printStationList() { + print(SHOW_STATION_INFO); + for (Station station : StationRepository.stations()) + print(INFO_HEADER + station.getName()); + } + + public void printLineList() { + print(SHOW_LINE_INFO); + for (Line line : LineRepository.lines()) + print(INFO_HEADER + line.getName()); + } + + + public void printMap() { + print(PRINT_INFO); + for (Line line : LineRepository.lines()) { + printLineAndItsStations(line); + printEmptyLine(); + } + } + + private void printLineAndItsStations(Line line) { + print(INFO_HEADER + line.getName()); + print(INFO_HEADER + DIVIDER); + for (Station station : line.getStations()) + print(INFO_HEADER + station.getName()); + } +}