diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..60ad9e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Created by https://www.toptal.com/developers/gitignore/api/java +# Edit at https://www.toptal.com/developers/gitignore?templates=java + +.idea/ +.gradle/ +build/ + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar +*.DS_Store + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +# End of https://www.toptal.com/developers/gitignore/api/java diff --git a/README.md b/README.md index 4444cb9..0426158 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -# code-review \ No newline at end of file +## 기능 구현 +- 등록 / 수정 / 삭제 +- 페이징 조회 +- author / content 검색 +- 빌드 파일 내보내기 +--- +- db/wiseSaying/create_dummy.sh 샘플 파일 생성 diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..4d1bf9c --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("java") +} + +group = "org.example" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(platform("org.junit:junit-bom:5.9.1")) + testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation("org.assertj:assertj-core:3.26.3") +} + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/db/wiseSaying/1.json b/db/wiseSaying/1.json new file mode 100644 index 0000000..6b6eda8 --- /dev/null +++ b/db/wiseSaying/1.json @@ -0,0 +1,5 @@ +{ + "id": 1, + "author": "Albert Einstein", + "content": "Life is like riding a bicycle. To keep your balance, you must keep moving." +} diff --git a/db/wiseSaying/10.json b/db/wiseSaying/10.json new file mode 100644 index 0000000..0397da3 --- /dev/null +++ b/db/wiseSaying/10.json @@ -0,0 +1,5 @@ +{ + "id": 10, + "author": "Aristotle", + "content": "Knowing yourself is the beginning of all wisdom." +} diff --git a/db/wiseSaying/11.json b/db/wiseSaying/11.json new file mode 100644 index 0000000..3be6545 --- /dev/null +++ b/db/wiseSaying/11.json @@ -0,0 +1,5 @@ +{ + "id": 11, + "author": "Maya Angelou", + "content": "You may not control all the events that happen to you, but you can decide not to be reduced by them." +} diff --git a/db/wiseSaying/12.json b/db/wiseSaying/12.json new file mode 100644 index 0000000..8b9c4d8 --- /dev/null +++ b/db/wiseSaying/12.json @@ -0,0 +1,5 @@ +{ + "id": 12, + "author": "John Lennon", + "content": "Life is what happens when you’re busy making other plans." +} diff --git a/db/wiseSaying/13.json b/db/wiseSaying/13.json new file mode 100644 index 0000000..be23083 --- /dev/null +++ b/db/wiseSaying/13.json @@ -0,0 +1,5 @@ +{ + "id": 13, + "author": "Vincent Van Gogh", + "content": "I dream my painting and I paint my dream." +} diff --git a/db/wiseSaying/14.json b/db/wiseSaying/14.json new file mode 100644 index 0000000..84666b2 --- /dev/null +++ b/db/wiseSaying/14.json @@ -0,0 +1,5 @@ +{ + "id": 14, + "author": "Albert Einstein", + "content": "Imagination is more important than knowledge." +} diff --git a/db/wiseSaying/15.json b/db/wiseSaying/15.json new file mode 100644 index 0000000..b329166 --- /dev/null +++ b/db/wiseSaying/15.json @@ -0,0 +1,5 @@ +{ + "id": 15, + "author": "Leonardo da Vinci", + "content": "Learning never exhausts the mind." +} diff --git a/db/wiseSaying/16.json b/db/wiseSaying/16.json new file mode 100644 index 0000000..7748612 --- /dev/null +++ b/db/wiseSaying/16.json @@ -0,0 +1,5 @@ +{ + "id": 16, + "author": "Yoda", + "content": "Do, or do not. There is no try." +} diff --git a/db/wiseSaying/2.json b/db/wiseSaying/2.json new file mode 100644 index 0000000..490a27d --- /dev/null +++ b/db/wiseSaying/2.json @@ -0,0 +1,5 @@ +{ + "id": 2, + "author": "Oscar Wilde", + "content": "Be yourself; everyone else is already taken." +} diff --git a/db/wiseSaying/3.json b/db/wiseSaying/3.json new file mode 100644 index 0000000..f9a23ae --- /dev/null +++ b/db/wiseSaying/3.json @@ -0,0 +1,5 @@ +{ + "id": 3, + "author": "Nelson Mandela", + "content": "It always seems impossible until it’s done." +} diff --git a/db/wiseSaying/4.json b/db/wiseSaying/4.json new file mode 100644 index 0000000..f1a0b82 --- /dev/null +++ b/db/wiseSaying/4.json @@ -0,0 +1,5 @@ +{ + "id": 4, + "author": "Mahatma Gandhi", + "content": "Be the change that you wish to see in the world." +} diff --git a/db/wiseSaying/5.json b/db/wiseSaying/5.json new file mode 100644 index 0000000..4da70f7 --- /dev/null +++ b/db/wiseSaying/5.json @@ -0,0 +1,5 @@ +{ + "id": 5, + "author": "Steve Jobs", + "content": "Your work is going to fill a large part of your life, and the only way to be truly satisfied is to do what you believe is great work." +} diff --git a/db/wiseSaying/6.json b/db/wiseSaying/6.json new file mode 100644 index 0000000..6a3eb78 --- /dev/null +++ b/db/wiseSaying/6.json @@ -0,0 +1,5 @@ +{ + "id": 6, + "author": "Walt Disney", + "content": "The way to get started is to quit talking and begin doing." +} diff --git a/db/wiseSaying/7.json b/db/wiseSaying/7.json new file mode 100644 index 0000000..056ad54 --- /dev/null +++ b/db/wiseSaying/7.json @@ -0,0 +1,5 @@ +{ + "id": 7, + "author": "Confucius", + "content": "Our greatest glory is not in never falling, but in rising every time we fall." +} diff --git a/db/wiseSaying/8.json b/db/wiseSaying/8.json new file mode 100644 index 0000000..f832953 --- /dev/null +++ b/db/wiseSaying/8.json @@ -0,0 +1,5 @@ +{ + "id": 8, + "author": "Eleanor Roosevelt", + "content": "The future belongs to those who believe in the beauty of their dreams." +} diff --git a/db/wiseSaying/9.json b/db/wiseSaying/9.json new file mode 100644 index 0000000..a8237f1 --- /dev/null +++ b/db/wiseSaying/9.json @@ -0,0 +1,5 @@ +{ + "id": 9, + "author": "Mark Twain", + "content": "The secret of getting ahead is getting started." +} diff --git a/db/wiseSaying/create_dummy.sh b/db/wiseSaying/create_dummy.sh new file mode 100644 index 0000000..fdbb838 --- /dev/null +++ b/db/wiseSaying/create_dummy.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +create_wise_saying_json() { + local id=$1 + local author=$2 + local content=$3 + + echo "{ + \"id\": $id, + \"author\": \"$author\", + \"content\": \"$content\" +}" > "$id.json" + + echo "Saved: $id.json" +} + +create_dummy() { + create_wise_saying_json 1 "Albert Einstein" "Life is like riding a bicycle. To keep your balance, you must keep moving." + create_wise_saying_json 2 "Oscar Wilde" "Be yourself; everyone else is already taken." + create_wise_saying_json 3 "Nelson Mandela" "It always seems impossible until it’s done." + create_wise_saying_json 4 "Mahatma Gandhi" "Be the change that you wish to see in the world." + create_wise_saying_json 5 "Steve Jobs" "Your work is going to fill a large part of your life, and the only way to be truly satisfied is to do what you believe is great work." + create_wise_saying_json 6 "Walt Disney" "The way to get started is to quit talking and begin doing." + create_wise_saying_json 7 "Confucius" "Our greatest glory is not in never falling, but in rising every time we fall." + create_wise_saying_json 8 "Eleanor Roosevelt" "The future belongs to those who believe in the beauty of their dreams." + create_wise_saying_json 9 "Mark Twain" "The secret of getting ahead is getting started." + create_wise_saying_json 10 "Aristotle" "Knowing yourself is the beginning of all wisdom." + create_wise_saying_json 11 "Maya Angelou" "You may not control all the events that happen to you, but you can decide not to be reduced by them." + create_wise_saying_json 12 "John Lennon" "Life is what happens when you’re busy making other plans." + create_wise_saying_json 13 "Vincent Van Gogh" "I dream my painting and I paint my dream." + create_wise_saying_json 14 "Albert Einstein" "Imagination is more important than knowledge." + create_wise_saying_json 15 "Leonardo da Vinci" "Learning never exhausts the mind." + create_wise_saying_json 16 "Yoda" "Do, or do not. There is no try." +} + +create_dummy diff --git a/db/wiseSaying/lastId.txt b/db/wiseSaying/lastId.txt new file mode 100644 index 0000000..8e2afd3 --- /dev/null +++ b/db/wiseSaying/lastId.txt @@ -0,0 +1 @@ +17 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3fa8f86 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..fcb6fca --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/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/HEAD/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 + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + 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 + + +# 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"' + +# 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 \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# 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..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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=. +@rem This is normally unused +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% equ 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% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..df2353a --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "saying" + diff --git a/src/main/java/com/ll/wiseSaying/App.java b/src/main/java/com/ll/wiseSaying/App.java new file mode 100644 index 0000000..8ec1b56 --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/App.java @@ -0,0 +1,197 @@ +package com.ll.wiseSaying; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +import static com.ll.wiseSaying.Status.*; + +public class App { + private static boolean ON_RUN = true; + + private WiseSayingController wiseSayingController; + private CommandLineMenu cli; + + public App() { + cli = new CommandLineMenu(); + wiseSayingController = new WiseSayingController(); + } + + public void startApp() { + cli.printWelcomeGreeting(); + while (ON_RUN) { + cli.printCmdInputInfo(); + try { + run(cli.cmdInput()); + } catch (Exception e) { + cli.printFailedInfo(); + } + } + } + + public void run(final String input) { + String[] command = input.split("\\?"); + if (invalidCommand(command)) return; + handleCommand(command); + } + + private void handleCommand(final String[] command) { + switch (MenuOption.findOptionByCommand(command[0])) { + case ADD: + runAddCommand(command); + break; + + case LIST: + runListCommand(command); + break; + + case UPDATE: + runUpdateCommand(command); + break; + + case DELETE: + runDeleteCommand(command); + break; + + case BUILD: + cli.printBuildCompleteInfo(wiseSayingController.build()); + break; + + case EXIT: + cli.printExitInfo(); + ON_RUN = false; + break; + + default: + cli.printInvalidCmdInfo(); + break; + } + } + + private void runDeleteCommand(String[] command) { + WiseSayingReponse response; + if (command.length == 2) { + response = wiseSayingController.delete(parseAttribute(command[1])); + if (response.getStatus() == DELETE_SUCCESS) { + cli.printDeletedInfo(response.getRecentID()); + return; + } + if (response.getStatus() == ID_NOT_FOUND) { + cli.printIDNotFoundInfo(response.getRecentID()); + return; + } + cli.printFailedInfo(); + return; + } + cli.printInvalidCmdInfo(); + return; + } + + private void runUpdateCommand(String[] command) { + WiseSayingReponse response; + String content; + String author; + if (command.length == 2) { + try { + WiseSaying oldWiseSaying = wiseSayingController + .findWiseSayingByID(Long.valueOf(parseAttribute(command[1]).get("id"))) + .getWiseSayingList().get(0); + cli.printOldContent(oldWiseSaying.getContent()); + content = cli.inputContent(); + cli.printOldAuthor(oldWiseSaying.getAuthor()); + author = cli.inputAuthor(); + } catch (NoSuchElementException e) { + cli.printIDNotFoundInfo(Long.valueOf(parseAttribute(command[1]).get("id"))); + return; + } catch (Exception e) { + cli.printFailedInfo(); + return; + } + response = wiseSayingController.update(parseAttribute(command[1]) + .add("content", content) + .add("author", author) + ); + if (response.getStatus() == ID_NOT_FOUND) { + cli.printIDNotFoundInfo(response.getRecentID()); + return; + } + if (response.getStatus() == UPDATE_SUCCESS) { + return; + } + cli.printFailedInfo(); + return; + } + cli.printInvalidCmdInfo(); + } + + private void runListCommand(String[] command) { + WiseSayingReponse response; + if (command.length == 1) { + response = wiseSayingController.list(); + if (response.getStatus() == LIST_SUCCESS) { + cli.printWiseSayingList( + response.getWiseSayingList(), + response.getCurrentPage(), + response.getTotalPage() + ); + return; + } + cli.printFailedInfo(); + return; + } + if (command.length == 2) { + response = wiseSayingController.list(parseAttribute(command[1])); + if (response.getStatus() == LIST_SUCCESS) { + cli.printWiseSayingList( + response.getWiseSayingList(), + response.getCurrentPage(), + response.getTotalPage() + ); + return; + } + cli.printFailedInfo(); + return; + } + cli.printInvalidCmdInfo(); + } + + private void runAddCommand(String[] command) { + WiseSayingReponse response; + String author; + String content; + if (command.length == 1) { + try { + content = cli.inputContent(); + author = cli.inputAuthor(); + } catch (Exception e) { + cli.printFailedInfo(); + return; + } + response = wiseSayingController.add(content, author); + if (response.getStatus() == ADD_SUCCESS) { + cli.printAdddedInfo(response.getRecentID()); + return; + } + cli.printFailedInfo(); + return; + } + cli.printInvalidCmdInfo(); + } + + private Attribute parseAttribute(final String command) { + Attribute attribute = new Attribute(); + Arrays.stream(command.split("&")) + .map(param -> param.split("=", 2)) + .filter(parts -> parts.length == 2) + .forEach(parts -> attribute.add(parts[0], parts[1])); + return attribute; + } + + private boolean invalidCommand(final String[] command) { + if (!Arrays.stream(MenuOption.values()) + .anyMatch(option -> option.getCommand().equals(command[0]))) { + cli.printInvalidCmdInfo(); + return true; + } + return false; + } +} diff --git a/src/main/java/com/ll/wiseSaying/Attribute.java b/src/main/java/com/ll/wiseSaying/Attribute.java new file mode 100644 index 0000000..1ff4bd7 --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/Attribute.java @@ -0,0 +1,25 @@ +package com.ll.wiseSaying; + +import java.util.HashMap; +import java.util.Map; + +public class Attribute { + private Map attributeMap; + + public Attribute() { + attributeMap = new HashMap<>(); + } + + public Attribute add(String name, String value) { + attributeMap.put(name, value); + return this; + } + + public String get(final String name) { + return attributeMap.get(name); + } + + public boolean has(final String name) { + return attributeMap.containsKey(name); + } +} diff --git a/src/main/java/com/ll/wiseSaying/CommandLineMenu.java b/src/main/java/com/ll/wiseSaying/CommandLineMenu.java new file mode 100644 index 0000000..88724f0 --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/CommandLineMenu.java @@ -0,0 +1,100 @@ +package com.ll.wiseSaying; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; + +public class CommandLineMenu { + private final String APP_WELCOME_GREETING = "== 명언 앱 =="; + private final String CMD_INPUT_INFO = "명령) "; + private final String CONTENT_INPUT_INFO = "명언 : "; + private final String AUTHOR_INPUT_INFO = "작가 : "; + private final String EXIT_INFO = "종료합니다."; + private final String INVALID_CMD_INFO = "잘못된 입력입니다."; + private final String ADDED_INFO = "번 명언이 등록되었습니다."; + private final String FAILED_INFO = "작업을 실패했습니다."; + private final String ID_NOT_FOUND_INFO = "번 명언은 존재하지 않습니다."; + private final String DELETED_INFO = "번 명언이 삭제되었습니다."; + private final String LIST_INFO_FORE = "번호 / 작가 / 명언\n"; + private final String BOUNDARY_LINE = "----------------------"; + private final String OLD_CONTENT_INFO = "명언(기존) : "; + private final String OLD_AUTHOR_INFO = "작가(기존) : "; + private final String BUILD_COMPLETE_INFO = " 파일의 내용이 갱신되었습니다."; + + private BufferedReader bufferedReader; + + public CommandLineMenu() { + bufferedReader = new BufferedReader(new InputStreamReader(System.in)); + } + + public void printWelcomeGreeting() { + System.out.println(APP_WELCOME_GREETING); + } + + public void printCmdInputInfo() { + System.out.print(CMD_INPUT_INFO); + } + + public String cmdInput() throws IOException { + return bufferedReader.readLine(); + } + + public String inputContent() throws IOException { + System.out.print(CONTENT_INPUT_INFO); + return bufferedReader.readLine(); + } + + public String inputAuthor() throws IOException { + System.out.print(AUTHOR_INPUT_INFO); + return bufferedReader.readLine(); + } + + public void printInvalidCmdInfo() { + System.out.println(INVALID_CMD_INFO); + } + + public void printExitInfo() { + System.out.println(EXIT_INFO); + } + + public void printAdddedInfo(Long id) { + System.out.println(id + ADDED_INFO); + } + + public void printFailedInfo() { + System.out.println(FAILED_INFO); + } + + public void printIDNotFoundInfo(final Long id) { + System.out.println(id + ID_NOT_FOUND_INFO); + } + + public void printDeletedInfo(final Long id) { + System.out.println(id + DELETED_INFO); + } + + public void printWiseSayingList(final List wiseSayingList, final Long currentPage, final Long maxPage) { + System.out.println(LIST_INFO_FORE + BOUNDARY_LINE); + for (WiseSaying wiseSaying : wiseSayingList) { + System.out.println( + wiseSaying.getId() + " / " + + wiseSaying.getAuthor() + " / " + + wiseSaying.getContent() + ); + } + System.out.println(BOUNDARY_LINE + "\n페이지 : " + currentPage + " / [" + maxPage + "]"); + } + + public void printOldContent(String oldContent) { + System.out.println(OLD_CONTENT_INFO + oldContent); + } + + public void printOldAuthor(String oldAuthor) { + System.out.println(OLD_AUTHOR_INFO + oldAuthor); + } + + public void printBuildCompleteInfo(final String buildFilename) { + System.out.println(buildFilename + BUILD_COMPLETE_INFO); + } +} diff --git a/src/main/java/com/ll/wiseSaying/FileUtil.java b/src/main/java/com/ll/wiseSaying/FileUtil.java new file mode 100644 index 0000000..436ff4c --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/FileUtil.java @@ -0,0 +1,85 @@ +package com.ll.wiseSaying; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +public class FileUtil { + public static List loadJSON(final String path) { + List outputs = new ArrayList<>(); + File directory = new File(path); + + if (directory.isDirectory()) { + File[] jsonFiles = directory.listFiles((dir, name) -> name.toLowerCase().endsWith(".json")); + if (jsonFiles != null && jsonFiles.length > 0) { + for (File jsonFile : jsonFiles) { + outputs.add(readFileContent(jsonFile)); + } + } + } + return outputs; + } + + private static String readFileContent(final File file) { + StringBuilder contentBuilder = new StringBuilder(); + + try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + contentBuilder.append(line).append(System.lineSeparator()); + } + } catch (IOException e) { + e.printStackTrace(); + } + + return contentBuilder.toString().trim(); + } + + public static void saveFile(final String path, final String content) { + try { + File file = new File(path); + + File parentDir = file.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); + } + + try (FileWriter fileWriter = new FileWriter(file)) { + fileWriter.write(content); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static String readTextFile(final String filePath) { + StringBuilder content = new StringBuilder(); + File file = new File(filePath); + + if (file.exists() && file.isFile()) { + try (BufferedReader reader = new BufferedReader(new FileReader(file, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + content.append(line).append("\n"); + } + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } else { + // invalid file path + return null; + } + + return content.toString(); + } + + public static boolean delete(final String filePath) { + File file = new File(filePath); + if (file.exists() && file.isFile()) { + return file.delete(); + } + return false; + } +} diff --git a/src/main/java/com/ll/wiseSaying/Main.java b/src/main/java/com/ll/wiseSaying/Main.java new file mode 100644 index 0000000..276356b --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/Main.java @@ -0,0 +1,8 @@ +package com.ll.wiseSaying; + +public class Main { + public static void main(String[] args) { + App app = new App(); + app.startApp(); + } +} diff --git a/src/main/java/com/ll/wiseSaying/MenuOption.java b/src/main/java/com/ll/wiseSaying/MenuOption.java new file mode 100644 index 0000000..aeaf493 --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/MenuOption.java @@ -0,0 +1,29 @@ +package com.ll.wiseSaying; + +import java.util.Arrays; + +public enum MenuOption { + ADD("등록"), + LIST("목록"), + UPDATE("수정"), + DELETE("삭제"), + BUILD("빌드"), + EXIT("종료"); + + private final String command; + + MenuOption(String command) { + this.command = command; + } + + static MenuOption findOptionByCommand(final String command) { + return Arrays.stream(MenuOption.values()) + .filter(option -> option.command.equals(command)) + .findAny() + .orElseThrow(() -> new IllegalStateException()); + } + + public String getCommand() { + return this.command; + } +} diff --git a/src/main/java/com/ll/wiseSaying/Status.java b/src/main/java/com/ll/wiseSaying/Status.java new file mode 100644 index 0000000..0a7d0fe --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/Status.java @@ -0,0 +1,10 @@ +package com.ll.wiseSaying; + +public enum Status { + LIST_SUCCESS, + ADD_SUCCESS, + UPDATE_SUCCESS, + DELETE_SUCCESS, + ID_NOT_FOUND, + FAILED; +} diff --git a/src/main/java/com/ll/wiseSaying/WiseSaying.java b/src/main/java/com/ll/wiseSaying/WiseSaying.java new file mode 100644 index 0000000..f0a337b --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/WiseSaying.java @@ -0,0 +1,31 @@ +package com.ll.wiseSaying; + +public class WiseSaying { + private Long id; + private String author; + private String content; + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(final String author) { + this.author = author; + } + + public String getContent() { + return content; + } + + public void setContent(final String content) { + this.content = content; + } +} diff --git a/src/main/java/com/ll/wiseSaying/WiseSayingController.java b/src/main/java/com/ll/wiseSaying/WiseSayingController.java new file mode 100644 index 0000000..c011a76 --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/WiseSayingController.java @@ -0,0 +1,129 @@ +package com.ll.wiseSaying; + +import java.util.List; + +import static com.ll.wiseSaying.Status.*; + +public class WiseSayingController { + private static final int DEFAULT_PAGE = 1; + private static final String KEYWORD_TYPE = "keywordType"; + private static final String KEYWORD = "keyword"; + private static final String PAGE = "page"; + private static final String AUTHOR = "author"; + private static final String CONTENT = "content"; + private static final String ID = "id"; + private WiseSayingService wiseSayingService; + + public WiseSayingController() { + wiseSayingService = new WiseSayingService(new WiseSayingJSONRepository()); + } + + + public WiseSayingReponse add(final String content, final String author) { + WiseSaying wiseSaying = new WiseSaying(); + wiseSaying.setContent(content); + wiseSaying.setAuthor(author); + + return new WiseSayingReponse() + .recentID(wiseSayingService.addWiseSaying(wiseSaying).getId()) + .status(ADD_SUCCESS); + } + + public WiseSayingReponse list() { + return list(new Attribute().add(PAGE, String.valueOf(DEFAULT_PAGE))); + } + + public WiseSayingReponse list(final Attribute attribute) { + WiseSayingPage wiseSayingPage; + Long page = 1L; + if (attribute.has(PAGE)) { + if (!attribute.get(PAGE).matches("[0-9.]+")) { + return new WiseSayingReponse().status(FAILED); + } + page = Long.valueOf(attribute.get(PAGE)); + } + + if (attribute.has(KEYWORD_TYPE) && attribute.has(KEYWORD)) { + // search by author + if (attribute.get(KEYWORD_TYPE).equals(AUTHOR)) { + wiseSayingPage = wiseSayingService.findWiseSayingByAuthor(attribute.get(KEYWORD), page); + return new WiseSayingReponse() + .addWiseSayingList(wiseSayingPage.getWiseSayingList()) + .totalPage(wiseSayingPage.getTotalPage()) + .currentPage(wiseSayingPage.getCurrentPage()) + .status(LIST_SUCCESS); + } + // search by content + if (attribute.get(KEYWORD_TYPE).equals(CONTENT)) { + wiseSayingPage = wiseSayingService.findWiseSayingByContent(attribute.get(KEYWORD), page); + return new WiseSayingReponse() + .addWiseSayingList(wiseSayingPage.getWiseSayingList()) + .totalPage(wiseSayingPage.getTotalPage()) + .currentPage(wiseSayingPage.getCurrentPage()) + .status(LIST_SUCCESS); + } + } + wiseSayingPage = wiseSayingService.findWiseSayingPage(page); + return new WiseSayingReponse() + .addWiseSayingList(wiseSayingPage.getWiseSayingList()) + .totalPage(wiseSayingPage.getTotalPage()) + .currentPage(wiseSayingPage.getCurrentPage()) + .status(LIST_SUCCESS); + } + + public WiseSayingReponse update(final Attribute attribute) { + String id; + if (!attribute.has(ID) || !attribute.has(AUTHOR) || !attribute.has(CONTENT)) { + return new WiseSayingReponse().status(FAILED); + } + id = attribute.get(ID); + if (!id.matches("^\\d+$")) { + return new WiseSayingReponse().status(FAILED); + } + if (idExists(id)) return new WiseSayingReponse() + .recentID(Long.valueOf(id)) + .status(ID_NOT_FOUND); + + WiseSaying wiseSaying = new WiseSaying(); + wiseSaying.setContent(attribute.get(CONTENT)); + wiseSaying.setAuthor(attribute.get(AUTHOR)); + wiseSayingService.updateWiseSaying(Long.valueOf(id), wiseSaying); + return new WiseSayingReponse() + .recentID(Long.valueOf(id)) + .status(UPDATE_SUCCESS); + } + + public WiseSayingReponse delete(final Attribute attribute) { + String id; + if (!attribute.has(ID)) { + return new WiseSayingReponse().status(FAILED); + } + id = attribute.get(ID); + if (!id.matches("^\\d+$")) { + return new WiseSayingReponse().status(FAILED); + } + if (idExists(id)) return new WiseSayingReponse() + .recentID(Long.valueOf(id)) + .status(ID_NOT_FOUND); + return new WiseSayingReponse() + .recentID(wiseSayingService.deleteWiseSaying(Long.valueOf(id))) + .status(DELETE_SUCCESS); + } + + private boolean idExists(String id) { + try { + wiseSayingService.findWiseSayingByID(Long.valueOf(id)); + } catch (Exception e) { + return true; + } + return false; + } + + public WiseSayingReponse findWiseSayingByID(final Long id) throws Exception { + return new WiseSayingReponse().addWiseSayingList(List.of(wiseSayingService.findWiseSayingByID(id))); + } + + public String build() { + return wiseSayingService.build(); + } +} diff --git a/src/main/java/com/ll/wiseSaying/WiseSayingJSONMapper.java b/src/main/java/com/ll/wiseSaying/WiseSayingJSONMapper.java new file mode 100644 index 0000000..d8d2820 --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/WiseSayingJSONMapper.java @@ -0,0 +1,66 @@ +package com.ll.wiseSaying; + +import java.util.Map; +import java.util.stream.Collectors; + +public class WiseSayingJSONMapper { + + public WiseSaying toObject(final String wiseSayingJSON) { + WiseSaying wiseSaying = new WiseSaying(); + wiseSaying.setId(Long.valueOf(extractValue(wiseSayingJSON, "id"))); + wiseSaying.setAuthor(extractValue(wiseSayingJSON, "author")); + wiseSaying.setContent(extractValue(wiseSayingJSON, "content")); + return wiseSaying; + } + + public String toJSON(final WiseSaying wiseSaying) { + StringBuilder jsonBuilder = new StringBuilder(); + jsonBuilder.append("{"); + jsonBuilder.append("\"id\": ").append(wiseSaying.getId()).append(", "); + jsonBuilder.append("\"content\": \"").append(wiseSaying.getContent()).append("\", "); + jsonBuilder.append("\"author\": \"").append(wiseSaying.getAuthor()).append("\""); + jsonBuilder.append("}"); + return jsonBuilder.toString(); + } + + public String toJSON(final Map wiseSayingMap) { + StringBuilder jsonBuilder = new StringBuilder(); + jsonBuilder.append("["); + jsonBuilder.append(wiseSayingMap.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(Map.Entry::getValue) + .map(this::toJSON) + .collect(Collectors.joining(", "))); + jsonBuilder.append("]"); + return jsonBuilder.toString(); + } + + private String extractValue(final String json, final String key) { + String searchKey = "\"" + key + "\":"; + int start = json.indexOf(searchKey); + if (start == -1) { + return null; + } + start = json.indexOf(searchKey) + searchKey.length(); + + // string + if (json.charAt(start) == ' ') { + start++; + } + if (json.charAt(start) == '"') { + int end = start + 1; + while (end < json.length() && json.charAt(end) != '"') { + end++; + } + return json.substring(start + 1, end); + } + + // value + int end = json.indexOf(",", start); + if (end == -1) { + end = json.indexOf("}", start); + } + String value = json.substring(start, end).trim(); + return value; + } +} diff --git a/src/main/java/com/ll/wiseSaying/WiseSayingJSONRepository.java b/src/main/java/com/ll/wiseSaying/WiseSayingJSONRepository.java new file mode 100644 index 0000000..38bc655 --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/WiseSayingJSONRepository.java @@ -0,0 +1,142 @@ +package com.ll.wiseSaying; + +import java.util.*; +import java.util.stream.Collectors; + +public class WiseSayingJSONRepository implements WiseSayingRepository { + private static final String DATA_FILE_PATH = "db/wiseSaying/"; + private static final String LAST_ID_FILE = "lastId.txt"; + private static final String BUILD_FILE = "data.json"; + private WiseSayingJSONMapper wiseSayingJSONMapper; + private Map wiseSayingMap; + private long idCounter; + + public WiseSayingJSONRepository() { + wiseSayingMap = new HashMap<>(); + wiseSayingJSONMapper = new WiseSayingJSONMapper(); + idCounter = 1L; + loadJSON(); + } + + private void loadJSON() { + List wiseSayingJSONs = FileUtil.loadJSON(DATA_FILE_PATH); + String loadedIDCounter = FileUtil.readTextFile(DATA_FILE_PATH + LAST_ID_FILE); + if (loadedIDCounter != null && loadedIDCounter.matches("^\\d+$")) { + idCounter = Long.parseLong(loadedIDCounter); + } + for (String wiseSayingJSON : wiseSayingJSONs) { + WiseSaying wiseSaying = wiseSayingJSONMapper.toObject(wiseSayingJSON); + wiseSayingMap.put(wiseSaying.getId(), wiseSaying); + if (idCounter <= wiseSaying.getId()) { + idCounter = wiseSaying.getId() + 1; + } + FileUtil.saveFile(DATA_FILE_PATH + LAST_ID_FILE, String.valueOf(idCounter)); + } + } + + @Override + public WiseSaying save(final WiseSaying wiseSaying) { + wiseSaying.setId(idCounter); + FileUtil.saveFile( + DATA_FILE_PATH + wiseSaying.getId() + ".json", + wiseSayingJSONMapper.toJSON(wiseSaying) + ); + wiseSayingMap.put(idCounter++, wiseSaying); + FileUtil.saveFile(DATA_FILE_PATH + LAST_ID_FILE, String.valueOf(idCounter)); + return wiseSaying; + } + + @Override + public WiseSaying findById(final Long wiseSayingID) { + if (wiseSayingMap.containsKey(wiseSayingID)) { + return wiseSayingMap.get(wiseSayingID); + } + throw new NoSuchElementException("존재하지 않는 명언 ID를 조회했습니다."); + } + + @Override + public long delete(final Long wiseSayingID) { + FileUtil.delete(DATA_FILE_PATH + wiseSayingID + ".json"); + wiseSayingMap.remove(wiseSayingID); + return wiseSayingID; + } + + @Override + public WiseSaying update(final Long wiseSayingID, final WiseSaying wiseSaying) { + wiseSaying.setId(wiseSayingID); + FileUtil.saveFile( + DATA_FILE_PATH + wiseSayingID + ".json", + wiseSayingJSONMapper.toJSON(wiseSaying) + ); + wiseSayingMap.put(wiseSayingID, wiseSaying); + return wiseSaying; + } + + @Override + public WiseSayingPage findByAuthorName(final String searchAuthorKeyword, final Long page, final int pageSize) { + WiseSayingPage wiseSayingPage = new WiseSayingPage(); + wiseSayingPage.totalPage((wiseSayingMap.entrySet() + .stream() + .filter(entry -> entry.getValue().getAuthor().contains(searchAuthorKeyword)) + .count() - 1) / pageSize + 1 + ); + wiseSayingPage.currentPage(page); + wiseSayingPage.addWiseSayingList(wiseSayingMap.entrySet() + .stream() + .filter(entry -> entry.getValue().getAuthor().contains(searchAuthorKeyword)) + .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder())) + .skip((page - 1) * pageSize) + .limit(pageSize) + .map(Map.Entry::getValue) + .collect(Collectors.toUnmodifiableList()) + ); + return wiseSayingPage; + } + + @Override + public WiseSayingPage findByContent(final String searchContentKeyword, final Long page, final int pageSize) { + WiseSayingPage wiseSayingPage = new WiseSayingPage(); + wiseSayingPage.totalPage((wiseSayingMap.entrySet() + .stream() + .filter(entry -> entry.getValue().getContent().contains(searchContentKeyword)) + .count() - 1) / pageSize + 1 + ); + wiseSayingPage.currentPage(page); + wiseSayingPage.addWiseSayingList(wiseSayingMap.entrySet() + .stream() + .filter(entry -> entry.getValue().getContent().contains(searchContentKeyword)) + .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder())) + .skip((page - 1) * pageSize) + .limit(pageSize) + .map(Map.Entry::getValue) + .collect(Collectors.toUnmodifiableList()) + ); + return wiseSayingPage; + } + + @Override + public WiseSayingPage findPage(final Long page, final int pageSize) { + WiseSayingPage wiseSayingPage = new WiseSayingPage(); + wiseSayingPage.totalPage((wiseSayingMap.entrySet() + .stream() + .count() - 1) / pageSize + 1 + ); + + wiseSayingPage.currentPage(page); + wiseSayingPage.addWiseSayingList(wiseSayingMap.entrySet() + .stream() + .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder())) + .skip((page - 1) * pageSize) + .limit(pageSize) + .map(Map.Entry::getValue) + .collect(Collectors.toUnmodifiableList()) + ); + return wiseSayingPage; + } + + @Override + public String build() { + FileUtil.saveFile(BUILD_FILE, wiseSayingJSONMapper.toJSON(wiseSayingMap)); + return BUILD_FILE; + } +} diff --git a/src/main/java/com/ll/wiseSaying/WiseSayingMapRepository.java b/src/main/java/com/ll/wiseSaying/WiseSayingMapRepository.java new file mode 100644 index 0000000..42ef49b --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/WiseSayingMapRepository.java @@ -0,0 +1,112 @@ +package com.ll.wiseSaying; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +public class WiseSayingMapRepository implements WiseSayingRepository { + private Map wiseSayingMap; + private long idCounter; + + public WiseSayingMapRepository() { + wiseSayingMap = new HashMap<>(); + idCounter = 1L; + } + + @Override + public WiseSaying save(final WiseSaying wiseSaying) { + wiseSaying.setId(idCounter); + wiseSayingMap.put(idCounter++, wiseSaying); + return wiseSaying; + } + + @Override + public WiseSaying findById(final Long wiseSayingID) { + if (wiseSayingMap.containsKey(wiseSayingID)) { + return wiseSayingMap.get(wiseSayingID); + } + throw new NoSuchElementException("존재하지 않는 명언 ID를 조회했습니다."); + } + + @Override + public long delete(final Long wiseSayingID) { + wiseSayingMap.remove(wiseSayingID); + return wiseSayingID; + } + + @Override + public WiseSaying update(final Long wiseSayingID, final WiseSaying wiseSaying) { + wiseSaying.setId(wiseSayingID); + wiseSayingMap.put(wiseSayingID, wiseSaying); + return wiseSaying; + } + + @Override + public WiseSayingPage findByAuthorName(final String searchAuthorKeyword, final Long page, final int pageSize) { + WiseSayingPage wiseSayingPage = new WiseSayingPage(); + wiseSayingPage.totalPage((wiseSayingMap.entrySet() + .stream() + .filter(entry -> entry.getValue().getAuthor().contains(searchAuthorKeyword)) + .count() - 1) / pageSize + 1 + ); + wiseSayingPage.currentPage(page); + wiseSayingPage.addWiseSayingList(wiseSayingMap.entrySet() + .stream() + .filter(entry -> entry.getValue().getAuthor().contains(searchAuthorKeyword)) + .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder())) + .skip((page - 1) * pageSize) + .limit(pageSize) + .map(Map.Entry::getValue) + .collect(Collectors.toUnmodifiableList()) + ); + return wiseSayingPage; + } + + @Override + public WiseSayingPage findByContent(final String searchContentKeyword, final Long page, final int pageSize) { + WiseSayingPage wiseSayingPage = new WiseSayingPage(); + wiseSayingPage.totalPage((wiseSayingMap.entrySet() + .stream() + .filter(entry -> entry.getValue().getContent().contains(searchContentKeyword)) + .count() - 1) / pageSize + 1 + ); + wiseSayingPage.currentPage(page); + wiseSayingPage.addWiseSayingList(wiseSayingMap.entrySet() + .stream() + .filter(entry -> entry.getValue().getContent().contains(searchContentKeyword)) + .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder())) + .skip((page - 1) * pageSize) + .limit(pageSize) + .map(Map.Entry::getValue) + .collect(Collectors.toUnmodifiableList()) + ); + return wiseSayingPage; + } + + @Override + public WiseSayingPage findPage(final Long page, final int pageSize) { + WiseSayingPage wiseSayingPage = new WiseSayingPage(); + wiseSayingPage.totalPage((wiseSayingMap.entrySet() + .stream() + .count() - 1) / pageSize + 1 + ); + + wiseSayingPage.currentPage(page); + wiseSayingPage.addWiseSayingList(wiseSayingMap.entrySet() + .stream() + .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder())) + .skip((page - 1) * pageSize) + .limit(pageSize) + .map(Map.Entry::getValue) + .collect(Collectors.toUnmodifiableList()) + ); + return wiseSayingPage; + } + + @Override + public String build() { + return null; + } +} diff --git a/src/main/java/com/ll/wiseSaying/WiseSayingPage.java b/src/main/java/com/ll/wiseSaying/WiseSayingPage.java new file mode 100644 index 0000000..d0359bf --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/WiseSayingPage.java @@ -0,0 +1,43 @@ +package com.ll.wiseSaying; + +import java.util.ArrayList; +import java.util.List; + +public class WiseSayingPage { + private List wiseSayingList; + private Long currentPage; + private Long totalPage; + + public WiseSayingPage() { + wiseSayingList = new ArrayList<>(); + currentPage = 0L; + totalPage = 0L; + } + + public List getWiseSayingList() { + return wiseSayingList; + } + + public WiseSayingPage addWiseSayingList(List wiseSayingList) { + this.wiseSayingList = wiseSayingList; + return this; + } + + public Long getCurrentPage() { + return currentPage; + } + + public WiseSayingPage currentPage(final long currentPage) { + this.currentPage = currentPage; + return this; + } + + public Long getTotalPage() { + return totalPage; + } + + public WiseSayingPage totalPage(final long maxPage) { + this.totalPage = maxPage; + return this; + } +} diff --git a/src/main/java/com/ll/wiseSaying/WiseSayingReponse.java b/src/main/java/com/ll/wiseSaying/WiseSayingReponse.java new file mode 100644 index 0000000..1b3d747 --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/WiseSayingReponse.java @@ -0,0 +1,63 @@ +package com.ll.wiseSaying; + +import java.util.ArrayList; +import java.util.List; + +public class WiseSayingReponse { + private Status status; + private List wiseSayingList; + private Long recentID; + private Long currentPage; + private Long totalPage; + + public WiseSayingReponse() { + status = Status.FAILED; + wiseSayingList = new ArrayList<>(); + recentID = 0L; + } + + public WiseSayingReponse status(final Status status) { + this.status = status; + return this; + } + + public WiseSayingReponse addWiseSayingList(final List wiseSayingList) { + this.wiseSayingList = wiseSayingList; + return this; + } + + public WiseSayingReponse recentID(final Long recentID) { + this.recentID = recentID; + return this; + } + + public WiseSayingReponse currentPage(final Long currentPage) { + this.currentPage = currentPage; + return this; + } + + public WiseSayingReponse totalPage(final Long totalPage) { + this.totalPage = totalPage; + return this; + } + + public Status getStatus() { + return status; + } + + public List getWiseSayingList() { + return wiseSayingList; + } + + public Long getRecentID() { + return recentID; + } + + public Long getCurrentPage() { + return currentPage; + } + + public Long getTotalPage() { + return totalPage; + } +} diff --git a/src/main/java/com/ll/wiseSaying/WiseSayingRepository.java b/src/main/java/com/ll/wiseSaying/WiseSayingRepository.java new file mode 100644 index 0000000..c9caefd --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/WiseSayingRepository.java @@ -0,0 +1,12 @@ +package com.ll.wiseSaying; + +public interface WiseSayingRepository { + WiseSaying save (WiseSaying wiseSaying); + WiseSaying findById(Long id) throws Exception; + long delete(Long wiseSayingID); + WiseSaying update(Long wiseSayingID, WiseSaying wiseSaying); + WiseSayingPage findByAuthorName(String searchAuthorKeyword, Long page, int pageSize); + WiseSayingPage findByContent(String searchContentKeyword, Long page, int pageSize); + WiseSayingPage findPage(Long page, int pageSize); + String build(); +} diff --git a/src/main/java/com/ll/wiseSaying/WiseSayingService.java b/src/main/java/com/ll/wiseSaying/WiseSayingService.java new file mode 100644 index 0000000..602a696 --- /dev/null +++ b/src/main/java/com/ll/wiseSaying/WiseSayingService.java @@ -0,0 +1,56 @@ +package com.ll.wiseSaying; + +public class WiseSayingService { + private static final int DEFAULT_PAGE_SIZE = 5; + + private WiseSayingRepository wiseSayingRepository; + + public WiseSayingService(final WiseSayingRepository wiseSayingRepository) { + this.wiseSayingRepository = wiseSayingRepository; + } + + public WiseSaying addWiseSaying(final WiseSaying wiseSaying) { + return wiseSayingRepository.save(wiseSaying); + } + + public WiseSaying findWiseSayingByID(final Long wiseSayingID) throws Exception { + return wiseSayingRepository.findById(wiseSayingID); + } + + public long deleteWiseSaying(final Long wiseSayingID) { + wiseSayingRepository.delete(wiseSayingID); + return wiseSayingID; + } + + public WiseSaying updateWiseSaying(final Long wiseSayingID, final WiseSaying wiseSaying) { + return wiseSayingRepository.update(wiseSayingID, wiseSaying); + } + + public WiseSayingPage findWiseSayingByAuthor(final String searchAuthorKeyword, final Long page) { + return findWiseSayingByAuthor(searchAuthorKeyword, page, DEFAULT_PAGE_SIZE); + } + + public WiseSayingPage findWiseSayingByAuthor(final String searchAuthorKeyword, final Long page, final int pageSize) { + return wiseSayingRepository.findByAuthorName(searchAuthorKeyword, page, pageSize); + } + + public WiseSayingPage findWiseSayingByContent(final String searchContent, final Long page) { + return findWiseSayingByContent(searchContent, page, DEFAULT_PAGE_SIZE); + } + + public WiseSayingPage findWiseSayingByContent(final String searchContent, final Long page, final int pageSize) { + return wiseSayingRepository.findByContent(searchContent, page, pageSize); + } + + public WiseSayingPage findWiseSayingPage(final Long page) { + return findWiseSayingPage(page, DEFAULT_PAGE_SIZE); + } + + public WiseSayingPage findWiseSayingPage(final Long page, final int pageSize) { + return wiseSayingRepository.findPage(page, pageSize); + } + + public String build() { + return wiseSayingRepository.build(); + } +} diff --git a/src/test/java/com/ll/wiseSaying/WiseSayingServiceTest.java b/src/test/java/com/ll/wiseSaying/WiseSayingServiceTest.java new file mode 100644 index 0000000..2b53616 --- /dev/null +++ b/src/test/java/com/ll/wiseSaying/WiseSayingServiceTest.java @@ -0,0 +1,159 @@ +package com.ll.wiseSaying; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; + +class WiseSayingServiceTest { + SoftAssertions softAssertions; + private WiseSayingService wiseSayingService; + + private final String SAMPLE_AUTHOR1 = "작자미상"; + private final String SAMPLE_CONTENT1 = "과거에 집착하지 마라"; + private final String SAMPLE_AUTHOR2 = "키케로"; + private final String SAMPLE_CONTENT2 = "삶이 있는 한 희망은 있다"; + private final String SAMPLE_AUTHOR3 = "로버트 엘리엇"; + private final String SAMPLE_CONTENT3 = "피할 수 없으면 즐겨라"; + private final String SAMPLE_AUTHOR4 = "괴테"; + private final String SAMPLE_CONTENT4 = "꿈을 계속 간직하고 있으면 반드시 실현할 때가 온다"; + private final String SAMPLE_AUTHOR5 = "윈스턴 처칠"; + private final String SAMPLE_CONTENT5 = "계속해서 꾸준한 노력으로 잠재력을 실현하라"; + private final String SAMPLE_AUTHOR6 = "윈스턴 처칠"; + private final String SAMPLE_CONTENT6 = "성공은 결론이 아니며 실패는 치명적인 것이 아니다. 중요한 것은 그 과정을 지속하는 용기다"; + + @BeforeEach + void setUp() { + softAssertions = new SoftAssertions(); + wiseSayingService = new WiseSayingService(new WiseSayingMapRepository()); + } + + void setUpSampleWiseSaying() { + addNewWiseSaying(SAMPLE_AUTHOR1, SAMPLE_CONTENT1); + addNewWiseSaying(SAMPLE_AUTHOR2, SAMPLE_CONTENT2); + addNewWiseSaying(SAMPLE_AUTHOR3, SAMPLE_CONTENT3); + addNewWiseSaying(SAMPLE_AUTHOR4, SAMPLE_CONTENT4); + addNewWiseSaying(SAMPLE_AUTHOR5, SAMPLE_CONTENT5); + addNewWiseSaying(SAMPLE_AUTHOR6, SAMPLE_CONTENT6); + } + + private WiseSaying addNewWiseSaying(final String author, final String content) { + return wiseSayingService.addWiseSaying(craeteWiseSaying(author, content)); + } + + private WiseSaying craeteWiseSaying(final String author, final String content) { + WiseSaying wiseSaying = new WiseSaying(); + wiseSaying.setAuthor(author); + wiseSaying.setContent(content); + return wiseSaying; + } + + @Test + @DisplayName("명언을 등록할 수 있다") + void addSayingTest() { + WiseSaying givenWiseSaying = addNewWiseSaying(SAMPLE_AUTHOR1, SAMPLE_CONTENT1); + + WiseSaying foundWiseSaying = wiseSayingService.addWiseSaying(givenWiseSaying); + softAssertions.assertThat(foundWiseSaying.getAuthor()).isEqualTo(givenWiseSaying.getAuthor()); + softAssertions.assertThat(foundWiseSaying.getContent()).isEqualTo(givenWiseSaying.getContent()); + softAssertions.assertAll(); + } + + @Test + @DisplayName("명언을 ID로 검색할 수 있다") + void findSayingTest() throws Exception { + WiseSaying givenWiseSaying = addNewWiseSaying(SAMPLE_AUTHOR1, SAMPLE_CONTENT1); + WiseSaying savedWiseSaying = wiseSayingService.addWiseSaying(givenWiseSaying); + + WiseSaying foundWiseSaying = wiseSayingService.findWiseSayingByID(savedWiseSaying.getId()); + softAssertions.assertThat(foundWiseSaying.getAuthor()).isEqualTo(givenWiseSaying.getAuthor()); + softAssertions.assertThat(foundWiseSaying.getContent()).isEqualTo(givenWiseSaying.getContent()); + softAssertions.assertAll(); + } + + @Test + @DisplayName("명언을 ID로 삭제할 수 있다") + void deleteSayingTest() { + WiseSaying givenWiseSaying = addNewWiseSaying(SAMPLE_AUTHOR1, SAMPLE_CONTENT1); + WiseSaying savedWiseSaying = wiseSayingService.addWiseSaying(givenWiseSaying); + + wiseSayingService.deleteWiseSaying(savedWiseSaying.getId()); + softAssertions.assertThatThrownBy(() -> wiseSayingService.findWiseSayingByID(savedWiseSaying.getId())) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("존재하지 않는 명언 ID를 조회했습니다."); + softAssertions.assertAll(); + } + + @Test + @DisplayName("명언을 ID로 수정할 수 있다") + void modifySayingTest() throws Exception { + WiseSaying givenWiseSaying = addNewWiseSaying(SAMPLE_AUTHOR1, SAMPLE_CONTENT1); + + String expectedAuthor = "홍길동"; + String expectedContent = "현재와 자신을 사랑하라."; + WiseSaying expectedWiseSaying = craeteWiseSaying(expectedAuthor, expectedContent); + + wiseSayingService.updateWiseSaying(givenWiseSaying.getId(), expectedWiseSaying); + softAssertions.assertThat(wiseSayingService.findWiseSayingByID(givenWiseSaying.getId())) + .usingRecursiveComparison(). + isEqualTo(expectedWiseSaying); + } + + @Test + @DisplayName("작가명으로 명언을 검색할 수 있다") + void searchByAuthorTest() throws Exception { + setUpSampleWiseSaying(); + String searchAuthorKeyword = "처칠"; + List expectedWiseSayingList = List.of( + wiseSayingService.findWiseSayingByID(6L), + wiseSayingService.findWiseSayingByID(5L) + ); + + List foundWiseSayingList = wiseSayingService.findWiseSayingByAuthor(searchAuthorKeyword, 1L) + .getWiseSayingList(); + + softAssertions.assertThat(foundWiseSayingList) + .usingRecursiveFieldByFieldElementComparator() + .containsExactlyElementsOf(expectedWiseSayingList); + softAssertions.assertAll(); + } + + @Test + @DisplayName("명언의 내용으로 명언을 검색할 수 있다") + void searchByContentTest() throws Exception { + setUpSampleWiseSaying(); + String searchContentKeyword = "실현"; + List expectedWiseSayingList = List.of( + wiseSayingService.findWiseSayingByID(5L), + wiseSayingService.findWiseSayingByID(4L) + ); + + List foundWiseSayingList = wiseSayingService.findWiseSayingByContent(searchContentKeyword, 1L) + .getWiseSayingList(); + + softAssertions.assertThat(foundWiseSayingList) + .usingRecursiveFieldByFieldElementComparator() + .containsExactlyElementsOf(expectedWiseSayingList); + softAssertions.assertAll(); + } + + @Test + @DisplayName("페이지 번호로 명언을 조회할 수 있다") + void findPageWiseSayingTest() throws Exception { + // GIVEN + setUpSampleWiseSaying(); + List expectedWiseSayingList = new ArrayList<>(); + for (Long id = 6L; id > 1L; id--) { + expectedWiseSayingList.add(wiseSayingService.findWiseSayingByID(id)); + } + + softAssertions.assertThat(wiseSayingService.findWiseSayingPage(1L).getWiseSayingList()) + .usingRecursiveFieldByFieldElementComparator() + .containsExactlyElementsOf(expectedWiseSayingList); + softAssertions.assertAll(); + } +} \ No newline at end of file