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