diff --git a/README.md b/README.md index ff5c1ab..888cdd8 100644 --- a/README.md +++ b/README.md @@ -1 +1,25 @@ -# All-rounder Backend Study +# ๐Ÿ–จ๏ธ mission-printer ๐Ÿ–จ + +## ๊ธฐ๋Šฅ ๋ช…์„ธ + +- ์ž…์ถœ๋ ฅ + - ์ˆซ์ž๋กœ ๊ตฌ์„ฑ๋œ ๋ฌธ์ž์—ด ์ž…๋ ฅ + - ์ˆซ์ž ์ž…๋ ฅ + - ์•ˆ๋‚ด ์ถœ๋ ฅ + - ์—ฌ๋Ÿฌ ๋ฌธ์ž์—ด ์ถœ๋ ฅ + +- ์•„์Šคํ‚ค ์•„ํŠธ ๋ณ€ํ™˜ + - ๋ฌธ์ž์—ด -> ์•„์Šคํ‚ค์•„ํŠธ ๋ณ€ํ™˜ + - ์•„์Šคํ‚ค์•„ํŠธ ๋ณ‘ํ•ฉ + +- ์ž‰ํฌ ๊ด€๋ฆฌ + - ์ž‰ํฌ ํ™•์ธ ๋ฐ ์ถœ๋ ฅ ์ œํ•œ + - ์ž‰ํฌ ๋ณด์ถฉ + +## ๊ตฌํ˜„ ๊ณ„ํš +AsciiGenerator +- ์•„์Šคํ‚ค์•„ํŠธ ์ƒ์„ฑ ๋ฐ ๋ณ€ํ™˜ + +Printer +- ์ž‰ํฌ ๊ด€๋ฆฌ, ์ถœ๋ ฅ ์š”์ฒญ + diff --git a/src/main/java/mission/AppConfig.java b/src/main/java/mission/AppConfig.java new file mode 100644 index 0000000..68debb4 --- /dev/null +++ b/src/main/java/mission/AppConfig.java @@ -0,0 +1,20 @@ +package mission; + +import mission.controller.PrinterController; +import mission.model.Printer; +import mission.view.InputView; +import mission.view.OutputView; + +public class AppConfig { + public InputView inputView() { + return new InputView(); + } + + public OutputView outputView() { + return new OutputView(); + } + + public PrinterController printerController() { + return new PrinterController(inputView(), outputView()); + } +} diff --git a/src/main/java/mission/Application.java b/src/main/java/mission/Application.java index 4bfd621..ee20130 100644 --- a/src/main/java/mission/Application.java +++ b/src/main/java/mission/Application.java @@ -1,7 +1,13 @@ package mission; +import mission.controller.PrinterController; + public class Application { public static void main(String[] args) { //Todo: ํ”„๋กœ๊ทธ๋žจ ๊ตฌํ˜„ + AppConfig appConfig = new AppConfig(); + PrinterController printerController = appConfig.printerController(); + + printerController.run(); } } diff --git a/src/main/java/mission/ProgramTerminateException.java b/src/main/java/mission/ProgramTerminateException.java new file mode 100644 index 0000000..44ff698 --- /dev/null +++ b/src/main/java/mission/ProgramTerminateException.java @@ -0,0 +1,9 @@ +package mission; + +public class ProgramTerminateException extends RuntimeException { + public ProgramTerminateException() {} + + public ProgramTerminateException(String message) { + super(message); + } +} diff --git a/src/main/java/mission/controller/PrinterController.java b/src/main/java/mission/controller/PrinterController.java new file mode 100644 index 0000000..e065f52 --- /dev/null +++ b/src/main/java/mission/controller/PrinterController.java @@ -0,0 +1,76 @@ +package mission.controller; + +import mission.ProgramTerminateException; +import mission.interfaces.PrintInterface; +import mission.model.AsciiGenerator; +import mission.model.Printer; +import mission.utils.Utility; +import mission.utils.Validator; +import mission.view.InputView; +import mission.view.OutputView; + +public class PrinterController { + InputView inputView; + OutputView outputView; + AsciiGenerator asciiGenerator; + Printer printer; + + public PrinterController(InputView inputView, OutputView outputView) { + class PrintInterfaceImpl implements PrintInterface { + @Override + public void print(String body) { + outputView.printMessage(body); + } + } + + this.inputView = inputView; + this.outputView = outputView; + asciiGenerator = new AsciiGenerator(); + printer = new Printer(new PrintInterfaceImpl()); + } + + public void run() { + outputView.printerExecuteMessage(); + while (true) { + try { + execute(); + } catch (ProgramTerminateException exception) { + break; + } catch (Exception exception) { + outputView.printMessage(exception.getMessage()); + } + } + outputView.printTerminateMessage(); + } + + private void execute() { + int selected = inputView.getFeatureSelection(); + if (selected == 1) { + print(); + return; + } + if (selected == 2) { + outputView.showInk(printer.getInk(), printer.getMaxInk()); + return; + } + if (selected == 3) { + printer.setInk(); + outputView.chargedInk(); + return; + } + if (selected == 4) { + throw new ProgramTerminateException(); + } + throw new IllegalArgumentException("[ERROR] ์ž˜๋ชป๋œ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค."); + } + + private void print() { + String line = inputView.getNumberLine(); + Validator.composeNumeric(line); + + int length = inputView.getLineLength(); + Validator.positiveInteger(length); + + printer.print(asciiGenerator.generateAsciiPrintout(Utility.stringToIntegers(line), length)); + } +} diff --git a/src/main/java/mission/interfaces/PrintInterface.java b/src/main/java/mission/interfaces/PrintInterface.java new file mode 100644 index 0000000..32ea40a --- /dev/null +++ b/src/main/java/mission/interfaces/PrintInterface.java @@ -0,0 +1,5 @@ +package mission.interfaces; + +public interface PrintInterface { + void print(String body); +} diff --git a/src/main/java/mission/model/AsciiGenerator.java b/src/main/java/mission/model/AsciiGenerator.java new file mode 100644 index 0000000..f7f16fa --- /dev/null +++ b/src/main/java/mission/model/AsciiGenerator.java @@ -0,0 +1,74 @@ +package mission.model; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import mission.utils.Utility; + +public class AsciiGenerator { + private final List> data; + + static private final List fileNames = List.of( + "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" + ); + static private final int ASCII_HEIGHT = 5; + + public AsciiGenerator() { + data = new ArrayList<>(); + fileNames.forEach(fileName -> { + try { + Path path = Paths.get(Objects.requireNonNull( + getClass().getClassLoader().getResource("numberAsciiDesign/" + fileName + ".txt") + ).toURI()); + String content = Files.readString(path); + + List paddedLines = Arrays.stream(content.split("\n")) + .map(line -> String.format("%-3s", line)) + .toList(); + + data.add(paddedLines); + } catch (URISyntaxException | IOException e) { + throw new IllegalStateException("[ERROR] ์•„์Šคํ‚ค์•„ํŠธ๋ฅผ ๋กœ๋“œํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + }); + } + + public List getNumberAsciiDesign(int number) { + try { + return data.get(number); + } catch (IndexOutOfBoundsException e) { + throw new IllegalStateException("[ERROR] ๋ฒ”์œ„๋ฅผ ์ดˆ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค. "); + } + } + + private List> getNumberAsciiDesign(List numbers) { + return numbers.stream().map(this::getNumberAsciiDesign).toList(); + } + + private List concatAsciiLine(List> lines) { + return IntStream.range(0, ASCII_HEIGHT) + .mapToObj(lineIndex -> { + List line = new ArrayList<>(); + lines.forEach(ascii -> line.add(ascii.get(lineIndex))); + return String.join(" ", line); + }) + .toList(); + } + + public List generateAsciiPrintout(List numbers, int width) { + return Utility.chunkList(numbers, width).stream() + .map(this::getNumberAsciiDesign) + .map(this::concatAsciiLine) + .flatMap(list -> Stream.concat(list.stream(), Stream.of(""))) + .toList(); + } +} diff --git a/src/main/java/mission/model/Printer.java b/src/main/java/mission/model/Printer.java new file mode 100644 index 0000000..2e50137 --- /dev/null +++ b/src/main/java/mission/model/Printer.java @@ -0,0 +1,46 @@ +package mission.model; + +import java.util.List; +import mission.interfaces.PrintInterface; +import mission.utils.Utility; + +public class Printer { + private static final int MAX_INK = 1000; + + PrintInterface printInterface; + int ink; + + public Printer(PrintInterface printInterface) { + this.printInterface = printInterface; + ink = MAX_INK; + } + + public int getMaxInk() { + return MAX_INK; + } + + public int getInk() { + return ink; + } + + public void setInk() { + this.ink = MAX_INK; + } + + public void print(List lines) { + lines.forEach(line -> { + int consume = Utility.countNonWhitespaceChars(line); + if (consume > ink) { + lastPrint(line); + throw new IllegalStateException("์ž‰ํฌ๊ฐ€ ๋ถ€์กฑํ•ด ์ถœ๋ ฅ์ด ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); + } + ink -= consume; + printInterface.print(line); + }); + } + + public void lastPrint(String line) { + printInterface.print(Utility.substringUntilNthChar(line, 'โ—ผ', ink)); + ink = 0; + } +} diff --git a/src/main/java/mission/utils/AsciiGenerator.java b/src/main/java/mission/utils/AsciiGenerator.java deleted file mode 100644 index 2498d7e..0000000 --- a/src/main/java/mission/utils/AsciiGenerator.java +++ /dev/null @@ -1,41 +0,0 @@ -package mission.utils; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class AsciiGenerator { - private final List> data; - - static private final List fileNames = List.of( - "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" - ); - - public AsciiGenerator() { - data = new ArrayList<>(); - fileNames.forEach(fileName -> { - try { - Path path = Paths.get(Objects.requireNonNull( - getClass().getClassLoader().getResource("numberAsciiDesign/" + fileName + ".txt") - ).toURI()); - String content = Files.readString(path); - data.add(List.of(content.split("\n"))); - } catch (URISyntaxException | IOException e) { - throw new IllegalStateException("[ERROR] ์•„์Šคํ‚ค์•„ํŠธ๋ฅผ ๋กœ๋“œํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค."); - } - }); - } - - public List getNumberAsciiDesign(int number) { - try { - return data.get(number); - } catch (IndexOutOfBoundsException e) { - throw new IllegalStateException("[ERROR] ๋ฒ”์œ„๋ฅผ ์ดˆ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค. "); - } - } -} diff --git a/src/main/java/mission/utils/Utility.java b/src/main/java/mission/utils/Utility.java new file mode 100644 index 0000000..274279c --- /dev/null +++ b/src/main/java/mission/utils/Utility.java @@ -0,0 +1,43 @@ +package mission.utils; + +import java.util.ArrayList; +import java.util.List; + +public class Utility { + public static List> chunkList(List list, int chunkSize) { + int size = list.size(); + List> result = new ArrayList<>(); + + for (int i = 0; i < size; i += chunkSize) { + result.add(list.subList(i, Math.min(size, i + chunkSize))); + } + + return result; + } + + public static List stringToIntegers(String line) { + return line.chars().map(c -> c - '0').boxed().toList(); + } + + public static int countNonWhitespaceChars(String line) { + if (line == null) return 0; + return (int) line.chars() + .filter(ch -> !Character.isWhitespace(ch)) + .count(); + } + + public static String substringUntilNthChar(String str, char target, int n) { + int count = 0; + + for (int i = 0; i < str.length(); i++) { + if (str.charAt(i) == target) { + count++; + if (count == n) { + return str.substring(0, i); + } + } + } + + return str; + } +} diff --git a/src/main/java/mission/utils/Validator.java b/src/main/java/mission/utils/Validator.java new file mode 100644 index 0000000..5298e91 --- /dev/null +++ b/src/main/java/mission/utils/Validator.java @@ -0,0 +1,15 @@ +package mission.utils; + +public class Validator { + public static void composeNumeric(String input) { + if (!input.matches("^[0-9]+$")) { + throw new IllegalStateException("[ERROR] ์ˆซ์ž๊ฐ€ ์•„๋‹Œ ๋ฌธ์ž๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค."); + } + } + + public static void positiveInteger(int value) { + if (value <= 0) { + throw new IllegalStateException("[ERROR] ์–‘์ˆ˜๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค."); + } + } +} diff --git a/src/main/java/mission/view/InputView.java b/src/main/java/mission/view/InputView.java new file mode 100644 index 0000000..048f170 --- /dev/null +++ b/src/main/java/mission/view/InputView.java @@ -0,0 +1,37 @@ +package mission.view; + +import java.util.Scanner; + +public class InputView { + Scanner scanner; + public InputView() { + scanner = new Scanner(System.in); + } + + public int getFeatureSelection() { + try { + System.out.println("์‚ฌ์šฉํ•  ๊ธฐ๋Šฅ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”. 1) ์ถœ๋ ฅ, 2) ์ž‰ํฌ ์ž”๋Ÿ‰ ํ™•์ธ, 3) ์ž‰ํฌ ๊ต์ฒด, 4) ํ”„๋กœ๊ทธ๋žจ ์ข…๋ฃŒ"); + return Integer.parseInt(scanner.nextLine()); + } catch (Exception e) { + throw new IllegalArgumentException("[ERROR] ์ž˜๋ชป๋œ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค. "); + } + } + + public String getNumberLine() { + try { + System.out.println("์ถœ๋ ฅํ•  ์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + return scanner.nextLine(); + } catch (Exception e) { + throw new IllegalArgumentException("[ERROR] ์ž˜๋ชป๋œ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค. "); + } + } + + public int getLineLength() { + try { + System.out.println("์šฉ์ง€ ํฌ๊ธฐ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + return Integer.parseInt(scanner.nextLine()); + } catch (Exception e) { + throw new IllegalArgumentException("[ERROR] ์ž˜๋ชป๋œ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค. "); + } + } +} diff --git a/src/main/java/mission/view/OutputView.java b/src/main/java/mission/view/OutputView.java new file mode 100644 index 0000000..32fefa5 --- /dev/null +++ b/src/main/java/mission/view/OutputView.java @@ -0,0 +1,33 @@ +package mission.view; + +import java.util.List; + +public class OutputView { + public void printPrintout(List printout) { + printout.forEach(System.out::println); + } + + public void printerExecuteMessage() { + System.out.println("ํ”„๋ฆฐํ„ฐ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค."); + } + + public void printMessage(String message) { + System.out.println(message); + } + + public void printEndMessage() { + System.out.println("์ถœ๋ ฅ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); + } + + public void showInk(int ink, int max) { + System.out.printf("์ž‰ํฌ ์ž”๋Ÿ‰ : %d, %d%n", ink, max); + } + + public void chargedInk() { + System.out.println("์ž‰ํฌ๋ฅผ ๊ต์ฒดํ•˜์˜€์Šต๋‹ˆ๋‹ค."); + } + + public void printTerminateMessage() { + System.out.println("ํ”„๋กœ๊ทธ๋žจ์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค."); + } +} diff --git a/src/test/java/mission/AsciiGeneratorTest.java b/src/test/java/mission/AsciiGeneratorTest.java index 910c2ed..39c66dc 100644 --- a/src/test/java/mission/AsciiGeneratorTest.java +++ b/src/test/java/mission/AsciiGeneratorTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.List; -import mission.utils.AsciiGenerator; +import mission.model.AsciiGenerator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,6 +30,13 @@ void getNumberAsciiDesign9Test() { assertEquals(zero, ascii); } + @Test + void getNumberAsciiDesign2Test() { + List ascii = asciiGenerator.getNumberAsciiDesign(2); + List zero = List.of("โ—ผโ—ผโ—ผ", " โ—ผ", "โ—ผโ—ผโ—ผ", "โ—ผ ", "โ—ผโ—ผโ—ผ"); + assertEquals(zero, ascii); + } + @Test void ๋ฒ”์œ„๋ฅผ_์ดˆ๊ณผํ•˜๋ฉด_์˜ˆ์™ธ_๋ฐœ์ƒ() { IllegalStateException exception = assertThrows( @@ -37,4 +44,10 @@ void getNumberAsciiDesign9Test() { () -> asciiGenerator.getNumberAsciiDesign(10) ); } + + @Test + void generateAsciiPrintoutTest() { + List result = asciiGenerator.generateAsciiPrintout(List.of(1, 2, 3, 4), 2); + result.forEach(System.out::println); + } }