diff --git a/project/.gitignore b/project/.gitignore
new file mode 100644
index 0000000..4212cf1
--- /dev/null
+++ b/project/.gitignore
@@ -0,0 +1,46 @@
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### local db files ###
+db/**
+
+### IntelliJ IDEA ###
+.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/project/.idea/.gitignore b/project/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/project/.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/project/.idea/gradle.xml b/project/.idea/gradle.xml
new file mode 100644
index 0000000..2a65317
--- /dev/null
+++ b/project/.idea/gradle.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/.idea/inspectionProfiles/Project_Default.xml b/project/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..7602851
--- /dev/null
+++ b/project/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/.idea/misc.xml b/project/.idea/misc.xml
new file mode 100644
index 0000000..fae6c8e
--- /dev/null
+++ b/project/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/.idea/vcs.xml b/project/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/project/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/build.gradle b/project/build.gradle
new file mode 100644
index 0000000..0b1f88a
--- /dev/null
+++ b/project/build.gradle
@@ -0,0 +1,43 @@
+plugins {
+ id 'java'
+}
+
+group = 'jpabook'
+version = '1.0-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform('org.junit:junit-bom:5.10.0')
+ testImplementation 'org.junit.jupiter:junit-jupiter'
+ testImplementation("org.assertj:assertj-core:3.26.3")
+
+ // Mockito core
+ testImplementation 'org.mockito:mockito-core:4.8.0'
+
+ // Mockito JUnit Jupiter support
+ testImplementation 'org.mockito:mockito-junit-jupiter:4.8.0'
+
+ // Lombok
+ implementation 'org.projectlombok:lombok:1.18.24'
+ annotationProcessor 'org.projectlombok:lombok:1.18.24'
+
+ // Jackson Databind (ObjectMapper 포함)
+ implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.3'
+
+ // SLF4J API
+ implementation 'org.slf4j:slf4j-api:1.7.36'
+
+ // Logback을 SLF4J 구현으로 사용하는 경우
+ implementation 'ch.qos.logback:logback-classic:1.2.11'
+
+ implementation 'org.json:json:20220924'
+
+
+}
+
+test {
+ useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/project/db/wiseSaying/1.json b/project/db/wiseSaying/1.json
new file mode 100644
index 0000000..cbd9ca8
--- /dev/null
+++ b/project/db/wiseSaying/1.json
@@ -0,0 +1 @@
+{"author":"111","id":1,"content":"11"}
\ No newline at end of file
diff --git a/project/db/wiseSaying/lastId.txt b/project/db/wiseSaying/lastId.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/project/db/wiseSaying/lastId.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/project/gradle/wrapper/gradle-wrapper.jar b/project/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..249e583
Binary files /dev/null and b/project/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/project/gradle/wrapper/gradle-wrapper.properties b/project/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ca85ac5
--- /dev/null
+++ b/project/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Dec 19 09:26:26 KST 2024
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/project/gradlew b/project/gradlew
new file mode 100755
index 0000000..1b6c787
--- /dev/null
+++ b/project/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/project/gradlew.bat b/project/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/project/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/project/package.json b/project/package.json
new file mode 100644
index 0000000..483ea3d
--- /dev/null
+++ b/project/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "project",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "private": true
+}
diff --git a/project/settings.gradle b/project/settings.gradle
new file mode 100644
index 0000000..e3a4a1f
--- /dev/null
+++ b/project/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'project'
+
diff --git a/project/src/main/java/wiseSaying/App.java b/project/src/main/java/wiseSaying/App.java
new file mode 100644
index 0000000..2228ce2
--- /dev/null
+++ b/project/src/main/java/wiseSaying/App.java
@@ -0,0 +1,96 @@
+package wiseSaying;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+
+@Slf4j
+public class App {
+
+ private WiseSayingController controller = WiseSayingController.getInstance();
+
+ private String condition;
+ private String commandType;
+
+ public void app() {
+ System.out.println("== 명언 앱 ==");
+ while (true) {
+ inputCommand();
+ }
+ }
+
+ public void inputCommand() {
+ readInputString();
+ selectMenu();
+ }
+
+ public void selectMenu() {
+ if (Command.containsCommand(commandType)) {
+ //등록 목록 삭제 수정 빌드 종료
+ switch (commandType) {
+ case "등록":
+ register();
+ break;
+ case "목록":
+ controller.search(condition);
+ break;
+ case "삭제" :
+ controller.remove(condition);
+ break;
+ case "수정" :
+ controller.modify(condition);
+ break;
+ case "빌드":
+ controller.build();
+ break;
+ case "종료":
+ controller.exit();
+ break;
+ }
+ } else {
+ System.out.println("다시 입력해 주세요");
+ }
+ }
+
+ private void register() {
+ System.out.print("명언 : ");
+ String content = reader();
+ System.out.print("작가 : ");
+ String author = reader();
+ Long registeredId = controller.register(content, author);
+ System.out.println(registeredId + "번 명언이 등록되었습니다.");
+ }
+
+ public void readInputString() {
+ try {
+ System.out.print("명령) ");
+ findRealCommand(reader());
+ } catch (Exception e) {
+ log.error("잘못된 명령 요청");
+ }
+ }
+
+ public String reader() {
+ BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
+ try {
+ return bf.readLine();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void findRealCommand(String input) {
+ commandType = input;
+ condition = input;
+
+ int questionMarkIndex = condition.indexOf('?');
+ if (questionMarkIndex != -1) {
+ // ?가 포함된 경우
+ commandType = condition.substring(0, questionMarkIndex);
+ }
+ }
+}
diff --git a/project/src/main/java/wiseSaying/Command.java b/project/src/main/java/wiseSaying/Command.java
new file mode 100644
index 0000000..43af60d
--- /dev/null
+++ b/project/src/main/java/wiseSaying/Command.java
@@ -0,0 +1,14 @@
+package wiseSaying;
+
+public enum Command {
+ 등록, 목록, 삭제, 수정, 빌드, 종료;
+
+ public static boolean containsCommand(String command) {
+ try {
+ Command.valueOf(command);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+}
diff --git a/project/src/main/java/wiseSaying/Main.java b/project/src/main/java/wiseSaying/Main.java
new file mode 100644
index 0000000..ac03fa9
--- /dev/null
+++ b/project/src/main/java/wiseSaying/Main.java
@@ -0,0 +1,8 @@
+package wiseSaying;
+
+public class Main {
+ public static void main(String[] args) {
+ new App().app();
+
+ }
+}
diff --git a/project/src/main/java/wiseSaying/Repository.java b/project/src/main/java/wiseSaying/Repository.java
new file mode 100644
index 0000000..5a708d1
--- /dev/null
+++ b/project/src/main/java/wiseSaying/Repository.java
@@ -0,0 +1,18 @@
+package wiseSaying;
+
+import java.util.Optional;
+import java.util.Set;
+
+public interface Repository {
+ Long add(WiseSaying wise);
+
+ Optional findById(Long id);
+
+ Set findByContent(String content);
+
+ Long modify(Long id, String content, String author);
+
+ Set findAll();
+
+ Long remove(Long id);
+}
diff --git a/project/src/main/java/wiseSaying/WiseSaying.java b/project/src/main/java/wiseSaying/WiseSaying.java
new file mode 100644
index 0000000..d225ee6
--- /dev/null
+++ b/project/src/main/java/wiseSaying/WiseSaying.java
@@ -0,0 +1,21 @@
+package wiseSaying;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter @Setter
+public class WiseSaying {
+ private static Long sequence = 0L;
+
+ private Long id;
+ private String content;
+ private String author;
+
+ public static WiseSaying createWise(String content, String author) {
+ return new WiseSaying(++sequence, content, author);
+ }
+}
diff --git a/project/src/main/java/wiseSaying/WiseSayingController.java b/project/src/main/java/wiseSaying/WiseSayingController.java
new file mode 100644
index 0000000..87135fd
--- /dev/null
+++ b/project/src/main/java/wiseSaying/WiseSayingController.java
@@ -0,0 +1,202 @@
+package wiseSaying;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.*;
+
+@Slf4j
+public class WiseSayingController {
+
+ private final static WiseSayingController instance = new WiseSayingController();
+ private WiseSayingController(){}
+ public static synchronized WiseSayingController getInstance() {
+ return instance;
+ }
+
+ public final static int pagingSize = 5;
+
+
+ private WiseSayingService service = WiseSayingService.getInstance();
+
+ public Long register(String content, String author) {
+ Long savedId = service.register(content, author);
+ return savedId;
+ }
+
+ public List search(String condition) {
+ String keywordType = null;
+ String keyword = null;
+
+ Map conditionMap = null;
+
+ //조건 ("?" 있는) 목록 조회
+ if (condition.contains("?")) {
+ conditionMap = parseParameters(condition);
+ if (conditionMap.containsKey("keywordType")) {
+ keywordType = conditionMap.get("keywordType");
+ System.out.println("----------------------");
+ System.out.println("검색타입 : " + keywordType);
+ } else {
+ keywordType = null;
+ }
+
+ if (conditionMap.containsKey("keyword")) {
+ keyword = conditionMap.get("keyword");
+ System.out.println("검색어 : " + keyword);
+ System.out.println("----------------------");
+ } else {
+ keyword = null;
+ }
+ }
+
+ Set searchSet = service.search(keywordType, keyword);
+
+ return paging(conditionMap, searchSet, keywordType, keyword);
+ }
+
+ public void remove(String command) {
+ if (command.contains("?")) {
+ Map conditionMap = parseParameters(command);
+ Long removeId = Long.parseLong(conditionMap.get("id"));
+
+ Long removedId = service.remove(removeId);
+ if (removedId != null) {
+ System.out.println(removedId + "번 명언이 삭제되었습니다.");
+ } else {
+ System.out.println(removeId + "번 명언은 존재하지 않습니다.");
+ }
+ } else {
+ System.out.println("id 값 입력이 필요합니다.");
+ }
+
+ }
+
+ public void modify(String command) {
+ if (!command.contains("?")) {
+ System.out.println("id 값 입력이 필요합니다.");
+ return;
+ }
+
+ Map map = parseParameters(command);
+ Long modifyId = Long.parseLong(map.get("id"));
+ WiseSaying modifyWise = service.searchOneWise(modifyId);
+ if (modifyWise == null) {
+ System.out.println(modifyId + "번 명언은 존재하지 않습니다");
+ return;
+ }
+
+ System.out.println("명언(기존) : " + modifyWise.getContent());
+ System.out.print("명언 : ");
+ String newContent = inputReader();
+
+ System.out.println("작가(기존) : " + modifyWise.getAuthor());
+ System.out.print("작가 : ");
+ String newAuthor = inputReader();
+
+ WiseSaying modifiedWise = service.modify(modifyId, newContent, newAuthor);
+ if (modifiedWise == null) {
+ System.out.println(modifyId + "번 명언 수정에 실패 하였습니다.");
+ return;
+ }
+ }
+
+
+ public void build() {
+ System.out.println("WiseSayingController.build");
+ Boolean buildResult = service.build();
+ if (buildResult) {
+ System.out.println("data.json 파일의 내용이 갱신되었습니다.");
+ } else {
+ System.out.println("data.json 파일 저장에 실패했습니다.");
+ }
+
+ }
+
+ public void exit() {
+ System.exit(0);
+ }
+
+
+ private String inputReader() {
+ BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
+ try {
+ String input = bf.readLine();
+ return input;
+ } catch (IOException e) {
+ log.error("input error", e);
+ return null;
+ }
+ }
+
+ private static Map parseParameters(String input) {
+ Map params = new HashMap<>();
+ String[] parts = input.split("\\?");
+ if (parts.length > 1) {
+ String[] queries = parts[1].split("&");
+ for (String query : queries) {
+ String[] keyValue = query.split("=");
+ if (keyValue.length > 1) {
+ params.put(keyValue[0], keyValue[1]);
+ }
+ }
+ }
+ return params;
+ }
+
+ private List setToListWithDesc(Set wiseSet) {
+ List wiseSayings = new ArrayList<>();
+ for (WiseSaying wiseSaying : wiseSet) {
+ wiseSayings.add(wiseSaying);
+ }
+ wiseSayings.sort((a,b) -> (int) (b.getId() - a.getId()));
+ return wiseSayings;
+ }
+
+ private List paging(Map conditionMap, Set searchSet, String keywordType, String keyword) {
+ List wiseSayings = setToListWithDesc(searchSet);
+ int page = 1;
+
+ if (conditionMap != null && conditionMap.containsKey("page")) {
+ page = Integer.parseInt(conditionMap.get("page"));
+ }
+
+ int totalItems = wiseSayings.size(); // 전체 명언 수
+ int totalPages = (totalItems + pagingSize - 1) / pagingSize; // 전체 페이지 수 계산
+
+ int startIndex = (page - 1) * pagingSize; // 현재 페이지의 시작 인덱스
+
+ if (startIndex > totalItems || startIndex < 0) {
+ System.out.println("잘못된 페이지 번호입니다.");
+ return null;
+ }
+
+ int endIndex = Math.min(startIndex + pagingSize, totalItems); // 현재 페이지의 끝 인덱스
+
+ List pageItems = wiseSayings.subList(startIndex, endIndex); // 현재 페이지에 해당하는 명언 목록 추출
+
+ print(pageItems, totalPages, page); // 출력
+
+ return pageItems;
+ }
+
+ private void print(List pageItems, int totalPages, int page) {
+ System.out.println("번호 / 작가 / 명언");
+ System.out.println("----------------------");
+ for (WiseSaying wise : pageItems) {
+ System.out.println(wise.getId() + " / " + wise.getAuthor() + " / " + wise.getContent());
+ }
+ // 페이지 네비게이션 정보 출력
+ System.out.print("페이지 : ");
+ for (int i = 1; i <= totalPages; i++) {
+ if (i == page) {
+ System.out.print("[" + i + "] ");
+ } else {
+ System.out.print(i + " ");
+ }
+ }
+ System.out.println();
+ }
+}
diff --git a/project/src/main/java/wiseSaying/WiseSayingRepository.java b/project/src/main/java/wiseSaying/WiseSayingRepository.java
new file mode 100644
index 0000000..8c55050
--- /dev/null
+++ b/project/src/main/java/wiseSaying/WiseSayingRepository.java
@@ -0,0 +1,77 @@
+package wiseSaying;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.HashSet;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+public class WiseSayingRepository implements Repository {
+
+ private static final WiseSayingRepository instance = new WiseSayingRepository();
+
+ private WiseSayingRepository() {}
+
+ public static synchronized WiseSayingRepository getInstance() {
+ return instance;
+ }
+
+ private Set wiseSet = new HashSet<>();
+
+ // db (in memory) 저장 후 저장된 ID 값 return
+ public Long add(WiseSaying wise) {
+ wiseSet.add(wise);
+ return wise.getId();
+ }
+
+ // ID로 Wise 단건 조회
+ public Optional findById(Long id) {
+ return wiseSet.stream()
+ .filter(wise -> wise.getId().equals(id))
+ .findFirst();
+ }
+
+ // 내용(content)으로 Wise 객체 조회
+ public Set findByContent(String content) {
+ return wiseSet.stream()
+ .filter(wise -> wise.getContent().contains(content))
+ .collect(Collectors.toSet());
+ }
+
+ // 저자(author)로 Wise 객체 조회
+ public Set findByAuthor(String author) {
+ return wiseSet.stream()
+ .filter(wise -> wise.getAuthor().contains(author))
+ .collect(Collectors.toSet());
+ }
+
+ // ID 로 검색 후 수정, 수정 후 수정된 ID 값 return
+ public Long modify(Long id, String content, String author) {
+ try {
+ WiseSaying modifiedWise = new WiseSaying(findById(id).orElseThrow(() -> new NoSuchElementException("수정 하고자 하는 명언을 찾을 수 없음.")).getId(), content, author);
+ remove(modifiedWise.getId());
+ return add(modifiedWise);
+ } catch (NoSuchElementException e) {
+ log.info("repository: ", e);
+ throw new RuntimeException("repository : 수정 실패");
+ }
+ }
+
+ // 전체 목록 조회
+ public Set findAll() {
+ return wiseSet;
+ }
+
+ // ID 로 삭제 후 삭제된 ID 값 return
+ public Long remove(Long id) {
+ wiseSet.remove(findById(id).orElseThrow(() -> new RuntimeException("삭제하고자 하는 명언을 찾을 수 없음.")));
+ return id;
+ }
+
+ public void clear() {
+ wiseSet.clear();
+ }
+}
diff --git a/project/src/main/java/wiseSaying/WiseSayingSaveFile.java b/project/src/main/java/wiseSaying/WiseSayingSaveFile.java
new file mode 100644
index 0000000..f5ac769
--- /dev/null
+++ b/project/src/main/java/wiseSaying/WiseSayingSaveFile.java
@@ -0,0 +1,54 @@
+package wiseSaying;
+
+
+import org.json.JSONArray;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Set;
+import org.json.JSONObject;
+
+public class WiseSayingSaveFile {
+ private static Path dbDirectory = Paths.get("db/wiseSaying");
+ private static Path lastIdFile = dbDirectory.resolve("lastId.txt");
+
+ public static void saveWiseByFile(WiseSaying wise) {
+ Path filePath = dbDirectory.resolve(wise.getId() + ".json");
+ JSONObject json = new JSONObject();
+ json.put("id", wise.getId());
+ json.put("content", wise.getContent());
+ json.put("author", wise.getAuthor());
+ try {
+ Files.write(filePath, json.toString().getBytes());
+ System.out.println("파일 저장 성공");
+ } catch (IOException e) {
+ System.out.println("파일 저장에 실패했습니다.");
+ }
+ }
+
+ public static void updateLastId(Long id) {
+ try {
+ Files.write(lastIdFile, String.valueOf(id).getBytes());
+ } catch (IOException e) {
+ System.out.println("파일 저장에 실패했습니다.");
+ }
+ }
+
+ public static void buildFile(Set wiseSet) {
+ Path filePath = dbDirectory.resolve("data.json");
+ JSONArray jsonArray = new JSONArray();
+ for (WiseSaying wise : wiseSet) {
+ JSONObject obj = new JSONObject();
+ obj.put("id", wise.getId());
+ obj.put("content", wise.getContent());
+ obj.put("author", wise.getAuthor());
+ jsonArray.put(obj);
+ }
+ try {
+ Files.write(filePath, jsonArray.toString().getBytes());
+ } catch (IOException e) {
+ throw new RuntimeException("파일 저장에 실패했습니다.");
+ }
+ }
+}
diff --git a/project/src/main/java/wiseSaying/WiseSayingService.java b/project/src/main/java/wiseSaying/WiseSayingService.java
new file mode 100644
index 0000000..271750e
--- /dev/null
+++ b/project/src/main/java/wiseSaying/WiseSayingService.java
@@ -0,0 +1,86 @@
+package wiseSaying;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Set;
+
+@Slf4j
+public class WiseSayingService {
+
+ private static final WiseSayingService instance = new WiseSayingService();
+
+ private WiseSayingService(){}
+
+ public static synchronized WiseSayingService getInstance() {
+ return instance;
+ }
+
+ private WiseSayingRepository repository = WiseSayingRepository.getInstance();
+
+ // 등록
+ public Long register(String content, String author) {
+ WiseSaying wise = WiseSaying.createWise(content, author);
+ Long addedId = repository.add(wise);
+ WiseSayingSaveFile.saveWiseByFile(repository.findById(addedId).orElseThrow(()-> new RuntimeException("해당 ID로 조회되는 명언이 없어서 FILE 저장 실패.")));
+ WiseSayingSaveFile.updateLastId(addedId);
+ return wise.getId();
+ }
+
+ // ID 로 삭제
+ public Long remove(Long id) {
+ try {
+ return repository.remove(id);
+ } catch (RuntimeException e) {
+ log.info("service: 해당 명언을 찾을 수 없음.");
+ return null;
+ }
+ }
+
+ public Set search(String keywordType, String keyword) {
+ if (keywordType == null && keyword == null) {
+ // 전체 목록 출력 : 조건 없음, page 설정 없음
+ return repository.findAll();
+ } else if (keywordType.equalsIgnoreCase("content")) {
+ // keywordType == content
+ return repository.findByContent(keyword);
+ } else if (keywordType.equalsIgnoreCase("author")) {
+ // keywordType == author
+ return repository.findByAuthor(keyword);
+ }
+ return null;
+ }
+
+ // ID 로 단건 조회
+ public WiseSaying searchOneWise(Long id) {
+ try {
+ return repository.findById(id).orElseThrow(() -> new RuntimeException("해당 ID로 조회되는 명언이 없음."));
+ } catch (RuntimeException e) {
+ return null;
+ }
+ }
+
+ // ID 로 수정 후 파일 수정 및 저장
+ public WiseSaying modify(Long id, String content, String author) {
+ try {
+ Long modifiedId = repository.modify(id, content, author);
+ WiseSaying modifiedWise = repository.findById(modifiedId).orElseThrow(() -> new RuntimeException("해당 ID로 조회되는 명언이 없어서 FILE 저장 실패."));
+ WiseSayingSaveFile.saveWiseByFile(modifiedWise);
+ return modifiedWise;
+ } catch (RuntimeException e) {
+ log.info("service : 수정 실패");
+ return null;
+ }
+ }
+
+ //data.json file save
+ public Boolean build() {
+ Set allWise = repository.findAll();
+ try {
+ WiseSayingSaveFile.buildFile(allWise);
+ return true;
+ } catch (RuntimeException e) {
+ return false;
+ }
+ }
+
+}
diff --git a/project/src/test/java/wiseSaying/TestUtil.java b/project/src/test/java/wiseSaying/TestUtil.java
new file mode 100644
index 0000000..3ab2fcd
--- /dev/null
+++ b/project/src/test/java/wiseSaying/TestUtil.java
@@ -0,0 +1,42 @@
+package wiseSaying;
+
+import java.io.*;
+import java.util.Scanner;
+
+public class TestUtil {
+ public static void eraseFile() {
+ // 삭제할 디렉토리 경로 설정
+ File directory = new File("db/wiseSaying");
+
+ // 디렉토리가 존재하고 실제 디렉토리인지 확인
+ if (directory.exists() && directory.isDirectory()) {
+ // 디렉토리 내의 모든 파일과 디렉토리 목록 가져오기
+ File[] files = directory.listFiles();
+
+ if (files != null) {
+ for (File file : files) {
+ // 파일인 경우 삭제
+ if (file.isFile()) {
+ boolean deleted = file.delete();
+ if (deleted) {
+ System.out.println("파일 삭제됨: " + file.getName());
+ } else {
+ System.out.println("파일 삭제 실패: " + file.getName());
+ }
+ }
+ // 하위 디렉토리가 있을 경우 재귀적으로 삭제하려면 아래 주석을 해제하세요.
+ /*
+ else if (file.isDirectory()) {
+ deleteDirectoryRecursively(file);
+ }
+ */
+ }
+ System.out.println("모든 파일 삭제를 시도했습니다.");
+ } else {
+ System.out.println("디렉토리 내에 삭제할 파일이 없습니다.");
+ }
+ } else {
+ System.out.println("디렉토리가 존재하지 않거나 디렉토리가 아닙니다: " + directory.getAbsolutePath());
+ }
+ }
+}
diff --git a/project/src/test/java/wiseSaying/WiseSayingControllerTest.java b/project/src/test/java/wiseSaying/WiseSayingControllerTest.java
new file mode 100644
index 0000000..f4ac41c
--- /dev/null
+++ b/project/src/test/java/wiseSaying/WiseSayingControllerTest.java
@@ -0,0 +1,105 @@
+package wiseSaying;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.*;
+
+
+class WiseSayingControllerTest {
+ private WiseSayingController controller;
+ private WiseSayingService service;
+ private WiseSayingRepository repository;
+
+ @BeforeEach
+ void before() {
+ controller = WiseSayingController.getInstance();
+ service = WiseSayingService.getInstance();
+ repository = WiseSayingRepository.getInstance();
+
+ for (int i = 1; i <= 10; i++) {
+ controller.register("content" + i, "author" + i);
+ }
+ }
+
+ @AfterEach
+ void after() {
+ repository.clear();
+ TestUtil.eraseFile();
+ }
+
+ @Test
+ @DisplayName("등록 후 전체 검색 - 성공")
+ void registerSuccess() {
+ // when
+ List searchList = controller.search("목록");
+
+ // then
+ assertThat(searchList.get(0).getContent()).isEqualTo("content10");
+ assertThat(searchList.get(0).getAuthor()).isEqualTo("author10");
+ }
+
+ @Test
+ @DisplayName("등록 후 조건 검색 - 성공")
+ void registerWithConditionSuccess() {
+ // when
+ List searchList = controller.search("목록?keywordType=content&keyword=1");
+
+ // then
+ assertThat(searchList.stream().findFirst().get().getContent()).isEqualTo("content10");
+ }
+ @Test
+ @DisplayName("등록 후 조건 검색 - 실패")
+ void registerWithConditionFail() {
+ // when
+ List searchList = controller.search("목록?keywordType=content&keyword=2");
+
+ // then
+ assertThat(searchList.get(0).getContent()).isEqualTo("content2");
+ }
+
+ @Test
+ @DisplayName("등록 후 조건 없이 & 페이지 있을 때 - 성공 ")
+ void registerWithPageSuccess() {
+ // when
+ List searchList = controller.search("목록?page=1");
+
+ // then
+ assertThat(searchList.get(0).getContent()).isEqualTo("content10");
+ }
+
+ @Test
+ @DisplayName("등록 후 조건 있을 때 & 페이지 있을 때 - 성공 ")
+ void registerWithConditionAndPageSuccess() {
+ // when
+ List searchList = controller.search("목록?keywordType=content&keyword=1&page=1");
+
+ // then
+ assertThat(searchList.get(0).getContent()).isEqualTo("content10");
+ }
+
+ @Test
+ @DisplayName("등록 후 조건 있을 때 & 페이지 있을 때 - 실패 : page miss ")
+ void registerWithConditionAndPageFail() {
+ // when
+ List searchList = controller.search("목록?keywordType=content&keyword=1&page=2");
+
+ // then
+ assertThat(searchList).isEqualTo(null);
+ }
+
+ @Test
+ @DisplayName("수정 후 정상 변경 검증")
+ void modifySuccess() {
+ // when
+ WiseSaying modified = service.modify(7L, "content77", "author77");
+ // then
+ assertThat(repository.findById(modified.getId()).orElseThrow().getContent()).isEqualTo("content77");
+ assertThat(repository.findById(modified.getId()).orElseThrow().getAuthor()).isEqualTo("author77");
+ }
+}
\ No newline at end of file