diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b63da45
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,42 @@
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..baa5c09
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+WiseSaying
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..446c193
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..fe0b0da
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..19025df
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,24 @@
+plugins {
+ id 'java'
+}
+
+group = 'com.ll.wiseSaying'
+version = '1.0-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
+ implementation 'com.fasterxml.jackson.core:jackson-core:2.15.2'
+ implementation 'com.fasterxml.jackson.core:jackson-annotations:2.15.2'
+
+ testImplementation platform('org.junit:junit-bom:5.9.1')
+ testImplementation 'org.junit.jupiter:junit-jupiter'
+ testImplementation("org.assertj:assertj-core:3.22.0")
+}
+
+test {
+ useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..249e583
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..3e69e8d
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Dec 19 12:12:15 KST 2024
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..1b6c787
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..f459626
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'WiseSaying'
+
diff --git a/src/main/java/com/ll/wiseSaying/basicDevelopment/App.java b/src/main/java/com/ll/wiseSaying/basicDevelopment/App.java
new file mode 100644
index 0000000..a641430
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/basicDevelopment/App.java
@@ -0,0 +1,54 @@
+package com.ll.wiseSaying.basicDevelopment;
+
+import java.util.Scanner;
+
+public class App {
+
+ private final Scanner scanner = new Scanner(System.in);
+ private final WiseSayingController controller = WiseSayingController.getInstance();
+
+ private enum Command {
+ REGISTER("등록"),
+ SEARCH("목록"),
+ DELETE("삭제"),
+ MODIFY("수정"),
+ BUILD("빌드"),
+ EXIT("종료");
+
+ final String cmd;
+
+ Command(String cmd) {
+ this.cmd = cmd;
+ }
+
+ public static Command of(String cmd) {
+ for (Command command : Command.values()) {
+ if (command.cmd.equals(cmd))
+ return command;
+ }
+ throw new IllegalArgumentException("잘못된 명령입니다.");
+ }
+ }
+
+ public void run() {
+ System.out.println("== 명언 앱 ==");
+
+ while (true) {
+ System.out.print("명령) ");
+ String cmd = scanner.nextLine();
+
+ Command mainCmd = Command.of(cmd.split("\\?")[0]);
+ if (mainCmd.equals(Command.EXIT)) {
+ break;
+ }
+
+ switch (mainCmd) {
+ case REGISTER-> controller.registerWiseSaying(scanner);
+ case SEARCH -> controller.searchWiseSaying(cmd);
+ case DELETE -> controller.deleteWiseSaying(cmd);
+ case MODIFY -> controller.updateWiseSaying(scanner, cmd);
+ case BUILD -> controller.buildData();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/ll/wiseSaying/basicDevelopment/Main.java b/src/main/java/com/ll/wiseSaying/basicDevelopment/Main.java
new file mode 100644
index 0000000..eddd33a
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/basicDevelopment/Main.java
@@ -0,0 +1,7 @@
+package com.ll.wiseSaying.basicDevelopment;
+
+public class Main {
+ public static void main(String[] args) {
+ new App().run();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ll/wiseSaying/basicDevelopment/WiseSaying.java b/src/main/java/com/ll/wiseSaying/basicDevelopment/WiseSaying.java
new file mode 100644
index 0000000..7a99cb5
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/basicDevelopment/WiseSaying.java
@@ -0,0 +1,33 @@
+package com.ll.wiseSaying.basicDevelopment;
+
+public class WiseSaying {
+ private final int id;
+ private String author;
+ private String content;
+
+ public WiseSaying(int id, String author, String content) {
+ this.id = id;
+ this.author = author;
+ this.content = content;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+}
diff --git a/src/main/java/com/ll/wiseSaying/basicDevelopment/WiseSayingController.java b/src/main/java/com/ll/wiseSaying/basicDevelopment/WiseSayingController.java
new file mode 100644
index 0000000..d60a102
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/basicDevelopment/WiseSayingController.java
@@ -0,0 +1,127 @@
+package com.ll.wiseSaying.basicDevelopment;
+
+import java.util.*;
+
+public class WiseSayingController {
+
+ private final static WiseSayingController instance = new WiseSayingController();
+
+ private WiseSayingController() {
+ }
+
+ public static synchronized WiseSayingController getInstance() {
+ return instance;
+ }
+
+ private final WiseSayingService wiseSayingService = WiseSayingService.getInstance();
+
+ private enum searchKeyword {
+ KEYWORD_TYPE("keywordType="),
+ KEYWORD("keyword="),
+ PAGE("page="),
+ AND("&"),
+ ID("id=");
+
+ final String word;
+
+ searchKeyword(String word) {
+ this.word = word;
+ }
+ }
+
+ public void registerWiseSaying(Scanner scanner) {
+ System.out.print("명언 : ");
+ String content = scanner.nextLine();
+ System.out.print("작가 : ");
+ String author = scanner.nextLine();
+
+ WiseSaying wiseSaying = wiseSayingService.register(content, author);
+ System.out.println(wiseSaying.getId() + "번 명언이 등록되었습니다.");
+ }
+
+ public void searchWiseSaying(String cmd) {
+ List wiseSayingList = new ArrayList<>();
+
+ if (cmd.contains(searchKeyword.KEYWORD.word) && cmd.contains(searchKeyword.KEYWORD_TYPE.word)) {
+ String keywordType = cmd.split(searchKeyword.KEYWORD_TYPE.word)[1].split(searchKeyword.AND.word)[0];
+ String keyword = cmd.split(searchKeyword.KEYWORD.word)[1].split(searchKeyword.AND.word)[0];
+
+ System.out.println("----------------------");
+ System.out.println("검색타입 : " + keywordType);
+ System.out.println("검색어 : " + keyword);
+ System.out.println("----------------------");
+
+ if (keywordType.equals("content")) {
+ wiseSayingList = wiseSayingService.findAllByContent(keyword);
+ } else if (keywordType.equals("author")) {
+ wiseSayingList = wiseSayingService.findAllByAuthor(keyword);
+ }
+ } else {
+ wiseSayingList = wiseSayingService.findAll();
+ }
+
+ printPagedList(cmd, wiseSayingList);
+ }
+
+ private void printPagedList(String cmd, List wiseSayingList) {
+ int pageSize = 5;
+ int page = cmd.contains(searchKeyword.PAGE.word)
+ ? Integer.parseInt(cmd.split(searchKeyword.PAGE.word)[1].split(searchKeyword.AND.word)[0]) : 1;
+
+ List resultList = wiseSayingService.getPagedWiseSayings(page, pageSize, wiseSayingList);
+ int totalPages = (int) Math.ceil((double) wiseSayingList.size() / pageSize);
+
+ System.out.println("번호 / 작가 / 명언");
+ System.out.println("----------------------");
+ for (WiseSaying wiseSaying : resultList) {
+ System.out.printf("%d / %s / %s%n", wiseSaying.getId(), wiseSaying.getAuthor(), wiseSaying.getContent());
+ }
+ System.out.println("----------------------");
+ System.out.print("페이지 : ");
+ for (int i = 1; i <= totalPages; i++) {
+ if (i != 1) {
+ System.out.print("/ ");
+ }
+
+ if (i == page) {
+ System.out.printf("[%d] ", i);
+ } else {
+ System.out.printf("%d ", i);
+ }
+ }
+ System.out.println();
+ }
+
+ public void deleteWiseSaying(String cmd) {
+ int id = Integer.parseInt(cmd.split(searchKeyword.ID.word)[1]);
+ if (wiseSayingService.deleteById(id)) {
+ System.out.println(id + "번 명언이 삭제되었습니다.");
+ } else {
+ System.out.println(id + "번 명언은 존재하지 않습니다.");
+ }
+ }
+
+ public void updateWiseSaying(Scanner scanner, String cmd) {
+ int id = Integer.parseInt(cmd.split(searchKeyword.ID.word)[1]);
+ WiseSaying wiseSaying = wiseSayingService.findById(id);
+
+ if (wiseSaying == null) {
+ System.out.println(id + "번 명언은 존재하지 않습니다.");
+ return;
+ }
+
+ System.out.println("명언(기존) : " + wiseSaying.getContent());
+ System.out.print("명언 : ");
+ String newContent = scanner.nextLine();
+ System.out.println("작가(기존) : " + wiseSaying.getAuthor());
+ System.out.print("작가 : ");
+ String newAuthor = scanner.nextLine();
+
+ wiseSayingService.update(id, newContent, newAuthor);
+ }
+
+ public void buildData() {
+ wiseSayingService.saveAll();
+ System.out.println("data.json 파일의 내용이 갱신되었습니다.");
+ }
+}
diff --git a/src/main/java/com/ll/wiseSaying/basicDevelopment/WiseSayingRepository.java b/src/main/java/com/ll/wiseSaying/basicDevelopment/WiseSayingRepository.java
new file mode 100644
index 0000000..8b7d515
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/basicDevelopment/WiseSayingRepository.java
@@ -0,0 +1,143 @@
+package com.ll.wiseSaying.basicDevelopment;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import static java.nio.file.Files.readAllBytes;
+
+public class WiseSayingRepository {
+
+ private final static WiseSayingRepository instance = new WiseSayingRepository();
+ private WiseSayingRepository() {
+ File folder = new File(DBPATH);
+ if (!folder.exists()) {
+ folder.mkdirs();
+ }
+
+ loadAll();
+ }
+ public static synchronized WiseSayingRepository getInstance() {
+ return instance;
+ }
+
+ private static final String DBPATH = "db/wiseSaying";
+
+ private int lastId = 0;
+ private final List wiseSayingList = new ArrayList<>();
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ public WiseSaying save(String content, String author) {
+ lastId++;
+
+ WiseSaying wiseSaying = new WiseSaying(lastId, author, content);
+ wiseSayingList.add(wiseSaying);
+ saveWiseSayingToFile(wiseSaying);
+
+ saveLastIdToFile();
+ return wiseSaying;
+ }
+
+ public List findAll() {
+ wiseSayingList.sort(Comparator.comparingInt(WiseSaying::getId).reversed());
+ return wiseSayingList;
+ }
+
+ public List findAllByContent(String content) {
+ return wiseSayingList.stream()
+ .filter(w -> w.getContent().contains(content))
+ .sorted(Comparator.comparingInt(WiseSaying::getId).reversed())
+ .toList();
+ }
+
+ public List findAllByAuthor(String author) {
+ return wiseSayingList.stream()
+ .filter(w -> w.getAuthor().contains(author))
+ .sorted(Comparator.comparingInt(WiseSaying::getId).reversed())
+ .toList();
+ }
+
+ public List findAllPaginated(int page, int size, List resultList) {
+ return resultList.stream()
+ .sorted(Comparator.comparingInt(WiseSaying::getId).reversed())
+ .skip((long) (page - 1) * size)
+ .limit(size)
+ .collect(Collectors.toList());
+ }
+
+ public boolean deleteById(int id) {
+ WiseSaying wiseSaying = findById(id);
+ if (wiseSaying != null) {
+ wiseSayingList.remove(wiseSaying);
+ new File(DBPATH + "/" + id + ".json").delete();
+ return true;
+ }
+ return false;
+ }
+
+ public WiseSaying findById(int id) {
+ return wiseSayingList.stream()
+ .filter(w -> w.getId() == id)
+ .findFirst()
+ .orElse(null);
+ }
+
+ public void update(int id, String content, String author) {
+ WiseSaying wiseSaying = findById(id);
+ if (wiseSaying != null) {
+ wiseSaying.setContent(content);
+ wiseSaying.setAuthor(author);
+ saveWiseSayingToFile(wiseSaying);
+ }
+ }
+
+ public void saveAll() {
+ try {
+ objectMapper.writerWithDefaultPrettyPrinter()
+ .writeValue(new File(DBPATH+"/data.json"), wiseSayingList);
+ } catch (IOException e) {
+ System.out.println("data 파일 저장 중 오류 : " + e.getMessage());
+ }
+ }
+
+ private void loadAll() {
+ File lastIdFile = new File(DBPATH + "/lastId.txt");
+ if (lastIdFile.exists()) {
+ try {
+ lastId = Integer.parseInt(new String(readAllBytes(lastIdFile.toPath())));
+ } catch (IOException ignored) {
+ }
+ }
+
+ File folder = new File(DBPATH);
+ File[] files = folder.listFiles((dir, name) -> name.endsWith(".json") && !name.equals("lastId.txt"));
+
+ if (files != null) {
+ for (File file : files) {
+ try {
+ WiseSaying wiseSaying = objectMapper.readValue(file, WiseSaying.class);
+ wiseSayingList.add(wiseSaying);
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }
+
+ private void saveWiseSayingToFile(WiseSaying wiseSaying) {
+ try {
+ objectMapper.writerWithDefaultPrettyPrinter().writeValue(new File(DBPATH+"/"+wiseSaying.getId()+".json"), wiseSaying);
+ } catch (IOException e) {
+ System.out.println("명언 저장 중 오류 : " + e.getMessage());
+ }
+ }
+
+ private void saveLastIdToFile() {
+ try {
+ Files.write(new File(DBPATH+"/lastId.txt").toPath(), String.valueOf(lastId).getBytes());
+ } catch (IOException ignored) {
+ }
+ }
+}
diff --git a/src/main/java/com/ll/wiseSaying/basicDevelopment/WiseSayingService.java b/src/main/java/com/ll/wiseSaying/basicDevelopment/WiseSayingService.java
new file mode 100644
index 0000000..f405b90
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/basicDevelopment/WiseSayingService.java
@@ -0,0 +1,50 @@
+package com.ll.wiseSaying.basicDevelopment;
+
+import java.util.List;
+
+public class WiseSayingService {
+
+ private final static WiseSayingService instance = new WiseSayingService();
+ private WiseSayingService() {}
+ public static synchronized WiseSayingService getInstance() {
+ return instance;
+ }
+
+ private final WiseSayingRepository wiseSayingRepository = WiseSayingRepository.getInstance();
+
+ public WiseSaying register(String content, String author) {
+ return wiseSayingRepository.save(content, author);
+ }
+
+ public List findAll() {
+ return wiseSayingRepository.findAll();
+ }
+
+ public List findAllByContent(String content) {
+ return wiseSayingRepository.findAllByContent(content);
+ }
+
+ public List findAllByAuthor(String author) {
+ return wiseSayingRepository.findAllByAuthor(author);
+ }
+
+ public List getPagedWiseSayings(int page, int size, List resultList) {
+ return wiseSayingRepository.findAllPaginated(page, size, resultList);
+ }
+
+ public boolean deleteById(int id) {
+ return wiseSayingRepository.deleteById(id);
+ }
+
+ public WiseSaying findById(int id) {
+ return wiseSayingRepository.findById(id);
+ }
+
+ public void update(int id, String content, String author) {
+ wiseSayingRepository.update(id, content, author);
+ }
+
+ public void saveAll() {
+ wiseSayingRepository.saveAll();
+ }
+}
diff --git a/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/Main.java b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/Main.java
new file mode 100644
index 0000000..9f754de
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/Main.java
@@ -0,0 +1,7 @@
+package com.ll.wiseSaying.testDrivenDevelopment;
+
+public class Main {
+ public static void main(String[] args) {
+ new TddApp().run();
+ }
+}
diff --git a/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddApp.java b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddApp.java
new file mode 100644
index 0000000..fc53574
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddApp.java
@@ -0,0 +1,38 @@
+package com.ll.wiseSaying.testDrivenDevelopment;
+
+import java.util.Scanner;
+
+public class TddApp {
+
+ private final TddWiseSayingController controller = getController();
+ private final Scanner scanner = new Scanner(System.in);
+
+ public void run() {
+ System.out.println("== 명언 앱 ==");
+
+ while (true) {
+ System.out.print("명령) ");
+ String command = scanner.nextLine();
+
+ String mainCommand = command.split("\\?")[0];
+
+ switch (mainCommand) {
+ case "등록" -> controller.register(scanner);
+ case "목록" -> controller.search(command);
+ case "수정" -> controller.modify(scanner, command);
+ case "삭제" -> controller.delete(command);
+ case "빌드" -> controller.build();
+ case "종료" -> {
+ return;
+ }
+ default -> System.out.println("명령을 입력해주세요.");
+ }
+ }
+ }
+
+ public TddWiseSayingController getController() {
+ TddWiseSayingRepository repository = new TddWiseSayingRepository();
+ TddWiseSayingService service = new TddWiseSayingService(repository);
+ return new TddWiseSayingController(service);
+ }
+}
diff --git a/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddPage.java b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddPage.java
new file mode 100644
index 0000000..737d428
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddPage.java
@@ -0,0 +1,51 @@
+package com.ll.wiseSaying.testDrivenDevelopment;
+
+import java.util.List;
+
+public class TddPage {
+
+ private final List wiseSayingList;
+ private final int totalPage;
+ private final int currentPage;
+
+ public TddPage(List wiseSayingList, int totalPage, int currentPage) {
+ this.wiseSayingList = wiseSayingList;
+ this.totalPage = totalPage;
+ this.currentPage = currentPage;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("번호 / 작가 / 명언\n");
+ sb.append("----------------------\n");
+
+ if (wiseSayingList.isEmpty() || currentPage>totalPage) {
+ sb.append("검색 결과가 존재하지 않습니다.");
+ return sb.toString();
+ }
+
+ for (T wiseSaying : wiseSayingList) {
+ sb.append(wiseSaying).append("\n");
+ }
+
+ sb.append("----------------------\n");
+ sb.append("페이지 :");
+
+ for (int i=1; i<=totalPage; i++) {
+ if (i == 1) {
+ sb.append(" ");
+ } else {
+ sb.append(" / ");
+ }
+
+ if (i == currentPage) {
+ sb.append("[").append(i).append("]");
+ } else {
+ sb.append(i);
+ }
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSaying.java b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSaying.java
new file mode 100644
index 0000000..18f77eb
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSaying.java
@@ -0,0 +1,37 @@
+package com.ll.wiseSaying.testDrivenDevelopment;
+
+public class TddWiseSaying {
+ private final int id;
+ private String author;
+ private String content;
+
+ public TddWiseSaying(int id, String author, String content) {
+ this.id = id;
+ this.author = author;
+ this.content = content;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public String toString() {
+ return id + " / " + author + " / " + content;
+ }
+}
diff --git a/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSayingController.java b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSayingController.java
new file mode 100644
index 0000000..68c8934
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSayingController.java
@@ -0,0 +1,105 @@
+package com.ll.wiseSaying.testDrivenDevelopment;
+
+import java.util.*;
+
+public class TddWiseSayingController {
+
+ private final TddWiseSayingService service;
+
+ public TddWiseSayingController(TddWiseSayingService service) {
+ this.service = service;
+ }
+
+ public void register(Scanner scanner) {
+
+ System.out.print("명언 : ");
+ String content = scanner.nextLine();
+ System.out.print("작가 : ");
+ String author = scanner.nextLine();
+
+ if (content.isEmpty() || author.isEmpty()) {
+ throw new IllegalArgumentException("명언과 작가를 모두 입력해주세요.");
+ }
+
+ int id = service.registerWiseSaying(author, content);
+ System.out.println(id + "번 명언이 등록되었습니다.");
+ }
+
+ public void search(String command) {
+
+ int pageSize = 5;
+ int pageNum = 1;
+ TddPage page;
+
+ Map keywordList = service.parseSearchKeyword(command);
+ if (!service.validateSearchKeyword(keywordList)) {
+ throw new IllegalArgumentException("명령을 다시 확인해주세요.");
+ }
+
+ if (!keywordList.isEmpty()) {
+ if (keywordList.containsKey("page")) {
+ pageNum = Integer.parseInt(keywordList.get("page"));
+ }
+
+ if (keywordList.size()==1) {
+ page = service.findAll(pageNum, pageSize);
+ } else {
+
+ String keywordType = command.split("keywordType=")[1].split("&")[0];
+ String keyword = command.split("keyword=")[1].split("&")[0];
+
+ System.out.println("----------------------");
+ System.out.println("검색타입 : " + keywordType);
+ System.out.println("검색어 : " + keyword);
+ System.out.println("----------------------");
+
+ page = service.findDetail(pageNum, pageSize, keywordType, keyword);
+ }
+ } else {
+ page = service.findAll(pageNum, pageSize);
+ }
+
+ System.out.println(page);
+ }
+
+ public void modify(Scanner scanner, String command) {
+ int id = service.parseId(command);
+ TddWiseSaying wiseSaying = service.findById(id);
+
+ if (wiseSaying == null) {
+ System.out.println(id + "번 명언은 존재하지 않습니다.");
+ return;
+ }
+
+ System.out.println("명언(기존) : " + wiseSaying.getContent());
+ System.out.print("명언 : ");
+ String newContent = scanner.nextLine();
+ System.out.println("작가(기존) : " + wiseSaying.getAuthor());
+ System.out.print("작가 : ");
+ String newAuthor = scanner.nextLine();
+
+ if (newAuthor.isBlank() || newContent.isBlank()) {
+ throw new IllegalArgumentException("정보를 모두 입력해주세요.");
+ }
+
+ service.modifyWiseSaying(id, newAuthor, newContent);
+ }
+
+ public void delete(String command) {
+ int id = service.parseId(command);
+
+ if (service.deleteById(id)) {
+ System.out.println(id + "번 명언이 삭제되었습니다.");
+ } else {
+ System.out.println(id + "번 명언은 존재하지 않습니다.");
+ }
+ }
+
+ public void build() {
+ if (service.saveAll()) {
+ System.out.println("data.json 파일의 내용이 갱신되었습니다.");
+ } else {
+ throw new IllegalArgumentException("빌드에 실패했습니다.");
+ }
+ }
+}
diff --git a/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSayingRepository.java b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSayingRepository.java
new file mode 100644
index 0000000..a01b108
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSayingRepository.java
@@ -0,0 +1,169 @@
+package com.ll.wiseSaying.testDrivenDevelopment;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import static java.nio.file.Files.readAllBytes;
+
+public class TddWiseSayingRepository {
+
+ public TddWiseSayingRepository() {
+ File folder = new File(DBPATH);
+ if (!folder.exists()) {
+ folder.mkdirs();
+ }
+
+ loadAllFromFile();
+ }
+
+ private static final String DBPATH = "db/wiseSaying";
+ private static final String LAST_ID_PATH = DBPATH + "/lastId.txt";
+ private final List wiseSayingList = new ArrayList<>();
+ private int lastId = 0;
+
+ public int registerWiseSaying(String author, String content) {
+ lastId++;
+ TddWiseSaying wiseSaying = new TddWiseSaying(lastId, author, content);
+ wiseSayingList.add(wiseSaying);
+ wiseSayingList.sort(Comparator.comparingInt(TddWiseSaying::getId).reversed());
+
+ saveLastIdToFile();
+ saveWiseSayingToFile(wiseSaying);
+
+ return lastId;
+ }
+
+ public TddPage findAll(int pageNum, int pageSize) {
+ int start = Math.min((pageNum-1) * pageSize, wiseSayingList.size());
+ int end = Math.min(start + pageSize, wiseSayingList.size());
+
+ List result = wiseSayingList.subList(start, end);
+ return new TddPage<>(result, result.size() / pageSize + 1, pageNum);
+ }
+
+ public TddPage findAllByContent(int pageNum, int pageSize, String content) {
+ List result = wiseSayingList.stream()
+ .filter(w -> w.getContent().contains(content))
+ .toList();
+
+ int start = Math.min((pageNum-1) * pageSize, result.size());
+ int end = Math.min(start + pageSize, result.size());
+
+ return new TddPage<>(result.subList(start, end), result.size() / pageSize + 1, pageNum);
+ }
+
+ public TddPage findAllByAuthor(int pageNum, int pageSize, String author) {
+ List result = wiseSayingList.stream()
+ .filter(w -> w.getAuthor().contains(author))
+ .toList();
+
+ int start = Math.min((pageNum-1) * pageSize, result.size());
+ int end = Math.min(start + pageSize, result.size());
+
+ return new TddPage<>(result.subList(start, end), result.size() / pageSize + 1, pageNum);
+ }
+
+ public TddWiseSaying findById(int id) {
+ return wiseSayingList.stream()
+ .filter(w -> w.getId() == id)
+ .findFirst()
+ .orElse(null);
+ }
+
+ public void modifyWiseSaying(int id, String author, String content) {
+ TddWiseSaying wiseSaying = findById(id);
+ wiseSaying.setContent(content);
+ wiseSaying.setAuthor(author);
+ saveWiseSayingToFile(wiseSaying);
+ }
+
+ public boolean deleteById(int id) {
+ TddWiseSaying wiseSaying = findById(id);
+ wiseSayingList.remove(wiseSaying);
+ return new File(DBPATH + "/" + id + ".json").delete();
+ }
+
+ public boolean saveAll() {
+ try {
+ File file = new File(DBPATH + "/data.json");
+ BufferedWriter bw = new BufferedWriter(new FileWriter(file));
+ bw.write("[\n");
+ for (TddWiseSaying w : wiseSayingList) {
+ String json = " {\n \"id\": " + w.getId()
+ + ",\n \"content\": \"" + w.getContent()
+ + "\",\n \"author\": \"" + w.getAuthor() + "\"\n }";
+ if (wiseSayingList.indexOf(w) < wiseSayingList.size()-1) {
+ json += ",";
+ }
+ bw.write(json);
+ }
+ bw.write("\n]");
+ bw.flush();
+ bw.close();
+
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ private void saveWiseSayingToFile(TddWiseSaying wiseSaying) {
+ try {
+ File file = new File(DBPATH + "/" + wiseSaying.getId() + ".json");
+ BufferedWriter bw = new BufferedWriter(new FileWriter(file));
+ String json = "{\n \"id\": " + wiseSaying.getId()
+ + ",\n \"content\": \"" + wiseSaying.getContent()
+ + "\",\n \"author\": \"" + wiseSaying.getAuthor() + "\"\n}";
+ bw.write(json);
+ bw.flush();
+ bw.close();
+ } catch (IOException ignored) {
+ }
+ }
+
+ private void saveLastIdToFile() {
+ try {
+ Files.write(new File(LAST_ID_PATH).toPath(), String.valueOf(lastId).getBytes());
+ } catch (IOException ignored) {
+ }
+ }
+
+ private void loadAllFromFile() {
+ File lastIdFile = new File(LAST_ID_PATH);
+
+ if (lastIdFile.exists()) {
+ try {
+ lastId = Integer.parseInt(new String(readAllBytes(lastIdFile.toPath())));
+ } catch (IOException ignored) {
+ }
+ }
+
+ File folder = new File(DBPATH);
+ File[] files = folder.listFiles((dir, name) -> name.endsWith(".json"));
+
+ if (files != null) {
+ for (File file : files) {
+ try {
+ BufferedReader br = new BufferedReader(new FileReader(file));
+ String json = br.readLine();
+
+ json = br.readLine();
+ String id = json.substring(json.indexOf(":") + 2, json.indexOf(",")).trim();
+ json = br.readLine();
+ String content = json.substring(json.indexOf(":") + 3, json.indexOf("\",")).trim();
+ json = br.readLine();
+ String author = json.substring(json.indexOf(":") + 3, json.lastIndexOf("\""));
+
+ TddWiseSaying wiseSaying = new TddWiseSaying(Integer.parseInt(id), author, content);
+ wiseSayingList.add(wiseSaying);
+ } catch (IOException ignored) {
+ }
+ }
+
+ wiseSayingList.sort(Comparator.comparingInt(TddWiseSaying::getId).reversed());
+ }
+ }
+}
diff --git a/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSayingService.java b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSayingService.java
new file mode 100644
index 0000000..fe2f733
--- /dev/null
+++ b/src/main/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSayingService.java
@@ -0,0 +1,79 @@
+package com.ll.wiseSaying.testDrivenDevelopment;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class TddWiseSayingService {
+
+ private final TddWiseSayingRepository repository;
+
+ public TddWiseSayingService(TddWiseSayingRepository repository) {
+ this.repository = repository;
+ }
+
+ public int registerWiseSaying(String author, String content) {
+ return repository.registerWiseSaying(author, content);
+ }
+
+ public TddPage findAll(int pageNum, int pageSize) {
+ return repository.findAll(pageNum, pageSize);
+ }
+
+ public TddPage findDetail(int pageNum, int pageSize, String keywordType, String keyword) {
+ if (keywordType.equals("content")) {
+ return repository.findAllByContent(pageNum, pageSize, keyword);
+ } else if (keywordType.equals("author")) {
+ return repository.findAllByAuthor(pageNum, pageSize, keyword);
+ }
+
+ return null;
+ }
+
+ public Map parseSearchKeyword(String command) {
+ Map result = new HashMap<>();
+ if (!command.contains("?")) return result;
+
+ String[] splitCommand = command.split("\\?")[1].split("&");
+
+ for (String s : splitCommand) {
+ result.put(s.split("=")[0], s.split("=")[1]);
+ }
+
+ return result;
+ }
+
+ public boolean validateSearchKeyword(Map keywordList) {
+ if (keywordList.isEmpty()) return true;
+
+ Set allowedKeys = Set.of("page", "keyword", "keywordType");
+
+ for (String key : keywordList.keySet()) {
+ if (!allowedKeys.contains(key)) {
+ return false;
+ }
+ }
+
+ return keywordList.containsKey("keywordType") == keywordList.containsKey("keyword");
+ }
+
+ public int parseId(String command) {
+ return Integer.parseInt(command.split("id=")[1]);
+ }
+
+ public TddWiseSaying findById(int id) {
+ return repository.findById(id);
+ }
+
+ public void modifyWiseSaying(int id, String author, String content) {
+ repository.modifyWiseSaying(id, author, content);
+ }
+
+ public boolean deleteById(int id) {
+ return repository.deleteById(id);
+ }
+
+ public boolean saveAll() {
+ return repository.saveAll();
+ }
+}
diff --git a/src/test/java/com/ll/wiseSaying/basicDevelopment/AppTest.java b/src/test/java/com/ll/wiseSaying/basicDevelopment/AppTest.java
new file mode 100644
index 0000000..3d25fc4
--- /dev/null
+++ b/src/test/java/com/ll/wiseSaying/basicDevelopment/AppTest.java
@@ -0,0 +1,28 @@
+package com.ll.wiseSaying.basicDevelopment;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Objects;
+
+public class AppTest {
+
+ private static final String DBPATH = "db/wiseSaying";
+
+ public static void clear() {
+ File folder = new File(DBPATH);
+
+ if (folder.exists() && folder.isDirectory()) {
+ for (File file : Objects.requireNonNull(folder.listFiles())) {
+ if (file.isFile()) {
+ file.delete();
+ }
+ }
+ }
+
+ try {
+ Files.write(new File(DBPATH+"/lastId.txt").toPath(), String.valueOf(0).getBytes());
+ } catch (IOException ignored) {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/ll/wiseSaying/basicDevelopment/TestUtil.java b/src/test/java/com/ll/wiseSaying/basicDevelopment/TestUtil.java
new file mode 100644
index 0000000..4c65ade
--- /dev/null
+++ b/src/test/java/com/ll/wiseSaying/basicDevelopment/TestUtil.java
@@ -0,0 +1,32 @@
+package com.ll.wiseSaying.basicDevelopment;
+
+import java.io.*;
+import java.util.Scanner;
+
+public class TestUtil {
+ // gen == generate 생성하다.
+ // 테스트용 스캐너 생성
+ public static Scanner genScanner(final String input) {
+ final InputStream in = new ByteArrayInputStream(input.getBytes());
+
+ return new Scanner(in);
+ }
+
+ // System.out의 출력을 스트림으로 받기
+ public static ByteArrayOutputStream setOutToByteArray() {
+ final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(output));
+
+ return output;
+ }
+
+ // setOutToByteArray 함수의 사용을 완료한 후 정리하는 함수, 출력을 다시 정상화 하는 함수
+ public static void clearSetOutToByteArray(final ByteArrayOutputStream output) {
+ System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
+ try {
+ output.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/ll/wiseSaying/basicDevelopment/WiseSayingControllerTest.java b/src/test/java/com/ll/wiseSaying/basicDevelopment/WiseSayingControllerTest.java
new file mode 100644
index 0000000..00a30e6
--- /dev/null
+++ b/src/test/java/com/ll/wiseSaying/basicDevelopment/WiseSayingControllerTest.java
@@ -0,0 +1,119 @@
+package com.ll.wiseSaying.basicDevelopment;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.DisplayName;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Scanner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class WiseSayingControllerTest {
+
+ private ByteArrayOutputStream outContent;
+ private WiseSayingController controller;
+
+ @BeforeEach
+ void beforeEach() {
+ AppTest.clear();
+ controller = WiseSayingController.getInstance();
+ outContent = TestUtil.setOutToByteArray();
+ }
+
+ @AfterEach
+ void afterEach() {
+ TestUtil.clearSetOutToByteArray(outContent);
+ }
+
+ @Test
+ @DisplayName("등록")
+ void testRegister() {
+ // given
+ String input = "현재를 사랑하라.\n작자미상\n";
+ Scanner scanner = TestUtil.genScanner(input);
+
+ // when
+ controller.registerWiseSaying(scanner);
+
+ // then
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("명언 :")
+ .contains("작가 :")
+ .contains("1번 명언이 등록되었습니다.");
+ }
+
+ @Test
+ @DisplayName("목록")
+ void testGetQuoteList() {
+ // given
+ String input = "현재를 사랑하라.\n작자미상\n";
+ Scanner scanner = TestUtil.genScanner(input);
+ controller.registerWiseSaying(scanner);
+
+ // when
+ controller.searchWiseSaying("목록");
+
+ // then
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("번호 / 작가 / 명언")
+ .contains("1 / 작자미상 / 현재를 사랑하라.");
+ }
+
+ @Test
+ @DisplayName("삭제")
+ void testDeleteQuote() {
+ // given
+ String input = "현재를 사랑하라.\n작자미상\n";
+ Scanner scanner = TestUtil.genScanner(input);
+ controller.registerWiseSaying(scanner);
+
+ // when
+ controller.deleteWiseSaying("삭제?id=1");
+
+ // then
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("1번 명언이 삭제되었습니다.");
+ }
+
+ @Test
+ @DisplayName("수정")
+ void testUpdateQuote() {
+ // given
+ String input = "현재를 사랑하라.\n작자미상\n";
+ Scanner scanner = TestUtil.genScanner(input);
+ controller.registerWiseSaying(scanner);
+
+ // when
+ String updateInput = "새로운 명언\n새로운 작자\n";
+ scanner = TestUtil.genScanner(updateInput);
+ controller.updateWiseSaying(scanner, "수정?id=1");
+ controller.searchWiseSaying("목록");
+
+ // then
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("명언(기존) : 현재를 사랑하라.")
+ .contains("명언 :")
+ .contains("작가(기존) : 작자미상")
+ .contains("작가 :")
+ .contains("번호 / 작가 / 명언")
+ .contains("1 / 새로운 작자 / 새로운 명언");
+ }
+
+ @Test
+ @DisplayName("빌드 명령어 테스트")
+ void testBuildData() {
+ // when
+ controller.buildData();
+
+ // then
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("data.json 파일의 내용이 갱신되었습니다.");
+ }
+}
diff --git a/src/test/java/com/ll/wiseSaying/testDrivenDevelopment/TddAppTest.java b/src/test/java/com/ll/wiseSaying/testDrivenDevelopment/TddAppTest.java
new file mode 100644
index 0000000..b6d07a9
--- /dev/null
+++ b/src/test/java/com/ll/wiseSaying/testDrivenDevelopment/TddAppTest.java
@@ -0,0 +1,28 @@
+package com.ll.wiseSaying.testDrivenDevelopment;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Objects;
+
+public class TddAppTest {
+
+ private static final String DBPATH = "db/wiseSaying";
+
+ public static void clear() {
+ File folder = new File(DBPATH);
+
+ if (folder.exists() && folder.isDirectory()) {
+ for (File file : Objects.requireNonNull(folder.listFiles())) {
+ if (file.isFile()) {
+ file.delete();
+ }
+ }
+ }
+
+ try {
+ Files.write(new File(DBPATH+"/lastId.txt").toPath(), String.valueOf(0).getBytes());
+ } catch (IOException ignored) {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/ll/wiseSaying/testDrivenDevelopment/TddTestUtil.java b/src/test/java/com/ll/wiseSaying/testDrivenDevelopment/TddTestUtil.java
new file mode 100644
index 0000000..dd1611c
--- /dev/null
+++ b/src/test/java/com/ll/wiseSaying/testDrivenDevelopment/TddTestUtil.java
@@ -0,0 +1,32 @@
+package com.ll.wiseSaying.testDrivenDevelopment;
+
+import java.io.*;
+import java.util.Scanner;
+
+public class TddTestUtil {
+ // gen == generate 생성하다.
+ // 테스트용 스캐너 생성
+ public static Scanner genScanner(final String input) {
+ final InputStream in = new ByteArrayInputStream(input.getBytes());
+
+ return new Scanner(in);
+ }
+
+ // System.out의 출력을 스트림으로 받기
+ public static ByteArrayOutputStream setOutToByteArray() {
+ final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(output));
+
+ return output;
+ }
+
+ // setOutToByteArray 함수의 사용을 완료한 후 정리하는 함수, 출력을 다시 정상화 하는 함수
+ public static void clearSetOutToByteArray(final ByteArrayOutputStream output) {
+ System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
+ try {
+ output.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSayingControllerTest.java b/src/test/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSayingControllerTest.java
new file mode 100644
index 0000000..0c073b8
--- /dev/null
+++ b/src/test/java/com/ll/wiseSaying/testDrivenDevelopment/TddWiseSayingControllerTest.java
@@ -0,0 +1,322 @@
+package com.ll.wiseSaying.testDrivenDevelopment;
+
+import org.junit.jupiter.api.*;
+
+import java.nio.file.Files;
+import java.io.*;
+import java.util.Scanner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class TddWiseSayingControllerTest {
+
+ private ByteArrayOutputStream outContent;
+ private TddWiseSayingController controller;
+
+ @BeforeEach
+ void beforeEach() {
+ TddAppTest.clear();
+ controller = new TddApp().getController();
+ outContent = TddTestUtil.setOutToByteArray();
+ }
+
+ @AfterEach
+ void afterEach() {
+ TddTestUtil.clearSetOutToByteArray(outContent);
+ }
+
+ @Test
+ @DisplayName("빈 테스트")
+ void test_Empty() {
+
+ }
+
+ @Test
+ @DisplayName("등록")
+ void testRegistration() {
+ String input = "현재를 사랑하라.\n작자미상";
+ Scanner scanner = TddTestUtil.genScanner(input);
+
+ controller.register(scanner);
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("명언 :")
+ .contains("작가 :")
+ .contains("1번 명언이 등록되었습니다.");
+ }
+
+ @Test
+ @DisplayName("등록: 빈 명언")
+ void testRegistration_EmptyContent() {
+ String input = "\n작자미상";
+ Scanner scanner = TddTestUtil.genScanner(input);
+
+ Exception exception = assertThrows(IllegalArgumentException.class,
+ () -> controller.register(scanner));
+
+ String output = outContent.toString();
+ assertThat(output)
+ .doesNotContain("1번 명언이 등록되었습니다.");
+ assertEquals(exception.getMessage(), "명언과 작가를 모두 입력해주세요.");
+ }
+
+ @Test
+ @DisplayName("단순 검색")
+ void testSearchAll() {
+ String input = "현재를 사랑하라.\n작자미상";
+ Scanner scanner = TddTestUtil.genScanner(input);
+ controller.register(scanner);
+
+ controller.search("목록");
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("번호 / 작가 / 명언")
+ .contains("1 / 작자미상 / 현재를 사랑하라.")
+ .contains("페이지 : [1]");
+ }
+
+ @Test
+ @DisplayName("페이지 검색")
+ void testSearch2ndPage() {
+ StringBuilder input = new StringBuilder();
+ Scanner scanner;
+ for (int i = 1; i <= 10; i++) {
+ input.setLength(0);
+ input.append("content").append(i).append("\nauthor").append(i).append("\n");
+ scanner = TddTestUtil.genScanner(input.toString());
+ controller.register(scanner);
+ }
+
+ controller.search("목록?page=2");
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("1번 명언이 등록되었습니다.")
+ .contains("10번 명언이 등록되었습니다.")
+ .contains("번호 / 작가 / 명언")
+ .contains("3 / author3 / content3")
+ .contains("페이지 : 1 / [2]");
+ }
+
+ @Test
+ @DisplayName("명언 키워드 검색")
+ void testSearchContentKeyword() {
+ StringBuilder input = new StringBuilder();
+ Scanner scanner;
+ for (int i = 0; i < 5; i++) {
+ input.setLength(0);
+ input.append("content").append(i % 2).append("\nauthor").append(i + 1).append("\n");
+ scanner = TddTestUtil.genScanner(input.toString());
+ controller.register(scanner);
+ }
+
+
+ controller.search("목록?keywordType=content&keyword=1");
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("번호 / 작가 / 명언")
+ .contains("4 / author4 / content1")
+ .contains("2 / author2 / content1")
+ .doesNotContain("1 / author1 / content0")
+ .contains("페이지 :");
+ }
+
+ @Test
+ @DisplayName("작가 키워드 검색")
+ void testSearchAuthorKeyword() {
+ StringBuilder input = new StringBuilder();
+ Scanner scanner;
+ for (int i = 0; i < 5; i++) {
+ input.setLength(0);
+ input.append("content").append(i + 1).append("\nauthor").append(i % 2).append("\n");
+ scanner = TddTestUtil.genScanner(input.toString());
+ controller.register(scanner);
+ }
+
+ controller.search("목록?keywordType=author&keyword=1");
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("번호 / 작가 / 명언")
+ .contains("4 / author1")
+ .contains("2 / author1")
+ .doesNotContain("1 / author0")
+ .contains("페이지 :");
+ }
+
+ @Test
+ @DisplayName("키워드 복합 검색")
+ void testSearchComplexKeyword() {
+ StringBuilder input = new StringBuilder();
+ Scanner scanner;
+ for (int i = 0; i < 18; i++) {
+ input.setLength(0);
+ input.append("content").append(i + 1).append("\nauthor").append(i % 2).append("\n");
+ scanner = TddTestUtil.genScanner(input.toString());
+ controller.register(scanner);
+ }
+
+ controller.search("목록?page=2&keywordType=author&keyword=1");
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("번호 / 작가 / 명언")
+ .contains("2 / author1")
+ .contains("페이지 :")
+ .contains("[2]");
+ }
+
+ @Test
+ @DisplayName("잘못된 키워드: 존재하지 않는 항목")
+ void testSearch_InvalidSearchKeyword() {
+ Exception exception = assertThrows(IllegalArgumentException.class,
+ () ->controller.search("목록?keyboard=3"));
+
+ String output = outContent.toString();
+ assertThat(output)
+ .doesNotContain("번호 / 작가 / 명언");
+ assertEquals(exception.getMessage(), "명령을 다시 확인해주세요.");
+ }
+
+ @Test
+ @DisplayName("잘못된 키워드: keywordType&keyword 세트가 아님")
+ void testSearch_NotSufficientSearchKeyword() {
+ Exception exception = assertThrows(IllegalArgumentException.class,
+ () ->controller.search("목록?keyword=key"));
+
+ String output = outContent.toString();
+ assertThat(output)
+ .doesNotContain("번호 / 작가 / 명언");
+ assertEquals(exception.getMessage(), "명령을 다시 확인해주세요.");
+ }
+
+ @Test
+ @DisplayName("검색 결과가 존재하지 않음")
+ void testSearch_EmptyResult() {
+ controller.search("목록?page=1&keywordType=content&keyword=현재");
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("번호 / 작가 / 명언")
+ .contains("검색 결과가 존재하지 않습니다.")
+ .doesNotContain("1 / 작자미상 / 현재를 사랑하라.");
+ }
+
+ @Test
+ @DisplayName("범위를 벗어난 페이지")
+ void testSearch_OutOfBounds() {
+ controller.search("목록?page=100");
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("번호 / 작가 / 명언")
+ .contains("검색 결과가 존재하지 않습니다.");
+ }
+
+ @Test
+ @DisplayName("수정")
+ void testModify() {
+ String existInput = "현재를 사랑하라.\n작자미상";
+ Scanner scanner = TddTestUtil.genScanner(existInput);
+ controller.register(scanner);
+
+ String newInput = "현재와 자신을 사랑하라.\n홍길동";
+ scanner = TddTestUtil.genScanner(newInput);
+
+ controller.modify(scanner, "수정?id=1");
+ controller.search("목록");
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("명언(기존) : 현재를 사랑하라.")
+ .contains("작가(기존) : 작자미상")
+ .contains("1 / 홍길동 / 현재와 자신을 사랑하라");
+ }
+
+ @Test
+ @DisplayName("수정 실패: 명언이 존재하지 않음")
+ void testModify_NotFound() {
+ String input = "현재와 자신을 사랑하라.\n홍길동";
+ Scanner scanner = TddTestUtil.genScanner(input);
+
+ controller.modify(scanner, "수정?id=1");
+ controller.search("목록");
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("1번 명언은 존재하지 않습니다.");
+ }
+
+ @Test
+ @DisplayName("수정 실패: 수정사항이 적절하지 않음")
+ void testModify_InvalidArgument() {
+ String existInput = "현재를 사랑하라.\n작자미상";
+ Scanner existScanner = TddTestUtil.genScanner(existInput);
+ controller.register(existScanner);
+
+ String newInput = "\n ";
+ Scanner newScanner = TddTestUtil.genScanner(newInput);
+
+ Exception exception = assertThrows(IllegalArgumentException.class,
+ () -> controller.modify(newScanner, "수정?id=1"));
+
+ assertEquals(exception.getMessage(), "정보를 모두 입력해주세요.");
+ }
+
+ @Test
+ @DisplayName("삭제 성공")
+ void testDelete_Success() {
+ String input = "현재를 사랑하라.\n작자미상";
+ Scanner scanner = TddTestUtil.genScanner(input);
+ controller.register(scanner);
+
+ controller.delete("삭제?id=1");
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("1번 명언이 삭제되었습니다.");
+ }
+
+ @Test
+ @DisplayName("삭제 실패")
+ void testDelete_Failure() {
+ controller.delete("삭제?id=1");
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("1번 명언은 존재하지 않습니다.");
+ }
+
+ @Test
+ @DisplayName("빌드")
+ void testBuild() throws IOException {
+ String input = "현재를 사랑하라.\n작자미상";
+ Scanner scanner = TddTestUtil.genScanner(input);
+ controller.register(scanner);
+
+ controller.build();
+
+ String output = outContent.toString();
+ assertThat(output)
+ .contains("data.json 파일의 내용이 갱신되었습니다.");
+
+ File dataFile = new File("db/wiseSaying/data.json");
+ assertThat(dataFile.exists()).isTrue();
+ String fileContent = Files.readString(dataFile.toPath());
+ String expectedContent = """
+ [
+ {
+ "id": 1,
+ "content": "현재를 사랑하라.",
+ "author": "작자미상"
+ }
+ ]
+ """;
+ assertThat(fileContent).isEqualToIgnoringWhitespace(expectedContent);
+ }
+}