diff --git a/.github/solving_process.md b/.github/solving_process.md new file mode 100644 index 000000000..7d6aa8807 --- /dev/null +++ b/.github/solving_process.md @@ -0,0 +1,3873 @@ +# 🧐 μ§€ν•˜μ²  노선도 경둜 쑰회 λ―Έμ…˜ + +[μš°μ•„ν•œν…Œν¬μ½”μŠ€](https://github.com/woowacourse) precourse 문제 +쀑 [μ§€ν•˜μ²  노선도 경둜 쑰회 λ―Έμ…˜](https://github.com/woowacourse/java-subway-path-precourse) 풀이 κΈ°λ‘ν•˜κΈ°. + +DDD ꡬ쑰와 MVC νŒ¨ν„΄μ„ μ μš©ν•˜μ—¬ TDD λ°©μ‹μœΌλ‘œ κ°œλ°œν•˜κ³ , μž…μΆœλ ₯ 및 ν”„λ‘œκ·Έλž˜λ° μš”κ΅¬μ‚¬ν•­μ„ λΆ€ν•©ν•˜λ„λ‘ ν’€μ–΄ λ³Ό μ˜ˆμ •. + +## 0. 섀계 + +### application + +| λΉ„μ¦ˆλ‹ˆμŠ€ | κΈ°λŠ₯ | +|:-------:|:------------------------------------------------------------------| +| section | - ꡬ간 CRUD
- κ·Έλž˜ν”„ λ…Έλ“œ μΆ”κ°€
- μ΅œλ‹¨κ±°λ¦¬ 경둜 κ΅¬ν•˜κΈ°
- μ΅œμ†Œμ‹œκ°„ 경둜 κ΅¬ν•˜κΈ° | + +### domain + +| λΉ„μ¦ˆλ‹ˆμŠ€ | κΈ°λŠ₯ | +|:-------:|:-----------------------| +| line | - λ…Έμ„  객체
- λ…Έμ„  CRUD | +| section | - ꡬ간 객체
- ꡬ간 CRUD | +| station | - μ—­ 객체
- μ—­ CRUD | + +### infrastructure + +| 클래슀 | κΈ°λŠ₯ | +|:-------------:|:------------------------| +| FileParser | - abstract
- 파일 검증 | +| XmlFileParser | - xml 파일 λ‘œλ“œ 및 데이터 νŒŒμ‹± | + +### presentation + +| 클래슀 | κΈ°λŠ₯ | +|:-------------------:|:-------------------------------| +| ViewController | - interface | +| IntroViewController | - 도메인 데이터 μ΄ˆκΈ°ν™”
- 인트둜 ν™”λ©΄ μ œμ–΄ | +| MainViewController | - 메인 ν™”λ©΄ μ œμ–΄ | +| PathViewController | - 경둜 κΈ°μ€€(ν™”λ©΄) μ œμ–΄ | +| LineController | - λ…Έμ„  λΉ„μ¦ˆλ‹ˆμŠ€ 처리 | +| SectionController | - ꡬ간 λΉ„μ¦ˆλ‹ˆμŠ€ 처리 | +| StationController | - μ—­ λΉ„μ¦ˆλ‹ˆμŠ€ 처리 | +| ShortCostController | - μ΅œλ‹¨(μ΅œμ†Œ) 경둜 처리 | + +### ui + +| 클래슀 | κΈ°λŠ₯ | +|:-------:|:------------| +| Console | - μ½˜μ†” μž…μΆœλ ₯ 처리 | + +### view + +| 클래슀 | κΈ°λŠ₯ | +|:-----------------:|:------------------------------------------------| +| View | - interface | +| MenuView | - 메뉴 좜λ ₯
- ν•­λͺ© μž…λ ₯
- 메뉴 선택 이벀트 λ°œμƒ | +| Menu | - interface
- 메뉴 λͺ©λ‘ 쑰회
- λͺ…λ Ήμ–΄ κΈ°μ€€ 단건 쑰회 | +| MenuEventRegister | - 메뉴 선택 이벀트 처리 등둝 | + +### util + +| 클래슀 | κΈ°λŠ₯ | +|:----------:|:------------| +| Validation | - 곡톡 μœ νš¨μ„± 검증 | + +## 1. Line CRUD + +```java +// LineDTOTest.java + +package subway.domain.line; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class LineDTOTest { + @Test + public void constructor__LineNameEssentialException() { + String message = "λ…Έμ„  이름은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new LineDTO(null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + assertThatThrownBy(() -> new LineDTO("")).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} +``` + +```java +// LineDTO.java + +package subway.domain.line; + +public class LineDTO { + private static final String LINE_NAME_ESSENTIAL_MESSAGE = "λ…Έμ„  이름은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + + private final String name; + + public LineDTO(String name) { + this.validate(name); + this.name = name; + } + + private void validate(String name) { + if (name == null || name.trim().isEmpty()) { + throw new IllegalArgumentException(LINE_NAME_ESSENTIAL_MESSAGE); + } + } + + public String getName() { + return this.name; + } +} +``` + +λ‹€μ–‘ν•œ κ³„μΈ΅μ—μ„œ 쓰일 κΈ°λ³Έ LineDTO κ΅¬ν˜„. + +```java +// LineService.java + +package subway.domain.line; + +import java.util.List; + +public class LineService { + public List findAll() { + return LineRepository.lines(); + } + + public void deleteAll() { + LineRepository.deleteAll(); + } +} +``` + +κΈ°λ³Έ 전체 쑰회 및 μ‚­μ œ κΈ°λŠ₯ 생성. + +### 1-1. CREATE + +```java +// LineRepositoryTest.java + +package subway.domain.line; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class LineRepositoryTest { + @Test + public void exists() { + Line line = new Line("test"); + assertThat(LineRepository.exists(line)).isEqualTo(false); + LineRepository.addLine(line); + assertThat(LineRepository.exists(line)).isEqualTo(true); + } + + @AfterEach + public void init() { + LineRepository.deleteAll(); + } +} +``` + +```java +// LineRepository.java + +package subway.domain.line; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class LineRepository { + public static boolean exists(Line other) { + return lines.stream().anyMatch(line -> line.getName().equals(other.getName())); + } +} +``` + +λ…Έμ„  쀑볡 생성 λ°©μ§€λ₯Ό μœ„ν•΄ 이름 κΈ°μ€€μœΌλ‘œ 쑴재 μ—¬λΆ€ 확인 κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// LineServiceTest.java + +package subway.domain.line; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class LineServiceTest { + private final LineService lineService = new LineService(); + + @Test + public void addLine() { + LineDTO lineDTO = new LineDTO("test"); + assertThat(lineService.findAll()).hasSize(0); + this.lineService.addLine(lineDTO); + assertThat(lineService.findAll()).hasSize(1); + } + + @Test + public void addLine__AlreadyExistsLineException() { + LineDTO lineDTO = new LineDTO("test"); + String message = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” λ…Έμ„ μž…λ‹ˆλ‹€."; + this.lineService.addLine(lineDTO); + assertThatThrownBy(() -> this.lineService.addLine(lineDTO)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @AfterEach + public void init() { + this.lineService.deleteAll(); + } +} +``` + +```java +// LineService.java + +package subway.domain.line; + +import java.util.List; + +public class LineService { + private static final String ALREADY_EXISTS_LINE_MESSAGE = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” λ…Έμ„ μž…λ‹ˆλ‹€."; + + public void addLine(LineDTO lineDTO) { + Line line = new Line(lineDTO.getName()); + if (LineRepository.exists(line)) { + throw new IllegalArgumentException(ALREADY_EXISTS_MESSAGE); + } + LineRepository.addLine(line); + } +} +``` + +λ…Έμ„  μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// LineController.java + +package subway.presentation; + +import subway.domain.line.LineDTO; +import subway.domain.line.LineService; + +public class LineController { + private final LineService lineService = new LineService(); + + public void addLine(String lineName) { + LineDTO lineDTO = new LineDTO(lineName); + this.lineService.addLine(lineDTO); + } +} +``` + +μ œμ–΄ 계측에 λ…Έμ„  μΆ”κ°€ κΈ°λŠ₯ λ§€ν•‘. + +### 1-2. READ + +```java +// LineRepositoryTest.java + +package subway.domain; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import subway.domain.line.Line; +import subway.domain.line.LineRepository; + +public class LineRepositoryTest { + @Test + public void findByName() { + String name = "test"; + Line line = new Line(name); + assertThat(LineRepository.findByName(name)).isNotPresent(); + LineRepository.addLine(line); + assertThat(LineRepository.findByName(name)).isPresent(); + } +} +``` + +```java +// LineRepository.java + +package subway.domain.line; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class LineRepository { + public static Optional findByName(String name) { + return lines.stream().filter(line -> line.getName().equals(name)).findFirst(); + } +} +``` + +```java +// LineServiceTest.java + +package subway.domain.line; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class LineServiceTest { + @Test + public void findOneByName() { + String name = "test"; + LineDTO lineDTO = new LineDTO(name); + this.lineService.addLine(lineDTO); + Line line = this.lineService.findOneByName(name); + assertThat(line.getName()).isEqualTo(name); + } + + @Test + public void findOneByName__NotExistsLineException() { + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ λ…Έμ„ μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> this.lineService.findOneByName("test")).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} +``` + +```java +// LineService.java + +package subway.domain.line; + +import java.util.List; + +public class LineService { + private static final String NOT_EXISTS_LINE_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ λ…Έμ„ μž…λ‹ˆλ‹€."; + + public Line findOneByName(String name) { + return LineRepository.findByName(name).orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_MESSAGE)); + } +} +``` + +λ…Έμ„  이름 κΈ°μ€€ 단건 쑰회 κΈ°λŠ₯ κ΅¬ν˜„. + +## 2. Station CRUD + +```java +// StationDTOTest.java + +package subway.domain.station; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class StationDTOTest { + @Test + public void constructor__StationNameEssentialException() { + String message = "μ—­ 이름은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new StationDTO(null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + assertThatThrownBy(() -> new StationDTO("")).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} +``` + +```java +// StationDTO.java + +package subway.domain.station; + +public class StationDTO { + private static final String STATION_NAME_ESSENTIAL_MESSAGE = "μ—­ 이름은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + + private final String name; + + public StationDTO(String name) { + this.validate(name); + this.name = name; + } + + private void validate(String name) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException(STATION_NAME_ESSENTIAL_MESSAGE); + } + } + + public String getName() { + return name; + } +} +``` + +λ‹€μ–‘ν•œ κ³„μΈ΅μ—μ„œ 쓰일 κΈ°λ³Έ StationDTO κ΅¬ν˜„. + +```java +// StationService.java + +package subway.domain.station; + +import java.util.List; + +public class StationService { + public List findAll() { + return StationRepository.stations(); + } + + public void deleteAll() { + StationRepository.deleteAll(); + } +} + +``` + +κΈ°λ³Έ 전체 쑰회 및 μ‚­μ œ κΈ°λŠ₯ 생성. + +### 2-1. CREATE + +```java +// StationRepositoryTest.java + +package subway.domain.station; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class StationRepositoryTest { + @Test + public void exists() { + Station station = new Station("test"); + assertThat(StationRepository.exists(station)).isEqualTo(false); + StationRepository.addStation(station); + assertThat(StationRepository.exists(station)).isEqualTo(true); + } + + @AfterEach + public void init() { + StationRepository.deleteAll(); + } +} +``` + +```java +// StationRepository.java + +package subway.domain.station; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class StationRepository { + public static boolean exists(Station other) { + return stations().stream().anyMatch(station -> station.getName().equals(other.getName())); + } +} +``` + +μ—­ 쀑볡 생성 λ°©μ§€λ₯Ό μœ„ν•΄ 이름 κΈ°μ€€μœΌλ‘œ 쑴재 μ—¬λΆ€ 확인 κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// StationServiceTest.java + +package subway.domain.station; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class StationServiceTest { + private final StationService stationService = new StationService(); + + @Test + public void addStation() { + StationDTO stationDTO = new StationDTO("test"); + assertThat(this.stationService.findAll()).hasSize(0); + this.stationService.addStation(stationDTO); + assertThat(this.stationService.findAll()).hasSize(1); + } + + @Test + public void addStation__AlreadyExistsStationException() { + StationDTO stationDTO = new StationDTO("test"); + String message = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” μ—­μž…λ‹ˆλ‹€."; + this.stationService.addStation(stationDTO); + assertThatThrownBy(() -> this.stationService.addStation(stationDTO)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + this.stationService.deleteAll(); + } + + @AfterEach + public void init() { + this.stationService.deleteAll(); + } +} +``` + +```java +// StationService.java + +package subway.domain.station; + +import java.util.List; + +public class StationService { + private static final String ALREADY_EXISTS_STATION_MESSAGE = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” μ—­μž…λ‹ˆλ‹€."; + + public void addStation(StationDTO stationDTO) { + Station station = new Station(stationDTO.getName()); + if (StationRepository.exists(station)) { + throw new IllegalArgumentException(ALREADY_EXISTS_STATION_MESSAGE); + } + StationRepository.addStation(station); + } +} +``` + +μ—­ μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// StationController.java + +package subway.presentation; + +import subway.domain.station.StationDTO; +import subway.domain.station.StationService; + +public class StationController { + private final StationService stationService = new StationService(); + + public void addStation(String name) { + StationDTO stationDTO = new StationDTO(name); + stationService.addStation(stationDTO); + } +} +``` + +μ œμ–΄ 계측에 μ—­ μΆ”κ°€ κΈ°λŠ₯ λ§€ν•‘. + +### 2-2. READ + +```java +// StationRepositoryTest.java + +package subway.domain.station; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class StationRepositoryTest { + @Test + public void findByName() { + String name = "test"; + Station station = new Station(name); + assertThat(StationRepository.findByName(name)).isNotPresent(); + StationRepository.addStation(station); + assertThat(StationRepository.findByName(name)).isPresent(); + } +} +``` + +```java +// StationRepository.java + +package subway.domain.station; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class StationRepository { + public static Optional findByName(String name) { + return stations.stream().filter(station -> station.getName().equals(name)).findFirst(); + } +} +``` + +```java +// StationRepositoryTest.java + +package subway.domain.station; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class StationServiceTest { + @Test + public void findOneByName() { + String name = "test"; + StationDTO stationDTO = new StationDTO(name); + this.stationService.addStation(stationDTO); + Station station = this.stationService.findOneByName(name); + assertThat(station.getName()).isEqualTo(name); + } + + @Test + public void findOneByName__NotExistsStationException() { + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ—­μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> this.stationService.findOneByName("test")).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} +``` + +```java +// StationService.java + +package subway.domain.station; + +import java.util.List; + +public class StationService { + private static final String NOT_EXISTS_STATION_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ—­μž…λ‹ˆλ‹€."; + + public Station findOneByName(String name) { + return StationRepository.findByName(name) + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_STATION_MESSAGE)); + } +} +``` + +μ—­ 이름 κΈ°μ€€ 단건 쑰회 κΈ°λŠ₯ κ΅¬ν˜„. + +## 3. Section CRUD + +```java +// Section.java + +package subway.domain.section; + +import subway.domain.line.Line; +import subway.domain.station.Station; + +public class Section { + private final Line line; + private final Station source; + private final Station sink; + private final int distance; + private final int time; + + public Section(Line line, Station source, Station sink, int distance, int time) { + this.line = line; + this.source = source; + this.sink = sink; + this.distance = distance; + this.time = time; + } + + public Line getLine() { + return line; + } + + public Station getSource() { + return source; + } + + public Station getSink() { + return sink; + } + + public int getDistance() { + return distance; + } + + public int getTime() { + return time; + } +} +``` + +ꡬ간 객체 생성. + +```java +// LineTest.java + +package subway.domain.line; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import subway.domain.section.Section; +import subway.domain.station.Station; + +public class LineTest { + @Test + public void addSection() { + Line line = new Line("line"); + Station source = new Station("source"); + Station sink = new Station("sink"); + Section section = new Section(line, source, sink, 0, 0); + assertThat(line.getSectionList()).hasSize(0); + line.addSection(section); + assertThat(line.getSectionList()).hasSize(1); + } + + @Test + public void addSection__SectionAddingOtherLineReceiveException() { + Line line = new Line("line"); + Line other = new Line("other"); + Station source = new Station("source"); + Station sink = new Station("sink"); + Section section = new Section(other, source, sink, 0, 0); + String message = "ꡬ간 μΆ”κ°€μ‹œ λ‹€λ₯Έ 노선을 λ°›μ•˜μŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> line.addSection(section)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} +``` + +```java +// Line.java + +package subway.domain.line; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import subway.domain.section.Section; + +public class Line { + private static final String SECTION_ADDING_OTHER_LINE_RECEIVE_MESSAGE = "ꡬ간 μΆ”κ°€μ‹œ λ‹€λ₯Έ 노선을 λ°›μ•˜μŠ΅λ‹ˆλ‹€."; + + private final List
sectionList = new ArrayList<>(); + + public void addSection(Section section) { + this.validateSection(section); + this.sectionList.add(section); + } + + private void validateSection(Section section) { + if (this != section.getLine()) { + throw new IllegalArgumentException(SECTION_ADDING_OTHER_LINE_RECEIVE_MESSAGE); + } + } +} +``` + +Line 1 : N λ§€μΉ­. + +Section μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// StationTest.java + +package subway.domain.station; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import subway.domain.line.Line; +import subway.domain.section.Section; + +public class StationTest { + @Test + public void addSection() { + Line line = new Line("line"); + Station source = new Station("source"); + Station sink = new Station("sink"); + Section section = new Section(line, source, sink, 0, 0); + assertThat(source.getSectionList()).hasSize(0); + source.addSection(section); + assertThat(source.getSectionList()).hasSize(1); + } + + @Test + public void addSection__SectionAddingOtherSourceStationReceiveException() { + Line line = new Line("line"); + Station source = new Station("source"); + Station other = new Station("other"); + Station sink = new Station("sink"); + Section section = new Section(line, other, sink, 0, 0); + String message = "ꡬ간 μΆ”κ°€μ‹œ λ‹€λ₯Έ μ‹œμž‘μ—­μ„ λ°›μ•˜μŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> source.addSection(section)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} +``` + +```java +// Station.java + +package subway.domain.station; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import subway.domain.section.Section; + +public class Station { + private static final String SECTION_ADDING_OTHER_SOURCE_STATION_RECEIVE_MESSAGE = "ꡬ간 μΆ”κ°€μ‹œ λ‹€λ₯Έ μ‹œμž‘μ—­μ„ λ°›μ•˜μŠ΅λ‹ˆλ‹€."; + + private final List
sectionList = new ArrayList<>(); + + public List
getSectionList() { + return Collections.unmodifiableList(this.sectionList); + } + + public void addSection(Section section) { + this.validateSection(section); + this.sectionList.add(section); + } + + public void validateSection(Section section) { + if (this != section.getSource()) { + throw new IllegalArgumentException(SECTION_ADDING_OTHER_SOURCE_STATION_RECEIVE_MESSAGE); + } + } +} +``` + +Station 1 : N λ§€μΉ­. + +Section μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// SectionDTOTest.java + +package subway.application.section.dto; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import subway.domain.line.LineDTO; +import subway.domain.station.StationDTO; + +public class SectionDTOTest { + @Test + public void constructor__SectionAddingLineInfoEssentialException() { + StationDTO sourceDTO = new StationDTO("source"); + StationDTO sinkDTO = new StationDTO("sink"); + String message = "ꡬ간 μƒμ„±μ‹œ λ…Έμ„  μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new SectionDTO(null, sourceDTO, sinkDTO)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void constructor__SectionAddingSourceStationInfoEssentialException() { + LineDTO lineDTO = new LineDTO("line"); + StationDTO sinkDTO = new StationDTO("sink"); + String message = "ꡬ간 μƒμ„±μ‹œ μ‹œμž‘μ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new SectionDTO(lineDTO, null, sinkDTO)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void constructor__SectionAddingSinkStationInfoEssentialException() { + LineDTO lineDTO = new LineDTO("line"); + StationDTO sourceDTO = new StationDTO("source"); + String message = "ꡬ간 μƒμ„±μ‹œ μ’…λ£Œμ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new SectionDTO(lineDTO, sourceDTO, null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} +``` + +```java +// SectionDTO.java + +package subway.application.section.dto; + +import subway.domain.line.LineDTO; +import subway.domain.station.StationDTO; + +public class SectionDTO { + private static final String SECTION_ADDING_LINE_INFO_ESSENTIAL_MESSAGE = "ꡬ간 μƒμ„±μ‹œ λ…Έμ„  μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + private static final String SECTION_ADDING_SOURCE_STATION_INFO_ESSENTIAL_MESSAGE = "ꡬ간 μƒμ„±μ‹œ μ‹œμž‘μ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + private static final String SECTION_ADDING_SINK_STATION_INFO_ESSENTIAL_MESSAGE = "ꡬ간 μƒμ„±μ‹œ μ’…λ£Œμ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + + private final LineDTO lineDTO; + private final StationDTO sourceDTO; + private final StationDTO sinkDTO; + private int distance; + private int time; + + public SectionDTO(LineDTO lineDTO, StationDTO sourceDTO, StationDTO sinkDTO) { + this(lineDTO, sourceDTO, sinkDTO, 0, 0); + } + + public SectionDTO(LineDTO lineDTO, StationDTO sourceDTO, StationDTO sinkDTO, int distance, int time) { + this.validate(lineDTO, sourceDTO, sinkDTO); + this.lineDTO = lineDTO; + this.sourceDTO = sourceDTO; + this.sinkDTO = sinkDTO; + this.distance = distance; + this.time = time; + } + + private void validate(LineDTO lineDTO, StationDTO sourceDTO, StationDTO sinkDTO) { + if (lineDTO == null) { + throw new IllegalArgumentException(SECTION_ADDING_LINE_INFO_ESSENTIAL_MESSAGE); + } + if (sourceDTO == null) { + throw new IllegalArgumentException(SECTION_ADDING_SOURCE_STATION_INFO_ESSENTIAL_MESSAGE); + } + if (sinkDTO == null) { + throw new IllegalArgumentException(SECTION_ADDING_SINK_STATION_INFO_ESSENTIAL_MESSAGE); + } + } + + public LineDTO getLineDTO() { + return lineDTO; + } + + public StationDTO getSourceDTO() { + return sourceDTO; + } + + public StationDTO getSinkDTO() { + return sinkDTO; + } + + public int getDistance() { + return distance; + } + + public void setDistance(int distance) { + this.distance = distance; + } + + public int getTime() { + return time; + } + + public void setTime(int time) { + this.time = time; + } +} +``` + +λ‹€μ–‘ν•œ κ³„μΈ΅μ—μ„œ 쓰일 κΈ°λ³Έ SectionDTO κ΅¬ν˜„. + +```java +// SectionRepository.java + +package subway.domain.section; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SectionRepository { + private static final List
sections = new ArrayList<>(); + + public static List
sections() { + return Collections.unmodifiableList(sections); + } + + public static void deleteAll() { + sections.clear(); + } +} +``` + +```java +// SectionService.java + +package subway.application.section.service; + +import java.util.List; + +import subway.domain.section.Section; +import subway.domain.section.SectionRepository; + +public class SectionService { + public List
findAll() { + return SectionRepository.sections(); + } + + public void deleteAll() { + SectionRepository.deleteAll(); + } +} +``` + +κΈ°λ³Έ 전체 쑰회 및 μ‚­μ œ κΈ°λŠ₯ 생성. + +### 3-1. CREATE + +```java +// SectionRepositoryTest.java + +package subway.domain.section; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import subway.domain.line.Line; +import subway.domain.station.Station; + +public class SectionRepositoryTest { + @Test + public void exists() { + Line line = new Line("line"); + Station source = new Station("source"); + Station sink = new Station("sink"); + Section section = new Section(line, source, sink, 0, 0); + assertThat(SectionRepository.exists(section)).isEqualTo(false); + SectionRepository.addSection(section); + assertThat(SectionRepository.exists(section)).isEqualTo(true); + } + + @AfterEach + public void init() { + SectionRepository.deleteAll(); + } +} +``` + +```java +// SectionRepository.java + +package subway.domain.section; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SectionRepository { + public static void addSection(Section section) { + sections.add(section); + } + + public static boolean exists(Section other) { + return sections.stream() + .anyMatch(section -> section.getLine() == other.getLine() && section.getSource() == other.getSource() + && section.getSink() == other.getSink()); + } +} +``` + +ꡬ간 데이터 μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„. + +ꡬ간 쀑볡 생성 λ°©μ§€λ₯Ό μœ„ν•΄ λ…Έμ„ , μ‹œμž‘μ—­, μ’…λ£Œμ—­ κΈ°μ€€μœΌλ‘œ 쑴재 μ—¬λΆ€ 확인 κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// SectionServiceTest.java + +package subway.application.section.service; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import subway.application.section.dto.SectionDTO; +import subway.domain.line.Line; +import subway.domain.line.LineDTO; +import subway.domain.line.LineService; +import subway.domain.station.Station; +import subway.domain.station.StationDTO; +import subway.domain.station.StationService; + +public class SectionServiceTest { + private final LineDTO lineDTO = new LineDTO("line"); + private final StationDTO sourceDTO = new StationDTO("source"); + private final StationDTO sinkDTO = new StationDTO("sink"); + + private final LineService lineService = new LineService(); + private final StationService stationService = new StationService(); + private final SectionService sectionService = new SectionService(); + + @BeforeEach + public void setup() { + this.lineService.addLine(lineDTO); + this.stationService.addStation(sourceDTO); + this.stationService.addStation(sinkDTO); + } + + @Test + public void addSection() { + Line line = this.lineService.findOneByName(lineDTO.getName()); + Station source = this.stationService.findOneByName(sourceDTO.getName()); + SectionDTO sectionDTO = new SectionDTO(lineDTO, sourceDTO, sinkDTO); + assertThat(this.sectionService.findAll()).hasSize(0); + assertThat(line.getSectionList()).hasSize(0); + assertThat(source.getSectionList()).hasSize(0); + this.sectionService.addSection(sectionDTO); + assertThat(this.sectionService.findAll()).hasSize(1); + assertThat(line.getSectionList()).hasSize(1); + assertThat(source.getSectionList()).hasSize(1); + } + + @Test + public void addSection__AlreadyExistsSectionException() { + SectionDTO sectionDTO = new SectionDTO(lineDTO, sourceDTO, sinkDTO); + String message = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” κ΅¬κ°„μž…λ‹ˆλ‹€."; + this.sectionService.addSection(sectionDTO); + assertThatThrownBy(() -> this.sectionService.addSection(sectionDTO)).isInstanceOf( + IllegalArgumentException.class) + .hasMessage(message); + } + + @AfterEach + public void init() { + lineService.deleteAll(); + stationService.deleteAll(); + sectionService.deleteAll(); + } +} +``` + +```java +// SectionService.java + +package subway.application.section.service; + +import java.util.List; + +import subway.application.section.dto.SectionDTO; +import subway.domain.line.Line; +import subway.domain.line.LineService; +import subway.domain.section.Section; +import subway.domain.section.SectionRepository; +import subway.domain.station.Station; +import subway.domain.station.StationService; + +public class SectionService { + private static final String ALREADY_EXISTS_SECTION_MESSAGE = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” κ΅¬κ°„μž…λ‹ˆλ‹€."; + + private final LineService lineService = new LineService(); + private final StationService stationService = new StationService(); + + public void addSection(SectionDTO sectionDTO) { + Line line = this.lineService.findOneByName(sectionDTO.getLineDTO().getName()); + Station source = this.stationService.findOneByName(sectionDTO.getSourceDTO().getName()); + Station sink = this.stationService.findOneByName(sectionDTO.getSinkDTO().getName()); + Section section = new Section(line, source, sink, sectionDTO.getDistance(), sectionDTO.getTime()); + if (SectionRepository.exists(section)) { + throw new IllegalArgumentException(ALREADY_EXISTS_SECTION_MESSAGE); + } + SectionRepository.addSection(section); + line.addSection(section); + source.addSection(section); + } +} +``` + +ꡬ간 μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// ValidationTest.java + +package subway.util; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class ValidationTest { + @Test + public void isNumeric() { + assertThat(Validation.isNumeric("1")).isEqualTo(true); + assertThat(Validation.isNumeric("")).isEqualTo(false); + assertThat(Validation.isNumeric("number")).isEqualTo(false); + } +} +``` + +```java +// Validation.java + +package subway.util; + +import java.util.regex.Pattern; + +public final class Validation { + private Validation() { + } + + public static boolean isNumeric(String str) { + return str != null && !str.isEmpty() && Pattern.matches("^[0-9]*$", str); + } +} +``` + +숫자 ν˜•μ‹μ˜ λ¬Έμžμ—΄ 확인 κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// SectionController.java + +package subway.presentation; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class SectionControllerTest { + private final SectionController sectionController = new SectionController(); + + @Test + public void addSection__InputEssentialDistanceException() { + String lineName = "line"; + String sourceName = "source"; + String sinkName = "sink"; + String time = "0"; + String message = "거리 μž…λ ₯은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy( + () -> this.sectionController.addSection(lineName, sourceName, sinkName, null, time)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + assertThatThrownBy( + () -> this.sectionController.addSection(lineName, sourceName, sinkName, "", time)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addSection__OnlyPossibleNumericInputDistanceException() { + String lineName = "line"; + String sourceName = "source"; + String sinkName = "sink"; + String time = "0"; + String message = "κ±°λ¦¬λŠ” 숫자 ν˜•μ‹μ˜ μž…λ ₯만 κ°€λŠ₯ν•©λ‹ˆλ‹€."; + assertThatThrownBy( + () -> this.sectionController.addSection(lineName, sourceName, sinkName, "distance", time)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addSection__InputEssentialTimeException() { + String lineName = "line"; + String sourceName = "source"; + String sinkName = "sink"; + String distance = "0"; + String message = "μ‹œκ°„ μž…λ ₯은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy( + () -> this.sectionController.addSection(lineName, sourceName, sinkName, distance, null)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + assertThatThrownBy( + () -> this.sectionController.addSection(lineName, sourceName, sinkName, distance, "")).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addSection__OnlyPossibleNumericInputTimeException() { + String lineName = "line"; + String sourceName = "source"; + String sinkName = "sink"; + String distance = "0"; + String message = "μ‹œκ°„μ€ 숫자 ν˜•μ‹μ˜ μž…λ ₯만 κ°€λŠ₯ν•©λ‹ˆλ‹€."; + assertThatThrownBy( + () -> this.sectionController.addSection(lineName, sourceName, sinkName, distance, "time")).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } +} +``` + +```java +// SectionController.java + +package subway.presentation; + +import subway.application.section.dto.SectionDTO; +import subway.application.section.service.SectionService; +import subway.domain.line.LineDTO; +import subway.domain.station.StationDTO; +import subway.util.Validation; + +public class SectionController { + private static final String INPUT_ESSENTIAL_DISTANCE_MESSAGE = "거리 μž…λ ₯은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + private static final String ONLY_POSSIBLE_NUMERIC_INPUT_DISTANCE_MESSAGE = "κ±°λ¦¬λŠ” 숫자 ν˜•μ‹μ˜ μž…λ ₯만 κ°€λŠ₯ν•©λ‹ˆλ‹€."; + private static final String INPUT_ESSENTIAL_TIME_MESSAGE = "μ‹œκ°„ μž…λ ₯은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + private static final String ONLY_POSSIBLE_NUMERIC_INPUT_TIME_MESSAGE = "μ‹œκ°„μ€ 숫자 ν˜•μ‹μ˜ μž…λ ₯만 κ°€λŠ₯ν•©λ‹ˆλ‹€."; + + private final SectionService sectionService = new SectionService(); + + public void addSection(String lineName, String sourceName, String sinkName, String _distance, String _time) { + LineDTO lineDTO = new LineDTO(lineName); + StationDTO sourceDTO = new StationDTO(sourceName); + StationDTO sinkDTO = new StationDTO(sinkName); + this.validateDistance(_distance); + int distance = Integer.parseInt(_distance); + this.validateTime(_time); + int time = Integer.parseInt(_time); + SectionDTO sectionDTO = new SectionDTO(lineDTO, sourceDTO, sinkDTO, distance, time); + this.sectionService.addSection(sectionDTO); + } + + private void validateDistance(String distance) { + if (distance == null || distance.trim().isEmpty()) { + throw new IllegalArgumentException(INPUT_ESSENTIAL_DISTANCE_MESSAGE); + } + if (!Validation.isNumeric(distance)) { + throw new IllegalArgumentException(ONLY_POSSIBLE_NUMERIC_INPUT_DISTANCE_MESSAGE); + } + } + + private void validateTime(String time) { + if (time == null || time.trim().isEmpty()) { + throw new IllegalArgumentException(INPUT_ESSENTIAL_TIME_MESSAGE); + } + if (!Validation.isNumeric(time)) { + throw new IllegalArgumentException(ONLY_POSSIBLE_NUMERIC_INPUT_TIME_MESSAGE); + } + } +} +``` + +μ œμ–΄ 계측에 ꡬ간 μΆ”κ°€ κΈ°λŠ₯ λ§€ν•‘. + +## 4. μ΅œλ‹¨ 거리 λ…Έλ“œ, κ°„μ„  μΆ”κ°€ + +### 4-1. λ…Έλ“œ + +```java +// ShortDistanceService.java + +package subway.application.section.service; + +import java.util.Collections; +import java.util.Set; + +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.DirectedWeightedMultigraph; + +import subway.domain.station.Station; + +class shortDistanceService { + private static final DirectedWeightedMultigraph graph = new DirectedWeightedMultigraph<>( + DefaultWeightedEdge.class); + + protected Set findAllNode() { + return Collections.unmodifiableSet(graph.vertexSet()); + } + + protected void deleteAllNode() { + Set nodes = new HashSet<>(this.findAllNode()); + graph.removeAllVertices(nodes); + } +} +``` + +κΈ°λ³Έ 전체 쑰회 및 μ‚­μ œ κΈ°λŠ₯ 생성. + +```java +// ShortDistanceServiceTest.java + +package subway.application.section.service; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import subway.domain.station.Station; + +class shortDistanceServiceTest { + private final Station source = new Station("source"); + + private final ShortDistanceService shortDistanceService = new ShortDistanceService(); + + @Test + public void addNode() { + assertThat(this.shortDistanceService.findAllNode()).hasSize(0); + this.shortDistanceService.addNode(this.source); + assertThat(this.shortDistanceService.findAllNode()).hasSize(1); + } + + @Test + public void addNode_AlreadyExistsNodeException() { + String message = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” λ…Έλ“œμž…λ‹ˆλ‹€."; + this.shortDistanceService.addNode(this.source); + assertThatThrownBy(() -> this.shortDistanceService.addNode(this.source)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @AfterEach + public void init() { + this.shortDistanceService.deleteAllNode(); + } +} +``` + +```java +// ShortDistanceService.java + +package subway.application.section.service; + +import java.util.Collections; +import java.util.Set; + +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.DirectedWeightedMultigraph; + +import subway.domain.station.Station; + +class shortDistanceService { + private static final String ALREADY_EXISTS_NODE_MESSAGE = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” λ…Έλ“œμž…λ‹ˆλ‹€."; + + protected void addNode(Station station) { + if (graph.containsVertex(station)) { + throw new IllegalArgumentException(ALREADY_EXISTS_NODE_MESSAGE); + } + graph.addVertex(station); + } +} +``` + +λ…Έμ„  μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// SectionService.java + +package subway.application.section.service; + +import java.util.List; + +import subway.application.section.dto.SectionDTO; +import subway.domain.line.Line; +import subway.domain.line.LineService; +import subway.domain.section.Section; +import subway.domain.section.SectionRepository; +import subway.domain.station.Station; +import subway.domain.station.StationDTO; +import subway.domain.station.StationService; + +public class SectionService { + private final ShortDistanceService shortDistanceService = new ShortDistanceService(); + + public void addNode(StationDTO stationDTO) { + Station station = this.stationService.findOneByName(stationDTO.getName()); + this.shortDistanceService.addNode(station); + } +} +``` + +λ¬΄λΆ„λ³„ν•œ λ…Έλ“œ μΆ”κ°€λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄ Section Service 계측을 ν†΅ν•΄μ„œλ§Œ μ²˜λ¦¬λ˜λ„λ‘ 함. + +## 4-2. κ°„μ„  + +```java +// ShortDistanceService.java + +package subway.application.section.service; + +import java.util.Collections; +import java.util.Set; + +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.DirectedWeightedMultigraph; + +import subway.domain.station.Station; + +class shortDistanceService { + protected Set findAllEdge() { + return Collections.unmodifiableSet(graph.edgeSet()); + } + + protected void deleteAllEdge() { + graph.removeAllEdges(graph.edgeSet()); + } + + protected void deleteAll() { + this.deleteAllEdge(); + this.deleteAllNode(); + } +} +``` + +κΈ°λ³Έ 전체 쑰회 및 μ‚­μ œ κΈ°λŠ₯ 생성. + +```java +// ShortDistanceServiceTest.java + +package subway.application.section.service; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import subway.domain.line.Line; +import subway.domain.section.Section; +import subway.domain.station.Station; + +class shortDistanceServiceTest { + private final Line line = new Line("line"); + private final Station sink = new Station("sink"); + + @Test + public void addEdge_NotExistsSourceNodeException() { + Section section = new Section(this.line, this.source, this.sink, 0, 0); + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ‹œμž‘λ…Έλ“œμž…λ‹ˆλ‹€."; + this.shortDistanceService.addNode(this.sink); + assertThatThrownBy(() -> this.shortDistanceService.addEdge(section)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addEdge_NotExistsSinkNodeException() { + Section section = new Section(this.line, this.source, this.sink, 0, 0); + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ’…λ£Œλ…Έλ“œμž…λ‹ˆλ‹€."; + this.shortDistanceService.addNode(this.source); + assertThatThrownBy(() -> this.shortDistanceService.addEdge(section)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addEdge_AlreadyExistsEdgeException() { + Section section = new Section(this.line, this.source, this.sink, 0, 0); + String message = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” κ°„μ„ μž…λ‹ˆλ‹€."; + this.shortDistanceService.addNode(this.source); + this.shortDistanceService.addNode(this.sink); + this.shortDistanceService.addEdge(section); + assertThatThrownBy(() -> this.shortDistanceService.addEdge(section)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @AfterEach + public void init() { + this.shortDistanceService.deleteAll(); + } +} +``` + +```java +// ShortDistanceService.java + +package subway.application.section.service; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.DirectedWeightedMultigraph; + +import subway.domain.section.Section; +import subway.domain.station.Station; + +class shortDistanceService { + private static final String NOT_EXISTS_SOURCE_NODE_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ‹œμž‘λ…Έλ“œμž…λ‹ˆλ‹€."; + private static final String NOT_EXISTS_SINK_NODE_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ’…λ£Œλ…Έλ“œμž…λ‹ˆλ‹€."; + private static final String ALREADY_EXISTS_EDGE_MESSAGE = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” κ°„μ„ μž…λ‹ˆλ‹€."; + + protected void addEdge(Section section) { + Station source = section.getSource(); + Station sink = section.getSink(); + this.validateSource(source); + this.validateSink(sink); + if (graph.containsEdge(source, sink)) { + throw new IllegalArgumentException(ALREADY_EXISTS_EDGE_MESSAGE); + } + graph.setEdgeWeight(graph.addEdge(source, sink), section.getDistance()); + } + + private void validateSource(Station source) { + if (!graph.containsVertex(source)) { + throw new IllegalArgumentException(NOT_EXISTS_SOURCE_NODE_MESSAGE); + } + } + + private void validateSink(Station sink) { + if (!graph.containsVertex(sink)) { + throw new IllegalArgumentException(NOT_EXISTS_SINK_NODE_MESSAGE); + } + } +} +``` + +κ°„μ„  μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// SectionService.java + +package subway.application.section.service; + +import java.util.List; + +import subway.application.section.dto.SectionDTO; +import subway.domain.line.Line; +import subway.domain.line.LineService; +import subway.domain.section.Section; +import subway.domain.section.SectionRepository; +import subway.domain.station.Station; +import subway.domain.station.StationDTO; +import subway.domain.station.StationService; + +public class SectionService { + public void addSection(SectionDTO sectionDTO) { + Line line = this.lineService.findOneByName(sectionDTO.getLineDTO().getName()); + Station source = this.stationService.findOneByName(sectionDTO.getSourceDTO().getName()); + Station sink = this.stationService.findOneByName(sectionDTO.getSinkDTO().getName()); + Section section = new Section(line, source, sink, sectionDTO.getDistance(), sectionDTO.getTime()); + if (SectionRepository.exists(section)) { + throw new IllegalArgumentException(ALREADY_EXISTS_SECTION_MESSAGE); + } + SectionRepository.addSection(section); + +shortDistanceService.addEdge(section); + line.addSection(section); + source.addSection(section); + } +} +``` + +λ¬΄λΆ„λ³„ν•œ κ°„μ„  μΆ”κ°€λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄ Section Service 계측을 ν†΅ν•΄μ„œλ§Œ μ²˜λ¦¬λ˜λ„λ‘ 함. + +## 5. μ΅œλ‹¨ 거리 경둜 κ΅¬ν•˜κΈ° + +```java +// ShortDistanceServiceTest.java + +package subway.application.section.service; + +import static org.assertj.core.api.Assertions.*; + +import org.jgrapht.GraphPath; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import subway.domain.line.Line; +import subway.domain.section.Section; +import subway.domain.station.Station; + +class shortDistanceServiceTest { + @Test + public void compute__NotExistsSourceNodeException() { + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ‹œμž‘λ…Έλ“œμž…λ‹ˆλ‹€."; + this.shortDistanceService.addNode(this.sink); + assertThatThrownBy(() -> this.shortDistanceService.compute(this.source, this.sink)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void compute__NotExistsSinkNodeException() { + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ’…λ£Œλ…Έλ“œμž…λ‹ˆλ‹€."; + this.shortDistanceService.addNode(this.source); + assertThatThrownBy(() -> this.shortDistanceService.compute(this.source, this.sink)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } +} +``` + +```java +// ShortDistanceService.java + +package subway.application.section.service; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.jgrapht.GraphPath; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.DirectedWeightedMultigraph; + +import subway.domain.section.Section; +import subway.domain.station.Station; + +class shortDistanceService { + protected GraphPath compute(Station source, Station sink) { + this.validateSource(source); + this.validateSink(sink); + DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath<>(graph); + return dijkstraShortestPath.getPath(source, sink); + } +} +``` + +μ΅œλ‹¨ 거리 경둜 λ°˜ν™˜ κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// ShortCostRequestTest.java + +package subway.application.section.dto; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import subway.domain.station.StationDTO; + +public class ShortCostRequestTest { + @Test + public void constructor__SourceStationInfoEssentialException() { + StationDTO sinkDTO = new StationDTO("sink"); + String message = "μ‹œμž‘μ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new ShortCostRequest(null, sinkDTO)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void constructor__SinkStationInfoEssentialException() { + StationDTO sourceDTO = new StationDTO("source"); + String message = "μ’…λ£Œμ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new ShortCostRequest(sourceDTO, null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void constructor__SameSourceAndSinkStationException() { + StationDTO sourceDTO = new StationDTO("same"); + StationDTO sinkDTO = new StationDTO("same"); + String message = "μΆœλ°œμ—­κ³Ό 도착역이 λ™μΌν•©λ‹ˆλ‹€."; + assertThatThrownBy(() -> new ShortCostRequest(sourceDTO, sinkDTO)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} +``` + +```java +// StationDTO.java + +package subway.domain.station; + +import java.util.Objects; + +public class StationDTO { + @Override + public boolean equals(Object object) { + if (this == object) + return true; + if (!(object instanceof StationDTO)) + return false; + StationDTO other = (StationDTO)object; + return Objects.equals(this.name, other.name); + } +} +``` + +동일 체크λ₯Ό μœ„ν•΄ 이름 κΈ°μ€€μœΌλ‘œ λΉ„κ΅λ˜λ„λ‘ equal μ˜€λ²„λΌμ΄λ”©. + +```java +// ShortCostRequest.java + +package subway.application.section.dto; + +import subway.domain.station.StationDTO; + +public class ShortCostRequest { + private static final String SOURCE_STATION_INFO_ESSENTIAL_MESSAGE = "μ‹œμž‘μ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + private static final String SINK_STATION_INFO_ESSENTIAL_MESSAGE = "μ’…λ£Œμ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + private static final String SAME_SOURCE_AND_SINK_STATION_MESSAGE = "μΆœλ°œμ—­κ³Ό 도착역이 λ™μΌν•©λ‹ˆλ‹€."; + + private final StationDTO sourceDTO; + private final StationDTO sinkDTO; + + public ShortCostRequest(StationDTO sourceDTO, StationDTO sinkDTO) { + this.validate(sourceDTO, sinkDTO); + this.sourceDTO = sourceDTO; + this.sinkDTO = sinkDTO; + } + + private void validate(StationDTO sourceDTO, StationDTO sinkDTO) { + if (sourceDTO == null) { + throw new IllegalArgumentException(SOURCE_STATION_INFO_ESSENTIAL_MESSAGE); + } + if (sinkDTO == null) { + throw new IllegalArgumentException(SINK_STATION_INFO_ESSENTIAL_MESSAGE); + } + if (sourceDTO.equals(sinkDTO)) { + throw new IllegalArgumentException(SAME_SOURCE_AND_SINK_STATION_MESSAGE); + } + } + + public StationDTO getSourceDTO() { + return sourceDTO; + } + + public StationDTO getSinkDTO() { + return sinkDTO; + } +} +``` + +μ΅œλ‹¨(μ΅œμ†Œ) 경둜 계산 μš”μ²­ DTO κ΅¬ν˜„. + +```java +// ShortCostResponseTest.java + +package subway.application.section.dto; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import subway.domain.line.Line; +import subway.domain.section.Section; +import subway.domain.station.Station; + +public class ShortCostResponseTest { + @Test + public void constructor() { + Line line = new Line("line"); + Station source = new Station("source"); + Station sink = new Station("sink"); + List stationList = Arrays.asList(source, sink); + int distance = 1; + int time = 8; + Section section = new Section(line, source, sink, distance, time); + source.addSection(section); + ShortCostResponse shortCostResponse = new ShortCostResponse(stationList); + assertThat(shortCostResponse.getTotalDistance()).isEqualTo(distance); + assertThat(shortCostResponse.getTotalTime()).isEqualTo(time); + assertThat(shortCostResponse.getStationNameList()).containsExactly(source.getName(), sink.getName()); + } +} +``` + +```java +// StationTest.java + +package subway.domain.station; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import subway.domain.line.Line; +import subway.domain.section.Section; + +public class StationTest { + @Test + public void findDistanceTo() { + Line line = new Line("line"); + Station source = new Station("source"); + Station sink = new Station("sink"); + int distance = 1; + Section section = new Section(line, source, sink, distance, 0); + source.addSection(section); + assertThat(source.findDistanceTo(sink)).isEqualTo(distance); + } + + @Test + public void findDistanceTo__NotExistsPathToSinkStationException() { + Station source = new Station("source"); + Station sink = new Station("sink"); + String message = "μ’…λ£Œμ—­κΉŒμ§€ κ²½λ‘œκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> source.findDistanceTo(sink)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void findTimeTo() { + Line line = new Line("line"); + Station source = new Station("source"); + Station sink = new Station("sink"); + int time = 8; + Section section = new Section(line, source, sink, 0, time); + source.addSection(section); + assertThat(source.findTimeTo(sink)).isEqualTo(time); + } + + @Test + public void findTimeTo__NotExistsPathToSinkStationException() { + Station source = new Station("source"); + Station sink = new Station("sink"); + String message = "μ’…λ£Œμ—­κΉŒμ§€ κ²½λ‘œκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> source.findTimeTo(sink)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} +``` + +```java +// Station.java + +package subway.domain.station; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import subway.domain.section.Section; + +public class Station { + private static final String NOT_EXISTS_PATH_TO_SINK_STATION_MESSAGE = "μ’…λ£Œμ—­κΉŒμ§€ κ²½λ‘œκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + + public int findDistanceTo(Station sink) { + return this.sectionList.stream() + .filter(section -> section.getSink() == sink) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_PATH_TO_SINK_STATION_MESSAGE)) + .getDistance(); + } + + public int findTimeTo(Station sink) { + return this.sectionList.stream() + .filter(section -> section.getSink() == sink) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_PATH_TO_SINK_STATION_MESSAGE)) + .getTime(); + } +} +``` + +μ§€μ • κ²½λ‘œκΉŒμ§€μ˜ 거리와 μ‹œκ°„μ„ κ³„μ‚°ν•˜κΈ° μœ„ν•΄ κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// ShortCostResponse.java + +package subway.application.section.dto; + +import java.util.ArrayList; +import java.util.List; + +import subway.domain.station.Station; + +public class ShortCostResponse { + private int totalDistance; + private int totalTime; + private final List stationNameList; + + public ShortCostResponse(List stationList) { + this.stationNameList = new ArrayList<>(); + int length = stationList.size(); + for (int index = 0; index < length; index++) { + Station source = stationList.get(index); + if (index + 1 < length) { + Station sink = stationList.get(index + 1); + totalDistance += source.findDistanceTo(sink); + totalTime += source.findTimeTo(sink); + } + stationNameList.add(source.getName()); + } + } + + public int getTotalDistance() { + return totalDistance; + } + + public void setTotalDistance(int totalDistance) { + this.totalDistance = totalDistance; + } + + public int getTotalTime() { + return totalTime; + } + + public void setTotalTime(int totalTime) { + this.totalTime = totalTime; + } + + public List getStationNameList() { + return stationNameList; + } +} +``` + +μ§€μ • κ²½λ‘œκΉŒμ§€ 총 거리, μ‹œκ°„κ³Ό μœ„μΉ˜ 정보λ₯Ό λ°˜ν™˜ ν•  수 μžˆλ„λ‘ κ΅¬ν˜„. + +```java +// SectionServiceTest.java + +package subway.application.section.service; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import subway.application.section.dto.SectionDTO; +import subway.application.section.dto.ShortCostRequest; +import subway.application.section.dto.ShortCostResponse; +import subway.domain.line.Line; +import subway.domain.line.LineDTO; +import subway.domain.line.LineService; +import subway.domain.station.Station; +import subway.domain.station.StationDTO; +import subway.domain.station.StationService; + +public class SectionServiceTest { + private final ShortDistanceService shortDistanceService = new ShortDistanceService(); + + @Test + public void computeShortDistance() { + int distance = 1; + int time = 8; + SectionDTO sectionDTO = new SectionDTO(this.lineDTO, this.sourceDTO, this.sinkDTO, distance, time); + ShortCostRequest shortCostRequest = new ShortCostRequest(this.sourceDTO, this.sinkDTO); + this.sectionService.addNode(this.sourceDTO); + this.sectionService.addNode(this.sinkDTO); + this.sectionService.addSection(sectionDTO); + ShortCostResponse shortCostResponse = this.sectionService.computeShortDistance(shortCostRequest); + assertThat(shortCostResponse.getTotalDistance()).isEqualTo(distance); + assertThat(shortCostResponse.getTotalTime()).isEqualTo(time); + assertThat(shortCostResponse.getStationNameList()).containsExactly(this.sourceDTO.getName(), + this.sinkDTO.getName()); + } + + @Test + public void computeShortDistance__NotExistsSourceStationException() { + StationDTO otherDTO = new StationDTO("other"); + ShortCostRequest shortCostRequest = new ShortCostRequest(otherDTO, this.sinkDTO); + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ‹œμž‘μ—­μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> this.sectionService.computeShortDistance(shortCostRequest)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void computeShortDistance__NotExistsSinkStationException() { + StationDTO otherDTO = new StationDTO("other"); + ShortCostRequest shortCostRequest = new ShortCostRequest(this.sourceDTO, otherDTO); + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ’…λ£Œμ—­μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> this.sectionService.computeShortDistance(shortCostRequest)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void computeShortDistance__NotConnectedSourceAndSinkStationException() { + ShortCostRequest shortCostRequest = new ShortCostRequest(this.sourceDTO, this.sinkDTO); + String message = "μ‹œμž‘ 지점과 μ’…λ£Œμ—­μ΄ μ—°κ²°λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + this.sectionService.addNode(this.sourceDTO); + this.sectionService.addNode(this.sinkDTO); + assertThatThrownBy(() -> this.sectionService.computeShortDistance(shortCostRequest)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @AfterEach + public void init() { + this.lineService.deleteAll(); + this.stationService.deleteAll(); + this.sectionService.deleteAll(); + this.shortDistanceService.deleteAll(); + } +} +``` + +```java +// StationService.java + +package subway.domain.station; + +import java.util.List; +import java.util.Optional; + +public class StationService { + public Optional findByName(String name) { + return StationRepository.findByName(name); + } +} +``` + +```java +// SectionService.java + +package subway.application.section.service; + +import java.util.List; + +import org.jgrapht.GraphPath; +import org.jgrapht.graph.DefaultWeightedEdge; + +import subway.application.section.dto.SectionDTO; +import subway.application.section.dto.ShortCostRequest; +import subway.application.section.dto.ShortCostResponse; +import subway.domain.line.Line; +import subway.domain.line.LineService; +import subway.domain.section.Section; +import subway.domain.section.SectionRepository; +import subway.domain.station.Station; +import subway.domain.station.StationDTO; +import subway.domain.station.StationService; + +public class SectionService { + private static final String NOT_EXISTS_SOURCE_STATION_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ‹œμž‘μ—­μž…λ‹ˆλ‹€."; + private static final String NOT_EXISTS_SINK_STATION_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ’…λ£Œμ—­μž…λ‹ˆλ‹€."; + private static final String NOT_CONNECTED_SOURCE_AND_SINK_STATION_MESSAGE = "μ‹œμž‘ 지점과 μ’…λ£Œμ—­μ΄ μ—°κ²°λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + + public ShortCostResponse computeShortDistance(ShortCostRequest shortCostRequest) { + Station source = this.stationService.findByName(shortCostRequest.getSourceDTO().getName()) + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_SOURCE_STATION_MESSAGE)); + Station sink = this.stationService.findByName(shortCostRequest.getSinkDTO().getName()) + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_SINK_STATION_MESSAGE)); + GraphPath graphPath = this.shortDistanceService.compute(source, sink); + if (graphPath == null) { + throw new IllegalArgumentException(NOT_CONNECTED_SOURCE_AND_SINK_STATION_MESSAGE); + } + return new ShortCostResponse(graphPath.getVertexList()); + } +} +``` + +μ΅œλ‹¨ 거리 계산 κ΅¬ν˜„. + +```java +// SectionController.java + +package subway.presentation; + +import subway.application.section.dto.SectionDTO; +import subway.application.section.dto.ShortCostRequest; +import subway.application.section.dto.ShortCostResponse; +import subway.application.section.service.SectionService; +import subway.domain.line.LineDTO; +import subway.domain.station.StationDTO; +import subway.util.Validation; + +public class SectionController { + public ShortCostResponse computeShortDistance(String sourceName, String sinkName) { + ShortCostRequest shortCostRequest = this.createShortCostRequest(sourceName, sinkName); + return this.sectionService.computeShortDistance(shortCostRequest); + } + + private ShortCostRequest createShortCostRequest(String sourceName, String sinkName) { + StationDTO sourceDTO = new StationDTO(sourceName); + StationDTO sinkDTO = new StationDTO(sinkName); + return new ShortCostRequest(sourceDTO, sinkDTO); + } +} +``` + +μ œμ–΄ 계측에 μ΅œλ‹¨ 거리 계산 κΈ°λŠ₯ λ§€ν•‘. + +## 6. κ·Έλž˜ν”„ κ΄€λ ¨ κΈ°λŠ₯ 톡합 + +```java +// ShortCostService.java + +package subway.application.section.service; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.jgrapht.GraphPath; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.AbstractBaseGraph; +import org.jgrapht.graph.DefaultWeightedEdge; + +import subway.domain.section.Section; +import subway.domain.station.Station; + +abstract class ShortCostService { + private final AbstractBaseGraph graph; + + public ShortCostService(AbstractBaseGraph graph) { + this.graph = graph; + } + + private static final String ALREADY_EXISTS_NODE_MESSAGE = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” λ…Έλ“œμž…λ‹ˆλ‹€."; + private static final String NOT_EXISTS_SOURCE_NODE_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ‹œμž‘λ…Έλ“œμž…λ‹ˆλ‹€."; + private static final String NOT_EXISTS_SINK_NODE_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ’…λ£Œλ…Έλ“œμž…λ‹ˆλ‹€."; + private static final String ALREADY_EXISTS_EDGE_MESSAGE = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” κ°„μ„ μž…λ‹ˆλ‹€."; + + protected Set findAllNode() { + return Collections.unmodifiableSet(graph.vertexSet()); + } + + protected Set findAllEdge() { + return Collections.unmodifiableSet(graph.edgeSet()); + } + + protected void addNode(Station station) { + if (graph.containsVertex(station)) { + throw new IllegalArgumentException(ALREADY_EXISTS_NODE_MESSAGE); + } + graph.addVertex(station); + } + + protected void addEdge(Section section) { + Station source = section.getSource(); + Station sink = section.getSink(); + this.validateSource(source); + this.validateSink(sink); + if (graph.containsEdge(source, sink)) { + throw new IllegalArgumentException(ALREADY_EXISTS_EDGE_MESSAGE); + } + graph.setEdgeWeight(graph.addEdge(source, sink), this.getWeight(section)); + } + + protected abstract double getWeight(Section section); + + protected GraphPath compute(Station source, Station sink) { + this.validateSource(source); + this.validateSink(sink); + DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath<>(graph); + return dijkstraShortestPath.getPath(source, sink); + } + + protected void validateSource(Station source) { + if (!graph.containsVertex(source)) { + throw new IllegalArgumentException(NOT_EXISTS_SOURCE_NODE_MESSAGE); + } + } + + protected void validateSink(Station sink) { + if (!graph.containsVertex(sink)) { + throw new IllegalArgumentException(NOT_EXISTS_SINK_NODE_MESSAGE); + } + } + + protected void deleteAllNode() { + Set nodes = new HashSet<>(this.findAllNode()); + graph.removeAllVertices(nodes); + } + + protected void deleteAllEdge() { + graph.removeAllEdges(graph.edgeSet()); + } + + protected void deleteAll() { + this.deleteAllEdge(); + this.deleteAllNode(); + } +} + +``` + +```java +// ShortDistanceService.java + +package subway.application.section.service; + +import org.jgrapht.graph.AbstractBaseGraph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.DirectedWeightedMultigraph; + +import subway.domain.section.Section; +import subway.domain.station.Station; + +class ShortDistanceService extends ShortCostService { + private static final AbstractBaseGraph graph = new DirectedWeightedMultigraph<>( + DefaultWeightedEdge.class); + + public ShortDistanceService() { + super(graph); + } + + @Override + protected double getWeight(Section section) { + return section.getDistance(); + } +} +``` + +이후에 κ΅¬ν˜„λ  μ΅œμ†Œ μ‹œκ°„ κ²½λ‘œμ™€ κ·Έλž˜ν”„ κ΄€λ ¨ κΈ°λŠ₯은 μœ μ‚¬ν•˜κΈ° λ•Œλ¬Έμ— λ”°λ‘œ 좔상 곡톡 클래슀λ₯Ό μƒμ„±ν•˜μ—¬ μ½”λ“œ 쀑볡 λ°©μ§€. + +## 7. μ΅œμ†Œ μ‹œκ°„ 경둜 κ΅¬ν•˜κΈ° + +```java +// ShortTimeService.java + +package subway.application.section.service; + +import org.jgrapht.graph.AbstractBaseGraph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.DirectedWeightedMultigraph; + +import subway.domain.section.Section; +import subway.domain.station.Station; + +class ShortTimeService extends ShortCostService { + private static final AbstractBaseGraph graph = new DirectedWeightedMultigraph<>( + DefaultWeightedEdge.class); + + public ShortTimeService() { + super(graph); + } + + @Override + protected double getWeight(Section section) { + return section.getTime(); + } +} +``` + +μ΅œμ†Œ μ‹œκ°„ 경둜 λ°˜ν™˜ κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// SectionService.java + +package subway.application.section.service; + +import java.util.List; + +import org.jgrapht.GraphPath; +import org.jgrapht.graph.DefaultWeightedEdge; + +import subway.application.section.dto.SectionDTO; +import subway.application.section.dto.ShortCostRequest; +import subway.application.section.dto.ShortCostResponse; +import subway.domain.line.Line; +import subway.domain.line.LineService; +import subway.domain.section.Section; +import subway.domain.section.SectionRepository; +import subway.domain.station.Station; +import subway.domain.station.StationDTO; +import subway.domain.station.StationService; + +public class SectionService { + private final ShortTimeService shortTimeService = new ShortTimeService(); + + public void addNode(StationDTO stationDTO) { + Station station = this.stationService.findOneByName(stationDTO.getName()); + this.shortDistanceService.addNode(station); ++ this.shortTimeService.addNode(station); + } + + public void addSection(SectionDTO sectionDTO) { + Line line = this.lineService.findOneByName(sectionDTO.getLineDTO().getName()); + Station source = this.stationService.findOneByName(sectionDTO.getSourceDTO().getName()); + Station sink = this.stationService.findOneByName(sectionDTO.getSinkDTO().getName()); + Section section = new Section(line, source, sink, sectionDTO.getDistance(), sectionDTO.getTime()); + if (SectionRepository.exists(section)) { + throw new IllegalArgumentException(ALREADY_EXISTS_SECTION_MESSAGE); + } + SectionRepository.addSection(section); + this.shortDistanceService.addEdge(section); ++ this.shortTimeService.addEdge(section); + line.addSection(section); + source.addSection(section); + } + + public ShortCostResponse computeShortTime(ShortCostRequest shortCostRequest) { + Station source = this.stationService.findByName(shortCostRequest.getSourceDTO().getName()) + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_SOURCE_STATION_MESSAGE)); + Station sink = this.stationService.findByName(shortCostRequest.getSinkDTO().getName()) + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_SINK_STATION_MESSAGE)); + GraphPath graphPath = this.shortDistanceService.compute(source, sink); + if (graphPath == null) { + throw new IllegalArgumentException(NOT_CONNECTED_SOURCE_AND_SINK_STATION_MESSAGE); + } + return new ShortCostResponse(graphPath.getVertexList()); + } +} +``` + +μ΅œμ†Œ μ‹œκ°„ κ·Έλž˜ν”„λ„ 같이 λ…Έλ“œ, κ°„μ„  좔가에 포함. + +μ΅œμ†Œ μ‹œκ°„ 계산 κ΅¬ν˜„. + +```java +// SectionServiceTest.java + +package subway.application.section.service; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import subway.application.section.dto.SectionDTO; +import subway.application.section.dto.ShortCostRequest; +import subway.application.section.dto.ShortCostResponse; +import subway.domain.line.Line; +import subway.domain.line.LineDTO; +import subway.domain.line.LineService; +import subway.domain.station.Station; +import subway.domain.station.StationDTO; +import subway.domain.station.StationService; + +public class SectionServiceTest { + private final ShortTimeService shortTimeService = new ShortTimeService(); + + @AfterEach + public void init() { + this.lineService.deleteAll(); + this.stationService.deleteAll(); + this.sectionService.deleteAll(); + this.shortDistanceService.deleteAll(); ++ this.shortTimeService.deleteAll(); + } +} +``` + +μ΅œμ†Œ μ‹œκ°„ κΈ°λŠ₯ λ„μž…μœΌλ‘œ μΈν•œ ν…ŒμŠ€νŠΈ μ΄ˆκΈ°ν™” μ†ŒμŠ€ λ³€κ²½. + +```java +// SectionController.java + +package subway.presentation; + +import subway.application.section.dto.SectionDTO; +import subway.application.section.dto.ShortCostRequest; +import subway.application.section.dto.ShortCostResponse; +import subway.application.section.service.SectionService; +import subway.domain.line.LineDTO; +import subway.domain.station.StationDTO; +import subway.util.Validation; + +public class SectionController { + public ShortCostResponse computeShortTime(String sourceName, String sinkName) { + ShortCostRequest shortCostRequest = this.createShortCostRequest(sourceName, sinkName); + return this.sectionService.computeShortTime(shortCostRequest); + } +} +``` + +μ œμ–΄ 계측에 μ΅œμ†Œ μ‹œκ°„ 계산 κΈ°λŠ₯ λ§€ν•‘. + +```java +// StationController.java + +package subway.presentation; + +import subway.application.section.service.SectionService;import subway.domain.station.StationDTO; +import subway.domain.station.StationService; + +public class StationController { + private final SectionService sectionService = new SectionService(); + + public void addStation(String name) { + StationDTO stationDTO = new StationDTO(name); + stationService.addStation(stationDTO); ++ this.sectionService.addNode(stationDTO); + } +} +``` + +μ—­κ³Ό 같이 λ…Έλ“œλ„ 같이 μΆ”κ°€λ˜λ„λ‘ λ³€κ²½. + +## 8. Screen Layer Skeleton + +### Ui + +```java +// Console.java + +package subway.screen.ui; + +import java.io.PrintStream; +import java.util.Scanner; + +public final class Console { + private static final Scanner scanner = new Scanner(System.in); + private static final PrintStream printer = System.out; + + private static final String HEADER_OUTPUT_FORMAT = "## %s"; + private static final String INFO_OUTPUT_FORMAT = "[INFO] %s"; + private static final String ERROR_OUTPUT_FORMAT = "[ERROR] %s"; + + public static String readline() { + return scanner.nextLine(); + } + + public static void println() { + printer.println(); + } + + public static void println(String message) { + printer.println(message); + } + + public static void printHeader(String message) { + println(String.format(HEADER_OUTPUT_FORMAT, message)); + } + + public static void printInfo(String message) { + println(String.format(INFO_OUTPUT_FORMAT, message)); + } + + public static void printError(String message) { + println(String.format(ERROR_OUTPUT_FORMAT, message)); + } + +} +``` + +μ½˜μ†” μž…μΆœλ ₯ κΈ°λŠ₯ κ΅¬ν˜„. + +### View + +```java +// View.java + +package subway.screen.view; + +public interface View { + String title(); + void show(); +} +``` + +ν™”λ©΄ κΈ°λ³Έ κΈ°λŠ₯ μ •μ˜. + +```java +// MenuTest.java + +package subway.screen.view; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class MenuTest { + private static class ClassMenu implements Menu { + @Override + public String getCommand() { + return ""; + } + + @Override + public String getName() { + return ""; + } + } + + private enum EnumMenu implements Menu { + ITEM("command", "name"); + + private final String command; + private final String name; + + EnumMenu(String command, String name) { + this.command = command; + this.name = name; + } + + @Override + public String getCommand() { + return this.command; + } + + @Override + public String getName() { + return this.name; + } + } + + @Test + public void findAll() { + EnumMenu[] enumMenus = EnumMenu.values(); + assertThat(Menu.findAll(EnumMenu.class)).containsExactly(enumMenus); + } + + @Test + public void findAll__NotEnumTypeClassException() { + String message = "Enum ν˜•μ‹μ˜ ν΄λž˜μŠ€κ°€ μ•„λ‹™λ‹ˆλ‹€."; + assertThatThrownBy(() -> Menu.findAll(ClassMenu.class)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void findByCommand() { + EnumMenu item = EnumMenu.ITEM; + assertThat(Menu.findByCommand(EnumMenu.class, "none")).isNotPresent(); + assertThat(Menu.findByCommand(EnumMenu.class, item.getCommand())).isPresent(); + } +} +``` + +```java +// Menu.java + +package subway.screen.view; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public interface Menu { + String getCommand(); + + String getName(); + + static List findAll(Class menuClass) { + Menu[] enumValues = menuClass.getEnumConstants(); + if(enumValues == null) { + throw new IllegalArgumentException("Enum ν˜•μ‹μ˜ ν΄λž˜μŠ€κ°€ μ•„λ‹™λ‹ˆλ‹€."); + } + return Arrays.stream(enumValues).collect(Collectors.toList()); + } + + static Optional findByCommand(Class menuClass, String command) { + List menuList = findAll(menuClass); + for (Menu menu : menuList) { + if (menu.getCommand().equals(command)) { + return Optional.of(menu); + } + } + return Optional.empty(); + } +} +``` + +메뉴 κΈ°λ³Έ κΈ°λŠ₯ μ •μ˜. + +메뉴 λͺ©λ‘ 쑰회 κΈ°λŠ₯ κ΅¬ν˜„. + +λͺ…λ Ήμ–΄ κΈ°μ€€ 단건 쑰회 κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// MenuEventManagerTest.java + +package subway.screen.view; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class MenuEventManagerTest { + private enum EnumMenu implements Menu { + ITEM("command", "name"), NONE("", ""); + + private final String command; + private final String name; + + EnumMenu(String command, String name) { + this.command = command; + this.name = name; + } + + @Override + public String getCommand() { + return this.command; + } + + @Override + public String getName() { + return this.name; + } + } + + @Test + public void addEventListener__NotReceivedMenuException() { + MenuEventManager menuEventManager = MenuEventManager.builder(); + String message = "메뉴λ₯Ό 전달받지 λͺ»ν•˜μ˜€μŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> menuEventManager.addEventListener(null, () -> { + })).isInstanceOf(IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addEventListener__NotReceivedHandlerException() { + MenuEventManager menuEventManager = MenuEventManager.builder(); + String message = "이벀트 ν•Έλ“€λŸ¬λ₯Ό 전달받지 λͺ»ν•˜μ˜€μŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> menuEventManager.addEventListener(EnumMenu.ITEM, null)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void select() { + MenuEventManager menuEventManager = MenuEventManager.builder().addEventListener(EnumMenu.ITEM, () -> { + }); + assertThat(menuEventManager.select(EnumMenu.NONE)).isNotPresent(); + assertThat(menuEventManager.select(EnumMenu.ITEM)).isPresent(); + } +} +``` + +```java +// MenuEventManager.java + +package subway.screen.view; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public final class MenuEventManager { + private static final String NOT_RECEIVED_MENU_MESSAGE = "메뉴λ₯Ό 전달받지 λͺ»ν•˜μ˜€μŠ΅λ‹ˆλ‹€."; + private static final String NOT_RECEIVED_HANDLER_MESSAGE = "이벀트 ν•Έλ“€λŸ¬λ₯Ό 전달받지 λͺ»ν•˜μ˜€μŠ΅λ‹ˆλ‹€."; + + private final Map handlerMap = new HashMap<>(); + + private MenuEventManager() { + } + + public static MenuEventManager builder() { + return new MenuEventManager(); + } + + public MenuEventManager addEventListener(Menu menu) { + return this.addEventListener(menu, () -> { + }); + } + + public MenuEventManager addEventListener(Menu menu, Runnable handler) { + this.validate(menu, handler); + this.handlerMap.put(menu, handler); + return this; + } + + private void validate(Menu menu, Runnable runnable) { + if (menu == null) { + throw new IllegalArgumentException(NOT_RECEIVED_MENU_MESSAGE); + } + if (runnable == null) { + throw new IllegalArgumentException(NOT_RECEIVED_HANDLER_MESSAGE); + } + } + + public void removeEventListener(Menu menu) { + this.handlerMap.remove(menu); + } + + public void removeAllEventListener() { + this.handlerMap.clear(); + } + + Optional select(Menu menu) { + return Optional.ofNullable(this.handlerMap.get(menu)); + } +} +``` + +메뉴 이벀트 등둝 및 μ‚­μ œν•  수 μžˆλŠ” κΈ°λŠ₯ κ΅¬ν˜„. + +```java +// MenuViewTest.java + +package subway.screen.view; + +import static org.assertj.core.api.Assertions.*; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import subway.screen.ui.Console; + +public class MenuViewTest { + private MockedStatic mockConsole; + + private enum TestMenu implements Menu { + ITEM("command", "name"); + + private final String command; + private final String name; + + TestMenu(String command, String name) { + this.command = command; + this.name = name; + } + + @Override + public String getCommand() { + return this.command; + } + + @Override + public String getName() { + return this.name; + } + } + + private static class TestMenuView extends MenuView { + public TestMenuView() { + } + + public TestMenuView(MenuEventManager menuEventManager) { + super(menuEventManager); + } + + @Override + protected Class getType() { + return TestMenu.class; + } + + @Override + public String title() { + return "ν…ŒμŠ€νŠΈ ν™”λ©΄"; + } + } + + @BeforeEach + public void setup() { + mockConsole = Mockito.mockStatic(Console.class); + } + + @Test + public void constructor__NotReceivedEventManagerException() { + String message = "이벀트 κ΄€λ¦¬μžλ₯Ό 전달받지 λͺ»ν•˜μ˜€μŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> new MenuView(null) { + @Override + public String title() { + return ""; + } + + @Override + protected Class getType() { + return null; + } + }).isInstanceOf(IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void show() { + TestMenu item = TestMenu.ITEM; + TestMenuView testMenuView = new TestMenuView(); + testMenuView.show(); + this.mockConsole.verify(() -> { + Console.printHeader(testMenuView.title()); + Console.println(String.format("%s. %s", item.command, item.name)); + Console.println(); + }); + } + + @Test + public void question() { + TestMenu item = TestMenu.ITEM; + TestMenuView testMenuView = new TestMenuView(); + this.mockConsole.when(Console::readline).thenReturn(item.command); + assertThat(testMenuView.question()).isEqualTo(item); + } + + @Test + public void question_CanNotSelectedFunctionException() { + TestMenu item = TestMenu.ITEM; + TestMenuView testMenuView = new TestMenuView(); + String message = "선택할 수 μ—†λŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€."; + this.mockConsole.when(Console::readline).thenReturn("none"); + this.mockConsole.when(Console::readline).thenReturn(item.command); + testMenuView.question(); + this.mockConsole.verify(() -> { + Console.println(); + Console.printError(message); + Console.println(); + + Console.println(); + }); + } + + @Test + public void onEvent() { + TestMenu item = TestMenu.ITEM; + AtomicInteger count = new AtomicInteger(0); + TestMenuView testMenuView = new TestMenuView( + MenuEventManager.builder().addEventListener(item, count::getAndIncrement)); + testMenuView.onEvent(item); + assertThat(count.get()).isEqualTo(1); + } + + @Test + public void onEvent__CanNotProcessEventHandlerException() { + TestMenu item = TestMenu.ITEM; + TestMenuView testMenuView = new TestMenuView(); + String message = "이벀트λ₯Ό μ²˜λ¦¬ν•  수 μ—†μŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> testMenuView.onEvent(null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + assertThatThrownBy(() -> testMenuView.onEvent(item)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @AfterEach + public void init() { + mockConsole.close(); + } +} +``` + +```java +// MenuView.java + +package subway.screen.view; + +import java.util.List; + +import subway.screen.ui.Console; + +public abstract class MenuView implements View { + private static final String NOT_RECEIVED_EVENT_MANAGER_MESSAGE = "이벀트 κ΄€λ¦¬μžλ₯Ό 전달받지 λͺ»ν•˜μ˜€μŠ΅λ‹ˆλ‹€."; + private static final String MENU_OUTPUT_FORMAT = "%s. %s"; + private static final String CAN_NOT_SELECTED_FUNCTION_MESSAGE = "선택할 수 μ—†λŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€."; + private static final String CAN_NOT_PROCESS_EVENT_HANDLER_MESSAGE = "이벀트λ₯Ό μ²˜λ¦¬ν•  수 μ—†μŠ΅λ‹ˆλ‹€."; + + private final MenuEventManager menuEventManager; + + protected MenuView() { + this(MenuEventManager.builder()); + } + + protected MenuView(MenuEventManager menuEventManager) { + if (menuEventManager == null) { + throw new IllegalArgumentException(NOT_RECEIVED_EVENT_MANAGER_MESSAGE); + } + this.menuEventManager = menuEventManager; + } + + protected abstract Class getType(); + + @Override + public void show() { + Console.printHeader(this.title()); + List menuList = Menu.findAll(this.getType()); + for (Menu menu : menuList) { + Console.println(String.format(MENU_OUTPUT_FORMAT, menu.getCommand(), menu.getName())); + } + Console.println(); + } + + public Menu question() { + do { + Console.printHeader("μ›ν•˜λŠ” κΈ°λŠ₯을 μ„ νƒν•˜μ„Έμš”."); + String command = Console.readline(); + try { + Menu menu = Menu.findByCommand(this.getType(), command) + .orElseThrow(() -> new IllegalArgumentException(CAN_NOT_SELECTED_FUNCTION_MESSAGE)); + Console.println(); + return menu; + } catch (IllegalArgumentException e) { + Console.println(); + Console.printError(e.getMessage()); + Console.println(); + } + } while (true); + } + + public void onEvent(Menu menu) { + Runnable handler = this.menuEventManager.select(menu) + .orElseThrow(() -> new IllegalArgumentException(CAN_NOT_PROCESS_EVENT_HANDLER_MESSAGE)); + handler.run(); + } +} +``` + +메뉴 ν™”λ©΄ 곡톡 κΈ°λŠ₯ κ΅¬ν˜„. + +## 9. 경둜 κΈ°μ€€(ν™”λ©΄) κ΅¬ν˜„ + +```java +// PathMenu.java + +package subway.screen.view.path; + +import subway.screen.view.Menu; + +public enum PathMenu implements Menu { + SHORT_DISTANCE("1", "μ΅œλ‹¨ 거리"), + SHORT_TIME("2", "μ΅œμ†Œ μ‹œκ°„"), + BACK("B", "λŒμ•„κ°€κΈ°"); + + private final String command; + private final String name; + + PathMenu(String command, String name) { + this.command = command; + this.name = name; + } + + @Override + public String getCommand() { + return this.command; + } + + @Override + public String getName() { + return this.name; + } +} +``` + +```java +// MainMenuView + +package subway.screen.view.path; + +import subway.screen.view.Menu; +import subway.screen.view.MenuView; + +public class PathMenuView extends MenuView { + public PathMenuView() { + super(); + } + + @Override + public String title() { + return "경둜 κΈ°μ€€"; + } + + @Override + protected Class getType() { + return PathMenu.class; + } +} +``` + +경둜 κΈ°μ€€(ν™”λ©΄) ꡬ성. + +```java +// ViewController.java + +package subway.presentation; + +public interface ViewController { + void execute(); +} +``` + +ν™”λ©΄ μ œμ–΄ κΈ°λ³Έ μ •μ˜. + +```java +// PathViewController.java + +package subway.presentation; + +import subway.application.section.dto.ShortCostResponse; +import subway.screen.ui.Console; +import subway.screen.view.Menu; +import subway.screen.view.path.PathMenuView; + +public class PathViewController implements ViewController { + private final PathMenuView pathMenuView = new PathMenuView(this); + private final SectionController sectionController = new SectionController(); + + @Override + public void execute() { + do { + pathMenuView.show(); + Menu menu = pathMenuView.question(); + try { + pathMenuView.onEvent(menu); + return; + } catch (IllegalArgumentException e) { + Console.printError(e.getMessage()); + Console.println(); + } + } while (true); + } + + public void handleShortDistance() { + String sourceName = this.requestSourceName(); + String sinkName = this.requestSinkName(); + ShortCostResponse shortCostResponse = this.sectionController.computeShortDistance(sourceName, sinkName); + this.printShortCostResponse(shortCostResponse); + } + + public void handleShortTime() { + String sourceName = this.requestSourceName(); + String sinkName = this.requestSinkName(); + ShortCostResponse shortCostResponse = this.sectionController.computeShortTime(sourceName, sinkName); + this.printShortCostResponse(shortCostResponse); + } + + private String requestSourceName() { + Console.printHeader("μΆœλ°œμ—­μ„ μž…λ ₯ν•˜μ„Έμš”"); + String sourceName = Console.readline(); + Console.println(); + return sourceName; + } + + private String requestSinkName() { + Console.printHeader("도착역을 μž…λ ₯ν•˜μ„Έμš”"); + String sinkName = Console.readline(); + Console.println(); + return sinkName; + } + + private void printShortCostResponse(ShortCostResponse shortCostResponse) { + Console.printHeader("쑰회 κ²°κ³Ό"); + Console.printInfo("---"); + Console.printInfo(String.format("총 거리 : %dkm", shortCostResponse.getTotalDistance())); + Console.printInfo(String.format("총 μ†Œμš” μ‹œκ°„ : %dλΆ„", shortCostResponse.getTotalTime())); + Console.printInfo("---"); + for (String stationName : shortCostResponse.getStationNameList()) { + Console.printInfo(stationName); + } + Console.println(); + } +} +``` + +경둜 κΈ°μ€€(ν™”λ©΄) μ œμ–΄ κ΅¬ν˜„. + +메뉴 선택 이벀트 처리 κ΅¬ν˜„. + +```java +// MainMenuView + +package subway.screen.view.path; + +import subway.presentation.PathViewController; +import subway.screen.view.Menu; +import subway.screen.view.MenuEventManager; +import subway.screen.view.MenuView; + +public class PathMenuView extends MenuView { + public PathMenuView(PathViewController pathViewController) { + super(MenuEventManager.builder() + .addEventListener(PathMenu.SHORT_DISTANCE, pathViewController::handleShortDistance) + .addEventListener(PathMenu.SHORT_TIME, pathViewController::handleShortTime) + .addEventListener(PathMenu.BACK)); + } +} +``` + +이벀트 처리 λ§€ν•‘. + +## 10. 메인 ν™”λ©΄ κ΅¬ν˜„ + +```java +// MainMenu.java + +package subway.screen.view.main; + +import subway.screen.view.Menu; + +public enum MainMenu implements Menu { + PATH_SEARCH("1", "경둜 쑰회"), END("Q", "μ’…λ£Œ"); + + private final String command; + private final String name; + + MainMenu(String command, String name) { + this.command = command; + this.name = name; + } + + @Override + public String getCommand() { + return this.command; + } + + @Override + public String getName() { + return this.name; + } +} +``` + +```java +// MainMenuView.java + +package subway.screen.view.main; + +import subway.screen.view.Menu; +import subway.screen.view.MenuView; + +public class MainMenuView extends MenuView { + public MainMenuView() { + super(); + } + + @Override + public String title() { + return "메인 ν™”λ©΄"; + } + + @Override + protected Class getType() { + return MainMenu.class; + } +} +``` + +메인 ν™”λ©΄ ꡬ성. + +```java +// MainViewController.java + +package subway.presentation; + +import subway.screen.ui.Console; +import subway.screen.view.Menu; +import subway.screen.view.main.MainMenuView; + +public class MainViewController implements ViewController { + private final MainMenuView mainMenuView = new MainMenuView(this); + + private boolean end; + + @Override + public void execute() { + this.end = false; + do { + mainMenuView.show(); + Menu menu = mainMenuView.question(); + try { + mainMenuView.onEvent(menu); + } catch (IllegalArgumentException e) { + Console.printError(e.getMessage()); + Console.println(); + } + } while (!this.end); + } + + public void handlePathSearch() { + PathViewController pathViewController = new PathViewController(); + pathViewController.execute(); + } + + public void handleEnd() { + this.end = true; + } +} +``` + +메인 ν™”λ©΄ μ œμ–΄ κ΅¬ν˜„. + +메뉴 선택 이벀트 처리 κ΅¬ν˜„. + +```java +// MainMenuView.java + +package subway.screen.view.main; + +import subway.presentation.MainViewController; +import subway.screen.view.Menu; +import subway.screen.view.MenuEventManager; +import subway.screen.view.MenuView; + +public class MainMenuView extends MenuView { + public MainMenuView(MainViewController mainViewController) { + super(MenuEventManager.builder() + .addEventListener(MainMenu.PATH_SEARCH, mainViewController::handlePathSearch) + .addEventListener(MainMenu.END, mainViewController::handleEnd)); + } +} +``` + +이벀트 처리 λ§€ν•‘. + +## 11. 파일 검증 + +```java +// FileParserTest.java + +package subway.infrastructure; + +import static org.assertj.core.api.Assertions.*; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class FileParserTest { + private File file; + private File directory; + + @BeforeEach + public void setup() { + this.directory = new File("./dummy"); + this.file = new File("./dummy.txt"); + try { + this.directory.mkdir(); + this.file.createNewFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static class TestFileParser extends FileParser { + public TestFileParser(File file) { + super(file); + } + + @Override + public boolean allowExtension(String extension) { + return "java".equals(extension); + } + + @Override + public List> parser() { + return null; + } + } + + @Test + public void constructor__NotExistsFileException() { + File none = new File("./none.txt"); + String message = "파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> new TestFileParser(null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + assertThatThrownBy(() -> new TestFileParser(none)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void constructor__NotReceivedFileTypeException() { + String message = "파일 μœ ν˜•μ΄ μ•„λ‹™λ‹ˆλ‹€."; + assertThatThrownBy(() -> new TestFileParser(this.directory)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void constructor__NotAllowedFileExtensionException() { + String message = "ν—ˆμš©λœ 파일 ν™•μž₯μžκ°€ μ•„λ‹™λ‹ˆλ‹€."; + assertThatThrownBy(() -> new TestFileParser(this.file)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @AfterEach + public void init() { + this.file.delete(); + this.directory.delete(); + } +} +``` + +```java +// FileParser.java + +package subway.infrastructure; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public abstract class FileParser { + private static final String NOT_EXISTS_FILE_MESSAGE = "파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + private static final String NOT_FILE_TYPE_MESSAGE = "파일 μœ ν˜•μ΄ μ•„λ‹™λ‹ˆλ‹€."; + private static final String NOT_ALLOWED_FILE_EXTENSION_MESSAGE = "ν—ˆμš©λœ 파일 ν™•μž₯μžκ°€ μ•„λ‹™λ‹ˆλ‹€."; + + protected final File file; + + public FileParser(String fileName) { + this(new File(Objects.requireNonNull(FileParser.class.getClassLoader().getResource(fileName)).getPath())); + } + + public FileParser(File file) { + this.validate(file); + this.file = file; + } + + private void validate(File file) { + if (file == null || !file.exists()) { + throw new IllegalArgumentException(NOT_EXISTS_FILE_MESSAGE); + } + if (!file.isFile()) { + throw new IllegalArgumentException(NOT_FILE_TYPE_MESSAGE); + } + if (!this.allowExtension(this.extractExtension(file.getName()))) { + throw new IllegalArgumentException(NOT_ALLOWED_FILE_EXTENSION_MESSAGE); + } + } + + private String extractExtension(String fileName) { + int index = fileName.lastIndexOf("."); + if (index > 0) { + return fileName.substring(index + 1); + } + return ""; + } + + public abstract boolean allowExtension(String extension); + + public abstract List> parser(); +} +``` + +파일 검증 곡톡 κΈ°λŠ₯ κ΅¬ν˜„. + +## 12. Xml Parser + +```java +// XmlFileParserTest.java + +package subway.infrastructure; + +import static org.assertj.core.api.Assertions.*; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class XmlFileParserTest { + private static final String content = "\n" + + "\n" + + " \n" + + " name\n" + + " \n" + + " name\n" + + " \n" + + " \n" + + "\n"; + private static File file; + + @BeforeAll + public static void setup() { + file = new File("file.xml"); + try { + file.createNewFile(); + try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file))) { + bufferedWriter.write(content); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public void parser() { + XmlFileParser xmlFileParser = new XmlFileParser(file); + List> dataList = xmlFileParser.parser(); + assertThat(dataList).hasSize(1); + Map data = dataList.get(0); + assertThat(data.get("name")).isInstanceOf(String.class).isEqualTo("name"); + assertThat(data.get("job")).isInstanceOf(Map.class); + Map job = (Map)data.get("job"); + assertThat(job.get("name")).isInstanceOf(String.class).isEqualTo("name"); + } + + @AfterAll + public static void init() { + file.delete(); + } +} +``` + +```java +// XmlFileParser.java + +package subway.infrastructure; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class XmlFileParser extends FileParser { + private static final int UNKNOWN_NODE_LIST = 0; + private static final int HAS_ELEMENT_NODE_LIST = 1; + private static final int HAS_TEXT_NODE_LIST = 2; + + private final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + public XmlFileParser(String fileName) { + super(fileName); + } + + public XmlFileParser(File file) { + super(file); + } + + @Override + public boolean allowExtension(String extension) { + return "xml".equals(extension); + } + + @Override + public List> parser() { + try { + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(file); + document.getDocumentElement().normalize(); + Element root = document.getDocumentElement(); + return this.find(root); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private List> find(Element root) { + List> data = new ArrayList<>(); + NodeList nodeList = root.getChildNodes(); + for (int index = 0; index < nodeList.getLength(); index++) { + Node node = nodeList.item(index); + if (node.getNodeType() == Node.ELEMENT_NODE) { + data.add(this.serialize(node.getChildNodes())); + } + } + return data; + } + + private Map serialize(NodeList parentNodeList) { + Map data = new HashMap<>(); + for (int index = 0; index < parentNodeList.getLength(); index++) { + Node child = parentNodeList.item(index); + int childType = child.getNodeType(); + String childName = child.getNodeName(); + if (childType == Node.ELEMENT_NODE) { + data.put(childName, this.getNodeData(child)); + } + } + return data; + } + + private Object getNodeData(Node node) { + NodeList nodeList = node.getChildNodes(); + int nodeListType = this.getNodeListType(nodeList); + if (nodeListType == HAS_ELEMENT_NODE_LIST) { + return this.serialize(nodeList); + } else if (nodeListType == HAS_TEXT_NODE_LIST) { + return this.getTextValue(node); + } + return null; + } + + private int getNodeListType(NodeList nodeList) { + for (int index = 0; index < nodeList.getLength(); index++) { + Node node = nodeList.item(index); + int nodeType = node.getNodeType(); + String _nodeValue = node.getNodeValue(); + if (nodeType == Node.ELEMENT_NODE) { + return HAS_ELEMENT_NODE_LIST; + } else if (nodeType == Node.TEXT_NODE && this.hasTextValue(_nodeValue)) { + return HAS_TEXT_NODE_LIST; + } + } + return UNKNOWN_NODE_LIST; + } + + private String getTextValue(Node node) { + NodeList nodeList = node.getChildNodes(); + for (int index = 0; index < nodeList.getLength(); index++) { + Node child = nodeList.item(index); + String _childValue = child.getNodeValue(); + if (child.getNodeType() == Node.TEXT_NODE && this.hasTextValue(_childValue)) { + return _childValue.trim(); + } + } + return ""; + } + + private boolean hasTextValue(String value) { + return value != null && !value.trim().isEmpty(); + } +} +``` + +Xml Parser κΈ°λŠ₯ κ΅¬ν˜„. + +## 13. 초기 데이터 μ •μ˜ + +```xml + + + + 2ν˜Έμ„  + + + 3ν˜Έμ„  + + + μ‹ λΆ„λ‹Ήμ„  + + +``` + +Line(λ…Έμ„ ) 데이터 μ •μ˜. + +```xml + + + + κ΅λŒ€μ—­ + + + 강남역 + + + μ—­μ‚Όμ—­ + + + 남뢀터미널역 + + + μ–‘μž¬μ—­ + + + μ–‘μž¬μ‹œλ―Όμ˜μˆ²μ—­ + + + 맀봉역 + + +``` + +Station(μ—­) 데이터 μ •μ˜. + +```xml + + + +
+ + 2ν˜Έμ„  + + + κ΅λŒ€μ—­ + + + 강남역 + + 2 + +
+
+ + 2ν˜Έμ„  + + + 강남역 + + + κ΅λŒ€μ—­ + + 2 + +
+
+ + 2ν˜Έμ„  + + + 강남역 + + + μ—­μ‚Όμ—­ + + 2 + +
+
+ + 2ν˜Έμ„  + + + μ—­μ‚Όμ—­ + + + 강남역 + + 2 + +
+ +
+ + 3ν˜Έμ„  + + + κ΅λŒ€μ—­ + + + 남뢀터미널역 + + 3 + +
+
+ + 3ν˜Έμ„  + + + 남뢀터미널역 + + + κ΅λŒ€μ—­ + + 3 + +
+
+ + 3ν˜Έμ„  + + + 남뢀터미널역 + + + μ–‘μž¬μ—­ + + 6 + +
+
+ + 3ν˜Έμ„  + + + μ–‘μž¬μ—­ + + + 남뢀터미널역 + + 6 + +
+
+ + 3ν˜Έμ„  + + + μ–‘μž¬μ—­ + + + 맀봉역 + + 1 + +
+
+ + 3ν˜Έμ„  + + + 맀봉역 + + + μ–‘μž¬μ—­ + + 1 + +
+ +
+ + μ‹ λΆ„λ‹Ήμ„  + + + 강남역 + + + μ–‘μž¬μ—­ + + 2 + +
+
+ + μ‹ λΆ„λ‹Ήμ„  + + + μ–‘μž¬μ—­ + + + 강남역 + + 2 + +
+
+ + μ‹ λΆ„λ‹Ήμ„  + + + μ–‘μž¬μ—­ + + + μ–‘μž¬μ‹œλ―Όμ˜μˆ²μ—­ + + 10 + +
+
+ + μ‹ λΆ„λ‹Ήμ„  + + + μ–‘μž¬μ‹œλ―Όμ˜μˆ²μ—­ + + + μ–‘μž¬μ—­ + + 10 + +
+
+``` + +Section(ꡬ간) 데이터 μ •μ˜. + +## 14. 인트둜 ν™”λ©΄ κ΅¬ν˜„ + +```java +// IntroViewController.java + +package subway.presentation; + +import java.util.List; +import java.util.Map; + +import subway.infrastructure.FileParser; +import subway.infrastructure.XmlFileParser; + +public class IntroViewController implements ViewController { + private final MainViewController mainViewController = new MainViewController(); + private final LineController lineController = new LineController(); + private final StationController stationController = new StationController(); + private final SectionController sectionController = new SectionController(); + + @Override + public void execute() { + this.setup(); + mainViewController.execute(); + } + + private void setup() { + this.loadLineData(); + this.loadStationData(); + this.loadSectionData(); + } + + private void loadLineData() { + FileParser fileParser = new XmlFileParser("line.xml"); + List> dataList = fileParser.parser(); + for (Map data : dataList) { + String lineName = data.get("name").toString(); + this.lineController.addLine(lineName); + } + } + + private void loadStationData() { + FileParser fileParser = new XmlFileParser("station.xml"); + List> dataList = fileParser.parser(); + for (Map data : dataList) { + String stationName = data.get("name").toString(); + this.stationController.addStation(stationName); + } + } + + private void loadSectionData() { + FileParser fileParser = new XmlFileParser("section.xml"); + List> dataList = fileParser.parser(); + for (Map data : dataList) { + Map line = (Map)data.get("line"); + String lineName = line.get("name").toString(); + Map source = (Map)data.get("source"); + String sourceName = source.get("name").toString(); + Map sink = (Map)data.get("sink"); + String sinkName = sink.get("name").toString(); + String distance = data.get("distance").toString(); + String time = data.get("time").toString(); + this.sectionController.addSection(lineName, sourceName, sinkName, distance, time); + } + } +} +``` + +인트둜 ν™”λ©΄ μ œμ–΄ κ΅¬ν˜„. + +## 15. μ• ν”Œλ¦¬μΌ€μ΄μ…˜ κ΅¬ν˜„ + +```java +// Application.java + +package subway; + +import subway.presentation.IntroViewController; + +public class Application { + public static void main(String[] args) { + IntroViewController introViewController = new IntroViewController(); + introViewController.execute(); + } +} +``` + +μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ‹€ν–‰ μ‹œ 인트둜 ν™”λ©΄ 호좜 κ΅¬ν˜„. \ No newline at end of file diff --git a/README.md b/README.md index bb0c84ebb..4b9c0ceb9 100644 --- a/README.md +++ b/README.md @@ -251,3 +251,7 @@ public void getDijkstraShortestPath() { ## πŸ“ License This project is [MIT](https://github.com/woowacourse/java-subway-path-precourse/blob/master/LICENSE.md) licensed. + +## 🧩 풀이 κ³Όμ • + +[solving_process.md](https://github.com/jaehyeonjung0613/java-subway-path-precourse/blob/main/.github/solving_process.md) \ No newline at end of file diff --git a/src/main/java/subway/Application.java b/src/main/java/subway/Application.java index 0bcf786cc..84be1382c 100644 --- a/src/main/java/subway/Application.java +++ b/src/main/java/subway/Application.java @@ -1,10 +1,10 @@ package subway; -import java.util.Scanner; +import subway.presentation.IntroViewController; public class Application { public static void main(String[] args) { - final Scanner scanner = new Scanner(System.in); - // TODO: ν”„λ‘œκ·Έλž¨ κ΅¬ν˜„ + IntroViewController introViewController = new IntroViewController(); + introViewController.execute(); } -} +} \ No newline at end of file diff --git a/src/main/java/subway/application/section/dto/SectionDTO.java b/src/main/java/subway/application/section/dto/SectionDTO.java new file mode 100644 index 000000000..abeee3d8d --- /dev/null +++ b/src/main/java/subway/application/section/dto/SectionDTO.java @@ -0,0 +1,69 @@ +package subway.application.section.dto; + +import subway.domain.line.LineDTO; +import subway.domain.station.StationDTO; + +public class SectionDTO { + private static final String SECTION_ADDING_LINE_INFO_ESSENTIAL_MESSAGE = "ꡬ간 μƒμ„±μ‹œ λ…Έμ„  μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + private static final String SECTION_ADDING_SOURCE_STATION_INFO_ESSENTIAL_MESSAGE = "ꡬ간 μƒμ„±μ‹œ μ‹œμž‘μ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + private static final String SECTION_ADDING_SINK_STATION_INFO_ESSENTIAL_MESSAGE = "ꡬ간 μƒμ„±μ‹œ μ’…λ£Œμ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + + private final LineDTO lineDTO; + private final StationDTO sourceDTO; + private final StationDTO sinkDTO; + private int distance; + private int time; + + public SectionDTO(LineDTO lineDTO, StationDTO sourceDTO, StationDTO sinkDTO) { + this(lineDTO, sourceDTO, sinkDTO, 0, 0); + } + + public SectionDTO(LineDTO lineDTO, StationDTO sourceDTO, StationDTO sinkDTO, int distance, int time) { + this.validate(lineDTO, sourceDTO, sinkDTO); + this.lineDTO = lineDTO; + this.sourceDTO = sourceDTO; + this.sinkDTO = sinkDTO; + this.distance = distance; + this.time = time; + } + + private void validate(LineDTO lineDTO, StationDTO sourceDTO, StationDTO sinkDTO) { + if (lineDTO == null) { + throw new IllegalArgumentException(SECTION_ADDING_LINE_INFO_ESSENTIAL_MESSAGE); + } + if (sourceDTO == null) { + throw new IllegalArgumentException(SECTION_ADDING_SOURCE_STATION_INFO_ESSENTIAL_MESSAGE); + } + if (sinkDTO == null) { + throw new IllegalArgumentException(SECTION_ADDING_SINK_STATION_INFO_ESSENTIAL_MESSAGE); + } + } + + public LineDTO getLineDTO() { + return lineDTO; + } + + public StationDTO getSourceDTO() { + return sourceDTO; + } + + public StationDTO getSinkDTO() { + return sinkDTO; + } + + public int getDistance() { + return distance; + } + + public void setDistance(int distance) { + this.distance = distance; + } + + public int getTime() { + return time; + } + + public void setTime(int time) { + this.time = time; + } +} diff --git a/src/main/java/subway/application/section/dto/ShortCostRequest.java b/src/main/java/subway/application/section/dto/ShortCostRequest.java new file mode 100644 index 000000000..716b0c39c --- /dev/null +++ b/src/main/java/subway/application/section/dto/ShortCostRequest.java @@ -0,0 +1,38 @@ +package subway.application.section.dto; + +import subway.domain.station.StationDTO; + +public class ShortCostRequest { + private static final String SOURCE_STATION_INFO_ESSENTIAL_MESSAGE = "μ‹œμž‘μ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + private static final String SINK_STATION_INFO_ESSENTIAL_MESSAGE = "μ’…λ£Œμ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + private static final String SAME_SOURCE_AND_SINK_STATION_MESSAGE = "μΆœλ°œμ—­κ³Ό 도착역이 λ™μΌν•©λ‹ˆλ‹€."; + + private final StationDTO sourceDTO; + private final StationDTO sinkDTO; + + public ShortCostRequest(StationDTO sourceDTO, StationDTO sinkDTO) { + this.validate(sourceDTO, sinkDTO); + this.sourceDTO = sourceDTO; + this.sinkDTO = sinkDTO; + } + + private void validate(StationDTO sourceDTO, StationDTO sinkDTO) { + if(sourceDTO == null) { + throw new IllegalArgumentException(SOURCE_STATION_INFO_ESSENTIAL_MESSAGE); + } + if(sinkDTO == null) { + throw new IllegalArgumentException(SINK_STATION_INFO_ESSENTIAL_MESSAGE); + } + if(sourceDTO.equals(sinkDTO)) { + throw new IllegalArgumentException(SAME_SOURCE_AND_SINK_STATION_MESSAGE); + } + } + + public StationDTO getSourceDTO() { + return sourceDTO; + } + + public StationDTO getSinkDTO() { + return sinkDTO; + } +} diff --git a/src/main/java/subway/application/section/dto/ShortCostResponse.java b/src/main/java/subway/application/section/dto/ShortCostResponse.java new file mode 100644 index 000000000..8bbed9219 --- /dev/null +++ b/src/main/java/subway/application/section/dto/ShortCostResponse.java @@ -0,0 +1,46 @@ +package subway.application.section.dto; + +import java.util.ArrayList; +import java.util.List; + +import subway.domain.station.Station; + +public class ShortCostResponse { + private int totalDistance; + private int totalTime; + private final List stationNameList; + + public ShortCostResponse(List stationList) { + this.stationNameList = new ArrayList<>(); + int length = stationList.size(); + for (int index = 0; index < length; index++) { + Station source = stationList.get(index); + if (index + 1 < length) { + Station sink = stationList.get(index + 1); + totalDistance += source.findDistanceTo(sink); + totalTime += source.findTimeTo(sink); + } + stationNameList.add(source.getName()); + } + } + + public int getTotalDistance() { + return totalDistance; + } + + public void setTotalDistance(int totalDistance) { + this.totalDistance = totalDistance; + } + + public int getTotalTime() { + return totalTime; + } + + public void setTotalTime(int totalTime) { + this.totalTime = totalTime; + } + + public List getStationNameList() { + return stationNameList; + } +} diff --git a/src/main/java/subway/application/section/service/SectionService.java b/src/main/java/subway/application/section/service/SectionService.java new file mode 100644 index 000000000..82fbae905 --- /dev/null +++ b/src/main/java/subway/application/section/service/SectionService.java @@ -0,0 +1,82 @@ +package subway.application.section.service; + +import java.util.List; + +import org.jgrapht.GraphPath; +import org.jgrapht.graph.DefaultWeightedEdge; + +import subway.application.section.dto.SectionDTO; +import subway.application.section.dto.ShortCostRequest; +import subway.application.section.dto.ShortCostResponse; +import subway.domain.line.Line; +import subway.domain.line.LineService; +import subway.domain.section.Section; +import subway.domain.section.SectionRepository; +import subway.domain.station.Station; +import subway.domain.station.StationDTO; +import subway.domain.station.StationService; + +public class SectionService { + private static final String ALREADY_EXISTS_SECTION_MESSAGE = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” κ΅¬κ°„μž…λ‹ˆλ‹€."; + private static final String NOT_EXISTS_SOURCE_STATION_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ‹œμž‘μ—­μž…λ‹ˆλ‹€."; + private static final String NOT_EXISTS_SINK_STATION_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ’…λ£Œμ—­μž…λ‹ˆλ‹€."; + private static final String NOT_CONNECTED_SOURCE_AND_SINK_STATION_MESSAGE = "μ‹œμž‘ 지점과 μ’…λ£Œμ—­μ΄ μ—°κ²°λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + + private final LineService lineService = new LineService(); + private final StationService stationService = new StationService(); + private final ShortDistanceService shortDistanceService = new ShortDistanceService(); + private final ShortTimeService shortTimeService = new ShortTimeService(); + + public List
findAll() { + return SectionRepository.sections(); + } + + public void addNode(StationDTO stationDTO) { + Station station = this.stationService.findOneByName(stationDTO.getName()); + this.shortDistanceService.addNode(station); + this.shortTimeService.addNode(station); + } + + public void addSection(SectionDTO sectionDTO) { + Line line = this.lineService.findOneByName(sectionDTO.getLineDTO().getName()); + Station source = this.stationService.findOneByName(sectionDTO.getSourceDTO().getName()); + Station sink = this.stationService.findOneByName(sectionDTO.getSinkDTO().getName()); + Section section = new Section(line, source, sink, sectionDTO.getDistance(), sectionDTO.getTime()); + if (SectionRepository.exists(section)) { + throw new IllegalArgumentException(ALREADY_EXISTS_SECTION_MESSAGE); + } + SectionRepository.addSection(section); + this.shortDistanceService.addEdge(section); + this.shortTimeService.addEdge(section); + line.addSection(section); + source.addSection(section); + } + + public ShortCostResponse computeShortDistance(ShortCostRequest shortCostRequest) { + Station source = this.stationService.findByName(shortCostRequest.getSourceDTO().getName()) + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_SOURCE_STATION_MESSAGE)); + Station sink = this.stationService.findByName(shortCostRequest.getSinkDTO().getName()) + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_SINK_STATION_MESSAGE)); + GraphPath graphPath = this.shortDistanceService.compute(source, sink); + if (graphPath == null) { + throw new IllegalArgumentException(NOT_CONNECTED_SOURCE_AND_SINK_STATION_MESSAGE); + } + return new ShortCostResponse(graphPath.getVertexList()); + } + + public ShortCostResponse computeShortTime(ShortCostRequest shortCostRequest) { + Station source = this.stationService.findByName(shortCostRequest.getSourceDTO().getName()) + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_SOURCE_STATION_MESSAGE)); + Station sink = this.stationService.findByName(shortCostRequest.getSinkDTO().getName()) + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_SINK_STATION_MESSAGE)); + GraphPath graphPath = this.shortDistanceService.compute(source, sink); + if (graphPath == null) { + throw new IllegalArgumentException(NOT_CONNECTED_SOURCE_AND_SINK_STATION_MESSAGE); + } + return new ShortCostResponse(graphPath.getVertexList()); + } + + public void deleteAll() { + SectionRepository.deleteAll(); + } +} diff --git a/src/main/java/subway/application/section/service/ShortCostService.java b/src/main/java/subway/application/section/service/ShortCostService.java new file mode 100644 index 000000000..54c501abf --- /dev/null +++ b/src/main/java/subway/application/section/service/ShortCostService.java @@ -0,0 +1,87 @@ +package subway.application.section.service; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.jgrapht.GraphPath; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.AbstractBaseGraph; +import org.jgrapht.graph.DefaultWeightedEdge; + +import subway.domain.section.Section; +import subway.domain.station.Station; + +abstract class ShortCostService { + private final AbstractBaseGraph graph; + + public ShortCostService(AbstractBaseGraph graph) { + this.graph = graph; + } + + private static final String ALREADY_EXISTS_NODE_MESSAGE = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” λ…Έλ“œμž…λ‹ˆλ‹€."; + private static final String NOT_EXISTS_SOURCE_NODE_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ‹œμž‘λ…Έλ“œμž…λ‹ˆλ‹€."; + private static final String NOT_EXISTS_SINK_NODE_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ’…λ£Œλ…Έλ“œμž…λ‹ˆλ‹€."; + private static final String ALREADY_EXISTS_EDGE_MESSAGE = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” κ°„μ„ μž…λ‹ˆλ‹€."; + + protected Set findAllNode() { + return Collections.unmodifiableSet(graph.vertexSet()); + } + + protected Set findAllEdge() { + return Collections.unmodifiableSet(graph.edgeSet()); + } + + protected void addNode(Station station) { + if (graph.containsVertex(station)) { + throw new IllegalArgumentException(ALREADY_EXISTS_NODE_MESSAGE); + } + graph.addVertex(station); + } + + protected void addEdge(Section section) { + Station source = section.getSource(); + Station sink = section.getSink(); + this.validateSource(source); + this.validateSink(sink); + if (graph.containsEdge(source, sink)) { + throw new IllegalArgumentException(ALREADY_EXISTS_EDGE_MESSAGE); + } + graph.setEdgeWeight(graph.addEdge(source, sink), this.getWeight(section)); + } + + protected abstract double getWeight(Section section); + + protected GraphPath compute(Station source, Station sink) { + this.validateSource(source); + this.validateSink(sink); + DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath<>(graph); + return dijkstraShortestPath.getPath(source, sink); + } + + protected void validateSource(Station source) { + if (!graph.containsVertex(source)) { + throw new IllegalArgumentException(NOT_EXISTS_SOURCE_NODE_MESSAGE); + } + } + + protected void validateSink(Station sink) { + if (!graph.containsVertex(sink)) { + throw new IllegalArgumentException(NOT_EXISTS_SINK_NODE_MESSAGE); + } + } + + protected void deleteAllNode() { + Set nodes = new HashSet<>(this.findAllNode()); + graph.removeAllVertices(nodes); + } + + protected void deleteAllEdge() { + graph.removeAllEdges(graph.edgeSet()); + } + + protected void deleteAll() { + this.deleteAllEdge(); + this.deleteAllNode(); + } +} diff --git a/src/main/java/subway/application/section/service/ShortDistanceService.java b/src/main/java/subway/application/section/service/ShortDistanceService.java new file mode 100644 index 000000000..c0be24d50 --- /dev/null +++ b/src/main/java/subway/application/section/service/ShortDistanceService.java @@ -0,0 +1,22 @@ +package subway.application.section.service; + +import org.jgrapht.graph.AbstractBaseGraph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.DirectedWeightedMultigraph; + +import subway.domain.section.Section; +import subway.domain.station.Station; + +class ShortDistanceService extends ShortCostService { + private static final AbstractBaseGraph graph = new DirectedWeightedMultigraph<>( + DefaultWeightedEdge.class); + + public ShortDistanceService() { + super(graph); + } + + @Override + protected double getWeight(Section section) { + return section.getDistance(); + } +} diff --git a/src/main/java/subway/application/section/service/ShortTimeService.java b/src/main/java/subway/application/section/service/ShortTimeService.java new file mode 100644 index 000000000..d0b775a4a --- /dev/null +++ b/src/main/java/subway/application/section/service/ShortTimeService.java @@ -0,0 +1,22 @@ +package subway.application.section.service; + +import org.jgrapht.graph.AbstractBaseGraph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.DirectedWeightedMultigraph; + +import subway.domain.section.Section; +import subway.domain.station.Station; + +class ShortTimeService extends ShortCostService { + private static final AbstractBaseGraph graph = new DirectedWeightedMultigraph<>( + DefaultWeightedEdge.class); + + public ShortTimeService() { + super(graph); + } + + @Override + protected double getWeight(Section section) { + return section.getTime(); + } +} diff --git a/src/main/java/subway/domain/Line.java b/src/main/java/subway/domain/Line.java deleted file mode 100644 index f4d738d5a..000000000 --- a/src/main/java/subway/domain/Line.java +++ /dev/null @@ -1,15 +0,0 @@ -package subway.domain; - -public class Line { - private String name; - - public Line(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - // μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„ -} diff --git a/src/main/java/subway/domain/Station.java b/src/main/java/subway/domain/Station.java deleted file mode 100644 index bdb142590..000000000 --- a/src/main/java/subway/domain/Station.java +++ /dev/null @@ -1,15 +0,0 @@ -package subway.domain; - -public class Station { - private String name; - - public Station(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - // μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„ -} diff --git a/src/main/java/subway/domain/line/Line.java b/src/main/java/subway/domain/line/Line.java new file mode 100644 index 000000000..7e2ac920b --- /dev/null +++ b/src/main/java/subway/domain/line/Line.java @@ -0,0 +1,37 @@ +package subway.domain.line; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import subway.domain.section.Section; + +public class Line { + private static final String SECTION_ADDING_OTHER_LINE_RECEIVE_MESSAGE = "ꡬ간 μΆ”κ°€μ‹œ λ‹€λ₯Έ 노선을 λ°›μ•˜μŠ΅λ‹ˆλ‹€."; + + private final String name; + private final List
sectionList = new ArrayList<>(); + + public Line(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public List
getSectionList() { + return Collections.unmodifiableList(this.sectionList); + } + + public void addSection(Section section) { + this.validateSection(section); + this.sectionList.add(section); + } + + private void validateSection(Section section) { + if(this != section.getLine()) { + throw new IllegalArgumentException(SECTION_ADDING_OTHER_LINE_RECEIVE_MESSAGE); + } + } +} diff --git a/src/main/java/subway/domain/line/LineDTO.java b/src/main/java/subway/domain/line/LineDTO.java new file mode 100644 index 000000000..6535443cf --- /dev/null +++ b/src/main/java/subway/domain/line/LineDTO.java @@ -0,0 +1,22 @@ +package subway.domain.line; + +public class LineDTO { + private static final String LINE_NAME_ESSENTIAL_MESSAGE = "λ…Έμ„  이름은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + + private final String name; + + public LineDTO(String name) { + this.validate(name); + this.name = name; + } + + private void validate(String name) { + if(name == null || name.trim().isEmpty()) { + throw new IllegalArgumentException(LINE_NAME_ESSENTIAL_MESSAGE); + } + } + + public String getName() { + return this.name; + } +} diff --git a/src/main/java/subway/domain/LineRepository.java b/src/main/java/subway/domain/line/LineRepository.java similarity index 62% rename from src/main/java/subway/domain/LineRepository.java rename to src/main/java/subway/domain/line/LineRepository.java index 2c4a723c9..caeb6af90 100644 --- a/src/main/java/subway/domain/LineRepository.java +++ b/src/main/java/subway/domain/line/LineRepository.java @@ -1,13 +1,18 @@ -package subway.domain; +package subway.domain.line; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; public class LineRepository { private static final List lines = new ArrayList<>(); + public static Optional findByName(String name) { + return lines.stream().filter(line -> line.getName().equals(name)).findFirst(); + } + public static List lines() { return Collections.unmodifiableList(lines); } @@ -23,4 +28,8 @@ public static boolean deleteLineByName(String name) { public static void deleteAll() { lines.clear(); } + + public static boolean exists(Line other) { + return lines.stream().anyMatch(line -> line.getName().equals(other.getName())); + } } diff --git a/src/main/java/subway/domain/line/LineService.java b/src/main/java/subway/domain/line/LineService.java new file mode 100644 index 000000000..23a7a3b4e --- /dev/null +++ b/src/main/java/subway/domain/line/LineService.java @@ -0,0 +1,28 @@ +package subway.domain.line; + +import java.util.List; + +public class LineService { + private static final String NOT_EXISTS_LINE_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ λ…Έμ„ μž…λ‹ˆλ‹€."; + private static final String ALREADY_EXISTS_LINE_MESSAGE = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” λ…Έμ„ μž…λ‹ˆλ‹€."; + + public Line findOneByName(String name) { + return LineRepository.findByName(name).orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_LINE_MESSAGE)); + } + + public List findAll() { + return LineRepository.lines(); + } + + public void addLine(LineDTO lineDTO) { + Line line = new Line(lineDTO.getName()); + if (LineRepository.exists(line)) { + throw new IllegalArgumentException(ALREADY_EXISTS_LINE_MESSAGE); + } + LineRepository.addLine(line); + } + + public void deleteAll() { + LineRepository.deleteAll(); + } +} diff --git a/src/main/java/subway/domain/section/Section.java b/src/main/java/subway/domain/section/Section.java new file mode 100644 index 000000000..d2f11bb30 --- /dev/null +++ b/src/main/java/subway/domain/section/Section.java @@ -0,0 +1,40 @@ +package subway.domain.section; + +import subway.domain.line.Line; +import subway.domain.station.Station; + +public class Section { + private final Line line; + private final Station source; + private final Station sink; + private final int distance; + private final int time; + + public Section(Line line, Station source, Station sink, int distance, int time) { + this.line = line; + this.source = source; + this.sink = sink; + this.distance = distance; + this.time = time; + } + + public Line getLine() { + return line; + } + + public Station getSource() { + return source; + } + + public Station getSink() { + return sink; + } + + public int getDistance() { + return distance; + } + + public int getTime() { + return time; + } +} diff --git a/src/main/java/subway/domain/section/SectionRepository.java b/src/main/java/subway/domain/section/SectionRepository.java new file mode 100644 index 000000000..c115f6554 --- /dev/null +++ b/src/main/java/subway/domain/section/SectionRepository.java @@ -0,0 +1,27 @@ +package subway.domain.section; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SectionRepository { + private static final List
sections = new ArrayList<>(); + + public static List
sections() { + return Collections.unmodifiableList(sections); + } + + public static void addSection(Section section) { + sections.add(section); + } + + public static void deleteAll() { + sections.clear(); + } + + public static boolean exists(Section other) { + return sections.stream() + .anyMatch(section -> section.getLine() == other.getLine() && section.getSource() == other.getSource() + && section.getSink() == other.getSink()); + } +} diff --git a/src/main/java/subway/domain/station/Station.java b/src/main/java/subway/domain/station/Station.java new file mode 100644 index 000000000..908388b6e --- /dev/null +++ b/src/main/java/subway/domain/station/Station.java @@ -0,0 +1,54 @@ +package subway.domain.station; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import subway.domain.section.Section; + +public class Station { + private static final String SECTION_ADDING_OTHER_SOURCE_STATION_RECEIVE_MESSAGE = "ꡬ간 μΆ”κ°€μ‹œ λ‹€λ₯Έ μ‹œμž‘μ—­μ„ λ°›μ•˜μŠ΅λ‹ˆλ‹€."; + private static final String NOT_EXISTS_PATH_TO_SINK_STATION_MESSAGE = "μ’…λ£Œμ—­κΉŒμ§€ κ²½λ‘œκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + + private final String name; + private final List
sectionList = new ArrayList<>(); + + public Station(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public List
getSectionList() { + return Collections.unmodifiableList(this.sectionList); + } + + public void addSection(Section section) { + this.validateSection(section); + this.sectionList.add(section); + } + + public void validateSection(Section section) { + if(this != section.getSource()) { + throw new IllegalArgumentException(SECTION_ADDING_OTHER_SOURCE_STATION_RECEIVE_MESSAGE); + } + } + + public int findDistanceTo(Station sink) { + return this.sectionList.stream() + .filter(section -> section.getSink() == sink) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_PATH_TO_SINK_STATION_MESSAGE)) + .getDistance(); + } + + public int findTimeTo(Station sink) { + return this.sectionList.stream() + .filter(section -> section.getSink() == sink) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_PATH_TO_SINK_STATION_MESSAGE)) + .getTime(); + } +} diff --git a/src/main/java/subway/domain/station/StationDTO.java b/src/main/java/subway/domain/station/StationDTO.java new file mode 100644 index 000000000..b22b88cee --- /dev/null +++ b/src/main/java/subway/domain/station/StationDTO.java @@ -0,0 +1,32 @@ +package subway.domain.station; + +import java.util.Objects; + +public class StationDTO { + private static final String STATION_NAME_ESSENTIAL_MESSAGE = "μ—­ 이름은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + + private final String name; + + public StationDTO(String name) { + this.validate(name); + this.name = name; + } + + private void validate(String name) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException(STATION_NAME_ESSENTIAL_MESSAGE); + } + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object object) { + if(this == object) return true; + if(!(object instanceof StationDTO)) return false; + StationDTO other = (StationDTO)object; + return Objects.equals(this.name, other.name); + } +} diff --git a/src/main/java/subway/domain/StationRepository.java b/src/main/java/subway/domain/station/StationRepository.java similarity index 62% rename from src/main/java/subway/domain/StationRepository.java rename to src/main/java/subway/domain/station/StationRepository.java index 8ed9d103f..1abd77fb4 100644 --- a/src/main/java/subway/domain/StationRepository.java +++ b/src/main/java/subway/domain/station/StationRepository.java @@ -1,9 +1,10 @@ -package subway.domain; +package subway.domain.station; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; public class StationRepository { private static final List stations = new ArrayList<>(); @@ -12,6 +13,10 @@ public static List stations() { return Collections.unmodifiableList(stations); } + public static Optional findByName(String name) { + return stations.stream().filter(station -> station.getName().equals(name)).findFirst(); + } + public static void addStation(Station station) { stations.add(station); } @@ -23,4 +28,8 @@ public static boolean deleteStation(String name) { public static void deleteAll() { stations.clear(); } + + public static boolean exists(Station other) { + return stations().stream().anyMatch(station -> station.getName().equals(other.getName())); + } } diff --git a/src/main/java/subway/domain/station/StationService.java b/src/main/java/subway/domain/station/StationService.java new file mode 100644 index 000000000..4374c1ec8 --- /dev/null +++ b/src/main/java/subway/domain/station/StationService.java @@ -0,0 +1,34 @@ +package subway.domain.station; + +import java.util.List; +import java.util.Optional; + +public class StationService { + private static final String NOT_EXISTS_STATION_MESSAGE = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ—­μž…λ‹ˆλ‹€."; + private static final String ALREADY_EXISTS_STATION_MESSAGE = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” μ—­μž…λ‹ˆλ‹€."; + + public Station findOneByName(String name) { + return StationRepository.findByName(name) + .orElseThrow(() -> new IllegalArgumentException(NOT_EXISTS_STATION_MESSAGE)); + } + + public Optional findByName(String name) { + return StationRepository.findByName(name); + } + + public List findAll() { + return StationRepository.stations(); + } + + public void addStation(StationDTO stationDTO) { + Station station = new Station(stationDTO.getName()); + if (StationRepository.exists(station)) { + throw new IllegalArgumentException(ALREADY_EXISTS_STATION_MESSAGE); + } + StationRepository.addStation(station); + } + + public void deleteAll() { + StationRepository.deleteAll(); + } +} diff --git a/src/main/java/subway/infrastructure/FileParser.java b/src/main/java/subway/infrastructure/FileParser.java new file mode 100644 index 000000000..8b77789b7 --- /dev/null +++ b/src/main/java/subway/infrastructure/FileParser.java @@ -0,0 +1,47 @@ +package subway.infrastructure; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public abstract class FileParser { + private static final String NOT_EXISTS_FILE_MESSAGE = "파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + private static final String NOT_FILE_TYPE_MESSAGE = "파일 μœ ν˜•μ΄ μ•„λ‹™λ‹ˆλ‹€."; + private static final String NOT_ALLOWED_FILE_EXTENSION_MESSAGE = "ν—ˆμš©λœ 파일 ν™•μž₯μžκ°€ μ•„λ‹™λ‹ˆλ‹€."; + + protected final File file; + + public FileParser(String fileName) { + this(new File(Objects.requireNonNull(FileParser.class.getClassLoader().getResource(fileName)).getPath())); + } + + public FileParser(File file) { + this.validate(file); + this.file = file; + } + + private void validate(File file) { + if (file == null || !file.exists()) { + throw new IllegalArgumentException(NOT_EXISTS_FILE_MESSAGE); + } + if (!file.isFile()) { + throw new IllegalArgumentException(NOT_FILE_TYPE_MESSAGE); + } + if (!this.allowExtension(this.extractExtension(file.getName()))) { + throw new IllegalArgumentException(NOT_ALLOWED_FILE_EXTENSION_MESSAGE); + } + } + + private String extractExtension(String fileName) { + int index = fileName.lastIndexOf("."); + if (index > 0) { + return fileName.substring(index + 1); + } + return ""; + } + + public abstract boolean allowExtension(String extension); + + public abstract List> parser(); +} diff --git a/src/main/java/subway/infrastructure/XmlFileParser.java b/src/main/java/subway/infrastructure/XmlFileParser.java new file mode 100644 index 000000000..5d65a6828 --- /dev/null +++ b/src/main/java/subway/infrastructure/XmlFileParser.java @@ -0,0 +1,115 @@ +package subway.infrastructure; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class XmlFileParser extends FileParser { + private static final int UNKNOWN_NODE_LIST = 0; + private static final int HAS_ELEMENT_NODE_LIST = 1; + private static final int HAS_TEXT_NODE_LIST = 2; + + private final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + public XmlFileParser(String fileName) { + super(fileName); + } + + public XmlFileParser(File file) { + super(file); + } + + @Override + public boolean allowExtension(String extension) { + return "xml".equals(extension); + } + + @Override + public List> parser() { + try { + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(file); + document.getDocumentElement().normalize(); + Element root = document.getDocumentElement(); + return this.find(root); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private List> find(Element root) { + List> data = new ArrayList<>(); + NodeList nodeList = root.getChildNodes(); + for (int index = 0; index < nodeList.getLength(); index++) { + Node node = nodeList.item(index); + if (node.getNodeType() == Node.ELEMENT_NODE) { + data.add(this.serialize(node.getChildNodes())); + } + } + return data; + } + + private Map serialize(NodeList parentNodeList) { + Map data = new HashMap<>(); + for (int index = 0; index < parentNodeList.getLength(); index++) { + Node child = parentNodeList.item(index); + int childType = child.getNodeType(); + String childName = child.getNodeName(); + if (childType == Node.ELEMENT_NODE) { + data.put(childName, this.getNodeData(child)); + } + } + return data; + } + + private Object getNodeData(Node node) { + NodeList nodeList = node.getChildNodes(); + int nodeListType = this.getNodeListType(nodeList); + if (nodeListType == HAS_ELEMENT_NODE_LIST) { + return this.serialize(nodeList); + } else if (nodeListType == HAS_TEXT_NODE_LIST) { + return this.getTextValue(node); + } + return null; + } + + private int getNodeListType(NodeList nodeList) { + for (int index = 0; index < nodeList.getLength(); index++) { + Node node = nodeList.item(index); + int nodeType = node.getNodeType(); + String _nodeValue = node.getNodeValue(); + if (nodeType == Node.ELEMENT_NODE) { + return HAS_ELEMENT_NODE_LIST; + } else if (nodeType == Node.TEXT_NODE && this.hasTextValue(_nodeValue)) { + return HAS_TEXT_NODE_LIST; + } + } + return UNKNOWN_NODE_LIST; + } + + private String getTextValue(Node node) { + NodeList nodeList = node.getChildNodes(); + for (int index = 0; index < nodeList.getLength(); index++) { + Node child = nodeList.item(index); + String _childValue = child.getNodeValue(); + if (child.getNodeType() == Node.TEXT_NODE && this.hasTextValue(_childValue)) { + return _childValue.trim(); + } + } + return ""; + } + + private boolean hasTextValue(String value) { + return value != null && !value.trim().isEmpty(); + } +} diff --git a/src/main/java/subway/presentation/IntroViewController.java b/src/main/java/subway/presentation/IntroViewController.java new file mode 100644 index 000000000..31b2ed232 --- /dev/null +++ b/src/main/java/subway/presentation/IntroViewController.java @@ -0,0 +1,60 @@ +package subway.presentation; + +import java.util.List; +import java.util.Map; + +import subway.infrastructure.FileParser; +import subway.infrastructure.XmlFileParser; + +public class IntroViewController implements ViewController { + private final MainViewController mainViewController = new MainViewController(); + private final LineController lineController = new LineController(); + private final StationController stationController = new StationController(); + private final SectionController sectionController = new SectionController(); + + @Override + public void execute() { + this.setup(); + mainViewController.execute(); + } + + private void setup() { + this.loadLineData(); + this.loadStationData(); + this.loadSectionData(); + } + + private void loadLineData() { + FileParser fileParser = new XmlFileParser("line.xml"); + List> dataList = fileParser.parser(); + for (Map data : dataList) { + String lineName = data.get("name").toString(); + this.lineController.addLine(lineName); + } + } + + private void loadStationData() { + FileParser fileParser = new XmlFileParser("station.xml"); + List> dataList = fileParser.parser(); + for (Map data : dataList) { + String stationName = data.get("name").toString(); + this.stationController.addStation(stationName); + } + } + + private void loadSectionData() { + FileParser fileParser = new XmlFileParser("section.xml"); + List> dataList = fileParser.parser(); + for (Map data : dataList) { + Map line = (Map)data.get("line"); + String lineName = line.get("name").toString(); + Map source = (Map)data.get("source"); + String sourceName = source.get("name").toString(); + Map sink = (Map)data.get("sink"); + String sinkName = sink.get("name").toString(); + String distance = data.get("distance").toString(); + String time = data.get("time").toString(); + this.sectionController.addSection(lineName, sourceName, sinkName, distance, time); + } + } +} diff --git a/src/main/java/subway/presentation/LineController.java b/src/main/java/subway/presentation/LineController.java new file mode 100644 index 000000000..b1318dfd9 --- /dev/null +++ b/src/main/java/subway/presentation/LineController.java @@ -0,0 +1,13 @@ +package subway.presentation; + +import subway.domain.line.LineDTO; +import subway.domain.line.LineService; + +public class LineController { + private final LineService lineService = new LineService(); + + public void addLine(String lineName) { + LineDTO lineDTO = new LineDTO(lineName); + this.lineService.addLine(lineDTO); + } +} diff --git a/src/main/java/subway/presentation/MainViewController.java b/src/main/java/subway/presentation/MainViewController.java new file mode 100644 index 000000000..36584f5d4 --- /dev/null +++ b/src/main/java/subway/presentation/MainViewController.java @@ -0,0 +1,35 @@ +package subway.presentation; + +import subway.screen.ui.Console; +import subway.screen.view.Menu; +import subway.screen.view.main.MainMenuView; + +public class MainViewController implements ViewController { + private final MainMenuView mainMenuView = new MainMenuView(this); + + private boolean end; + + @Override + public void execute() { + this.end = false; + do { + mainMenuView.show(); + Menu menu = mainMenuView.question(); + try { + mainMenuView.onEvent(menu); + } catch (IllegalArgumentException e) { + Console.printError(e.getMessage()); + Console.println(); + } + } while (!this.end); + } + + public void handlePathSearch() { + PathViewController pathViewController = new PathViewController(); + pathViewController.execute(); + } + + public void handleEnd() { + this.end = true; + } +} diff --git a/src/main/java/subway/presentation/PathViewController.java b/src/main/java/subway/presentation/PathViewController.java new file mode 100644 index 000000000..b6504c429 --- /dev/null +++ b/src/main/java/subway/presentation/PathViewController.java @@ -0,0 +1,66 @@ +package subway.presentation; + +import subway.application.section.dto.ShortCostResponse; +import subway.screen.ui.Console; +import subway.screen.view.Menu; +import subway.screen.view.path.PathMenuView; + +public class PathViewController implements ViewController { + private final PathMenuView pathMenuView = new PathMenuView(this); + private final SectionController sectionController = new SectionController(); + + @Override + public void execute() { + do { + pathMenuView.show(); + Menu menu = pathMenuView.question(); + try { + pathMenuView.onEvent(menu); + return; + } catch (IllegalArgumentException e) { + Console.printError(e.getMessage()); + Console.println(); + } + } while (true); + } + + public void handleShortDistance() { + String sourceName = this.requestSourceName(); + String sinkName = this.requestSinkName(); + ShortCostResponse shortCostResponse = this.sectionController.computeShortDistance(sourceName, sinkName); + this.printShortCostResponse(shortCostResponse); + } + + public void handleShortTime() { + String sourceName = this.requestSourceName(); + String sinkName = this.requestSinkName(); + ShortCostResponse shortCostResponse = this.sectionController.computeShortTime(sourceName, sinkName); + this.printShortCostResponse(shortCostResponse); + } + + private String requestSourceName() { + Console.printHeader("μΆœλ°œμ—­μ„ μž…λ ₯ν•˜μ„Έμš”"); + String sourceName = Console.readline(); + Console.println(); + return sourceName; + } + + private String requestSinkName() { + Console.printHeader("도착역을 μž…λ ₯ν•˜μ„Έμš”"); + String sinkName = Console.readline(); + Console.println(); + return sinkName; + } + + private void printShortCostResponse(ShortCostResponse shortCostResponse) { + Console.printHeader("쑰회 κ²°κ³Ό"); + Console.printInfo("---"); + Console.printInfo(String.format("총 거리 : %dkm", shortCostResponse.getTotalDistance())); + Console.printInfo(String.format("총 μ†Œμš” μ‹œκ°„ : %dλΆ„", shortCostResponse.getTotalTime())); + Console.printInfo("---"); + for (String stationName : shortCostResponse.getStationNameList()) { + Console.printInfo(stationName); + } + Console.println(); + } +} diff --git a/src/main/java/subway/presentation/SectionController.java b/src/main/java/subway/presentation/SectionController.java new file mode 100644 index 000000000..9ddf5405d --- /dev/null +++ b/src/main/java/subway/presentation/SectionController.java @@ -0,0 +1,64 @@ +package subway.presentation; + +import subway.application.section.dto.SectionDTO; +import subway.application.section.dto.ShortCostRequest; +import subway.application.section.dto.ShortCostResponse; +import subway.application.section.service.SectionService; +import subway.domain.line.LineDTO; +import subway.domain.station.StationDTO; +import subway.util.Validation; + +public class SectionController { + private static final String INPUT_ESSENTIAL_DISTANCE_MESSAGE = "거리 μž…λ ₯은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + private static final String ONLY_POSSIBLE_NUMERIC_INPUT_DISTANCE_MESSAGE = "κ±°λ¦¬λŠ” 숫자 ν˜•μ‹μ˜ μž…λ ₯만 κ°€λŠ₯ν•©λ‹ˆλ‹€."; + private static final String INPUT_ESSENTIAL_TIME_MESSAGE = "μ‹œκ°„ μž…λ ₯은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + private static final String ONLY_POSSIBLE_NUMERIC_INPUT_TIME_MESSAGE = "μ‹œκ°„μ€ 숫자 ν˜•μ‹μ˜ μž…λ ₯만 κ°€λŠ₯ν•©λ‹ˆλ‹€."; + + private final SectionService sectionService = new SectionService(); + + public void addSection(String lineName, String sourceName, String sinkName, String _distance, String _time) { + LineDTO lineDTO = new LineDTO(lineName); + StationDTO sourceDTO = new StationDTO(sourceName); + StationDTO sinkDTO = new StationDTO(sinkName); + this.validateDistance(_distance); + int distance = Integer.parseInt(_distance); + this.validateTime(_time); + int time = Integer.parseInt(_time); + SectionDTO sectionDTO = new SectionDTO(lineDTO, sourceDTO, sinkDTO, distance, time); + this.sectionService.addSection(sectionDTO); + } + + private void validateDistance(String distance) { + if (distance == null || distance.trim().isEmpty()) { + throw new IllegalArgumentException(INPUT_ESSENTIAL_DISTANCE_MESSAGE); + } + if (!Validation.isNumeric(distance)) { + throw new IllegalArgumentException(ONLY_POSSIBLE_NUMERIC_INPUT_DISTANCE_MESSAGE); + } + } + + private void validateTime(String time) { + if (time == null || time.trim().isEmpty()) { + throw new IllegalArgumentException(INPUT_ESSENTIAL_TIME_MESSAGE); + } + if (!Validation.isNumeric(time)) { + throw new IllegalArgumentException(ONLY_POSSIBLE_NUMERIC_INPUT_TIME_MESSAGE); + } + } + + public ShortCostResponse computeShortDistance(String sourceName, String sinkName) { + ShortCostRequest shortCostRequest = this.createShortCostRequest(sourceName, sinkName); + return this.sectionService.computeShortDistance(shortCostRequest); + } + + public ShortCostResponse computeShortTime(String sourceName, String sinkName) { + ShortCostRequest shortCostRequest = this.createShortCostRequest(sourceName, sinkName); + return this.sectionService.computeShortTime(shortCostRequest); + } + + private ShortCostRequest createShortCostRequest(String sourceName, String sinkName) { + StationDTO sourceDTO = new StationDTO(sourceName); + StationDTO sinkDTO = new StationDTO(sinkName); + return new ShortCostRequest(sourceDTO, sinkDTO); + } +} diff --git a/src/main/java/subway/presentation/StationController.java b/src/main/java/subway/presentation/StationController.java new file mode 100644 index 000000000..84af3844c --- /dev/null +++ b/src/main/java/subway/presentation/StationController.java @@ -0,0 +1,15 @@ +package subway.presentation; + +import subway.application.section.service.SectionService;import subway.domain.station.StationDTO; +import subway.domain.station.StationService; + +public class StationController { + private final StationService stationService = new StationService(); + private final SectionService sectionService = new SectionService(); + + public void addStation(String name) { + StationDTO stationDTO = new StationDTO(name); + this.stationService.addStation(stationDTO); + this.sectionService.addNode(stationDTO); + } +} diff --git a/src/main/java/subway/presentation/ViewController.java b/src/main/java/subway/presentation/ViewController.java new file mode 100644 index 000000000..365a0c4e9 --- /dev/null +++ b/src/main/java/subway/presentation/ViewController.java @@ -0,0 +1,5 @@ +package subway.presentation; + +public interface ViewController { + void execute(); +} diff --git a/src/main/java/subway/screen/ui/Console.java b/src/main/java/subway/screen/ui/Console.java new file mode 100644 index 000000000..5c72fd351 --- /dev/null +++ b/src/main/java/subway/screen/ui/Console.java @@ -0,0 +1,38 @@ +package subway.screen.ui; + +import java.io.PrintStream; +import java.util.Scanner; + +public final class Console { + private static final Scanner scanner = new Scanner(System.in); + private static final PrintStream printer = System.out; + + private static final String HEADER_OUTPUT_FORMAT = "## %s"; + private static final String INFO_OUTPUT_FORMAT = "[INFO] %s"; + private static final String ERROR_OUTPUT_FORMAT = "[ERROR] %s"; + + public static String readline() { + return scanner.nextLine(); + } + + public static void println() { + printer.println(); + } + + public static void println(String message) { + printer.println(message); + } + + public static void printHeader(String message) { + println(String.format(HEADER_OUTPUT_FORMAT, message)); + } + + public static void printInfo(String message) { + println(String.format(INFO_OUTPUT_FORMAT, message)); + } + + public static void printError(String message) { + println(String.format(ERROR_OUTPUT_FORMAT, message)); + } + +} diff --git a/src/main/java/subway/screen/view/Menu.java b/src/main/java/subway/screen/view/Menu.java new file mode 100644 index 000000000..f6c6a17fc --- /dev/null +++ b/src/main/java/subway/screen/view/Menu.java @@ -0,0 +1,30 @@ +package subway.screen.view; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public interface Menu { + String getCommand(); + + String getName(); + + static List findAll(Class menuClass) { + Menu[] enumValues = menuClass.getEnumConstants(); + if(enumValues == null) { + throw new IllegalArgumentException("Enum ν˜•μ‹μ˜ ν΄λž˜μŠ€κ°€ μ•„λ‹™λ‹ˆλ‹€."); + } + return Arrays.stream(enumValues).collect(Collectors.toList()); + } + + static Optional findByCommand(Class menuClass, String command) { + List menuList = findAll(menuClass); + for (Menu menu : menuList) { + if (menu.getCommand().equals(command)) { + return Optional.of(menu); + } + } + return Optional.empty(); + } +} diff --git a/src/main/java/subway/screen/view/MenuEventManager.java b/src/main/java/subway/screen/view/MenuEventManager.java new file mode 100644 index 000000000..47134cb4b --- /dev/null +++ b/src/main/java/subway/screen/view/MenuEventManager.java @@ -0,0 +1,51 @@ +package subway.screen.view; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public final class MenuEventManager { + private static final String NOT_RECEIVED_MENU_MESSAGE = "메뉴λ₯Ό 전달받지 λͺ»ν•˜μ˜€μŠ΅λ‹ˆλ‹€."; + private static final String NOT_RECEIVED_HANDLER_MESSAGE = "이벀트 ν•Έλ“€λŸ¬λ₯Ό 전달받지 λͺ»ν•˜μ˜€μŠ΅λ‹ˆλ‹€."; + + private final Map handlerMap = new HashMap<>(); + + private MenuEventManager() { + } + + public static MenuEventManager builder() { + return new MenuEventManager(); + } + + public MenuEventManager addEventListener(Menu menu) { + return this.addEventListener(menu, () -> { + }); + } + + public MenuEventManager addEventListener(Menu menu, Runnable handler) { + this.validate(menu, handler); + this.handlerMap.put(menu, handler); + return this; + } + + private void validate(Menu menu, Runnable runnable) { + if (menu == null) { + throw new IllegalArgumentException(NOT_RECEIVED_MENU_MESSAGE); + } + if (runnable == null) { + throw new IllegalArgumentException(NOT_RECEIVED_HANDLER_MESSAGE); + } + } + + public void removeEventListener(Menu menu) { + this.handlerMap.remove(menu); + } + + public void removeAllEventListener() { + this.handlerMap.clear(); + } + + Optional select(Menu menu) { + return Optional.ofNullable(this.handlerMap.get(menu)); + } +} diff --git a/src/main/java/subway/screen/view/MenuView.java b/src/main/java/subway/screen/view/MenuView.java new file mode 100644 index 000000000..4e5559d55 --- /dev/null +++ b/src/main/java/subway/screen/view/MenuView.java @@ -0,0 +1,60 @@ +package subway.screen.view; + +import java.util.List; + +import subway.screen.ui.Console; + +public abstract class MenuView implements View { + private static final String NOT_RECEIVED_EVENT_MANAGER_MESSAGE = "이벀트 κ΄€λ¦¬μžλ₯Ό 전달받지 λͺ»ν•˜μ˜€μŠ΅λ‹ˆλ‹€."; + private static final String MENU_OUTPUT_FORMAT = "%s. %s"; + private static final String CAN_NOT_SELECTED_FUNCTION_MESSAGE = "선택할 수 μ—†λŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€."; + private static final String CAN_NOT_PROCESS_EVENT_HANDLER_MESSAGE = "이벀트λ₯Ό μ²˜λ¦¬ν•  수 μ—†μŠ΅λ‹ˆλ‹€."; + + private final MenuEventManager menuEventManager; + + protected MenuView() { + this(MenuEventManager.builder()); + } + + protected MenuView(MenuEventManager menuEventManager) { + if (menuEventManager == null) { + throw new IllegalArgumentException(NOT_RECEIVED_EVENT_MANAGER_MESSAGE); + } + this.menuEventManager = menuEventManager; + } + + protected abstract Class getType(); + + @Override + public void show() { + Console.printHeader(this.title()); + List menuList = Menu.findAll(this.getType()); + for (Menu menu : menuList) { + Console.println(String.format(MENU_OUTPUT_FORMAT, menu.getCommand(), menu.getName())); + } + Console.println(); + } + + public Menu question() { + do { + Console.printHeader("μ›ν•˜λŠ” κΈ°λŠ₯을 μ„ νƒν•˜μ„Έμš”."); + String command = Console.readline(); + try { + Menu menu = Menu.findByCommand(this.getType(), command) + .orElseThrow(() -> new IllegalArgumentException(CAN_NOT_SELECTED_FUNCTION_MESSAGE)); + Console.println(); + return menu; + } catch (IllegalArgumentException e) { + Console.println(); + Console.printError(e.getMessage()); + Console.println(); + } + } while (true); + } + + public void onEvent(Menu menu) { + Runnable handler = this.menuEventManager.select(menu) + .orElseThrow(() -> new IllegalArgumentException(CAN_NOT_PROCESS_EVENT_HANDLER_MESSAGE)); + handler.run(); + } +} diff --git a/src/main/java/subway/screen/view/View.java b/src/main/java/subway/screen/view/View.java new file mode 100644 index 000000000..ab128c2c5 --- /dev/null +++ b/src/main/java/subway/screen/view/View.java @@ -0,0 +1,6 @@ +package subway.screen.view; + +public interface View { + String title(); + void show(); +} diff --git a/src/main/java/subway/screen/view/main/MainMenu.java b/src/main/java/subway/screen/view/main/MainMenu.java new file mode 100644 index 000000000..ffcefde92 --- /dev/null +++ b/src/main/java/subway/screen/view/main/MainMenu.java @@ -0,0 +1,25 @@ +package subway.screen.view.main; + +import subway.screen.view.Menu; + +public enum MainMenu implements Menu { + PATH_SEARCH("1", "경둜 쑰회"), END("Q", "μ’…λ£Œ"); + + private final String command; + private final String name; + + MainMenu(String command, String name) { + this.command = command; + this.name = name; + } + + @Override + public String getCommand() { + return this.command; + } + + @Override + public String getName() { + return this.name; + } +} diff --git a/src/main/java/subway/screen/view/main/MainMenuView.java b/src/main/java/subway/screen/view/main/MainMenuView.java new file mode 100644 index 000000000..94fe97c2f --- /dev/null +++ b/src/main/java/subway/screen/view/main/MainMenuView.java @@ -0,0 +1,24 @@ +package subway.screen.view.main; + +import subway.presentation.MainViewController; +import subway.screen.view.Menu; +import subway.screen.view.MenuEventManager; +import subway.screen.view.MenuView; + +public class MainMenuView extends MenuView { + public MainMenuView(MainViewController mainViewController) { + super(MenuEventManager.builder() + .addEventListener(MainMenu.PATH_SEARCH, mainViewController::handlePathSearch) + .addEventListener(MainMenu.END, mainViewController::handleEnd)); + } + + @Override + public String title() { + return "메인 ν™”λ©΄"; + } + + @Override + protected Class getType() { + return MainMenu.class; + } +} diff --git a/src/main/java/subway/screen/view/path/PathMenu.java b/src/main/java/subway/screen/view/path/PathMenu.java new file mode 100644 index 000000000..c7f160db1 --- /dev/null +++ b/src/main/java/subway/screen/view/path/PathMenu.java @@ -0,0 +1,27 @@ +package subway.screen.view.path; + +import subway.screen.view.Menu; + +public enum PathMenu implements Menu { + SHORT_DISTANCE("1", "μ΅œλ‹¨ 거리"), + SHORT_TIME("2", "μ΅œμ†Œ μ‹œκ°„"), + BACK("B", "λŒμ•„κ°€κΈ°"); + + private final String command; + private final String name; + + PathMenu(String command, String name) { + this.command = command; + this.name = name; + } + + @Override + public String getCommand() { + return this.command; + } + + @Override + public String getName() { + return this.name; + } +} diff --git a/src/main/java/subway/screen/view/path/PathMenuView.java b/src/main/java/subway/screen/view/path/PathMenuView.java new file mode 100644 index 000000000..5ff4d048a --- /dev/null +++ b/src/main/java/subway/screen/view/path/PathMenuView.java @@ -0,0 +1,25 @@ +package subway.screen.view.path; + +import subway.presentation.PathViewController; +import subway.screen.view.Menu; +import subway.screen.view.MenuEventManager; +import subway.screen.view.MenuView; + +public class PathMenuView extends MenuView { + public PathMenuView(PathViewController pathViewController) { + super(MenuEventManager.builder() + .addEventListener(PathMenu.SHORT_DISTANCE, pathViewController::handleShortDistance) + .addEventListener(PathMenu.SHORT_TIME, pathViewController::handleShortTime) + .addEventListener(PathMenu.BACK)); + } + + @Override + public String title() { + return "경둜 κΈ°μ€€"; + } + + @Override + protected Class getType() { + return PathMenu.class; + } +} diff --git a/src/main/java/subway/util/Validation.java b/src/main/java/subway/util/Validation.java new file mode 100644 index 000000000..1b265efe2 --- /dev/null +++ b/src/main/java/subway/util/Validation.java @@ -0,0 +1,12 @@ +package subway.util; + +import java.util.regex.Pattern; + +public final class Validation { + private Validation() { + } + + public static boolean isNumeric(String str) { + return str != null && !str.isEmpty() && Pattern.matches("^[0-9]*$", str); + } +} diff --git a/src/main/resources/line.xml b/src/main/resources/line.xml new file mode 100644 index 000000000..d5b666d7e --- /dev/null +++ b/src/main/resources/line.xml @@ -0,0 +1,12 @@ + + + + 2ν˜Έμ„  + + + 3ν˜Έμ„  + + + μ‹ λΆ„λ‹Ήμ„  + + \ No newline at end of file diff --git a/src/main/resources/section.xml b/src/main/resources/section.xml new file mode 100644 index 000000000..5cbce3f01 --- /dev/null +++ b/src/main/resources/section.xml @@ -0,0 +1,188 @@ + + + +
+ + 2ν˜Έμ„  + + + κ΅λŒ€μ—­ + + + 강남역 + + 2 + +
+
+ + 2ν˜Έμ„  + + + 강남역 + + + κ΅λŒ€μ—­ + + 2 + +
+
+ + 2ν˜Έμ„  + + + 강남역 + + + μ—­μ‚Όμ—­ + + 2 + +
+
+ + 2ν˜Έμ„  + + + μ—­μ‚Όμ—­ + + + 강남역 + + 2 + +
+ +
+ + 3ν˜Έμ„  + + + κ΅λŒ€μ—­ + + + 남뢀터미널역 + + 3 + +
+
+ + 3ν˜Έμ„  + + + 남뢀터미널역 + + + κ΅λŒ€μ—­ + + 3 + +
+
+ + 3ν˜Έμ„  + + + 남뢀터미널역 + + + μ–‘μž¬μ—­ + + 6 + +
+
+ + 3ν˜Έμ„  + + + μ–‘μž¬μ—­ + + + 남뢀터미널역 + + 6 + +
+
+ + 3ν˜Έμ„  + + + μ–‘μž¬μ—­ + + + 맀봉역 + + 1 + +
+
+ + 3ν˜Έμ„  + + + 맀봉역 + + + μ–‘μž¬μ—­ + + 1 + +
+ +
+ + μ‹ λΆ„λ‹Ήμ„  + + + 강남역 + + + μ–‘μž¬μ—­ + + 2 + +
+
+ + μ‹ λΆ„λ‹Ήμ„  + + + μ–‘μž¬μ—­ + + + 강남역 + + 2 + +
+
+ + μ‹ λΆ„λ‹Ήμ„  + + + μ–‘μž¬μ—­ + + + μ–‘μž¬μ‹œλ―Όμ˜μˆ²μ—­ + + 10 + +
+
+ + μ‹ λΆ„λ‹Ήμ„  + + + μ–‘μž¬μ‹œλ―Όμ˜μˆ²μ—­ + + + μ–‘μž¬μ—­ + + 10 + +
+
\ No newline at end of file diff --git a/src/main/resources/station.xml b/src/main/resources/station.xml new file mode 100644 index 000000000..d9ae4c9a1 --- /dev/null +++ b/src/main/resources/station.xml @@ -0,0 +1,24 @@ + + + + κ΅λŒ€μ—­ + + + 강남역 + + + μ—­μ‚Όμ—­ + + + 남뢀터미널역 + + + μ–‘μž¬μ—­ + + + μ–‘μž¬μ‹œλ―Όμ˜μˆ²μ—­ + + + 맀봉역 + + \ No newline at end of file diff --git a/src/test/java/subway/application/section/dto/SectionDTOTest.java b/src/test/java/subway/application/section/dto/SectionDTOTest.java new file mode 100644 index 000000000..9c2814e76 --- /dev/null +++ b/src/test/java/subway/application/section/dto/SectionDTOTest.java @@ -0,0 +1,37 @@ +package subway.application.section.dto; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import subway.domain.line.LineDTO; +import subway.domain.station.StationDTO; + +public class SectionDTOTest { + @Test + public void constructor__SectionAddingLineInfoEssentialException() { + StationDTO sourceDTO = new StationDTO("source"); + StationDTO sinkDTO = new StationDTO("sink"); + String message = "ꡬ간 μƒμ„±μ‹œ λ…Έμ„  μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new SectionDTO(null, sourceDTO, sinkDTO)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void constructor__SectionAddingSourceStationInfoEssentialException() { + LineDTO lineDTO = new LineDTO("line"); + StationDTO sinkDTO = new StationDTO("sink"); + String message = "ꡬ간 μƒμ„±μ‹œ μ‹œμž‘μ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new SectionDTO(lineDTO, null, sinkDTO)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void constructor__SectionAddingSinkStationInfoEssentialException() { + LineDTO lineDTO = new LineDTO("line"); + StationDTO sourceDTO = new StationDTO("source"); + String message = "ꡬ간 μƒμ„±μ‹œ μ’…λ£Œμ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new SectionDTO(lineDTO, sourceDTO, null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} diff --git a/src/test/java/subway/application/section/dto/ShortCostRequestTest.java b/src/test/java/subway/application/section/dto/ShortCostRequestTest.java new file mode 100644 index 000000000..d647e1cf0 --- /dev/null +++ b/src/test/java/subway/application/section/dto/ShortCostRequestTest.java @@ -0,0 +1,34 @@ +package subway.application.section.dto; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import subway.domain.station.StationDTO; + +public class ShortCostRequestTest { + @Test + public void constructor__SourceStationInfoEssentialException() { + StationDTO sinkDTO = new StationDTO("sink"); + String message = "μ‹œμž‘μ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new ShortCostRequest(null, sinkDTO)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void constructor__SinkStationInfoEssentialException() { + StationDTO sourceDTO = new StationDTO("source"); + String message = "μ’…λ£Œμ—­ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new ShortCostRequest(sourceDTO, null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void constructor__SameSourceAndSinkStationException() { + StationDTO sourceDTO = new StationDTO("same"); + StationDTO sinkDTO = new StationDTO("same"); + String message = "μΆœλ°œμ—­κ³Ό 도착역이 λ™μΌν•©λ‹ˆλ‹€."; + assertThatThrownBy(() -> new ShortCostRequest(sourceDTO, sinkDTO)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} diff --git a/src/test/java/subway/application/section/dto/ShortCostResponseTest.java b/src/test/java/subway/application/section/dto/ShortCostResponseTest.java new file mode 100644 index 000000000..d58b355b9 --- /dev/null +++ b/src/test/java/subway/application/section/dto/ShortCostResponseTest.java @@ -0,0 +1,30 @@ +package subway.application.section.dto; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import subway.domain.line.Line; +import subway.domain.section.Section; +import subway.domain.station.Station; + +public class ShortCostResponseTest { + @Test + public void constructor() { + Line line = new Line("line"); + Station source = new Station("source"); + Station sink = new Station("sink"); + List stationList = Arrays.asList(source, sink); + int distance = 1; + int time = 8; + Section section = new Section(line, source, sink, distance, time); + source.addSection(section); + ShortCostResponse shortCostResponse = new ShortCostResponse(stationList); + assertThat(shortCostResponse.getTotalDistance()).isEqualTo(distance); + assertThat(shortCostResponse.getTotalTime()).isEqualTo(time); + assertThat(shortCostResponse.getStationNameList()).containsExactly(source.getName(), sink.getName()); + } +} diff --git a/src/test/java/subway/application/section/service/SectionServiceTest.java b/src/test/java/subway/application/section/service/SectionServiceTest.java new file mode 100644 index 000000000..2ee1f96fd --- /dev/null +++ b/src/test/java/subway/application/section/service/SectionServiceTest.java @@ -0,0 +1,117 @@ +package subway.application.section.service; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import subway.application.section.dto.SectionDTO; +import subway.application.section.dto.ShortCostRequest; +import subway.application.section.dto.ShortCostResponse; +import subway.domain.line.Line; +import subway.domain.line.LineDTO; +import subway.domain.line.LineService; +import subway.domain.station.Station; +import subway.domain.station.StationDTO; +import subway.domain.station.StationService; + +public class SectionServiceTest { + private final LineDTO lineDTO = new LineDTO("line"); + private final StationDTO sourceDTO = new StationDTO("source"); + private final StationDTO sinkDTO = new StationDTO("sink"); + + private final LineService lineService = new LineService(); + private final StationService stationService = new StationService(); + private final SectionService sectionService = new SectionService(); + private final ShortDistanceService shortDistanceService = new ShortDistanceService(); + private final ShortTimeService shortTimeService = new ShortTimeService(); + + @BeforeEach + public void setup() { + this.lineService.addLine(lineDTO); + this.stationService.addStation(sourceDTO); + this.stationService.addStation(sinkDTO); + } + + @Test + public void addSection() { + Line line = this.lineService.findOneByName(lineDTO.getName()); + Station source = this.stationService.findOneByName(sourceDTO.getName()); + SectionDTO sectionDTO = new SectionDTO(lineDTO, sourceDTO, sinkDTO); + assertThat(this.sectionService.findAll()).hasSize(0); + assertThat(line.getSectionList()).hasSize(0); + assertThat(source.getSectionList()).hasSize(0); + this.sectionService.addNode(this.sourceDTO); + this.sectionService.addNode(this.sinkDTO); + this.sectionService.addSection(sectionDTO); + assertThat(this.sectionService.findAll()).hasSize(1); + assertThat(line.getSectionList()).hasSize(1); + assertThat(source.getSectionList()).hasSize(1); + } + + @Test + public void addSection__AlreadyExistsSectionException() { + SectionDTO sectionDTO = new SectionDTO(lineDTO, sourceDTO, sinkDTO); + String message = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” κ΅¬κ°„μž…λ‹ˆλ‹€."; + this.sectionService.addNode(this.sourceDTO); + this.sectionService.addNode(this.sinkDTO); + this.sectionService.addSection(sectionDTO); + assertThatThrownBy(() -> this.sectionService.addSection(sectionDTO)).isInstanceOf( + IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void computeShortDistance() { + int distance = 1; + int time = 8; + SectionDTO sectionDTO = new SectionDTO(this.lineDTO, this.sourceDTO, this.sinkDTO, distance, time); + ShortCostRequest shortCostRequest = new ShortCostRequest(this.sourceDTO, this.sinkDTO); + this.sectionService.addNode(this.sourceDTO); + this.sectionService.addNode(this.sinkDTO); + this.sectionService.addSection(sectionDTO); + ShortCostResponse shortCostResponse = this.sectionService.computeShortDistance(shortCostRequest); + assertThat(shortCostResponse.getTotalDistance()).isEqualTo(distance); + assertThat(shortCostResponse.getTotalTime()).isEqualTo(time); + assertThat(shortCostResponse.getStationNameList()).containsExactly(this.sourceDTO.getName(), + this.sinkDTO.getName()); + } + + @Test + public void computeShortDistance__NotExistsSourceStationException() { + StationDTO otherDTO = new StationDTO("other"); + ShortCostRequest shortCostRequest = new ShortCostRequest(otherDTO, this.sinkDTO); + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ‹œμž‘μ—­μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> this.sectionService.computeShortDistance(shortCostRequest)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void computeShortDistance__NotExistsSinkStationException() { + StationDTO otherDTO = new StationDTO("other"); + ShortCostRequest shortCostRequest = new ShortCostRequest(this.sourceDTO, otherDTO); + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ’…λ£Œμ—­μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> this.sectionService.computeShortDistance(shortCostRequest)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void computeShortDistance__NotConnectedSourceAndSinkStationException() { + ShortCostRequest shortCostRequest = new ShortCostRequest(this.sourceDTO, this.sinkDTO); + String message = "μ‹œμž‘ 지점과 μ’…λ£Œμ—­μ΄ μ—°κ²°λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + this.sectionService.addNode(this.sourceDTO); + this.sectionService.addNode(this.sinkDTO); + assertThatThrownBy(() -> this.sectionService.computeShortDistance(shortCostRequest)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @AfterEach + public void init() { + this.lineService.deleteAll(); + this.stationService.deleteAll(); + this.sectionService.deleteAll(); + this.shortDistanceService.deleteAll(); + this.shortTimeService.deleteAll(); + } +} diff --git a/src/test/java/subway/application/section/service/ShortDistanceServiceTest.java b/src/test/java/subway/application/section/service/ShortDistanceServiceTest.java new file mode 100644 index 000000000..e80441ad4 --- /dev/null +++ b/src/test/java/subway/application/section/service/ShortDistanceServiceTest.java @@ -0,0 +1,107 @@ +package subway.application.section.service; + +import static org.assertj.core.api.Assertions.*; + +import org.jgrapht.GraphPath; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import subway.domain.line.Line; +import subway.domain.section.Section; +import subway.domain.station.Station; + +public class ShortDistanceServiceTest { + private final Line line = new Line("line"); + private final Station source = new Station("source"); + private final Station sink = new Station("sink"); + + private final ShortDistanceService shortDistanceService = new ShortDistanceService(); + + @Test + public void addNode() { + assertThat(this.shortDistanceService.findAllNode()).hasSize(0); + this.shortDistanceService.addNode(this.source); + assertThat(this.shortDistanceService.findAllNode()).hasSize(1); + } + + @Test + public void addNode_AlreadyExistsNodeException() { + String message = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” λ…Έλ“œμž…λ‹ˆλ‹€."; + this.shortDistanceService.addNode(this.source); + assertThatThrownBy(() -> this.shortDistanceService.addNode(this.source)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addEdge() { + Section section = new Section(this.line, this.source, this.sink, 0, 0); + this.shortDistanceService.addNode(this.source); + this.shortDistanceService.addNode(this.sink); + assertThat(this.shortDistanceService.findAllEdge()).hasSize(0); + this.shortDistanceService.addEdge(section); + assertThat(this.shortDistanceService.findAllEdge()).hasSize(1); + } + + @Test + public void addEdge_NotExistsSourceNodeException() { + Section section = new Section(this.line, this.source, this.sink, 0, 0); + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ‹œμž‘λ…Έλ“œμž…λ‹ˆλ‹€."; + this.shortDistanceService.addNode(this.sink); + assertThatThrownBy(() -> this.shortDistanceService.addEdge(section)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addEdge_NotExistsSinkNodeException() { + Section section = new Section(this.line, this.source, this.sink, 0, 0); + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ’…λ£Œλ…Έλ“œμž…λ‹ˆλ‹€."; + this.shortDistanceService.addNode(this.source); + assertThatThrownBy(() -> this.shortDistanceService.addEdge(section)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addEdge_AlreadyExistsEdgeException() { + Section section = new Section(this.line, this.source, this.sink, 0, 0); + String message = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” κ°„μ„ μž…λ‹ˆλ‹€."; + this.shortDistanceService.addNode(this.source); + this.shortDistanceService.addNode(this.sink); + this.shortDistanceService.addEdge(section); + assertThatThrownBy(() -> this.shortDistanceService.addEdge(section)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void compute() { + int distance = 7; + Section section = new Section(this.line, this.source, this.sink, distance, 0); + this.shortDistanceService.addNode(this.source); + this.shortDistanceService.addNode(this.sink); + this.shortDistanceService.addEdge(section); + GraphPath graphPath = this.shortDistanceService.compute(this.source, this.sink); + assertThat(graphPath.getWeight()).isEqualTo(distance); + assertThat(graphPath.getVertexList()).containsExactly(this.source, this.sink); + } + + @Test + public void compute__NotExistsSourceNodeException() { + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ‹œμž‘λ…Έλ“œμž…λ‹ˆλ‹€."; + this.shortDistanceService.addNode(this.sink); + assertThatThrownBy(() -> this.shortDistanceService.compute(this.source, this.sink)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void compute__NotExistsSinkNodeException() { + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ’…λ£Œλ…Έλ“œμž…λ‹ˆλ‹€."; + this.shortDistanceService.addNode(this.source); + assertThatThrownBy(() -> this.shortDistanceService.compute(this.source, this.sink)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @AfterEach + public void init() { + this.shortDistanceService.deleteAll(); + } +} diff --git a/src/test/java/subway/domain/line/LineDTOTest.java b/src/test/java/subway/domain/line/LineDTOTest.java new file mode 100644 index 000000000..6cdc8fe0d --- /dev/null +++ b/src/test/java/subway/domain/line/LineDTOTest.java @@ -0,0 +1,16 @@ +package subway.domain.line; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class LineDTOTest { + @Test + public void constructor__LineNameEssentialException() { + String message = "λ…Έμ„  이름은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new LineDTO(null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + assertThatThrownBy(() -> new LineDTO("")).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} diff --git a/src/test/java/subway/domain/line/LineRepositoryTest.java b/src/test/java/subway/domain/line/LineRepositoryTest.java new file mode 100644 index 000000000..ec0a7acab --- /dev/null +++ b/src/test/java/subway/domain/line/LineRepositoryTest.java @@ -0,0 +1,30 @@ +package subway.domain.line; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +public class LineRepositoryTest { + @Test + public void exists() { + Line line = new Line("test"); + assertThat(LineRepository.exists(line)).isEqualTo(false); + LineRepository.addLine(line); + assertThat(LineRepository.exists(line)).isEqualTo(true); + } + + @Test + public void findByName() { + String name = "test"; + Line line = new Line(name); + assertThat(LineRepository.findByName(name)).isNotPresent(); + LineRepository.addLine(line); + assertThat(LineRepository.findByName(name)).isPresent(); + } + + @AfterEach + public void init() { + LineRepository.deleteAll(); + } +} diff --git a/src/test/java/subway/domain/line/LineServiceTest.java b/src/test/java/subway/domain/line/LineServiceTest.java new file mode 100644 index 000000000..93dacca95 --- /dev/null +++ b/src/test/java/subway/domain/line/LineServiceTest.java @@ -0,0 +1,48 @@ +package subway.domain.line; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +public class LineServiceTest { + private final LineService lineService = new LineService(); + + @Test + public void addLine() { + LineDTO lineDTO = new LineDTO("test"); + assertThat(this.lineService.findAll()).hasSize(0); + this.lineService.addLine(lineDTO); + assertThat(this.lineService.findAll()).hasSize(1); + } + + @Test + public void addLine__AlreadyExistsLineException() { + LineDTO lineDTO = new LineDTO("test"); + String message = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” λ…Έμ„ μž…λ‹ˆλ‹€."; + this.lineService.addLine(lineDTO); + assertThatThrownBy(() -> this.lineService.addLine(lineDTO)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void findOneByName() { + String name = "test"; + LineDTO lineDTO = new LineDTO(name); + this.lineService.addLine(lineDTO); + Line line = this.lineService.findOneByName(name); + assertThat(line.getName()).isEqualTo(name); + } + + @Test + public void findOneByName__NotExistsLineException() { + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ λ…Έμ„ μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> this.lineService.findOneByName("test")).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @AfterEach + public void init() { + this.lineService.deleteAll(); + } +} diff --git a/src/test/java/subway/domain/line/LineTest.java b/src/test/java/subway/domain/line/LineTest.java new file mode 100644 index 000000000..6b476159e --- /dev/null +++ b/src/test/java/subway/domain/line/LineTest.java @@ -0,0 +1,33 @@ +package subway.domain.line; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import subway.domain.section.Section; +import subway.domain.station.Station; + +public class LineTest { + @Test + public void addSection() { + Line line = new Line("line"); + Station source = new Station("source"); + Station sink = new Station("sink"); + Section section = new Section(line, source, sink, 0, 0); + assertThat(line.getSectionList()).hasSize(0); + line.addSection(section); + assertThat(line.getSectionList()).hasSize(1); + } + + @Test + public void addSection__SectionAddingOtherLineReceiveException() { + Line line = new Line("line"); + Line other = new Line("other"); + Station source = new Station("source"); + Station sink = new Station("sink"); + Section section = new Section(other, source, sink, 0, 0); + String message = "ꡬ간 μΆ”κ°€μ‹œ λ‹€λ₯Έ 노선을 λ°›μ•˜μŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> line.addSection(section)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} diff --git a/src/test/java/subway/domain/section/SectionRepositoryTest.java b/src/test/java/subway/domain/section/SectionRepositoryTest.java new file mode 100644 index 000000000..952a407f4 --- /dev/null +++ b/src/test/java/subway/domain/section/SectionRepositoryTest.java @@ -0,0 +1,27 @@ +package subway.domain.section; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import subway.domain.line.Line; +import subway.domain.station.Station; + +public class SectionRepositoryTest { + @Test + public void exists() { + Line line = new Line("line"); + Station source = new Station("source"); + Station sink = new Station("sink"); + Section section = new Section(line, source, sink, 0, 0); + assertThat(SectionRepository.exists(section)).isEqualTo(false); + SectionRepository.addSection(section); + assertThat(SectionRepository.exists(section)).isEqualTo(true); + } + + @AfterEach + public void init() { + SectionRepository.deleteAll(); + } +} diff --git a/src/test/java/subway/domain/station/StationDTOTest.java b/src/test/java/subway/domain/station/StationDTOTest.java new file mode 100644 index 000000000..76ea8b460 --- /dev/null +++ b/src/test/java/subway/domain/station/StationDTOTest.java @@ -0,0 +1,16 @@ +package subway.domain.station; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class StationDTOTest { + @Test + public void constructor__StationNameEssentialException() { + String message = "μ—­ 이름은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> new StationDTO(null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + assertThatThrownBy(() -> new StationDTO("")).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} diff --git a/src/test/java/subway/domain/station/StationRepositoryTest.java b/src/test/java/subway/domain/station/StationRepositoryTest.java new file mode 100644 index 000000000..207331ba5 --- /dev/null +++ b/src/test/java/subway/domain/station/StationRepositoryTest.java @@ -0,0 +1,30 @@ +package subway.domain.station; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +public class StationRepositoryTest { + @Test + public void exists() { + Station station = new Station("test"); + assertThat(StationRepository.exists(station)).isEqualTo(false); + StationRepository.addStation(station); + assertThat(StationRepository.exists(station)).isEqualTo(true); + } + + @Test + public void findByName() { + String name = "test"; + Station station = new Station(name); + assertThat(StationRepository.findByName(name)).isNotPresent(); + StationRepository.addStation(station); + assertThat(StationRepository.findByName(name)).isPresent(); + } + + @AfterEach + public void init() { + StationRepository.deleteAll(); + } +} diff --git a/src/test/java/subway/domain/station/StationServiceTest.java b/src/test/java/subway/domain/station/StationServiceTest.java new file mode 100644 index 000000000..b59807fe7 --- /dev/null +++ b/src/test/java/subway/domain/station/StationServiceTest.java @@ -0,0 +1,48 @@ +package subway.domain.station; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +public class StationServiceTest { + private final StationService stationService = new StationService(); + + @Test + public void addStation() { + StationDTO stationDTO = new StationDTO("test"); + assertThat(this.stationService.findAll()).hasSize(0); + this.stationService.addStation(stationDTO); + assertThat(this.stationService.findAll()).hasSize(1); + } + + @Test + public void addStation__AlreadyExistsStationException() { + StationDTO stationDTO = new StationDTO("test"); + String message = "이미 λ“±λ‘λ˜μ–΄μžˆλŠ” μ—­μž…λ‹ˆλ‹€."; + this.stationService.addStation(stationDTO); + assertThatThrownBy(() -> this.stationService.addStation(stationDTO)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void findOneByName() { + String name = "test"; + StationDTO stationDTO = new StationDTO(name); + this.stationService.addStation(stationDTO); + Station station = this.stationService.findOneByName(name); + assertThat(station.getName()).isEqualTo(name); + } + + @Test + public void findOneByName__NotExistsStationException() { + String message = "μ‘΄μž¬ν•˜μ§€ μ•Šμ€ μ—­μž…λ‹ˆλ‹€."; + assertThatThrownBy(() -> this.stationService.findOneByName("test")).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @AfterEach + public void init() { + this.stationService.deleteAll(); + } +} diff --git a/src/test/java/subway/domain/station/StationTest.java b/src/test/java/subway/domain/station/StationTest.java new file mode 100644 index 000000000..a4df622b7 --- /dev/null +++ b/src/test/java/subway/domain/station/StationTest.java @@ -0,0 +1,73 @@ +package subway.domain.station; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import subway.domain.line.Line; +import subway.domain.section.Section; + +public class StationTest { + @Test + public void addSection() { + Line line = new Line("line"); + Station source = new Station("source"); + Station sink = new Station("sink"); + Section section = new Section(line, source, sink, 0, 0); + assertThat(source.getSectionList()).hasSize(0); + source.addSection(section); + assertThat(source.getSectionList()).hasSize(1); + } + + @Test + public void addSection__SectionAddingOtherSourceStationReceiveException() { + Line line = new Line("line"); + Station source = new Station("source"); + Station other = new Station("other"); + Station sink = new Station("sink"); + Section section = new Section(line, other, sink, 0, 0); + String message = "ꡬ간 μΆ”κ°€μ‹œ λ‹€λ₯Έ μ‹œμž‘μ—­μ„ λ°›μ•˜μŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> source.addSection(section)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void findDistanceTo() { + Line line = new Line("line"); + Station source = new Station("source"); + Station sink = new Station("sink"); + int distance = 1; + Section section = new Section(line, source, sink, distance, 0); + source.addSection(section); + assertThat(source.findDistanceTo(sink)).isEqualTo(distance); + } + + @Test + public void findDistanceTo__NotExistsPathToSinkStationException() { + Station source = new Station("source"); + Station sink = new Station("sink"); + String message = "μ’…λ£Œμ—­κΉŒμ§€ κ²½λ‘œκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> source.findDistanceTo(sink)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void findTimeTo() { + Line line = new Line("line"); + Station source = new Station("source"); + Station sink = new Station("sink"); + int time = 8; + Section section = new Section(line, source, sink, 0, time); + source.addSection(section); + assertThat(source.findTimeTo(sink)).isEqualTo(time); + } + + @Test + public void findTimeTo__NotExistsPathToSinkStationException() { + Station source = new Station("source"); + Station sink = new Station("sink"); + String message = "μ’…λ£Œμ—­κΉŒμ§€ κ²½λ‘œκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> source.findTimeTo(sink)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } +} diff --git a/src/test/java/subway/infrastructure/FileParserTest.java b/src/test/java/subway/infrastructure/FileParserTest.java new file mode 100644 index 000000000..c0427802d --- /dev/null +++ b/src/test/java/subway/infrastructure/FileParserTest.java @@ -0,0 +1,75 @@ +package subway.infrastructure; + +import static org.assertj.core.api.Assertions.*; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class FileParserTest { + private File file; + private File directory; + + @BeforeEach + public void setup() { + this.directory = new File("./dummy"); + this.file = new File("./dummy.txt"); + try { + this.directory.mkdir(); + this.file.createNewFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static class TestFileParser extends FileParser { + public TestFileParser(File file) { + super(file); + } + + @Override + public boolean allowExtension(String extension) { + return "java".equals(extension); + } + + @Override + public List> parser() { + return null; + } + } + + @Test + public void constructor__NotExistsFileException() { + File none = new File("./none.txt"); + String message = "파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> new TestFileParser(null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + assertThatThrownBy(() -> new TestFileParser(none)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void constructor__NotReceivedFileTypeException() { + String message = "파일 μœ ν˜•μ΄ μ•„λ‹™λ‹ˆλ‹€."; + assertThatThrownBy(() -> new TestFileParser(this.directory)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void constructor__NotAllowedFileExtensionException() { + String message = "ν—ˆμš©λœ 파일 ν™•μž₯μžκ°€ μ•„λ‹™λ‹ˆλ‹€."; + assertThatThrownBy(() -> new TestFileParser(this.file)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @AfterEach + public void init() { + this.file.delete(); + this.directory.delete(); + } +} diff --git a/src/test/java/subway/infrastructure/XmlFileParserTest.java b/src/test/java/subway/infrastructure/XmlFileParserTest.java new file mode 100644 index 000000000..4177b2c02 --- /dev/null +++ b/src/test/java/subway/infrastructure/XmlFileParserTest.java @@ -0,0 +1,57 @@ +package subway.infrastructure; + +import static org.assertj.core.api.Assertions.*; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class XmlFileParserTest { + private static final String content = "\n" + + "\n" + + " \n" + + " name\n" + + " \n" + + " name\n" + + " \n" + + " \n" + + "\n"; + private static File file; + + @BeforeAll + public static void setup() { + file = new File("file.xml"); + try { + file.createNewFile(); + try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file))) { + bufferedWriter.write(content); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public void parser() { + XmlFileParser xmlFileParser = new XmlFileParser(file); + List> dataList = xmlFileParser.parser(); + assertThat(dataList).hasSize(1); + Map data = dataList.get(0); + assertThat(data.get("name")).isInstanceOf(String.class).isEqualTo("name"); + assertThat(data.get("job")).isInstanceOf(Map.class); + Map job = (Map)data.get("job"); + assertThat(job.get("name")).isInstanceOf(String.class).isEqualTo("name"); + } + + @AfterAll + public static void init() { + file.delete(); + } +} diff --git a/src/test/java/subway/presentation/SectionControllerTest.java b/src/test/java/subway/presentation/SectionControllerTest.java new file mode 100644 index 000000000..17dc29dc5 --- /dev/null +++ b/src/test/java/subway/presentation/SectionControllerTest.java @@ -0,0 +1,63 @@ +package subway.presentation; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class SectionControllerTest { + private final SectionController sectionController = new SectionController(); + + @Test + public void addSection__InputEssentialDistanceException() { + String lineName = "line"; + String sourceName = "source"; + String sinkName = "sink"; + String time = "0"; + String message = "거리 μž…λ ₯은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy( + () -> this.sectionController.addSection(lineName, sourceName, sinkName, null, time)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + assertThatThrownBy( + () -> this.sectionController.addSection(lineName, sourceName, sinkName, "", time)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addSection__OnlyPossibleNumericInputDistanceException() { + String lineName = "line"; + String sourceName = "source"; + String sinkName = "sink"; + String time = "0"; + String message = "κ±°λ¦¬λŠ” 숫자 ν˜•μ‹μ˜ μž…λ ₯만 κ°€λŠ₯ν•©λ‹ˆλ‹€."; + assertThatThrownBy( + () -> this.sectionController.addSection(lineName, sourceName, sinkName, "distance", time)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addSection__InputEssentialTimeException() { + String lineName = "line"; + String sourceName = "source"; + String sinkName = "sink"; + String distance = "0"; + String message = "μ‹œκ°„ μž…λ ₯은 ν•„μˆ˜μž…λ‹ˆλ‹€."; + assertThatThrownBy( + () -> this.sectionController.addSection(lineName, sourceName, sinkName, distance, null)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + assertThatThrownBy( + () -> this.sectionController.addSection(lineName, sourceName, sinkName, distance, "")).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addSection__OnlyPossibleNumericInputTimeException() { + String lineName = "line"; + String sourceName = "source"; + String sinkName = "sink"; + String distance = "0"; + String message = "μ‹œκ°„μ€ 숫자 ν˜•μ‹μ˜ μž…λ ₯만 κ°€λŠ₯ν•©λ‹ˆλ‹€."; + assertThatThrownBy( + () -> this.sectionController.addSection(lineName, sourceName, sinkName, distance, "time")).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } +} diff --git a/src/test/java/subway/screen/view/MenuEventManagerTest.java b/src/test/java/subway/screen/view/MenuEventManagerTest.java new file mode 100644 index 000000000..0c01ab575 --- /dev/null +++ b/src/test/java/subway/screen/view/MenuEventManagerTest.java @@ -0,0 +1,53 @@ +package subway.screen.view; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class MenuEventManagerTest { + private enum EnumMenu implements Menu { + ITEM("command", "name"), NONE("", ""); + + private final String command; + private final String name; + + EnumMenu(String command, String name) { + this.command = command; + this.name = name; + } + + @Override + public String getCommand() { + return this.command; + } + + @Override + public String getName() { + return this.name; + } + } + + @Test + public void addEventListener__NotReceivedMenuException() { + MenuEventManager menuEventManager = MenuEventManager.builder(); + String message = "메뉴λ₯Ό 전달받지 λͺ»ν•˜μ˜€μŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> menuEventManager.addEventListener(null, () -> { + })).isInstanceOf(IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void addEventListener__NotReceivedHandlerException() { + MenuEventManager menuEventManager = MenuEventManager.builder(); + String message = "이벀트 ν•Έλ“€λŸ¬λ₯Ό 전달받지 λͺ»ν•˜μ˜€μŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> menuEventManager.addEventListener(EnumMenu.ITEM, null)).isInstanceOf( + IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void select() { + MenuEventManager menuEventManager = MenuEventManager.builder().addEventListener(EnumMenu.ITEM, () -> { + }); + assertThat(menuEventManager.select(EnumMenu.NONE)).isNotPresent(); + assertThat(menuEventManager.select(EnumMenu.ITEM)).isPresent(); + } +} diff --git a/src/test/java/subway/screen/view/MenuTest.java b/src/test/java/subway/screen/view/MenuTest.java new file mode 100644 index 000000000..755ddd6cd --- /dev/null +++ b/src/test/java/subway/screen/view/MenuTest.java @@ -0,0 +1,61 @@ +package subway.screen.view; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class MenuTest { + private static class ClassMenu implements Menu { + @Override + public String getCommand() { + return ""; + } + + @Override + public String getName() { + return ""; + } + } + + private enum EnumMenu implements Menu { + ITEM("command", "name"); + + private final String command; + private final String name; + + EnumMenu(String command, String name) { + this.command = command; + this.name = name; + } + + @Override + public String getCommand() { + return this.command; + } + + @Override + public String getName() { + return this.name; + } + } + + @Test + public void findAll() { + EnumMenu[] enumMenus = EnumMenu.values(); + assertThat(Menu.findAll(EnumMenu.class)).containsExactly(enumMenus); + } + + @Test + public void findAll__NotEnumTypeClassException() { + String message = "Enum ν˜•μ‹μ˜ ν΄λž˜μŠ€κ°€ μ•„λ‹™λ‹ˆλ‹€."; + assertThatThrownBy(() -> Menu.findAll(ClassMenu.class)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @Test + public void findByCommand() { + EnumMenu item = EnumMenu.ITEM; + assertThat(Menu.findByCommand(EnumMenu.class, "none")).isNotPresent(); + assertThat(Menu.findByCommand(EnumMenu.class, item.getCommand())).isPresent(); + } +} diff --git a/src/test/java/subway/screen/view/MenuViewTest.java b/src/test/java/subway/screen/view/MenuViewTest.java new file mode 100644 index 000000000..111ddd4ad --- /dev/null +++ b/src/test/java/subway/screen/view/MenuViewTest.java @@ -0,0 +1,142 @@ +package subway.screen.view; + +import static org.assertj.core.api.Assertions.*; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import subway.screen.ui.Console; + +public class MenuViewTest { + private MockedStatic mockConsole; + + private enum TestMenu implements Menu { + ITEM("command", "name"); + + private final String command; + private final String name; + + TestMenu(String command, String name) { + this.command = command; + this.name = name; + } + + @Override + public String getCommand() { + return this.command; + } + + @Override + public String getName() { + return this.name; + } + } + + private static class TestMenuView extends MenuView { + public TestMenuView() { + } + + public TestMenuView(MenuEventManager menuEventManager) { + super(menuEventManager); + } + + @Override + protected Class getType() { + return TestMenu.class; + } + + @Override + public String title() { + return "ν…ŒμŠ€νŠΈ ν™”λ©΄"; + } + } + + @BeforeEach + public void setup() { + mockConsole = Mockito.mockStatic(Console.class); + } + + @Test + public void constructor__NotReceivedEventManagerException() { + String message = "이벀트 κ΄€λ¦¬μžλ₯Ό 전달받지 λͺ»ν•˜μ˜€μŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> new MenuView(null) { + @Override + public String title() { + return ""; + } + + @Override + protected Class getType() { + return null; + } + }).isInstanceOf(IllegalArgumentException.class).hasMessage(message); + } + + @Test + public void show() { + TestMenu item = TestMenu.ITEM; + TestMenuView testMenuView = new TestMenuView(); + testMenuView.show(); + this.mockConsole.verify(() -> { + Console.printHeader(testMenuView.title()); + Console.println(String.format("%s. %s", item.command, item.name)); + Console.println(); + }); + } + + @Test + public void question() { + TestMenu item = TestMenu.ITEM; + TestMenuView testMenuView = new TestMenuView(); + this.mockConsole.when(Console::readline).thenReturn(item.command); + assertThat(testMenuView.question()).isEqualTo(item); + } + + @Test + public void question_CanNotSelectedFunctionException() { + TestMenu item = TestMenu.ITEM; + TestMenuView testMenuView = new TestMenuView(); + String message = "선택할 수 μ—†λŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€."; + this.mockConsole.when(Console::readline).thenReturn("none"); + this.mockConsole.when(Console::readline).thenReturn(item.command); + testMenuView.question(); + this.mockConsole.verify(() -> { + Console.println(); + Console.printError(message); + Console.println(); + + Console.println(); + }); + } + + @Test + public void onEvent() { + TestMenu item = TestMenu.ITEM; + AtomicInteger count = new AtomicInteger(0); + TestMenuView testMenuView = new TestMenuView( + MenuEventManager.builder().addEventListener(item, count::getAndIncrement)); + testMenuView.onEvent(item); + assertThat(count.get()).isEqualTo(1); + } + + @Test + public void onEvent__CanNotProcessEventHandlerException() { + TestMenu item = TestMenu.ITEM; + TestMenuView testMenuView = new TestMenuView(); + String message = "이벀트λ₯Ό μ²˜λ¦¬ν•  수 μ—†μŠ΅λ‹ˆλ‹€."; + assertThatThrownBy(() -> testMenuView.onEvent(null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + assertThatThrownBy(() -> testMenuView.onEvent(item)).isInstanceOf(IllegalArgumentException.class) + .hasMessage(message); + } + + @AfterEach + public void init() { + mockConsole.close(); + } +} diff --git a/src/test/java/subway/util/ValidationTest.java b/src/test/java/subway/util/ValidationTest.java new file mode 100644 index 000000000..0ec5c905f --- /dev/null +++ b/src/test/java/subway/util/ValidationTest.java @@ -0,0 +1,14 @@ +package subway.util; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class ValidationTest { + @Test + public void isNumeric() { + assertThat(Validation.isNumeric("1")).isEqualTo(true); + assertThat(Validation.isNumeric("")).isEqualTo(false); + assertThat(Validation.isNumeric("number")).isEqualTo(false); + } +}