From 556af3f47a96b32898ab4cdbd65b16486a4871e8 Mon Sep 17 00:00:00 2001 From: damithc Date: Mon, 25 May 2020 00:58:18 +0800 Subject: [PATCH 01/31] Add Gradle support --- build.gradle | 41 +++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58695 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 183 +++++++++++++++++++++++ gradlew.bat | 103 +++++++++++++ text-ui-test/runtest.sh | 0 6 files changed, 332 insertions(+) create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat mode change 100644 => 100755 text-ui-test/runtest.sh diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..885198fcfa --- /dev/null +++ b/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "seedu.duke.Duke" +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + +run{ + standardInput = System.in +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f3d88b1c2faf2fc91d853cd5d4242b5547257070 GIT binary patch literal 58695 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..b7c8c5dbf5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..2fe81a7d95 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or 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 UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$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 "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..62bd9b9cce --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@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 init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +: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/text-ui-test/runtest.sh b/text-ui-test/runtest.sh old mode 100644 new mode 100755 From 07f5535cd199968a0933c6f8b08b49923112d931 Mon Sep 17 00:00:00 2001 From: Devesh Date: Wed, 17 Aug 2022 19:08:50 +0800 Subject: [PATCH 02/31] Customised Duke to Drake, added Greet-Echo-Exit game loop --- media/logo.txt | 15 +++++++++++++++ src/main/java/Drake.java | 30 ++++++++++++++++++++++++++++++ src/main/java/Duke.java | 10 ---------- 3 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 media/logo.txt create mode 100644 src/main/java/Drake.java delete mode 100644 src/main/java/Duke.java diff --git a/media/logo.txt b/media/logo.txt new file mode 100644 index 0000000000..a905704baa --- /dev/null +++ b/media/logo.txt @@ -0,0 +1,15 @@ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣾⣿⣿⣿⣶⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⣿⡯⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⠿⠛⠃⠉⠈⠉⠉⢻⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⢸⣿⣏⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⡆⡀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣀⣀⠀⠀⠀⣴⣾⡯⣿⠊⡎⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⣽⣿⣿⡿⠓⠆⠀⠀⠀⠈⠁⠀⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⢫⡎⠉⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⣧⡀⠀⢐⣶⣶⡧⣤⡄⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣿⣿⣸⣯⡑⠒⠊⣘⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣾⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⢀⠀⠀⠀ +⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⠟⢹⣿⣿⣿⣿⣿⣿⣶⣦ +⠀⠀⣀⣤⣶⣿⣿⣿⣿⣿⣿⣿⣧⠀⠈⠉⠉⠉⠁⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿ +⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿ +⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿ \ No newline at end of file diff --git a/src/main/java/Drake.java b/src/main/java/Drake.java new file mode 100644 index 0000000000..1dd78cabae --- /dev/null +++ b/src/main/java/Drake.java @@ -0,0 +1,30 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Objects; +import java.util.Scanner; + +public class Drake { + + public static void main(String[] args) throws FileNotFoundException { + Scanner sc = new Scanner(new File("media/logo.txt")); + StringBuilder logo = new StringBuilder(); + while (sc.hasNextLine()) + logo.append(sc.nextLine()).append("\n"); + System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); + System.out.println("You used to call me on my cellphone\n" + logo); + System.out.println("Drake's (me) the kind of guy to help you out uwu"); + System.out.println("Go ahead, make that hotline bling"); + System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); + + sc = new Scanner(System.in); + String command = sc.nextLine(); + while (!Objects.equals(command, "bye")) { + System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); + System.out.println(command); + System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); + command = sc.nextLine(); + } + System.out.println("I'm down for you always. See you (✿˶˘ ³˘)~♡"); + } + +} diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} From bf8a7158212191c5717a2c084a8f359767f10917 Mon Sep 17 00:00:00 2001 From: Devesh Date: Thu, 18 Aug 2022 23:16:51 +0800 Subject: [PATCH 03/31] changed Greet-Echo-Exit to Add-List-Exit loop --- src/main/java/Drake.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/Drake.java b/src/main/java/Drake.java index 1dd78cabae..66a5290541 100644 --- a/src/main/java/Drake.java +++ b/src/main/java/Drake.java @@ -1,5 +1,6 @@ import java.io.File; import java.io.FileNotFoundException; +import java.util.ArrayList; import java.util.Objects; import java.util.Scanner; @@ -18,9 +19,17 @@ public static void main(String[] args) throws FileNotFoundException { sc = new Scanner(System.in); String command = sc.nextLine(); + ArrayList list = new ArrayList<>(); while (!Objects.equals(command, "bye")) { System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); - System.out.println(command); + if (command.equals("list")) { + int i = 1; + for (String item : list) + System.out.println(i++ + ". " + item); + } else { + System.out.println("added: " + command); + list.add(command); + } System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); command = sc.nextLine(); } From 00e733ef63a43352506a004c7cfa65da58cd4b4d Mon Sep 17 00:00:00 2001 From: Devesh Date: Mon, 22 Aug 2022 10:41:16 +0800 Subject: [PATCH 04/31] added Mark as Done functionality --- src/main/java/Drake.java | 20 ++++++++++++++++---- src/main/java/Task.java | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 src/main/java/Task.java diff --git a/src/main/java/Drake.java b/src/main/java/Drake.java index 66a5290541..a283a6c08b 100644 --- a/src/main/java/Drake.java +++ b/src/main/java/Drake.java @@ -5,7 +5,6 @@ import java.util.Scanner; public class Drake { - public static void main(String[] args) throws FileNotFoundException { Scanner sc = new Scanner(new File("media/logo.txt")); StringBuilder logo = new StringBuilder(); @@ -19,16 +18,29 @@ public static void main(String[] args) throws FileNotFoundException { sc = new Scanner(System.in); String command = sc.nextLine(); - ArrayList list = new ArrayList<>(); + ArrayList list = new ArrayList<>(); while (!Objects.equals(command, "bye")) { System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); if (command.equals("list")) { + System.out.println("Here are the tasks in your list:"); int i = 1; - for (String item : list) + for (Task item : list) System.out.println(i++ + ". " + item); + } else if (command.startsWith("mark")) { + String[] commands = command.split(" "); + int taskNo = Integer.parseInt(commands[1]); + System.out.println("I've marked this task as done!"); + list.get(taskNo - 1).markAsDone(); + System.out.println(list.get(taskNo - 1)); + } else if (command.startsWith("unmark")) { + String[] commands = command.split(" "); + int taskNo = Integer.parseInt(commands[1]); + System.out.println("I've marked this task as not done (yet ;))"); + list.get(taskNo - 1).unmarkAsDone(); + System.out.println(list.get(taskNo - 1)); } else { System.out.println("added: " + command); - list.add(command); + list.add(new Task(command)); } System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); command = sc.nextLine(); diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 0000000000..e1dc36b282 --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,26 @@ +public class Task { + protected String description; + protected boolean isDone; + + public Task(String description) { + this.description = description; + this.isDone = false; + } + + public String getStatusIcon() { + return (isDone ? "✓" : " "); // mark done task with X + } + + public void markAsDone() { + isDone = true; + } + + public void unmarkAsDone() { + isDone = false; + } + + @Override + public String toString() { + return "[" + getStatusIcon() + "] " + description; + } +} From ccefb6079ee19fbd0feb7e4f6d337eb63cd67388 Mon Sep 17 00:00:00 2001 From: Devesh Date: Mon, 22 Aug 2022 11:15:04 +0800 Subject: [PATCH 05/31] added ToDo Event Deadline tasks --- src/main/java/Deadline.java | 14 ++++++++++++++ src/main/java/Drake.java | 22 +++++++++++++++++++--- src/main/java/Event.java | 13 +++++++++++++ src/main/java/Todo.java | 11 +++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 src/main/java/Deadline.java create mode 100644 src/main/java/Event.java create mode 100644 src/main/java/Todo.java diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 0000000000..2de70b7540 --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,14 @@ +public class Deadline extends Task { + + protected String by; + + public Deadline(String description, String by) { + super(description); + this.by = by; + } + + @Override + public String toString() { + return "[D]" + super.toString() + " (by: " + by + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/Drake.java b/src/main/java/Drake.java index a283a6c08b..649411c929 100644 --- a/src/main/java/Drake.java +++ b/src/main/java/Drake.java @@ -38,9 +38,25 @@ public static void main(String[] args) throws FileNotFoundException { System.out.println("I've marked this task as not done (yet ;))"); list.get(taskNo - 1).unmarkAsDone(); System.out.println(list.get(taskNo - 1)); - } else { - System.out.println("added: " + command); - list.add(new Task(command)); + } else if (command.startsWith("todo")) { + System.out.println("I've added this task:"); + list.add(new Todo(command.substring("todo ".length()))); + System.out.println(list.get(list.size() - 1)); + System.out.println("You now have " + list.size() + " tasks in the list"); + } else if (command.startsWith("deadline")) { + System.out.println("I've added this task:"); + String filtered = command.substring("deadline ".length()); + String[] commands = filtered.split("/by"); + list.add(new Deadline(commands[0], commands[1])); + System.out.println(list.get(list.size() - 1)); + System.out.println("You now have " + list.size() + " tasks in the list"); + } else if (command.startsWith("event")) { + System.out.println("I've added this task:"); + String filtered = command.substring("event ".length()); + String[] commands = filtered.split("/at"); + list.add(new Event(commands[0], commands[1])); + System.out.println(list.get(list.size() - 1)); + System.out.println("You now have " + list.size() + " tasks in the list"); } System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); command = sc.nextLine(); diff --git a/src/main/java/Event.java b/src/main/java/Event.java new file mode 100644 index 0000000000..953aa48199 --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,13 @@ +public class Event extends Task { + protected String at; + + public Event(String description, String at) { + super(description); + this.at = at; + } + + @Override + public String toString() { + return "[E]" + super.toString() + " (at: " + at + ")"; + } +} diff --git a/src/main/java/Todo.java b/src/main/java/Todo.java new file mode 100644 index 0000000000..931cfd4f9e --- /dev/null +++ b/src/main/java/Todo.java @@ -0,0 +1,11 @@ +public class Todo extends Task { + + public Todo(String description) { + super(description); + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } +} From 0cb5430f17e000e3816e6ea0db8d0f7f0b19eac0 Mon Sep 17 00:00:00 2001 From: Devesh Date: Tue, 23 Aug 2022 01:59:51 +0800 Subject: [PATCH 06/31] Update UI testing To make the UI testing work, I had to remove the logo.txt file since it contained non-ASCII symbols. --- media/logo.txt | 15 ------------- src/main/java/Drake.java | 32 +++++++++++++++----------- src/main/java/Task.java | 2 +- text-ui-test/EXPECTED.TXT | 47 +++++++++++++++++++++++++++++++++------ text-ui-test/input.txt | 7 ++++++ text-ui-test/runtest.sh | 2 +- 6 files changed, 68 insertions(+), 37 deletions(-) delete mode 100644 media/logo.txt diff --git a/media/logo.txt b/media/logo.txt deleted file mode 100644 index a905704baa..0000000000 --- a/media/logo.txt +++ /dev/null @@ -1,15 +0,0 @@ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣾⣿⣿⣿⣶⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⣿⡯⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⠿⠛⠃⠉⠈⠉⠉⢻⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⢸⣿⣏⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⡆⡀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣀⣀⠀⠀⠀⣴⣾⡯⣿⠊⡎⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⣽⣿⣿⡿⠓⠆⠀⠀⠀⠈⠁⠀⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⢫⡎⠉⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⣧⡀⠀⢐⣶⣶⡧⣤⡄⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣿⣿⣸⣯⡑⠒⠊⣘⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣾⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⢀⠀⠀⠀ -⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⠟⢹⣿⣿⣿⣿⣿⣿⣶⣦ -⠀⠀⣀⣤⣶⣿⣿⣿⣿⣿⣿⣿⣧⠀⠈⠉⠉⠉⠁⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿ -⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿ -⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿ -⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿ \ No newline at end of file diff --git a/src/main/java/Drake.java b/src/main/java/Drake.java index 649411c929..38a132b5b1 100644 --- a/src/main/java/Drake.java +++ b/src/main/java/Drake.java @@ -1,26 +1,32 @@ -import java.io.File; -import java.io.FileNotFoundException; + import java.util.ArrayList; import java.util.Objects; import java.util.Scanner; public class Drake { - public static void main(String[] args) throws FileNotFoundException { - Scanner sc = new Scanner(new File("media/logo.txt")); + public static void main(String[] args) { + + + System.out.println("------------------------------------------------------"); + System.out.println("You used to call me on my cellphone"); StringBuilder logo = new StringBuilder(); - while (sc.hasNextLine()) - logo.append(sc.nextLine()).append("\n"); - System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); - System.out.println("You used to call me on my cellphone\n" + logo); + for (int i = 0; i < 5; i++) { + logo.append("DRAKE ".repeat(4)); + logo.append("DRAKE"); + if (i == 4) break; + logo.append("\n"); + } + System.out.println(logo); + System.out.println("!@#$%^&*()-+!@#$%^&*()`~`!@#$"); System.out.println("Drake's (me) the kind of guy to help you out uwu"); System.out.println("Go ahead, make that hotline bling"); - System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); + System.out.println("------------------------------------------------------"); - sc = new Scanner(System.in); + Scanner sc = new Scanner(System.in); String command = sc.nextLine(); ArrayList list = new ArrayList<>(); while (!Objects.equals(command, "bye")) { - System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); + System.out.println("------------------------------------------------------"); if (command.equals("list")) { System.out.println("Here are the tasks in your list:"); int i = 1; @@ -58,10 +64,10 @@ public static void main(String[] args) throws FileNotFoundException { System.out.println(list.get(list.size() - 1)); System.out.println("You now have " + list.size() + " tasks in the list"); } - System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆"); + System.out.println("------------------------------------------------------"); command = sc.nextLine(); } - System.out.println("I'm down for you always. See you (✿˶˘ ³˘)~♡"); + System.out.println("I'm down for you always. See you <3"); } } diff --git a/src/main/java/Task.java b/src/main/java/Task.java index e1dc36b282..b95b065648 100644 --- a/src/main/java/Task.java +++ b/src/main/java/Task.java @@ -8,7 +8,7 @@ public Task(String description) { } public String getStatusIcon() { - return (isDone ? "✓" : " "); // mark done task with X + return (isDone ? "X" : " "); // mark done task with X } public void markAsDone() { diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..8f7538535a 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,40 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - +------------------------------------------------------ +You used to call me on my cellphone +DRAKE DRAKE DRAKE DRAKE DRAKE +DRAKE DRAKE DRAKE DRAKE DRAKE +DRAKE DRAKE DRAKE DRAKE DRAKE +DRAKE DRAKE DRAKE DRAKE DRAKE +DRAKE DRAKE DRAKE DRAKE DRAKE +!@#$%^&*()-+!@#$%^&*()`~`!@#$ +Drake's (me) the kind of guy to help you out uwu +Go ahead, make that hotline bling +------------------------------------------------------ +------------------------------------------------------ +I've added this task: +[T][ ] borrow book +You now have 1 tasks in the list +------------------------------------------------------ +------------------------------------------------------ +Here are the tasks in your list: +1. [T][ ] borrow book +------------------------------------------------------ +------------------------------------------------------ +I've added this task: +[E][ ] project meeting (at: Mon 2-4pm) +You now have 2 tasks in the list +------------------------------------------------------ +------------------------------------------------------ +Here are the tasks in your list: +1. [T][ ] borrow book +2. [E][ ] project meeting (at: Mon 2-4pm) +------------------------------------------------------ +------------------------------------------------------ +I've marked this task as done! +[E][X] project meeting (at: Mon 2-4pm) +------------------------------------------------------ +------------------------------------------------------ +Here are the tasks in your list: +1. [T][ ] borrow book +2. [E][X] project meeting (at: Mon 2-4pm) +------------------------------------------------------ +I'm down for you always. See you <3 diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..5f49835f98 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,7 @@ +todo borrow book +list +event project meeting /at Mon 2-4pm +list +mark 2 +list +bye \ No newline at end of file diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index c9ec870033..2ca8215272 100644 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -20,7 +20,7 @@ then fi # run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ../bin Duke < input.txt > ACTUAL.TXT +java -classpath ../bin Drake < input.txt > ACTUAL.TXT # convert to UNIX format cp EXPECTED.TXT EXPECTED-UNIX.TXT From a678bd547fb13442e4f194d201e733357bf4fa18 Mon Sep 17 00:00:00 2001 From: Devesh Date: Tue, 23 Aug 2022 02:48:17 +0800 Subject: [PATCH 07/31] Add Exceptions I added four Exception classes, supporting empty description, no date, unknown command errors --- src/main/java/Drake.java | 49 ++++++++++++++----- src/main/java/DrakeException.java | 5 ++ src/main/java/EmptyDescriptionException.java | 5 ++ .../java/IncompatibleCommandException.java | 9 ++++ src/main/java/UnknownCommandException.java | 5 ++ 5 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 src/main/java/DrakeException.java create mode 100644 src/main/java/EmptyDescriptionException.java create mode 100644 src/main/java/IncompatibleCommandException.java create mode 100644 src/main/java/UnknownCommandException.java diff --git a/src/main/java/Drake.java b/src/main/java/Drake.java index 38a132b5b1..b3c2bd2c90 100644 --- a/src/main/java/Drake.java +++ b/src/main/java/Drake.java @@ -4,7 +4,7 @@ import java.util.Scanner; public class Drake { - public static void main(String[] args) { + public static void main(String[] args) throws DrakeException { System.out.println("------------------------------------------------------"); @@ -34,35 +34,60 @@ public static void main(String[] args) { System.out.println(i++ + ". " + item); } else if (command.startsWith("mark")) { String[] commands = command.split(" "); - int taskNo = Integer.parseInt(commands[1]); - System.out.println("I've marked this task as done!"); - list.get(taskNo - 1).markAsDone(); - System.out.println(list.get(taskNo - 1)); + try { + int taskNo = Integer.parseInt(commands[1]); + if (taskNo <= list.size()) { + System.out.println("I've marked this task as done!"); + list.get(taskNo - 1).markAsDone(); + System.out.println(list.get(taskNo - 1)); + } else { + throw new IncompatibleCommandException("That task number doesn't exist!"); + } + } catch (NumberFormatException e) { + throw new IncompatibleCommandException("Where's the number?"); + } } else if (command.startsWith("unmark")) { String[] commands = command.split(" "); - int taskNo = Integer.parseInt(commands[1]); - System.out.println("I've marked this task as not done (yet ;))"); - list.get(taskNo - 1).unmarkAsDone(); - System.out.println(list.get(taskNo - 1)); + try { + int taskNo = Integer.parseInt(commands[1]); + if (taskNo <= list.size()) { + System.out.println("I've marked this task as not done (yet ;))"); + list.get(taskNo - 1).unmarkAsDone(); + System.out.println(list.get(taskNo - 1)); + } else { + throw new IncompatibleCommandException("That task number doesn't exist!"); + } + } catch (NumberFormatException e) { + throw new IncompatibleCommandException("Where's the number?"); + } } else if (command.startsWith("todo")) { + String description = command.substring("todo ".length()); + if (description.length() == 0) + throw new EmptyDescriptionException(); System.out.println("I've added this task:"); - list.add(new Todo(command.substring("todo ".length()))); + list.add(new Todo(description)); System.out.println(list.get(list.size() - 1)); System.out.println("You now have " + list.size() + " tasks in the list"); } else if (command.startsWith("deadline")) { - System.out.println("I've added this task:"); String filtered = command.substring("deadline ".length()); String[] commands = filtered.split("/by"); + if (commands.length == 1) + throw new IncompatibleCommandException("A deadline task without a deadline?"); + System.out.println("I've added this task:"); list.add(new Deadline(commands[0], commands[1])); System.out.println(list.get(list.size() - 1)); System.out.println("You now have " + list.size() + " tasks in the list"); } else if (command.startsWith("event")) { - System.out.println("I've added this task:"); String filtered = command.substring("event ".length()); String[] commands = filtered.split("/at"); + if (commands.length == 1) + throw new IncompatibleCommandException("An event task without an event time?"); + System.out.println("I've added this task:"); list.add(new Event(commands[0], commands[1])); System.out.println(list.get(list.size() - 1)); System.out.println("You now have " + list.size() + " tasks in the list"); + } else { + throw new UnknownCommandException(); } System.out.println("------------------------------------------------------"); command = sc.nextLine(); diff --git a/src/main/java/DrakeException.java b/src/main/java/DrakeException.java new file mode 100644 index 0000000000..876a9c8c36 --- /dev/null +++ b/src/main/java/DrakeException.java @@ -0,0 +1,5 @@ +public class DrakeException extends Exception { + public DrakeException(String errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/EmptyDescriptionException.java b/src/main/java/EmptyDescriptionException.java new file mode 100644 index 0000000000..1ae8ee03c6 --- /dev/null +++ b/src/main/java/EmptyDescriptionException.java @@ -0,0 +1,5 @@ +public class EmptyDescriptionException extends DrakeException { + public EmptyDescriptionException() { + super("Well well well! A task without a description is like a picture of Singapore without MBS"); + } +} diff --git a/src/main/java/IncompatibleCommandException.java b/src/main/java/IncompatibleCommandException.java new file mode 100644 index 0000000000..1bf711596d --- /dev/null +++ b/src/main/java/IncompatibleCommandException.java @@ -0,0 +1,9 @@ +public class IncompatibleCommandException extends DrakeException { + public IncompatibleCommandException() { + super("These two go together like oil mixes with water"); + } + + public IncompatibleCommandException(String error) { + super("These two go together like oil mixes with water. " + error); + } +} diff --git a/src/main/java/UnknownCommandException.java b/src/main/java/UnknownCommandException.java new file mode 100644 index 0000000000..8b7557c3e3 --- /dev/null +++ b/src/main/java/UnknownCommandException.java @@ -0,0 +1,5 @@ +public class UnknownCommandException extends DrakeException { + public UnknownCommandException() { + super("Uh oh spaghettios I don't know what that means!"); + } +} From cbacd5af8d3d66c8a11462f4c493a4c7516b085b Mon Sep 17 00:00:00 2001 From: Devesh Date: Tue, 23 Aug 2022 02:55:16 +0800 Subject: [PATCH 08/31] Add Delete --- src/main/java/Drake.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/Drake.java b/src/main/java/Drake.java index b3c2bd2c90..5d9ddba787 100644 --- a/src/main/java/Drake.java +++ b/src/main/java/Drake.java @@ -86,6 +86,19 @@ public static void main(String[] args) throws DrakeException { list.add(new Event(commands[0], commands[1])); System.out.println(list.get(list.size() - 1)); System.out.println("You now have " + list.size() + " tasks in the list"); + } else if (command.startsWith("delete")) { + String[] commands = command.split(" "); + try { + int taskNo = Integer.parseInt(commands[1]); + if (taskNo <= list.size()) { + System.out.println("I've removed this task: "); + System.out.println(list.remove(taskNo - 1)); + } else { + throw new IncompatibleCommandException("That task number doesn't exist!"); + } + } catch (NumberFormatException e) { + throw new IncompatibleCommandException("Where's the number?"); + } } else { throw new UnknownCommandException(); } From 32fe38079805d5c1891051183c1940341ac4e41c Mon Sep 17 00:00:00 2001 From: Devesh Date: Mon, 29 Aug 2022 15:05:58 +0800 Subject: [PATCH 09/31] Store Dates as a java.time.LocalDate object --- src/main/java/Deadline.java | 9 ++++++--- src/main/java/Drake.java | 4 ++-- src/main/java/Event.java | 9 ++++++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 2de70b7540..840c270ddb 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -1,14 +1,17 @@ +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + public class Deadline extends Task { - protected String by; + protected final LocalDate by; public Deadline(String description, String by) { super(description); - this.by = by; + this.by = LocalDate.parse(by); } @Override public String toString() { - return "[D]" + super.toString() + " (by: " + by + ")"; + return "[D]" + super.toString() + " (by: " + by.format(DateTimeFormatter.ofPattern("dd MMM yyyy")) + ")"; } } \ No newline at end of file diff --git a/src/main/java/Drake.java b/src/main/java/Drake.java index 5d9ddba787..6338b238b1 100644 --- a/src/main/java/Drake.java +++ b/src/main/java/Drake.java @@ -74,7 +74,7 @@ public static void main(String[] args) throws DrakeException { if (commands.length == 1) throw new IncompatibleCommandException("A deadline task without a deadline?"); System.out.println("I've added this task:"); - list.add(new Deadline(commands[0], commands[1])); + list.add(new Deadline(commands[0], commands[1].trim())); System.out.println(list.get(list.size() - 1)); System.out.println("You now have " + list.size() + " tasks in the list"); } else if (command.startsWith("event")) { @@ -83,7 +83,7 @@ public static void main(String[] args) throws DrakeException { if (commands.length == 1) throw new IncompatibleCommandException("An event task without an event time?"); System.out.println("I've added this task:"); - list.add(new Event(commands[0], commands[1])); + list.add(new Event(commands[0], commands[1].trim())); System.out.println(list.get(list.size() - 1)); System.out.println("You now have " + list.size() + " tasks in the list"); } else if (command.startsWith("delete")) { diff --git a/src/main/java/Event.java b/src/main/java/Event.java index 953aa48199..a70120be95 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -1,13 +1,16 @@ +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + public class Event extends Task { - protected String at; + protected final LocalDate at; public Event(String description, String at) { super(description); - this.at = at; + this.at = LocalDate.parse(at); } @Override public String toString() { - return "[E]" + super.toString() + " (at: " + at + ")"; + return "[E]" + super.toString() + " (at: " + at.format(DateTimeFormatter.ofPattern("dd MMM yyyy")) + ")"; } } From 76a778e4979a5af652c20523a51a0a0452aa9b2a Mon Sep 17 00:00:00 2001 From: Devesh Date: Mon, 29 Aug 2022 17:17:00 +0800 Subject: [PATCH 10/31] Use switch case for task commands instead of if-else --- src/main/java/Deadline.java | 9 +- src/main/java/Drake.java | 163 +++++++++++++++++++++--------------- src/main/java/Event.java | 9 +- 3 files changed, 100 insertions(+), 81 deletions(-) diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 840c270ddb..2de70b7540 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -1,17 +1,14 @@ -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; - public class Deadline extends Task { - protected final LocalDate by; + protected String by; public Deadline(String description, String by) { super(description); - this.by = LocalDate.parse(by); + this.by = by; } @Override public String toString() { - return "[D]" + super.toString() + " (by: " + by.format(DateTimeFormatter.ofPattern("dd MMM yyyy")) + ")"; + return "[D]" + super.toString() + " (by: " + by + ")"; } } \ No newline at end of file diff --git a/src/main/java/Drake.java b/src/main/java/Drake.java index 6338b238b1..54931d501d 100644 --- a/src/main/java/Drake.java +++ b/src/main/java/Drake.java @@ -1,9 +1,10 @@ - import java.util.ArrayList; import java.util.Objects; import java.util.Scanner; + public class Drake { + public static void main(String[] args) throws DrakeException { @@ -24,83 +25,107 @@ public static void main(String[] args) throws DrakeException { Scanner sc = new Scanner(System.in); String command = sc.nextLine(); + ArrayList list = new ArrayList<>(); + while (!Objects.equals(command, "bye")) { - System.out.println("------------------------------------------------------"); + int firstSpace = command.indexOf(' '); + String firstWord; if (command.equals("list")) { - System.out.println("Here are the tasks in your list:"); - int i = 1; - for (Task item : list) - System.out.println(i++ + ". " + item); - } else if (command.startsWith("mark")) { - String[] commands = command.split(" "); - try { - int taskNo = Integer.parseInt(commands[1]); - if (taskNo <= list.size()) { - System.out.println("I've marked this task as done!"); - list.get(taskNo - 1).markAsDone(); - System.out.println(list.get(taskNo - 1)); - } else { - throw new IncompatibleCommandException("That task number doesn't exist!"); + firstWord = "list"; + } else if (firstSpace == -1) { + throw new UnknownCommandException(); + } else { + firstWord = command.substring(0, firstSpace); + } + System.out.println("------------------------------------------------------"); + switch (firstWord) { + case "list": + System.out.println("Here are the tasks in your list:"); + int i = 1; + for (Task item : list) + System.out.println(i++ + ". " + item); + break; + case "mark": { + String[] commands = command.split(" "); + try { + int taskNo = Integer.parseInt(commands[1]); + if (taskNo <= list.size()) { + System.out.println("I've marked this task as done!"); + list.get(taskNo - 1).markAsDone(); + System.out.println(list.get(taskNo - 1)); + } else { + throw new IncompatibleCommandException("That task number doesn't exist!"); + } + } catch (NumberFormatException e) { + throw new IncompatibleCommandException("Where's the number?"); } - } catch (NumberFormatException e) { - throw new IncompatibleCommandException("Where's the number?"); + break; } - } else if (command.startsWith("unmark")) { - String[] commands = command.split(" "); - try { - int taskNo = Integer.parseInt(commands[1]); - if (taskNo <= list.size()) { - System.out.println("I've marked this task as not done (yet ;))"); - list.get(taskNo - 1).unmarkAsDone(); - System.out.println(list.get(taskNo - 1)); - } else { - throw new IncompatibleCommandException("That task number doesn't exist!"); + case "unmark": { + String[] commands = command.split(" "); + try { + int taskNo = Integer.parseInt(commands[1]); + if (taskNo <= list.size()) { + System.out.println("I've marked this task as not done (yet ;))"); + list.get(taskNo - 1).unmarkAsDone(); + System.out.println(list.get(taskNo - 1)); + } else { + throw new IncompatibleCommandException("That task number doesn't exist!"); + } + } catch (NumberFormatException e) { + throw new IncompatibleCommandException("Where's the number?"); } - } catch (NumberFormatException e) { - throw new IncompatibleCommandException("Where's the number?"); + break; + } + case "todo": + String description = command.substring("todo ".length()); + if (description.length() == 0) + throw new EmptyDescriptionException(); + System.out.println("I've added this task:"); + list.add(new Todo(description)); + System.out.println(list.get(list.size() - 1)); + System.out.println("You now have " + list.size() + " tasks in the list"); + break; + case "deadline": { + String filtered = command.substring("deadline ".length()); + String[] commands = filtered.split("/by"); + if (commands.length == 1) + throw new IncompatibleCommandException("A deadline task without a deadline?"); + System.out.println("I've added this task:"); + list.add(new Deadline(commands[0], commands[1].trim())); + System.out.println(list.get(list.size() - 1)); + System.out.println("You now have " + list.size() + " tasks in the list"); + break; } - } else if (command.startsWith("todo")) { - String description = command.substring("todo ".length()); - if (description.length() == 0) - throw new EmptyDescriptionException(); - System.out.println("I've added this task:"); - list.add(new Todo(description)); - System.out.println(list.get(list.size() - 1)); - System.out.println("You now have " + list.size() + " tasks in the list"); - } else if (command.startsWith("deadline")) { - String filtered = command.substring("deadline ".length()); - String[] commands = filtered.split("/by"); - if (commands.length == 1) - throw new IncompatibleCommandException("A deadline task without a deadline?"); - System.out.println("I've added this task:"); - list.add(new Deadline(commands[0], commands[1].trim())); - System.out.println(list.get(list.size() - 1)); - System.out.println("You now have " + list.size() + " tasks in the list"); - } else if (command.startsWith("event")) { - String filtered = command.substring("event ".length()); - String[] commands = filtered.split("/at"); - if (commands.length == 1) - throw new IncompatibleCommandException("An event task without an event time?"); - System.out.println("I've added this task:"); - list.add(new Event(commands[0], commands[1].trim())); - System.out.println(list.get(list.size() - 1)); - System.out.println("You now have " + list.size() + " tasks in the list"); - } else if (command.startsWith("delete")) { - String[] commands = command.split(" "); - try { - int taskNo = Integer.parseInt(commands[1]); - if (taskNo <= list.size()) { - System.out.println("I've removed this task: "); - System.out.println(list.remove(taskNo - 1)); - } else { - throw new IncompatibleCommandException("That task number doesn't exist!"); + case "event": { + String filtered = command.substring("event ".length()); + String[] commands = filtered.split("/at"); + if (commands.length == 1) + throw new IncompatibleCommandException("An event task without an event time?"); + System.out.println("I've added this task:"); + list.add(new Event(commands[0], commands[1].trim())); + System.out.println(list.get(list.size() - 1)); + System.out.println("You now have " + list.size() + " tasks in the list"); + break; + } + case "delete": { + String[] commands = command.split(" "); + try { + int taskNo = Integer.parseInt(commands[1]); + if (taskNo <= list.size()) { + System.out.println("I've removed this task: "); + System.out.println(list.remove(taskNo - 1)); + } else { + throw new IncompatibleCommandException("That task number doesn't exist!"); + } + } catch (NumberFormatException e) { + throw new IncompatibleCommandException("Where's the number?"); } - } catch (NumberFormatException e) { - throw new IncompatibleCommandException("Where's the number?"); + break; } - } else { - throw new UnknownCommandException(); + default: + throw new UnknownCommandException(); } System.out.println("------------------------------------------------------"); command = sc.nextLine(); diff --git a/src/main/java/Event.java b/src/main/java/Event.java index a70120be95..953aa48199 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -1,16 +1,13 @@ -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; - public class Event extends Task { - protected final LocalDate at; + protected String at; public Event(String description, String at) { super(description); - this.at = LocalDate.parse(at); + this.at = at; } @Override public String toString() { - return "[E]" + super.toString() + " (at: " + at.format(DateTimeFormatter.ofPattern("dd MMM yyyy")) + ")"; + return "[E]" + super.toString() + " (at: " + at + ")"; } } From e8289ae8b61844e3bb822f30475e233ccbf57bb8 Mon Sep 17 00:00:00 2001 From: Devesh Date: Mon, 29 Aug 2022 17:30:18 +0800 Subject: [PATCH 11/31] Add save feature Tasks are now saved to a file on the hard disk, which is retrieved in later runs of the program --- data/tasks.txt | 2 ++ src/main/java/Deadline.java | 7 ++++++ src/main/java/Drake.java | 49 ++++++++++++++++++++++++++++++++++++- src/main/java/Event.java | 7 ++++++ src/main/java/Task.java | 7 ++++++ 5 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 data/tasks.txt diff --git a/data/tasks.txt b/data/tasks.txt new file mode 100644 index 0000000000..7a9aa77364 --- /dev/null +++ b/data/tasks.txt @@ -0,0 +1,2 @@ +T; ;haha +D; ;lol ;2020-12-13 diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 2de70b7540..fcaa58c4af 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -1,3 +1,6 @@ +import java.io.FileWriter; +import java.io.IOException; + public class Deadline extends Task { protected String by; @@ -7,6 +10,10 @@ public Deadline(String description, String by) { this.by = by; } + public void writeToFile(FileWriter writer) throws IOException { + writer.write(String.format("D;%s;%s;%s\n", getStatusIcon(), description, by)); + } + @Override public String toString() { return "[D]" + super.toString() + " (by: " + by + ")"; diff --git a/src/main/java/Drake.java b/src/main/java/Drake.java index 54931d501d..edf8f4cf26 100644 --- a/src/main/java/Drake.java +++ b/src/main/java/Drake.java @@ -1,3 +1,7 @@ + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import java.util.ArrayList; import java.util.Objects; import java.util.Scanner; @@ -5,7 +9,7 @@ public class Drake { - public static void main(String[] args) throws DrakeException { + public static void main(String[] args) throws DrakeException, IOException { System.out.println("------------------------------------------------------"); @@ -28,6 +32,42 @@ public static void main(String[] args) throws DrakeException { ArrayList list = new ArrayList<>(); + File taskFile = new File("data/tasks.txt"); + if (taskFile.createNewFile()) { + System.out.println("Task file created: " + taskFile.getName()); + } else { + System.out.println("Task file already exists."); + Scanner fileReader = new Scanner(taskFile); + while (fileReader.hasNext()) { + String[] taskParts = fileReader.nextLine().split(";"); + switch (taskParts[0]) { + case "D": + Deadline deadline = new Deadline(taskParts[2], taskParts[3]); + if (taskParts[1].equals("X")) { + deadline.markAsDone(); + } + list.add(deadline); + break; + case "T": + Task task = new Task(taskParts[2]); + if (taskParts[1].equals("X")) { + task.markAsDone(); + } + list.add(task); + break; + case "E": + Event event = new Event(taskParts[2], taskParts[3]); + if (taskParts[1].equals("X")) { + event.markAsDone(); + } + list.add(event); + break; + default: + throw new UnknownCommandException(); + } + } + } + while (!Objects.equals(command, "bye")) { int firstSpace = command.indexOf(' '); String firstWord; @@ -130,6 +170,13 @@ public static void main(String[] args) throws DrakeException { System.out.println("------------------------------------------------------"); command = sc.nextLine(); } + taskFile.delete(); + File updatedTaskFile = new File("data/tasks.txt"); + FileWriter updatedTaskFileWriter = new FileWriter(updatedTaskFile); + for (Task task : list) { + task.writeToFile(updatedTaskFileWriter); + } + updatedTaskFileWriter.close(); System.out.println("I'm down for you always. See you <3"); } diff --git a/src/main/java/Event.java b/src/main/java/Event.java index 953aa48199..a964eeb8ba 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -1,3 +1,6 @@ +import java.io.FileWriter; +import java.io.IOException; + public class Event extends Task { protected String at; @@ -6,6 +9,10 @@ public Event(String description, String at) { this.at = at; } + public void writeToFile(FileWriter writer) throws IOException { + writer.write(String.format("E;%s;%s;%s\n", getStatusIcon(), description, at)); + } + @Override public String toString() { return "[E]" + super.toString() + " (at: " + at + ")"; diff --git a/src/main/java/Task.java b/src/main/java/Task.java index b95b065648..dc715304b1 100644 --- a/src/main/java/Task.java +++ b/src/main/java/Task.java @@ -1,3 +1,6 @@ +import java.io.FileWriter; +import java.io.IOException; + public class Task { protected String description; protected boolean isDone; @@ -19,6 +22,10 @@ public void unmarkAsDone() { isDone = false; } + public void writeToFile(FileWriter writer) throws IOException { + writer.write(String.format("T;%s;%s\n", getStatusIcon(), description)); + } + @Override public String toString() { return "[" + getStatusIcon() + "] " + description; From 5db588f49e703c25ae50ed26a56e715c31b8334c Mon Sep 17 00:00:00 2001 From: Devesh Date: Tue, 30 Aug 2022 10:35:57 +0800 Subject: [PATCH 12/31] Store Date as a java.time.LocalDate object --- src/main/java/Deadline.java | 9 ++++++--- src/main/java/Event.java | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 2de70b7540..840c270ddb 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -1,14 +1,17 @@ +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + public class Deadline extends Task { - protected String by; + protected final LocalDate by; public Deadline(String description, String by) { super(description); - this.by = by; + this.by = LocalDate.parse(by); } @Override public String toString() { - return "[D]" + super.toString() + " (by: " + by + ")"; + return "[D]" + super.toString() + " (by: " + by.format(DateTimeFormatter.ofPattern("dd MMM yyyy")) + ")"; } } \ No newline at end of file diff --git a/src/main/java/Event.java b/src/main/java/Event.java index 953aa48199..a70120be95 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -1,13 +1,16 @@ +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + public class Event extends Task { - protected String at; + protected final LocalDate at; public Event(String description, String at) { super(description); - this.at = at; + this.at = LocalDate.parse(at); } @Override public String toString() { - return "[E]" + super.toString() + " (at: " + at + ")"; + return "[E]" + super.toString() + " (at: " + at.format(DateTimeFormatter.ofPattern("dd MMM yyyy")) + ")"; } } From e844c5f74e4353d8c7bd23213ef5c2bda036aaa4 Mon Sep 17 00:00:00 2001 From: Devesh Date: Fri, 9 Sep 2022 07:42:15 +0800 Subject: [PATCH 13/31] Refactor the code in Drake.java into multiple classes Ui, Storage, Parser, *Command classes were created. --- .gitignore | 1 + data/tasks.txt | 3 +- src/main/java/ByeCommand.java | 14 ++ src/main/java/Command.java | 9 + src/main/java/CommandType.java | 5 + src/main/java/CreateTaskCommand.java | 15 ++ src/main/java/Deadline.java | 14 +- src/main/java/DeadlineCommand.java | 25 +++ src/main/java/DeleteCommand.java | 20 ++ src/main/java/Drake.java | 198 +++--------------- src/main/java/Event.java | 14 +- src/main/java/EventCommand.java | 25 +++ src/main/java/InvalidTaskNumberException.java | 5 + src/main/java/ListCommand.java | 11 + src/main/java/MarkCommand.java | 20 ++ src/main/java/Parser.java | 37 ++++ src/main/java/Storage.java | 110 ++++++++++ src/main/java/Task.java | 12 +- src/main/java/TaskList.java | 40 ++++ src/main/java/TaskOperationCommand.java | 24 +++ src/main/java/Todo.java | 11 + src/main/java/TodoCommand.java | 17 ++ src/main/java/Ui.java | 49 +++++ src/main/java/UnmarkCommand.java | 20 ++ 24 files changed, 511 insertions(+), 188 deletions(-) create mode 100644 src/main/java/ByeCommand.java create mode 100644 src/main/java/Command.java create mode 100644 src/main/java/CommandType.java create mode 100644 src/main/java/CreateTaskCommand.java create mode 100644 src/main/java/DeadlineCommand.java create mode 100644 src/main/java/DeleteCommand.java create mode 100644 src/main/java/EventCommand.java create mode 100644 src/main/java/InvalidTaskNumberException.java create mode 100644 src/main/java/ListCommand.java create mode 100644 src/main/java/MarkCommand.java create mode 100644 src/main/java/Parser.java create mode 100644 src/main/java/Storage.java create mode 100644 src/main/java/TaskList.java create mode 100644 src/main/java/TaskOperationCommand.java create mode 100644 src/main/java/TodoCommand.java create mode 100644 src/main/java/Ui.java create mode 100644 src/main/java/UnmarkCommand.java diff --git a/.gitignore b/.gitignore index f69985ef1f..3ffdd400bf 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT +/data/tasks.txt \ No newline at end of file diff --git a/data/tasks.txt b/data/tasks.txt index 7a9aa77364..7ac792a1c9 100644 --- a/data/tasks.txt +++ b/data/tasks.txt @@ -1,2 +1 @@ -T; ;haha -D; ;lol ;2020-12-13 +T; ; diff --git a/src/main/java/ByeCommand.java b/src/main/java/ByeCommand.java new file mode 100644 index 0000000000..24886d7873 --- /dev/null +++ b/src/main/java/ByeCommand.java @@ -0,0 +1,14 @@ +import java.io.IOException; + +public class ByeCommand extends Command { + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { + storage.close(); + ui.printBye(); + } + + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/Command.java b/src/main/java/Command.java new file mode 100644 index 0000000000..c814c06de1 --- /dev/null +++ b/src/main/java/Command.java @@ -0,0 +1,9 @@ +import java.io.IOException; + +public abstract class Command { + public abstract void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException; + + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/CommandType.java b/src/main/java/CommandType.java new file mode 100644 index 0000000000..285575b33d --- /dev/null +++ b/src/main/java/CommandType.java @@ -0,0 +1,5 @@ +public enum CommandType { + MARK, + UNMARK, + DELETE, +} diff --git a/src/main/java/CreateTaskCommand.java b/src/main/java/CreateTaskCommand.java new file mode 100644 index 0000000000..96dfd549b1 --- /dev/null +++ b/src/main/java/CreateTaskCommand.java @@ -0,0 +1,15 @@ +import java.io.IOException; + +public abstract class CreateTaskCommand extends Command { + + protected String description; + + public CreateTaskCommand(String fullInput) { + description = fullInput.substring(fullInput.indexOf(' ') + 1); + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { + ui.printLine(tasks.getSizeToString()); + } +} diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 7f8b01057b..e25c95b370 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -1,8 +1,7 @@ -import java.io.FileWriter; -import java.io.IOException; - import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; public class Deadline extends Task { @@ -13,8 +12,13 @@ public Deadline(String description, String by) { this.by = LocalDate.parse(by); } - public void writeToFile(FileWriter writer) throws IOException { - writer.write(String.format("D;%s;%s;%s\n", getStatusIcon(), description, by)); + @Override + public List toList() { + List result = new ArrayList<>(); + result.add("D"); + result.addAll(super.toList()); + result.add(by.toString()); + return result; } @Override diff --git a/src/main/java/DeadlineCommand.java b/src/main/java/DeadlineCommand.java new file mode 100644 index 0000000000..d96bf99996 --- /dev/null +++ b/src/main/java/DeadlineCommand.java @@ -0,0 +1,25 @@ +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DeadlineCommand extends CreateTaskCommand { + + private final static Pattern descriptionPattern = Pattern.compile("(?.*) /by (?.*)"); + + public DeadlineCommand(String fullInput) { + super(fullInput); + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { + Matcher match = descriptionPattern.matcher(description); + if (!match.matches()) { + throw new IncompatibleCommandException("A deadline task without a deadline?"); + } + ui.printLine("I've added this task:"); + Task addedTask = tasks.addTask(new Deadline(match.group("taskName"), match.group("by"))); + ui.printLine(addedTask); + storage.addTask(addedTask); + super.execute(tasks, ui, storage); + } +} diff --git a/src/main/java/DeleteCommand.java b/src/main/java/DeleteCommand.java new file mode 100644 index 0000000000..4391f665d3 --- /dev/null +++ b/src/main/java/DeleteCommand.java @@ -0,0 +1,20 @@ +import java.io.IOException; + +public class DeleteCommand extends TaskOperationCommand { + + public DeleteCommand(String fullInput) throws IncompatibleCommandException { + super(fullInput); + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws InvalidTaskNumberException, IOException { + if (tasks.isValidTaskNumber(taskNumber)) { + System.out.println("I've removed this task: "); + ui.printLine(tasks.getTaskToString(taskNumber)); + tasks.removeTask(taskNumber); + storage.updateTask(taskNumber, CommandType.DELETE); + } else { + throw new InvalidTaskNumberException(); + } + } +} diff --git a/src/main/java/Drake.java b/src/main/java/Drake.java index edf8f4cf26..4da7bbe748 100644 --- a/src/main/java/Drake.java +++ b/src/main/java/Drake.java @@ -1,183 +1,41 @@ - -import java.io.File; -import java.io.FileWriter; import java.io.IOException; -import java.util.ArrayList; -import java.util.Objects; -import java.util.Scanner; - public class Drake { - public static void main(String[] args) throws DrakeException, IOException { - - - System.out.println("------------------------------------------------------"); - System.out.println("You used to call me on my cellphone"); - StringBuilder logo = new StringBuilder(); - for (int i = 0; i < 5; i++) { - logo.append("DRAKE ".repeat(4)); - logo.append("DRAKE"); - if (i == 4) break; - logo.append("\n"); - } - System.out.println(logo); - System.out.println("!@#$%^&*()-+!@#$%^&*()`~`!@#$"); - System.out.println("Drake's (me) the kind of guy to help you out uwu"); - System.out.println("Go ahead, make that hotline bling"); - System.out.println("------------------------------------------------------"); - - Scanner sc = new Scanner(System.in); - String command = sc.nextLine(); + private final Storage storage; + private final TaskList tasks; + private final Ui ui; - ArrayList list = new ArrayList<>(); + public Drake() throws IOException { + ui = new Ui(); + storage = new Storage(); + tasks = new TaskList(storage.fileToList()); + } - File taskFile = new File("data/tasks.txt"); - if (taskFile.createNewFile()) { - System.out.println("Task file created: " + taskFile.getName()); - } else { - System.out.println("Task file already exists."); - Scanner fileReader = new Scanner(taskFile); - while (fileReader.hasNext()) { - String[] taskParts = fileReader.nextLine().split(";"); - switch (taskParts[0]) { - case "D": - Deadline deadline = new Deadline(taskParts[2], taskParts[3]); - if (taskParts[1].equals("X")) { - deadline.markAsDone(); - } - list.add(deadline); - break; - case "T": - Task task = new Task(taskParts[2]); - if (taskParts[1].equals("X")) { - task.markAsDone(); - } - list.add(task); - break; - case "E": - Event event = new Event(taskParts[2], taskParts[3]); - if (taskParts[1].equals("X")) { - event.markAsDone(); - } - list.add(event); - break; - default: - throw new UnknownCommandException(); - } + public void run() { + ui.showWelcome(); + boolean isExit = false; + while (!isExit) { + try { + String fullInput = ui.readInput(); + ui.printDash(); // show the divider line ("_______") + Command c = Parser.parse(fullInput); + c.execute(tasks, ui, storage); + isExit = c.isExit(); + } catch (IOException | DrakeException e) { + ui.printError(e.getMessage()); + } finally { + ui.printDash(); } } + } - while (!Objects.equals(command, "bye")) { - int firstSpace = command.indexOf(' '); - String firstWord; - if (command.equals("list")) { - firstWord = "list"; - } else if (firstSpace == -1) { - throw new UnknownCommandException(); - } else { - firstWord = command.substring(0, firstSpace); - } - System.out.println("------------------------------------------------------"); - switch (firstWord) { - case "list": - System.out.println("Here are the tasks in your list:"); - int i = 1; - for (Task item : list) - System.out.println(i++ + ". " + item); - break; - case "mark": { - String[] commands = command.split(" "); - try { - int taskNo = Integer.parseInt(commands[1]); - if (taskNo <= list.size()) { - System.out.println("I've marked this task as done!"); - list.get(taskNo - 1).markAsDone(); - System.out.println(list.get(taskNo - 1)); - } else { - throw new IncompatibleCommandException("That task number doesn't exist!"); - } - } catch (NumberFormatException e) { - throw new IncompatibleCommandException("Where's the number?"); - } - break; - } - case "unmark": { - String[] commands = command.split(" "); - try { - int taskNo = Integer.parseInt(commands[1]); - if (taskNo <= list.size()) { - System.out.println("I've marked this task as not done (yet ;))"); - list.get(taskNo - 1).unmarkAsDone(); - System.out.println(list.get(taskNo - 1)); - } else { - throw new IncompatibleCommandException("That task number doesn't exist!"); - } - } catch (NumberFormatException e) { - throw new IncompatibleCommandException("Where's the number?"); - } - break; - } - case "todo": - String description = command.substring("todo ".length()); - if (description.length() == 0) - throw new EmptyDescriptionException(); - System.out.println("I've added this task:"); - list.add(new Todo(description)); - System.out.println(list.get(list.size() - 1)); - System.out.println("You now have " + list.size() + " tasks in the list"); - break; - case "deadline": { - String filtered = command.substring("deadline ".length()); - String[] commands = filtered.split("/by"); - if (commands.length == 1) - throw new IncompatibleCommandException("A deadline task without a deadline?"); - System.out.println("I've added this task:"); - list.add(new Deadline(commands[0], commands[1].trim())); - System.out.println(list.get(list.size() - 1)); - System.out.println("You now have " + list.size() + " tasks in the list"); - break; - } - case "event": { - String filtered = command.substring("event ".length()); - String[] commands = filtered.split("/at"); - if (commands.length == 1) - throw new IncompatibleCommandException("An event task without an event time?"); - System.out.println("I've added this task:"); - list.add(new Event(commands[0], commands[1].trim())); - System.out.println(list.get(list.size() - 1)); - System.out.println("You now have " + list.size() + " tasks in the list"); - break; - } - case "delete": { - String[] commands = command.split(" "); - try { - int taskNo = Integer.parseInt(commands[1]); - if (taskNo <= list.size()) { - System.out.println("I've removed this task: "); - System.out.println(list.remove(taskNo - 1)); - } else { - throw new IncompatibleCommandException("That task number doesn't exist!"); - } - } catch (NumberFormatException e) { - throw new IncompatibleCommandException("Where's the number?"); - } - break; - } - default: - throw new UnknownCommandException(); - } - System.out.println("------------------------------------------------------"); - command = sc.nextLine(); - } - taskFile.delete(); - File updatedTaskFile = new File("data/tasks.txt"); - FileWriter updatedTaskFileWriter = new FileWriter(updatedTaskFile); - for (Task task : list) { - task.writeToFile(updatedTaskFileWriter); + public static void main(String[] args) { + try { + new Drake().run(); + } catch (IOException e) { + System.out.println(e.getMessage()); } - updatedTaskFileWriter.close(); - System.out.println("I'm down for you always. See you <3"); } } diff --git a/src/main/java/Event.java b/src/main/java/Event.java index d3a8c0ae7c..3454e7a3d5 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -1,8 +1,7 @@ -import java.io.FileWriter; -import java.io.IOException; - import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; public class Event extends Task { protected final LocalDate at; @@ -12,8 +11,13 @@ public Event(String description, String at) { this.at = LocalDate.parse(at); } - public void writeToFile(FileWriter writer) throws IOException { - writer.write(String.format("E;%s;%s;%s\n", getStatusIcon(), description, at)); + @Override + public List toList() { + List result = new ArrayList<>(); + result.add("E"); + result.addAll(super.toList()); + result.add(at.toString()); + return result; } @Override diff --git a/src/main/java/EventCommand.java b/src/main/java/EventCommand.java new file mode 100644 index 0000000000..120f4a7de2 --- /dev/null +++ b/src/main/java/EventCommand.java @@ -0,0 +1,25 @@ +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class EventCommand extends CreateTaskCommand { + + private final static Pattern descriptionPattern = Pattern.compile("(?.*) /at (?.*)"); + + public EventCommand(String fullInput) { + super(fullInput); + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { + Matcher match = descriptionPattern.matcher(description); + if (!match.matches()) { + throw new IncompatibleCommandException("An event task without an event time?"); + } + ui.printLine("I've added this task:"); + Task addedTask = tasks.addTask(new Event(match.group("taskName"), match.group("at"))); + ui.printLine(addedTask); + storage.addTask(addedTask); + super.execute(tasks, ui, storage); + } +} diff --git a/src/main/java/InvalidTaskNumberException.java b/src/main/java/InvalidTaskNumberException.java new file mode 100644 index 0000000000..6abfee2d66 --- /dev/null +++ b/src/main/java/InvalidTaskNumberException.java @@ -0,0 +1,5 @@ +public class InvalidTaskNumberException extends IncompatibleCommandException { + public InvalidTaskNumberException() { + super("That task number doesn't exist!"); + } +} diff --git a/src/main/java/ListCommand.java b/src/main/java/ListCommand.java new file mode 100644 index 0000000000..3585973b21 --- /dev/null +++ b/src/main/java/ListCommand.java @@ -0,0 +1,11 @@ +import java.io.IOException; + +public class ListCommand extends Command { + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { + ui.printLine("Here are the tasks in your list:"); + for (int i = 1; tasks.isValidTaskNumber(i); i++) { + ui.printLine(i + ". " + tasks.getTaskToString(i)); + } + } +} diff --git a/src/main/java/MarkCommand.java b/src/main/java/MarkCommand.java new file mode 100644 index 0000000000..68d375a8e9 --- /dev/null +++ b/src/main/java/MarkCommand.java @@ -0,0 +1,20 @@ +import java.io.IOException; + +public class MarkCommand extends TaskOperationCommand { + + public MarkCommand(String fullInput) throws IncompatibleCommandException { + super(fullInput); + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws InvalidTaskNumberException, IOException { + if (tasks.isValidTaskNumber(taskNumber)) { + System.out.println("I've marked this task as done!"); + tasks.markAsDone(taskNumber); + storage.updateTask(taskNumber, CommandType.MARK); + ui.printLine(tasks.getTaskToString(taskNumber)); + } else { + throw new InvalidTaskNumberException(); + } + } +} diff --git a/src/main/java/Parser.java b/src/main/java/Parser.java new file mode 100644 index 0000000000..1e9f0ccab5 --- /dev/null +++ b/src/main/java/Parser.java @@ -0,0 +1,37 @@ +public class Parser { + + public static Command parse(String fullInput) throws UnknownCommandException, + IncompatibleCommandException, EmptyDescriptionException { + int firstSpace = fullInput.indexOf(' '); + String commandText; + if (fullInput.matches("list|bye")) { + commandText = fullInput; + } else if (firstSpace == -1 && fullInput.matches("deadline|event|todo")) { + throw new EmptyDescriptionException(); + } else if (firstSpace == -1) { + throw new UnknownCommandException(); + } else { + commandText = fullInput.substring(0, firstSpace); + } + switch (commandText) { + case "list": + return new ListCommand(); + case "mark": + return new MarkCommand(fullInput); + case "unmark": + return new UnmarkCommand(fullInput); + case "todo": + return new TodoCommand(fullInput); + case "deadline": + return new DeadlineCommand(fullInput); + case "event": + return new EventCommand(fullInput); + case "delete": + return new DeleteCommand(fullInput); + case "bye": + return new ByeCommand(); + default: + throw new UnknownCommandException(); + } + } +} diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java new file mode 100644 index 0000000000..aedc6c2bd7 --- /dev/null +++ b/src/main/java/Storage.java @@ -0,0 +1,110 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class Storage { + + List> tasks; + private static final String TASK_FILE_PATH = "data/tasks.txt"; + private FileWriter fileWriter; + private final File taskFile; + + public Storage() throws IOException { + taskFile = new File(TASK_FILE_PATH); + tasks = new ArrayList<>(); + addTasks(fileToList()); + } + + public List fileToList() { + ArrayList list = new ArrayList<>(); + Scanner fileReader; + try { + fileReader = new Scanner(taskFile); + } catch (FileNotFoundException e) { + return list; + } + while (fileReader.hasNext()) { + String[] taskParts = fileReader.nextLine().split(";"); + switch (taskParts[0]) { + case "D": + Deadline deadline = new Deadline(taskParts[2], taskParts[3]); + if (taskParts[1].equals("X")) { + deadline.markAsDone(); + } + list.add(deadline); + break; + case "T": + Task task = new Todo(taskParts[2]); + if (taskParts[1].equals("X")) { + task.markAsDone(); + } + list.add(task); + break; + case "E": + Event event = new Event(taskParts[2], taskParts[3]); + if (taskParts[1].equals("X")) { + event.markAsDone(); + } + list.add(event); + break; + } + } + return list; + } + + public void updateTask(int taskNumber, CommandType command) throws IOException { + switch (command) { + case MARK: + tasks.get(taskNumber - 1).set(1, "X"); + break; + + case UNMARK: + tasks.get(taskNumber - 1).set(1, " "); + break; + + case DELETE: + tasks.remove(taskNumber - 1); + } + + updateFile(); + } + + private void updateFile() throws IOException { + fileWriter = new FileWriter(TASK_FILE_PATH); + for (List task : tasks) { + StringBuilder taskString = new StringBuilder(task.get(0)).append(";"); + for (int i = 1; i < task.size(); i++) { + taskString.append(task.get(i)); + if (i != task.size() - 1) { + taskString.append(";"); + } + } + fileWriter.write(taskString.append("\n").toString()); + System.out.println("yo"); + } + for (List task : tasks) + System.out.println(String.join(", ", task)); + } + + public void addTask(Task addedTask) throws IOException { + tasks.add(addedTask.toList()); + updateFile(); + } + + private void addTasks(List addedTasks) throws IOException { + for (Task addedTask : addedTasks) { + addTask(addedTask); + } + updateFile(); + } + + public void close() throws IOException { + if (fileWriter != null) { + fileWriter.close(); + } + } +} diff --git a/src/main/java/Task.java b/src/main/java/Task.java index dc715304b1..1a2e32ff74 100644 --- a/src/main/java/Task.java +++ b/src/main/java/Task.java @@ -1,7 +1,7 @@ -import java.io.FileWriter; -import java.io.IOException; +import java.util.Arrays; +import java.util.List; -public class Task { +public abstract class Task { protected String description; protected boolean isDone; @@ -18,12 +18,12 @@ public void markAsDone() { isDone = true; } - public void unmarkAsDone() { + public void markAsNotDone() { isDone = false; } - public void writeToFile(FileWriter writer) throws IOException { - writer.write(String.format("T;%s;%s\n", getStatusIcon(), description)); + public List toList() { + return Arrays.asList(description, getStatusIcon()); } @Override diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java new file mode 100644 index 0000000000..2e2390981b --- /dev/null +++ b/src/main/java/TaskList.java @@ -0,0 +1,40 @@ +import java.util.List; + +public class TaskList { + + private final List list; + + public TaskList(List list) { + this.list = list; + } + + + public boolean isValidTaskNumber(int taskNumber) { + return taskNumber <= list.size(); + } + + public void markAsDone(int taskNumber) { + list.get(taskNumber - 1).markAsDone(); + } + + public String getTaskToString(int taskNumber) { + return list.get(taskNumber - 1).toString(); + } + + public void markAsNotDone(int taskNumber) { + list.get(taskNumber - 1).markAsNotDone(); + } + + public void removeTask(int taskNumber) { + list.remove(taskNumber - 1); + } + + public String getSizeToString() { + return "You now have " + list.size() + " tasks in the list"; + } + + public Task addTask(Task task) { + list.add(task); + return task; + } +} diff --git a/src/main/java/TaskOperationCommand.java b/src/main/java/TaskOperationCommand.java new file mode 100644 index 0000000000..c7c647c681 --- /dev/null +++ b/src/main/java/TaskOperationCommand.java @@ -0,0 +1,24 @@ +import java.io.IOException; + +public abstract class TaskOperationCommand extends Command { + + protected final int taskNumber; + + public TaskOperationCommand(String fullInput) throws IncompatibleCommandException { + String[] commands = fullInput.split(" "); + try { + taskNumber = Integer.parseInt(commands[1]); + } catch (NumberFormatException e) { + throw new IncompatibleCommandException("Where's the number?"); + } + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws InvalidTaskNumberException, IOException { + if (tasks.isValidTaskNumber(taskNumber)) { + ui.printLine(tasks.getTaskToString(taskNumber)); + } else { + throw new InvalidTaskNumberException(); + } + } +} diff --git a/src/main/java/Todo.java b/src/main/java/Todo.java index 931cfd4f9e..ab02f558cb 100644 --- a/src/main/java/Todo.java +++ b/src/main/java/Todo.java @@ -1,9 +1,20 @@ +import java.util.ArrayList; +import java.util.List; + public class Todo extends Task { public Todo(String description) { super(description); } + @Override + public List toList() { + List result = new ArrayList<>(); + result.add("T"); + result.addAll(super.toList()); + return result; + } + @Override public String toString() { return "[T]" + super.toString(); diff --git a/src/main/java/TodoCommand.java b/src/main/java/TodoCommand.java new file mode 100644 index 0000000000..f792a44af2 --- /dev/null +++ b/src/main/java/TodoCommand.java @@ -0,0 +1,17 @@ +import java.io.IOException; + +public class TodoCommand extends CreateTaskCommand { + + public TodoCommand(String fullInput) { + super(fullInput); + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { + ui.printLine("I've added this task:"); + Task addedTask = tasks.addTask(new Todo(description)); + ui.printLine(addedTask); + storage.addTask(addedTask); + super.execute(tasks, ui, storage); + } +} diff --git a/src/main/java/Ui.java b/src/main/java/Ui.java new file mode 100644 index 0000000000..ee3655248f --- /dev/null +++ b/src/main/java/Ui.java @@ -0,0 +1,49 @@ +import java.util.Scanner; + +public class Ui { + private final String DASH = "------------------------------------------------------"; + public static final String ANSI_RESET = "\u001B[0m"; + public static final String ANSI_RED = "\u001B[31m"; + private final Scanner sc; + + public Ui() { + this.sc = new Scanner(System.in); + } + + public void showWelcome() { + System.out.println(DASH); + System.out.println("You used to call me on my cellphone"); + StringBuilder logo = new StringBuilder(); + for (int i = 0; i < 5; i++) { + logo.append("DRAKE ".repeat(4)); + logo.append("DRAKE"); + if (i == 4) break; + logo.append("\n"); + } + System.out.println(logo); + System.out.println("!@#$%^&*()-+!@#$%^&*()`~`!@#$"); + System.out.println("Drake's (me) the kind of guy to help you out uwu"); + System.out.println("Go ahead, make that hotline bling"); + System.out.println(DASH); + } + + public String readInput() { + return sc.nextLine().trim(); + } + + public void printLine(Object line) { + System.out.println(line); + } + + public void printBye() { + System.out.println("I'm down for you always. See you " + ANSI_RED + "<3" + ANSI_RESET); + } + + public void printDash() { + System.out.println(DASH); + } + + public void printError(String errorMessage) { + System.out.println(ANSI_RED + errorMessage + ANSI_RESET); + } +} diff --git a/src/main/java/UnmarkCommand.java b/src/main/java/UnmarkCommand.java new file mode 100644 index 0000000000..02b10601cf --- /dev/null +++ b/src/main/java/UnmarkCommand.java @@ -0,0 +1,20 @@ +import java.io.IOException; + +public class UnmarkCommand extends TaskOperationCommand { + + public UnmarkCommand(String fullInput) throws IncompatibleCommandException { + super(fullInput); + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws InvalidTaskNumberException, IOException { + if (tasks.isValidTaskNumber(taskNumber)) { + System.out.println("I've marked this task as not done (yet ;))"); + tasks.markAsNotDone(taskNumber); + storage.updateTask(taskNumber, CommandType.UNMARK); + ui.printLine(tasks.getTaskToString(taskNumber)); + } else { + throw new InvalidTaskNumberException(); + } + } +} From 482dd0bebfad3699ce8fa55ddbb459203d3dead2 Mon Sep 17 00:00:00 2001 From: Devesh Date: Sat, 10 Sep 2022 11:40:20 +0800 Subject: [PATCH 14/31] Fix non-persistent storage and refactor Storage class The task list is not persistent across restarts. Do not delete the task file after every command. This ensures the task file is not deleted and replaced with an empty file after a restart. --- data/tasks.txt | 4 +- src/main/java/ByeCommand.java | 1 - src/main/java/DeleteCommand.java | 2 +- src/main/java/Drake.java | 4 +- src/main/java/MarkCommand.java | 2 +- src/main/java/Storage.java | 72 +++++++++++++++---------- src/main/java/TaskOperationCommand.java | 2 +- src/main/java/UnmarkCommand.java | 2 +- 8 files changed, 53 insertions(+), 36 deletions(-) diff --git a/data/tasks.txt b/data/tasks.txt index 7ac792a1c9..edebdcb0c3 100644 --- a/data/tasks.txt +++ b/data/tasks.txt @@ -1 +1,3 @@ -T; ; +T;haha; +D;yo;X;2020-02-02 +T;andre; diff --git a/src/main/java/ByeCommand.java b/src/main/java/ByeCommand.java index 24886d7873..2520d36970 100644 --- a/src/main/java/ByeCommand.java +++ b/src/main/java/ByeCommand.java @@ -3,7 +3,6 @@ public class ByeCommand extends Command { @Override public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { - storage.close(); ui.printBye(); } diff --git a/src/main/java/DeleteCommand.java b/src/main/java/DeleteCommand.java index 4391f665d3..1e6db01ba4 100644 --- a/src/main/java/DeleteCommand.java +++ b/src/main/java/DeleteCommand.java @@ -7,7 +7,7 @@ public DeleteCommand(String fullInput) throws IncompatibleCommandException { } @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws InvalidTaskNumberException, IOException { + public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { if (tasks.isValidTaskNumber(taskNumber)) { System.out.println("I've removed this task: "); ui.printLine(tasks.getTaskToString(taskNumber)); diff --git a/src/main/java/Drake.java b/src/main/java/Drake.java index 4da7bbe748..b0ee0a8673 100644 --- a/src/main/java/Drake.java +++ b/src/main/java/Drake.java @@ -6,7 +6,7 @@ public class Drake { private final TaskList tasks; private final Ui ui; - public Drake() throws IOException { + public Drake() throws IOException, DrakeException { ui = new Ui(); storage = new Storage(); tasks = new TaskList(storage.fileToList()); @@ -35,6 +35,8 @@ public static void main(String[] args) { new Drake().run(); } catch (IOException e) { System.out.println(e.getMessage()); + } catch (DrakeException e) { + throw new RuntimeException(e); } } diff --git a/src/main/java/MarkCommand.java b/src/main/java/MarkCommand.java index 68d375a8e9..839631345a 100644 --- a/src/main/java/MarkCommand.java +++ b/src/main/java/MarkCommand.java @@ -7,7 +7,7 @@ public MarkCommand(String fullInput) throws IncompatibleCommandException { } @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws InvalidTaskNumberException, IOException { + public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { if (tasks.isValidTaskNumber(taskNumber)) { System.out.println("I've marked this task as done!"); tasks.markAsDone(taskNumber); diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java index aedc6c2bd7..a85f4c2bac 100644 --- a/src/main/java/Storage.java +++ b/src/main/java/Storage.java @@ -10,10 +10,10 @@ public class Storage { List> tasks; private static final String TASK_FILE_PATH = "data/tasks.txt"; - private FileWriter fileWriter; + private static final String TASK_FILE_DIR = "data"; private final File taskFile; - public Storage() throws IOException { + public Storage() throws IOException, DrakeException { taskFile = new File(TASK_FILE_PATH); tasks = new ArrayList<>(); addTasks(fileToList()); @@ -31,22 +31,22 @@ public List fileToList() { String[] taskParts = fileReader.nextLine().split(";"); switch (taskParts[0]) { case "D": - Deadline deadline = new Deadline(taskParts[2], taskParts[3]); - if (taskParts[1].equals("X")) { + Deadline deadline = new Deadline(taskParts[1], taskParts[3]); + if (taskParts[2].equals("X")) { deadline.markAsDone(); } list.add(deadline); break; case "T": - Task task = new Todo(taskParts[2]); - if (taskParts[1].equals("X")) { + Task task = new Todo(taskParts[1]); + if (taskParts[2].equals("X")) { task.markAsDone(); } list.add(task); break; case "E": - Event event = new Event(taskParts[2], taskParts[3]); - if (taskParts[1].equals("X")) { + Event event = new Event(taskParts[1], taskParts[3]); + if (taskParts[2].equals("X")) { event.markAsDone(); } list.add(event); @@ -56,14 +56,14 @@ public List fileToList() { return list; } - public void updateTask(int taskNumber, CommandType command) throws IOException { + public void updateTask(int taskNumber, CommandType command) throws DrakeException { switch (command) { case MARK: - tasks.get(taskNumber - 1).set(1, "X"); + tasks.get(taskNumber - 1).set(2, "X"); break; case UNMARK: - tasks.get(taskNumber - 1).set(1, " "); + tasks.get(taskNumber - 1).set(2, " "); break; case DELETE: @@ -73,38 +73,52 @@ public void updateTask(int taskNumber, CommandType command) throws IOException { updateFile(); } - private void updateFile() throws IOException { - fileWriter = new FileWriter(TASK_FILE_PATH); - for (List task : tasks) { - StringBuilder taskString = new StringBuilder(task.get(0)).append(";"); - for (int i = 1; i < task.size(); i++) { - taskString.append(task.get(i)); - if (i != task.size() - 1) { - taskString.append(";"); - } + //Inspired by parnikkapore's PR + private void updateFile() throws DrakeException { + try { + File fileDir = new File(TASK_FILE_DIR); + if (!fileDir.isDirectory() && !fileDir.mkdirs()) { + throw new DrakeException("Higher powers taking a hold on me... I cannot save the task list."); } - fileWriter.write(taskString.append("\n").toString()); - System.out.println("yo"); + + FileWriter fileWriter = new FileWriter(TASK_FILE_PATH); + for (List task : tasks) { + fileWriter.write(listToCsv(task)); + } + fileWriter.close(); + } catch (IOException e){ + throw new DrakeException( + "Higher powers taking a hold on me... I cannot save the task list. This might help: " + e); } - for (List task : tasks) - System.out.println(String.join(", ", task)); } - public void addTask(Task addedTask) throws IOException { + public void addTask(Task addedTask) throws DrakeException { tasks.add(addedTask.toList()); updateFile(); } - private void addTasks(List addedTasks) throws IOException { + private void addTasks(List addedTasks) throws DrakeException { for (Task addedTask : addedTasks) { addTask(addedTask); } updateFile(); } - public void close() throws IOException { - if (fileWriter != null) { - fileWriter.close(); + private String listToCsv(List list) { + StringBuilder csv = new StringBuilder(list.get(0)).append(";"); + for (int i = 1; i < list.size(); i++) { + csv.append(list.get(i)); + if (i != list.size() - 1) { + csv.append(";"); + } + } + return csv.append("\n").toString(); + } + + //For debugging + private void printTasks() { + for (List task : tasks) { + System.out.println(task); } } } diff --git a/src/main/java/TaskOperationCommand.java b/src/main/java/TaskOperationCommand.java index c7c647c681..91dfc56180 100644 --- a/src/main/java/TaskOperationCommand.java +++ b/src/main/java/TaskOperationCommand.java @@ -14,7 +14,7 @@ public TaskOperationCommand(String fullInput) throws IncompatibleCommandExceptio } @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws InvalidTaskNumberException, IOException { + public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { if (tasks.isValidTaskNumber(taskNumber)) { ui.printLine(tasks.getTaskToString(taskNumber)); } else { diff --git a/src/main/java/UnmarkCommand.java b/src/main/java/UnmarkCommand.java index 02b10601cf..ff0a3747ea 100644 --- a/src/main/java/UnmarkCommand.java +++ b/src/main/java/UnmarkCommand.java @@ -7,7 +7,7 @@ public UnmarkCommand(String fullInput) throws IncompatibleCommandException { } @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws InvalidTaskNumberException, IOException { + public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { if (tasks.isValidTaskNumber(taskNumber)) { System.out.println("I've marked this task as not done (yet ;))"); tasks.markAsNotDone(taskNumber); From a1d2f7158d7f6591d0c81eb118579d40563a09e1 Mon Sep 17 00:00:00 2001 From: Devesh Date: Sat, 10 Sep 2022 12:14:15 +0800 Subject: [PATCH 15/31] Refactor code into multiple packages under the duke package The classes are all unorganised inside one directory. Divide into multiple packages as the number of classes. This ensures better organisation of code. --- data/tasks.txt | 3 ++- src/main/java/{ => drake}/Drake.java | 4 ++++ src/main/java/{ => drake}/DrakeException.java | 2 ++ .../{ => drake}/EmptyDescriptionException.java | 2 ++ .../{ => drake}/IncompatibleCommandException.java | 5 ++--- .../{ => drake}/InvalidTaskNumberException.java | 2 ++ src/main/java/{ => drake}/Parser.java | 4 ++++ src/main/java/{ => drake}/Storage.java | 15 ++++++++------- src/main/java/{ => drake}/TaskList.java | 4 ++++ src/main/java/{ => drake}/Ui.java | 4 +++- .../java/{ => drake}/UnknownCommandException.java | 2 ++ .../java/{ => drake/commands}/ByeCommand.java | 7 +++++++ src/main/java/{ => drake/commands}/Command.java | 7 +++++++ .../java/{ => drake/commands}/CommandType.java | 2 ++ .../{ => drake/commands}/CreateTaskCommand.java | 7 +++++++ .../{ => drake/commands}/DeadlineCommand.java | 6 ++++++ .../java/{ => drake/commands}/DeleteCommand.java | 4 ++++ .../java/{ => drake/commands}/EventCommand.java | 6 ++++++ .../java/{ => drake/commands}/ListCommand.java | 7 +++++++ .../java/{ => drake/commands}/MarkCommand.java | 4 ++++ .../commands}/TaskOperationCommand.java | 4 ++++ .../java/{ => drake/commands}/TodoCommand.java | 9 +++++++++ .../java/{ => drake/commands}/UnmarkCommand.java | 4 ++++ src/main/java/{ => drake/tasks}/Deadline.java | 2 ++ src/main/java/{ => drake/tasks}/Event.java | 2 ++ src/main/java/{ => drake/tasks}/Task.java | 2 ++ src/main/java/{ => drake/tasks}/Todo.java | 2 ++ text-ui-test/EXPECTED.TXT | 2 +- 28 files changed, 111 insertions(+), 13 deletions(-) rename src/main/java/{ => drake}/Drake.java (96%) rename src/main/java/{ => drake}/DrakeException.java (89%) rename src/main/java/{ => drake}/EmptyDescriptionException.java (93%) rename src/main/java/{ => drake}/IncompatibleCommandException.java (64%) rename src/main/java/{ => drake}/InvalidTaskNumberException.java (91%) rename src/main/java/{ => drake}/Parser.java (96%) rename src/main/java/{ => drake}/Storage.java (95%) rename src/main/java/{ => drake}/TaskList.java (95%) rename src/main/java/{ => drake}/Ui.java (93%) rename src/main/java/{ => drake}/UnknownCommandException.java (91%) rename src/main/java/{ => drake/commands}/ByeCommand.java (71%) rename src/main/java/{ => drake/commands}/Command.java (66%) rename src/main/java/{ => drake/commands}/CommandType.java (71%) rename src/main/java/{ => drake/commands}/CreateTaskCommand.java (78%) rename src/main/java/{ => drake/commands}/DeadlineCommand.java (90%) rename src/main/java/{ => drake/commands}/DeleteCommand.java (94%) rename src/main/java/{ => drake/commands}/EventCommand.java (90%) rename src/main/java/{ => drake/commands}/ListCommand.java (76%) rename src/main/java/{ => drake/commands}/MarkCommand.java (94%) rename src/main/java/{ => drake/commands}/TaskOperationCommand.java (94%) rename src/main/java/{ => drake/commands}/TodoCommand.java (75%) rename src/main/java/{ => drake/commands}/UnmarkCommand.java (94%) rename src/main/java/{ => drake/tasks}/Deadline.java (97%) rename src/main/java/{ => drake/tasks}/Event.java (96%) rename src/main/java/{ => drake/tasks}/Task.java (96%) rename src/main/java/{ => drake/tasks}/Todo.java (95%) diff --git a/data/tasks.txt b/data/tasks.txt index edebdcb0c3..4662f702ef 100644 --- a/data/tasks.txt +++ b/data/tasks.txt @@ -1,3 +1,4 @@ -T;haha; +T;haha;X D;yo;X;2020-02-02 T;andre; +T;commit to github; diff --git a/src/main/java/Drake.java b/src/main/java/drake/Drake.java similarity index 96% rename from src/main/java/Drake.java rename to src/main/java/drake/Drake.java index b0ee0a8673..b34537e02c 100644 --- a/src/main/java/Drake.java +++ b/src/main/java/drake/Drake.java @@ -1,3 +1,7 @@ +package drake; + +import drake.commands.Command; + import java.io.IOException; public class Drake { diff --git a/src/main/java/DrakeException.java b/src/main/java/drake/DrakeException.java similarity index 89% rename from src/main/java/DrakeException.java rename to src/main/java/drake/DrakeException.java index 876a9c8c36..5542fce8eb 100644 --- a/src/main/java/DrakeException.java +++ b/src/main/java/drake/DrakeException.java @@ -1,3 +1,5 @@ +package drake; + public class DrakeException extends Exception { public DrakeException(String errorMessage) { super(errorMessage); diff --git a/src/main/java/EmptyDescriptionException.java b/src/main/java/drake/EmptyDescriptionException.java similarity index 93% rename from src/main/java/EmptyDescriptionException.java rename to src/main/java/drake/EmptyDescriptionException.java index 1ae8ee03c6..db84395877 100644 --- a/src/main/java/EmptyDescriptionException.java +++ b/src/main/java/drake/EmptyDescriptionException.java @@ -1,3 +1,5 @@ +package drake; + public class EmptyDescriptionException extends DrakeException { public EmptyDescriptionException() { super("Well well well! A task without a description is like a picture of Singapore without MBS"); diff --git a/src/main/java/IncompatibleCommandException.java b/src/main/java/drake/IncompatibleCommandException.java similarity index 64% rename from src/main/java/IncompatibleCommandException.java rename to src/main/java/drake/IncompatibleCommandException.java index 1bf711596d..8c60f6f942 100644 --- a/src/main/java/IncompatibleCommandException.java +++ b/src/main/java/drake/IncompatibleCommandException.java @@ -1,7 +1,6 @@ +package drake; + public class IncompatibleCommandException extends DrakeException { - public IncompatibleCommandException() { - super("These two go together like oil mixes with water"); - } public IncompatibleCommandException(String error) { super("These two go together like oil mixes with water. " + error); diff --git a/src/main/java/InvalidTaskNumberException.java b/src/main/java/drake/InvalidTaskNumberException.java similarity index 91% rename from src/main/java/InvalidTaskNumberException.java rename to src/main/java/drake/InvalidTaskNumberException.java index 6abfee2d66..08bc377636 100644 --- a/src/main/java/InvalidTaskNumberException.java +++ b/src/main/java/drake/InvalidTaskNumberException.java @@ -1,3 +1,5 @@ +package drake; + public class InvalidTaskNumberException extends IncompatibleCommandException { public InvalidTaskNumberException() { super("That task number doesn't exist!"); diff --git a/src/main/java/Parser.java b/src/main/java/drake/Parser.java similarity index 96% rename from src/main/java/Parser.java rename to src/main/java/drake/Parser.java index 1e9f0ccab5..d86adb0306 100644 --- a/src/main/java/Parser.java +++ b/src/main/java/drake/Parser.java @@ -1,3 +1,7 @@ +package drake; + +import drake.commands.*; + public class Parser { public static Command parse(String fullInput) throws UnknownCommandException, diff --git a/src/main/java/Storage.java b/src/main/java/drake/Storage.java similarity index 95% rename from src/main/java/Storage.java rename to src/main/java/drake/Storage.java index a85f4c2bac..cf53fa6195 100644 --- a/src/main/java/Storage.java +++ b/src/main/java/drake/Storage.java @@ -1,3 +1,11 @@ +package drake; + +import drake.commands.CommandType; +import drake.tasks.Deadline; +import drake.tasks.Event; +import drake.tasks.Task; +import drake.tasks.Todo; + import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; @@ -114,11 +122,4 @@ private String listToCsv(List list) { } return csv.append("\n").toString(); } - - //For debugging - private void printTasks() { - for (List task : tasks) { - System.out.println(task); - } - } } diff --git a/src/main/java/TaskList.java b/src/main/java/drake/TaskList.java similarity index 95% rename from src/main/java/TaskList.java rename to src/main/java/drake/TaskList.java index 2e2390981b..e1fbfe92ce 100644 --- a/src/main/java/TaskList.java +++ b/src/main/java/drake/TaskList.java @@ -1,3 +1,7 @@ +package drake; + +import drake.tasks.Task; + import java.util.List; public class TaskList { diff --git a/src/main/java/Ui.java b/src/main/java/drake/Ui.java similarity index 93% rename from src/main/java/Ui.java rename to src/main/java/drake/Ui.java index ee3655248f..f152a922ec 100644 --- a/src/main/java/Ui.java +++ b/src/main/java/drake/Ui.java @@ -1,3 +1,5 @@ +package drake; + import java.util.Scanner; public class Ui { @@ -22,7 +24,7 @@ public void showWelcome() { } System.out.println(logo); System.out.println("!@#$%^&*()-+!@#$%^&*()`~`!@#$"); - System.out.println("Drake's (me) the kind of guy to help you out uwu"); + System.out.println("drake.Drake's (me) the kind of guy to help you out uwu"); System.out.println("Go ahead, make that hotline bling"); System.out.println(DASH); } diff --git a/src/main/java/UnknownCommandException.java b/src/main/java/drake/UnknownCommandException.java similarity index 91% rename from src/main/java/UnknownCommandException.java rename to src/main/java/drake/UnknownCommandException.java index 8b7557c3e3..534c89b8e4 100644 --- a/src/main/java/UnknownCommandException.java +++ b/src/main/java/drake/UnknownCommandException.java @@ -1,3 +1,5 @@ +package drake; + public class UnknownCommandException extends DrakeException { public UnknownCommandException() { super("Uh oh spaghettios I don't know what that means!"); diff --git a/src/main/java/ByeCommand.java b/src/main/java/drake/commands/ByeCommand.java similarity index 71% rename from src/main/java/ByeCommand.java rename to src/main/java/drake/commands/ByeCommand.java index 2520d36970..057d8f015c 100644 --- a/src/main/java/ByeCommand.java +++ b/src/main/java/drake/commands/ByeCommand.java @@ -1,3 +1,10 @@ +package drake.commands; + +import drake.DrakeException; +import drake.Storage; +import drake.TaskList; +import drake.Ui; + import java.io.IOException; public class ByeCommand extends Command { diff --git a/src/main/java/Command.java b/src/main/java/drake/commands/Command.java similarity index 66% rename from src/main/java/Command.java rename to src/main/java/drake/commands/Command.java index c814c06de1..69efc9f845 100644 --- a/src/main/java/Command.java +++ b/src/main/java/drake/commands/Command.java @@ -1,3 +1,10 @@ +package drake.commands; + +import drake.DrakeException; +import drake.Storage; +import drake.TaskList; +import drake.Ui; + import java.io.IOException; public abstract class Command { diff --git a/src/main/java/CommandType.java b/src/main/java/drake/commands/CommandType.java similarity index 71% rename from src/main/java/CommandType.java rename to src/main/java/drake/commands/CommandType.java index 285575b33d..b881f73b7c 100644 --- a/src/main/java/CommandType.java +++ b/src/main/java/drake/commands/CommandType.java @@ -1,3 +1,5 @@ +package drake.commands; + public enum CommandType { MARK, UNMARK, diff --git a/src/main/java/CreateTaskCommand.java b/src/main/java/drake/commands/CreateTaskCommand.java similarity index 78% rename from src/main/java/CreateTaskCommand.java rename to src/main/java/drake/commands/CreateTaskCommand.java index 96dfd549b1..b3c3a4d2ba 100644 --- a/src/main/java/CreateTaskCommand.java +++ b/src/main/java/drake/commands/CreateTaskCommand.java @@ -1,3 +1,10 @@ +package drake.commands; + +import drake.DrakeException; +import drake.Storage; +import drake.TaskList; +import drake.Ui; + import java.io.IOException; public abstract class CreateTaskCommand extends Command { diff --git a/src/main/java/DeadlineCommand.java b/src/main/java/drake/commands/DeadlineCommand.java similarity index 90% rename from src/main/java/DeadlineCommand.java rename to src/main/java/drake/commands/DeadlineCommand.java index d96bf99996..9299021bfe 100644 --- a/src/main/java/DeadlineCommand.java +++ b/src/main/java/drake/commands/DeadlineCommand.java @@ -1,3 +1,9 @@ +package drake.commands; + +import drake.*; +import drake.tasks.Deadline; +import drake.tasks.Task; + import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/DeleteCommand.java b/src/main/java/drake/commands/DeleteCommand.java similarity index 94% rename from src/main/java/DeleteCommand.java rename to src/main/java/drake/commands/DeleteCommand.java index 1e6db01ba4..79f8e3f155 100644 --- a/src/main/java/DeleteCommand.java +++ b/src/main/java/drake/commands/DeleteCommand.java @@ -1,3 +1,7 @@ +package drake.commands; + +import drake.*; + import java.io.IOException; public class DeleteCommand extends TaskOperationCommand { diff --git a/src/main/java/EventCommand.java b/src/main/java/drake/commands/EventCommand.java similarity index 90% rename from src/main/java/EventCommand.java rename to src/main/java/drake/commands/EventCommand.java index 120f4a7de2..aa6e671721 100644 --- a/src/main/java/EventCommand.java +++ b/src/main/java/drake/commands/EventCommand.java @@ -1,3 +1,9 @@ +package drake.commands; + +import drake.*; +import drake.tasks.Event; +import drake.tasks.Task; + import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/ListCommand.java b/src/main/java/drake/commands/ListCommand.java similarity index 76% rename from src/main/java/ListCommand.java rename to src/main/java/drake/commands/ListCommand.java index 3585973b21..a9e67a2f51 100644 --- a/src/main/java/ListCommand.java +++ b/src/main/java/drake/commands/ListCommand.java @@ -1,3 +1,10 @@ +package drake.commands; + +import drake.DrakeException; +import drake.Storage; +import drake.TaskList; +import drake.Ui; + import java.io.IOException; public class ListCommand extends Command { diff --git a/src/main/java/MarkCommand.java b/src/main/java/drake/commands/MarkCommand.java similarity index 94% rename from src/main/java/MarkCommand.java rename to src/main/java/drake/commands/MarkCommand.java index 839631345a..d74d004f1e 100644 --- a/src/main/java/MarkCommand.java +++ b/src/main/java/drake/commands/MarkCommand.java @@ -1,3 +1,7 @@ +package drake.commands; + +import drake.*; + import java.io.IOException; public class MarkCommand extends TaskOperationCommand { diff --git a/src/main/java/TaskOperationCommand.java b/src/main/java/drake/commands/TaskOperationCommand.java similarity index 94% rename from src/main/java/TaskOperationCommand.java rename to src/main/java/drake/commands/TaskOperationCommand.java index 91dfc56180..4dc38f68d3 100644 --- a/src/main/java/TaskOperationCommand.java +++ b/src/main/java/drake/commands/TaskOperationCommand.java @@ -1,3 +1,7 @@ +package drake.commands; + +import drake.*; + import java.io.IOException; public abstract class TaskOperationCommand extends Command { diff --git a/src/main/java/TodoCommand.java b/src/main/java/drake/commands/TodoCommand.java similarity index 75% rename from src/main/java/TodoCommand.java rename to src/main/java/drake/commands/TodoCommand.java index f792a44af2..0e14b38ce8 100644 --- a/src/main/java/TodoCommand.java +++ b/src/main/java/drake/commands/TodoCommand.java @@ -1,3 +1,12 @@ +package drake.commands; + +import drake.DrakeException; +import drake.Storage; +import drake.TaskList; +import drake.Ui; +import drake.tasks.Task; +import drake.tasks.Todo; + import java.io.IOException; public class TodoCommand extends CreateTaskCommand { diff --git a/src/main/java/UnmarkCommand.java b/src/main/java/drake/commands/UnmarkCommand.java similarity index 94% rename from src/main/java/UnmarkCommand.java rename to src/main/java/drake/commands/UnmarkCommand.java index ff0a3747ea..1d985b7bea 100644 --- a/src/main/java/UnmarkCommand.java +++ b/src/main/java/drake/commands/UnmarkCommand.java @@ -1,3 +1,7 @@ +package drake.commands; + +import drake.*; + import java.io.IOException; public class UnmarkCommand extends TaskOperationCommand { diff --git a/src/main/java/Deadline.java b/src/main/java/drake/tasks/Deadline.java similarity index 97% rename from src/main/java/Deadline.java rename to src/main/java/drake/tasks/Deadline.java index e25c95b370..c956f11fec 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/drake/tasks/Deadline.java @@ -1,3 +1,5 @@ +package drake.tasks; + import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; diff --git a/src/main/java/Event.java b/src/main/java/drake/tasks/Event.java similarity index 96% rename from src/main/java/Event.java rename to src/main/java/drake/tasks/Event.java index 3454e7a3d5..60fd5ddb2e 100644 --- a/src/main/java/Event.java +++ b/src/main/java/drake/tasks/Event.java @@ -1,3 +1,5 @@ +package drake.tasks; + import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; diff --git a/src/main/java/Task.java b/src/main/java/drake/tasks/Task.java similarity index 96% rename from src/main/java/Task.java rename to src/main/java/drake/tasks/Task.java index 1a2e32ff74..3d640f788b 100644 --- a/src/main/java/Task.java +++ b/src/main/java/drake/tasks/Task.java @@ -1,3 +1,5 @@ +package drake.tasks; + import java.util.Arrays; import java.util.List; diff --git a/src/main/java/Todo.java b/src/main/java/drake/tasks/Todo.java similarity index 95% rename from src/main/java/Todo.java rename to src/main/java/drake/tasks/Todo.java index ab02f558cb..17ae507bb4 100644 --- a/src/main/java/Todo.java +++ b/src/main/java/drake/tasks/Todo.java @@ -1,3 +1,5 @@ +package drake.tasks; + import java.util.ArrayList; import java.util.List; diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 8f7538535a..d815a02ad4 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -6,7 +6,7 @@ DRAKE DRAKE DRAKE DRAKE DRAKE DRAKE DRAKE DRAKE DRAKE DRAKE DRAKE DRAKE DRAKE DRAKE DRAKE !@#$%^&*()-+!@#$%^&*()`~`!@#$ -Drake's (me) the kind of guy to help you out uwu +drake.Drake's (me) the kind of guy to help you out uwu Go ahead, make that hotline bling ------------------------------------------------------ ------------------------------------------------------ From aca11a1c0b6aa00bb6d062b6cda2060c74a24d4d Mon Sep 17 00:00:00 2001 From: Devesh Date: Sat, 10 Sep 2022 17:47:25 +0800 Subject: [PATCH 16/31] Add JUnit tests TaskListTest was added to test the behavior of the TaskList class --- src/test/java/drake/TaskListTest.java | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/test/java/drake/TaskListTest.java diff --git a/src/test/java/drake/TaskListTest.java b/src/test/java/drake/TaskListTest.java new file mode 100644 index 0000000000..e44fea5c3e --- /dev/null +++ b/src/test/java/drake/TaskListTest.java @@ -0,0 +1,31 @@ +package drake; + +import drake.tasks.Task; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + + +public class TaskListTest { + @Test + public void isValidTaskNumber_smokeTest() { + TaskList taskList = new TaskList(Arrays.asList(null, null)); + assertFalse(taskList.isValidTaskNumber(3)); + } + + @Test + public void getSizeToString_smokeTest() { + TaskList taskList = new TaskList(Arrays.asList(null, null)); + assertEquals("You now have 2 tasks in the list", taskList.getSizeToString()); + } + + @Test + public void addTask_smokeTest() { + ArrayList tasks = new ArrayList<>(); + TaskList taskList = new TaskList(tasks); + assertNull(taskList.addTask(null)); + } +} From c2fe1afb2a82b873ceadffe3b8a96d1dc333af5e Mon Sep 17 00:00:00 2001 From: Devesh Date: Sat, 10 Sep 2022 20:18:05 +0800 Subject: [PATCH 17/31] Add JavaDoc to public methods and classes --- src/main/java/drake/Drake.java | 12 ++++- src/main/java/drake/DrakeException.java | 9 ++++ .../java/drake/EmptyDescriptionException.java | 7 +++ .../drake/IncompatibleCommandException.java | 8 ++++ .../drake/InvalidTaskNumberException.java | 7 +++ src/main/java/drake/Parser.java | 8 ++++ src/main/java/drake/Storage.java | 32 ++++++++++++- src/main/java/drake/TaskList.java | 47 ++++++++++++++++++- src/main/java/drake/Ui.java | 35 +++++++++++++- .../java/drake/UnknownCommandException.java | 7 +++ src/main/java/drake/commands/Command.java | 18 +++++++ src/main/java/drake/commands/CommandType.java | 3 ++ .../drake/commands/CreateTaskCommand.java | 17 +++++++ .../java/drake/commands/DeadlineCommand.java | 18 +++++++ .../java/drake/commands/EventCommand.java | 18 +++++++ .../drake/commands/TaskOperationCommand.java | 18 +++++++ src/main/java/drake/commands/TodoCommand.java | 18 +++++++ 17 files changed, 276 insertions(+), 6 deletions(-) diff --git a/src/main/java/drake/Drake.java b/src/main/java/drake/Drake.java index b34537e02c..39af06ce2d 100644 --- a/src/main/java/drake/Drake.java +++ b/src/main/java/drake/Drake.java @@ -4,19 +4,22 @@ import java.io.IOException; +/** + * Entrypoint for the Drake to-do list chatbot. + */ public class Drake { private final Storage storage; private final TaskList tasks; private final Ui ui; - public Drake() throws IOException, DrakeException { + private Drake() throws IOException, DrakeException { ui = new Ui(); storage = new Storage(); tasks = new TaskList(storage.fileToList()); } - public void run() { + private void run() { ui.showWelcome(); boolean isExit = false; while (!isExit) { @@ -34,6 +37,11 @@ public void run() { } } + /** + * Entrypoint. + * + * @param args Command-line arguments. + */ public static void main(String[] args) { try { new Drake().run(); diff --git a/src/main/java/drake/DrakeException.java b/src/main/java/drake/DrakeException.java index 5542fce8eb..1995ed9c80 100644 --- a/src/main/java/drake/DrakeException.java +++ b/src/main/java/drake/DrakeException.java @@ -1,6 +1,15 @@ package drake; +/** + * An exception with user-facing messages that sound like Drake. + */ public class DrakeException extends Exception { + + /** + * Constructor + * + * @param errorMessage User-facing error message that sound like Drake. + */ public DrakeException(String errorMessage) { super(errorMessage); } diff --git a/src/main/java/drake/EmptyDescriptionException.java b/src/main/java/drake/EmptyDescriptionException.java index db84395877..5922c8b174 100644 --- a/src/main/java/drake/EmptyDescriptionException.java +++ b/src/main/java/drake/EmptyDescriptionException.java @@ -1,6 +1,13 @@ package drake; +/** + * An exception for tasks without a description with a user-facing message that sound like Drake. + */ public class EmptyDescriptionException extends DrakeException { + + /** + * Constructor + */ public EmptyDescriptionException() { super("Well well well! A task without a description is like a picture of Singapore without MBS"); } diff --git a/src/main/java/drake/IncompatibleCommandException.java b/src/main/java/drake/IncompatibleCommandException.java index 8c60f6f942..de1995ab05 100644 --- a/src/main/java/drake/IncompatibleCommandException.java +++ b/src/main/java/drake/IncompatibleCommandException.java @@ -1,7 +1,15 @@ package drake; +/** + * An exception for incompatible commands with user-facing messages that sound like Drake. + */ public class IncompatibleCommandException extends DrakeException { + /** + * Constructor + * + * @param error Customised error message for the incompatible commands. + */ public IncompatibleCommandException(String error) { super("These two go together like oil mixes with water. " + error); } diff --git a/src/main/java/drake/InvalidTaskNumberException.java b/src/main/java/drake/InvalidTaskNumberException.java index 08bc377636..252cad49ca 100644 --- a/src/main/java/drake/InvalidTaskNumberException.java +++ b/src/main/java/drake/InvalidTaskNumberException.java @@ -1,6 +1,13 @@ package drake; +/** + * An exception for an invalid task number with user-facing messages that sound like Drake. + */ public class InvalidTaskNumberException extends IncompatibleCommandException { + + /** + * Constructor + */ public InvalidTaskNumberException() { super("That task number doesn't exist!"); } diff --git a/src/main/java/drake/Parser.java b/src/main/java/drake/Parser.java index d86adb0306..1b858ab38d 100644 --- a/src/main/java/drake/Parser.java +++ b/src/main/java/drake/Parser.java @@ -2,8 +2,16 @@ import drake.commands.*; +/** + * Command parser. + */ public class Parser { + /** + * Parses and executes the given input using the given module instances. + * @param fullInput The input given to the bot. + * @return The Command parsed from the user input. + */ public static Command parse(String fullInput) throws UnknownCommandException, IncompatibleCommandException, EmptyDescriptionException { int firstSpace = fullInput.indexOf(' '); diff --git a/src/main/java/drake/Storage.java b/src/main/java/drake/Storage.java index cf53fa6195..d906fff7da 100644 --- a/src/main/java/drake/Storage.java +++ b/src/main/java/drake/Storage.java @@ -14,6 +14,9 @@ import java.util.List; import java.util.Scanner; +/** + * Task list saving and loading functionalities. + */ public class Storage { List> tasks; @@ -21,12 +24,21 @@ public class Storage { private static final String TASK_FILE_DIR = "data"; private final File taskFile; + /** + * Constructor using the default savefile location. + * + */ public Storage() throws IOException, DrakeException { taskFile = new File(TASK_FILE_PATH); tasks = new ArrayList<>(); addTasks(fileToList()); } + /** + * Reads tasks from the task file into a list of Tasks. + * + * @return A list of Tasks present in the task file. + */ public List fileToList() { ArrayList list = new ArrayList<>(); Scanner fileReader; @@ -64,6 +76,13 @@ public List fileToList() { return list; } + /** + * Updates the task in the task list. + * + * @param taskNumber The task number of the task to update. + * @param command The update demanded by the user. + * @throws DrakeException when saving fails. + */ public void updateTask(int taskNumber, CommandType command) throws DrakeException { switch (command) { case MARK: @@ -81,8 +100,13 @@ public void updateTask(int taskNumber, CommandType command) throws DrakeExceptio updateFile(); } - //Inspired by parnikkapore's PR + /** + * Updates the task file to the current state of the task list. + * + * @throws DrakeException when saving fails + */ private void updateFile() throws DrakeException { + //Inspired by parnikkapore's PR try { File fileDir = new File(TASK_FILE_DIR); if (!fileDir.isDirectory() && !fileDir.mkdirs()) { @@ -100,6 +124,12 @@ private void updateFile() throws DrakeException { } } + /** + * Adds a task to the task file. + * + * @param addedTask The task to be added to the file. + * @throws DrakeException when saving fails. + */ public void addTask(Task addedTask) throws DrakeException { tasks.add(addedTask.toList()); updateFile(); diff --git a/src/main/java/drake/TaskList.java b/src/main/java/drake/TaskList.java index e1fbfe92ce..afbe6529d6 100644 --- a/src/main/java/drake/TaskList.java +++ b/src/main/java/drake/TaskList.java @@ -4,39 +4,84 @@ import java.util.List; +/** + * Represents a list of tasks. + */ public class TaskList { private final List list; + /** + * Constructor. Loads the task list from a list of Tasks. + * + * @param list The initial task list. + */ public TaskList(List list) { this.list = list; } - + /** + * Checks if the given task number is valid. + * + * @param taskNumber The given task number. + * @return Whether the task number is valid. + */ public boolean isValidTaskNumber(int taskNumber) { return taskNumber <= list.size(); } + /** + * Marks the task with the given task number as done. + * + * @param taskNumber The given task number. + */ public void markAsDone(int taskNumber) { list.get(taskNumber - 1).markAsDone(); } + /** + * Gets the String representation of the task with the given task number. + * + * @param taskNumber The given task number. + * @return The String representation of the requested task. + */ public String getTaskToString(int taskNumber) { return list.get(taskNumber - 1).toString(); } + /** + * Marks the task with the given task number as not done. + * + * @param taskNumber The given task number. + */ public void markAsNotDone(int taskNumber) { list.get(taskNumber - 1).markAsNotDone(); } + /** + * Removes the task with the given task number from the task list. + * + * @param taskNumber The given task number. + */ public void removeTask(int taskNumber) { list.remove(taskNumber - 1); } + /** + * Gets the String representation of the size of the task list in a sentence. + * + * @return A sentence with the size of the task list. + */ public String getSizeToString() { return "You now have " + list.size() + " tasks in the list"; } + /** + * Adds the given task to the task list. + * + * @param task The given task. + * @return The task added to the task list. + */ public Task addTask(Task task) { list.add(task); return task; diff --git a/src/main/java/drake/Ui.java b/src/main/java/drake/Ui.java index f152a922ec..a03fa21417 100644 --- a/src/main/java/drake/Ui.java +++ b/src/main/java/drake/Ui.java @@ -2,16 +2,25 @@ import java.util.Scanner; +/** + * Interact with the user. + */ public class Ui { private final String DASH = "------------------------------------------------------"; - public static final String ANSI_RESET = "\u001B[0m"; - public static final String ANSI_RED = "\u001B[31m"; + private static final String ANSI_RESET = "\u001B[0m"; + private static final String ANSI_RED = "\u001B[31m"; private final Scanner sc; + /** + * Constructor. + */ public Ui() { this.sc = new Scanner(System.in); } + /** + * Greets the user by printing the welcome message. + */ public void showWelcome() { System.out.println(DASH); System.out.println("You used to call me on my cellphone"); @@ -29,22 +38,44 @@ public void showWelcome() { System.out.println(DASH); } + /** + * Reads input from the console into a String. + * + * @return The trimmed input line as a String. + */ public String readInput() { return sc.nextLine().trim(); } + /** + * Prints the given line into the console. + * + * @param line The line to print. + */ public void printLine(Object line) { System.out.println(line); } + /** + * Prints the exit message. + * + */ public void printBye() { System.out.println("I'm down for you always. See you " + ANSI_RED + "<3" + ANSI_RESET); } + /** + * Prints a dash. + */ public void printDash() { System.out.println(DASH); } + /** + * Prints the given error message with special formatting. + * + * @param errorMessage The given error message. + */ public void printError(String errorMessage) { System.out.println(ANSI_RED + errorMessage + ANSI_RESET); } diff --git a/src/main/java/drake/UnknownCommandException.java b/src/main/java/drake/UnknownCommandException.java index 534c89b8e4..212c08372d 100644 --- a/src/main/java/drake/UnknownCommandException.java +++ b/src/main/java/drake/UnknownCommandException.java @@ -1,6 +1,13 @@ package drake; +/** + * An exception for when an unknown command is entered with user-facing messages that sound like Drake. + */ public class UnknownCommandException extends DrakeException { + + /** + * Constructor with the Drake-sounding exception for an unknown command. + */ public UnknownCommandException() { super("Uh oh spaghettios I don't know what that means!"); } diff --git a/src/main/java/drake/commands/Command.java b/src/main/java/drake/commands/Command.java index 69efc9f845..b28eeadf54 100644 --- a/src/main/java/drake/commands/Command.java +++ b/src/main/java/drake/commands/Command.java @@ -7,9 +7,27 @@ import java.io.IOException; +/** + * Represents a command given by the user. + */ public abstract class Command { + + /** + * Executes the command. + * + * @param tasks The task list before the command is executed. + * @param ui Gives access to the UI of the program. + * @param storage Gives access to local storage. + * @throws IOException when there is an issue with the IO. + * @throws DrakeException when there is inappropriate input or save file issues. + */ public abstract void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException; + /** + * Checks whether the user has given the bye command. + * + * @return True if the user has given the bye command, false otherwise. + */ public boolean isExit() { return false; } diff --git a/src/main/java/drake/commands/CommandType.java b/src/main/java/drake/commands/CommandType.java index b881f73b7c..73a58b540d 100644 --- a/src/main/java/drake/commands/CommandType.java +++ b/src/main/java/drake/commands/CommandType.java @@ -1,5 +1,8 @@ package drake.commands; +/** + * Types of commands to update tasks. + */ public enum CommandType { MARK, UNMARK, diff --git a/src/main/java/drake/commands/CreateTaskCommand.java b/src/main/java/drake/commands/CreateTaskCommand.java index b3c3a4d2ba..1787b6a392 100644 --- a/src/main/java/drake/commands/CreateTaskCommand.java +++ b/src/main/java/drake/commands/CreateTaskCommand.java @@ -7,14 +7,31 @@ import java.io.IOException; +/** + * Represents a command given by the user to create a new task. + */ public abstract class CreateTaskCommand extends Command { protected String description; + /** + * Constructor. + * + * @param fullInput The input given by the user. + */ public CreateTaskCommand(String fullInput) { description = fullInput.substring(fullInput.indexOf(' ') + 1); } + /** + * Executes the command to create a new task, printing the size of the task list after execution. + * + * @param tasks The task list before the command is executed. + * @param ui Gives access to the UI of the program. + * @param storage Gives access to local storage. + * @throws IOException when there is an issue with the IO. + * @throws DrakeException when there is inappropriate input or save file issues. + */ @Override public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { ui.printLine(tasks.getSizeToString()); diff --git a/src/main/java/drake/commands/DeadlineCommand.java b/src/main/java/drake/commands/DeadlineCommand.java index 9299021bfe..b55caaac56 100644 --- a/src/main/java/drake/commands/DeadlineCommand.java +++ b/src/main/java/drake/commands/DeadlineCommand.java @@ -8,14 +8,32 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * Represents a command given by the user to create a new task with a deadline. + */ public class DeadlineCommand extends CreateTaskCommand { private final static Pattern descriptionPattern = Pattern.compile("(?.*) /by (?.*)"); + /** + * Constructor. + * + * @param fullInput The user input. + */ public DeadlineCommand(String fullInput) { super(fullInput); } + /** + * Executes the command to create a new deadline task, saving the new task and + * printing the size of the task list after execution. + * + * @param tasks The task list before the command is executed. + * @param ui Gives access to the UI of the program. + * @param storage Gives access to local storage. + * @throws IOException when there is an issue with the IO. + * @throws DrakeException when there is inappropriate input or save file issues. + */ @Override public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { Matcher match = descriptionPattern.matcher(description); diff --git a/src/main/java/drake/commands/EventCommand.java b/src/main/java/drake/commands/EventCommand.java index aa6e671721..f0ca913e6a 100644 --- a/src/main/java/drake/commands/EventCommand.java +++ b/src/main/java/drake/commands/EventCommand.java @@ -8,14 +8,32 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * Represents a command given by the user to create a new event task. + */ public class EventCommand extends CreateTaskCommand { private final static Pattern descriptionPattern = Pattern.compile("(?.*) /at (?.*)"); + /** + * Constructor. + * + * @param fullInput The user input. + */ public EventCommand(String fullInput) { super(fullInput); } + /** + * Executes the command to create a new event task, saving the new task and + * printing the size of the task list after execution. + * + * @param tasks The task list before the command is executed. + * @param ui Gives access to the UI of the program. + * @param storage Gives access to local storage. + * @throws IOException when there is an issue with the IO. + * @throws DrakeException when there is inappropriate input or save file issues. + */ @Override public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { Matcher match = descriptionPattern.matcher(description); diff --git a/src/main/java/drake/commands/TaskOperationCommand.java b/src/main/java/drake/commands/TaskOperationCommand.java index 4dc38f68d3..d71e4a792f 100644 --- a/src/main/java/drake/commands/TaskOperationCommand.java +++ b/src/main/java/drake/commands/TaskOperationCommand.java @@ -4,10 +4,19 @@ import java.io.IOException; +/** + * Represents a command given by the user to perform an operation on a task currently in the list. + */ public abstract class TaskOperationCommand extends Command { protected final int taskNumber; + /** + * Constructor. + * + * @param fullInput The user input. + * @throws IncompatibleCommandException when the user input does not contain a task number. + */ public TaskOperationCommand(String fullInput) throws IncompatibleCommandException { String[] commands = fullInput.split(" "); try { @@ -17,6 +26,15 @@ public TaskOperationCommand(String fullInput) throws IncompatibleCommandExceptio } } + /** + * Executes the command to perform an operation on a task currently in the list. + * + * @param tasks The task list before the command is executed. + * @param ui Gives access to the UI of the program. + * @param storage Gives access to local storage. + * @throws IOException when there is an issue with the IO. + * @throws DrakeException when there is inappropriate input or save file issues. + */ @Override public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { if (tasks.isValidTaskNumber(taskNumber)) { diff --git a/src/main/java/drake/commands/TodoCommand.java b/src/main/java/drake/commands/TodoCommand.java index 0e14b38ce8..17109b47d1 100644 --- a/src/main/java/drake/commands/TodoCommand.java +++ b/src/main/java/drake/commands/TodoCommand.java @@ -9,12 +9,30 @@ import java.io.IOException; +/** + * Represents a command given by the user to create a new to-do task. + */ public class TodoCommand extends CreateTaskCommand { + /** + * Constructor. + * + * @param fullInput The user input. + */ public TodoCommand(String fullInput) { super(fullInput); } + /** + * Executes the command to create a new to-do task, saving the new task and + * printing the size of the task list after execution. + * + * @param tasks The task list before the command is executed. + * @param ui Gives access to the UI of the program. + * @param storage Gives access to local storage. + * @throws IOException when there is an issue with the IO. + * @throws DrakeException when there is inappropriate input or save file issues. + */ @Override public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { ui.printLine("I've added this task:"); From 9a260941aea98bc7b3199343df0f6b8efd832804 Mon Sep 17 00:00:00 2001 From: Devesh Date: Sat, 10 Sep 2022 20:26:27 +0800 Subject: [PATCH 18/31] Comply with coding standard The Drake entrypoint was modified to handle exceptions better according to the coding standard. --- src/main/java/drake/Drake.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/drake/Drake.java b/src/main/java/drake/Drake.java index b34537e02c..eb35fcb94d 100644 --- a/src/main/java/drake/Drake.java +++ b/src/main/java/drake/Drake.java @@ -37,10 +37,8 @@ public void run() { public static void main(String[] args) { try { new Drake().run(); - } catch (IOException e) { + } catch (IOException | DrakeException e) { System.out.println(e.getMessage()); - } catch (DrakeException e) { - throw new RuntimeException(e); } } From 5ae8bfbd1d1bab143f694c8c4568bb34ebc2f478 Mon Sep 17 00:00:00 2001 From: Devesh Date: Sat, 10 Sep 2022 20:52:07 +0800 Subject: [PATCH 19/31] Add Find functionality --- src/main/java/drake/Parser.java | 2 ++ src/main/java/drake/TaskList.java | 13 +++++++ src/main/java/drake/commands/FindCommand.java | 34 +++++++++++++++++++ src/main/java/drake/tasks/Task.java | 3 ++ 4 files changed, 52 insertions(+) create mode 100644 src/main/java/drake/commands/FindCommand.java diff --git a/src/main/java/drake/Parser.java b/src/main/java/drake/Parser.java index d86adb0306..0670ddef6c 100644 --- a/src/main/java/drake/Parser.java +++ b/src/main/java/drake/Parser.java @@ -34,6 +34,8 @@ public static Command parse(String fullInput) throws UnknownCommandException, return new DeleteCommand(fullInput); case "bye": return new ByeCommand(); + case "find": + return new FindCommand(fullInput); default: throw new UnknownCommandException(); } diff --git a/src/main/java/drake/TaskList.java b/src/main/java/drake/TaskList.java index e1fbfe92ce..953ce68a90 100644 --- a/src/main/java/drake/TaskList.java +++ b/src/main/java/drake/TaskList.java @@ -2,6 +2,7 @@ import drake.tasks.Task; +import java.util.ArrayList; import java.util.List; public class TaskList { @@ -41,4 +42,16 @@ public Task addTask(Task task) { list.add(task); return task; } + + public TaskList filter(List searchKeywords) { + TaskList result = new TaskList(new ArrayList<>()); + + for (Task task : list) { + if (task.isMatch(searchKeywords)) { + result.addTask(task); + } + } + + return result; + } } diff --git a/src/main/java/drake/commands/FindCommand.java b/src/main/java/drake/commands/FindCommand.java new file mode 100644 index 0000000000..1b271f6ce6 --- /dev/null +++ b/src/main/java/drake/commands/FindCommand.java @@ -0,0 +1,34 @@ +package drake.commands; + +import drake.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; + +public class FindCommand extends Command { + + private final ArrayList searchKeywords; + public FindCommand(String fullInput) { + searchKeywords = new ArrayList<>(); + searchKeywords.addAll(Arrays.asList(fullInput.split(" "))); + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { + //Inspired by parnikkapore's PR + if (searchKeywords.size() == 0) { + throw new IncompatibleCommandException("Where are the wordssss :weary_face:"); + } + + TaskList matches = tasks.filter(searchKeywords); + + ui.printLine(String.format("Here are the tasks that match \"%s\":", + String.join("\", \"", searchKeywords))); + + for (int i = 0; matches.isValidTaskNumber(i); i++) { + ui.printLine(String.format("%d. %s", i + 1, matches.getTaskToString(i))); + } + + } +} diff --git a/src/main/java/drake/tasks/Task.java b/src/main/java/drake/tasks/Task.java index 3d640f788b..a1dc11a475 100644 --- a/src/main/java/drake/tasks/Task.java +++ b/src/main/java/drake/tasks/Task.java @@ -28,6 +28,9 @@ public List toList() { return Arrays.asList(description, getStatusIcon()); } + public boolean isMatch(List searchKeywords) { + return searchKeywords.stream().allMatch(description::contains); + } @Override public String toString() { return "[" + getStatusIcon() + "] " + description; From 90f26db78b334009edc1f1f2bc8d91eb992c89f7 Mon Sep 17 00:00:00 2001 From: Devesh Date: Sun, 11 Sep 2022 12:27:12 +0800 Subject: [PATCH 20/31] Fix list.get(-1) in Find functionality --- src/main/java/drake/TaskList.java | 2 +- src/main/java/drake/commands/FindCommand.java | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/drake/TaskList.java b/src/main/java/drake/TaskList.java index 953ce68a90..3739c89518 100644 --- a/src/main/java/drake/TaskList.java +++ b/src/main/java/drake/TaskList.java @@ -15,7 +15,7 @@ public TaskList(List list) { public boolean isValidTaskNumber(int taskNumber) { - return taskNumber <= list.size(); + return taskNumber >= 1 && taskNumber <= list.size(); } public void markAsDone(int taskNumber) { diff --git a/src/main/java/drake/commands/FindCommand.java b/src/main/java/drake/commands/FindCommand.java index 1b271f6ce6..924714d4bf 100644 --- a/src/main/java/drake/commands/FindCommand.java +++ b/src/main/java/drake/commands/FindCommand.java @@ -9,24 +9,25 @@ public class FindCommand extends Command { private final ArrayList searchKeywords; - public FindCommand(String fullInput) { + public FindCommand(String fullInput) throws IncompatibleCommandException { + int firstSpace = fullInput.indexOf(" "); + if (firstSpace == -1) { + throw new IncompatibleCommandException("Where are the wordssss :weary_face:"); + } + String afterFirstSpace = fullInput.substring(firstSpace + 1); searchKeywords = new ArrayList<>(); - searchKeywords.addAll(Arrays.asList(fullInput.split(" "))); + searchKeywords.addAll(Arrays.asList(afterFirstSpace.split(" "))); } @Override public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { //Inspired by parnikkapore's PR - if (searchKeywords.size() == 0) { - throw new IncompatibleCommandException("Where are the wordssss :weary_face:"); - } - TaskList matches = tasks.filter(searchKeywords); ui.printLine(String.format("Here are the tasks that match \"%s\":", String.join("\", \"", searchKeywords))); - for (int i = 0; matches.isValidTaskNumber(i); i++) { + for (int i = 1; matches.isValidTaskNumber(i); i++) { ui.printLine(String.format("%d. %s", i + 1, matches.getTaskToString(i))); } From b7a10b486ae79d809cab3c15f72ca108491f6efb Mon Sep 17 00:00:00 2001 From: Devesh Date: Sun, 11 Sep 2022 13:43:47 +0800 Subject: [PATCH 21/31] Fix style issues --- build.gradle | 5 + config/checkstyle/checkstyle.xml | 434 +++++++++++++++++++++++++++++ config/checkstyle/suppressions.xml | 10 + src/main/java/drake/Drake.java | 4 +- src/main/java/drake/TaskList.java | 9 +- 5 files changed, 458 insertions(+), 4 deletions(-) create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 config/checkstyle/suppressions.xml diff --git a/build.gradle b/build.gradle index 885198fcfa..5328ee12cb 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'application' id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'checkstyle' } repositories { @@ -39,3 +40,7 @@ shadowJar { run{ standardInput = System.in } + +checkstyle { + toolVersion = '10.2' +} \ No newline at end of file diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..fb88cedfc2 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..135ea49ee0 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/java/drake/Drake.java b/src/main/java/drake/Drake.java index 59be35c0d8..5c994077a8 100644 --- a/src/main/java/drake/Drake.java +++ b/src/main/java/drake/Drake.java @@ -1,9 +1,9 @@ package drake; -import drake.commands.Command; - import java.io.IOException; +import drake.commands.Command; + /** * Entrypoint for the Drake to-do list chatbot. */ diff --git a/src/main/java/drake/TaskList.java b/src/main/java/drake/TaskList.java index f9d1e94570..25fa551bcc 100644 --- a/src/main/java/drake/TaskList.java +++ b/src/main/java/drake/TaskList.java @@ -1,10 +1,10 @@ package drake; -import drake.tasks.Task; - import java.util.ArrayList; import java.util.List; +import drake.tasks.Task; + /** * Represents a list of tasks. */ @@ -88,6 +88,11 @@ public Task addTask(Task task) { return task; } + /** + * Filters the task list and returns the tasks that match the given search keywords. + * @param searchKeywords The given search keywords. + * @return The tasks that match the given search keywords. + */ public TaskList filter(List searchKeywords) { TaskList result = new TaskList(new ArrayList<>()); From 7836de33e7b54800e9f46a77e9a2d1f05430266c Mon Sep 17 00:00:00 2001 From: Devesh Date: Tue, 13 Sep 2022 05:02:37 +0800 Subject: [PATCH 22/31] Add GUI to Drake The JavaFX technology was used to implement the GUI. --- build.gradle | 14 +++ data/tasks.txt | 2 +- src/main/java/drake/Drake.java | 10 +- src/main/java/drake/Parser.java | 11 +- src/main/java/drake/Storage.java | 19 ++-- src/main/java/drake/TaskList.java | 7 ++ src/main/java/drake/Ui.java | 53 ++++++---- src/main/java/drake/commands/ByeCommand.java | 12 ++- src/main/java/drake/commands/Command.java | 14 +-- .../drake/commands/CreateTaskCommand.java | 16 +-- .../java/drake/commands/DeadlineCommand.java | 33 +++--- .../java/drake/commands/DeleteCommand.java | 30 ++++-- .../java/drake/commands/EventCommand.java | 32 +++--- src/main/java/drake/commands/FindCommand.java | 28 ++++-- src/main/java/drake/commands/ListCommand.java | 17 +++- src/main/java/drake/commands/MarkCommand.java | 22 +++- .../drake/commands/TaskOperationCommand.java | 24 +++-- src/main/java/drake/commands/TodoCommand.java | 23 +++-- .../java/drake/commands/UnmarkCommand.java | 22 +++- src/main/java/drake/gui/ChatMessage.java | 69 +++++++++++++ src/main/java/drake/gui/Launcher.java | 12 +++ src/main/java/drake/gui/Main.java | 33 ++++++ src/main/java/drake/gui/Window.java | 94 ++++++++++++++++++ src/main/resources/media/drake.png | Bin 0 -> 23657 bytes src/main/resources/media/lostsoul.png | Bin 0 -> 1614 bytes src/main/resources/view/ChatMessage.fxml | 36 +++++++ src/main/resources/view/Window.fxml | 24 +++++ 27 files changed, 538 insertions(+), 119 deletions(-) create mode 100644 src/main/java/drake/gui/ChatMessage.java create mode 100644 src/main/java/drake/gui/Launcher.java create mode 100644 src/main/java/drake/gui/Main.java create mode 100644 src/main/java/drake/gui/Window.java create mode 100644 src/main/resources/media/drake.png create mode 100644 src/main/resources/media/lostsoul.png create mode 100644 src/main/resources/view/ChatMessage.fxml create mode 100644 src/main/resources/view/Window.fxml diff --git a/build.gradle b/build.gradle index 5328ee12cb..65440143da 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,20 @@ repositories { dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' + String javaFxVersion = '11' + + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' } test { diff --git a/data/tasks.txt b/data/tasks.txt index 4662f702ef..c40df5980b 100644 --- a/data/tasks.txt +++ b/data/tasks.txt @@ -1,4 +1,4 @@ -T;haha;X D;yo;X;2020-02-02 T;andre; T;commit to github; +T;haha; diff --git a/src/main/java/drake/Drake.java b/src/main/java/drake/Drake.java index 5c994077a8..0e910d2a6f 100644 --- a/src/main/java/drake/Drake.java +++ b/src/main/java/drake/Drake.java @@ -1,6 +1,7 @@ package drake; import java.io.IOException; +import java.util.List; import drake.commands.Command; @@ -20,14 +21,17 @@ private Drake() throws IOException, DrakeException { } private void run() { - ui.showWelcome(); + ui.replyWelcome(); boolean isExit = false; while (!isExit) { try { String fullInput = ui.readInput(); ui.printDash(); // show the divider line ("_______") Command c = Parser.parse(fullInput); - c.execute(tasks, ui, storage); + List reply = c.execute(tasks, ui, storage); + for (String line : reply) { + ui.printLine(line); + } isExit = c.isExit(); } catch (IOException | DrakeException e) { ui.printError(e.getMessage()); @@ -38,7 +42,7 @@ private void run() { } /** - * Entrypoint. + * Entrypoint for the command-line app. * * @param args Command-line arguments. */ diff --git a/src/main/java/drake/Parser.java b/src/main/java/drake/Parser.java index c40bf13346..34ec3728f6 100644 --- a/src/main/java/drake/Parser.java +++ b/src/main/java/drake/Parser.java @@ -1,6 +1,15 @@ package drake; -import drake.commands.*; +import drake.commands.ByeCommand; +import drake.commands.Command; +import drake.commands.DeadlineCommand; +import drake.commands.DeleteCommand; +import drake.commands.EventCommand; +import drake.commands.FindCommand; +import drake.commands.ListCommand; +import drake.commands.MarkCommand; +import drake.commands.TodoCommand; +import drake.commands.UnmarkCommand; /** * Command parser. diff --git a/src/main/java/drake/Storage.java b/src/main/java/drake/Storage.java index d906fff7da..39026d853a 100644 --- a/src/main/java/drake/Storage.java +++ b/src/main/java/drake/Storage.java @@ -1,11 +1,5 @@ package drake; -import drake.commands.CommandType; -import drake.tasks.Deadline; -import drake.tasks.Event; -import drake.tasks.Task; -import drake.tasks.Todo; - import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; @@ -14,14 +8,20 @@ import java.util.List; import java.util.Scanner; +import drake.commands.CommandType; +import drake.tasks.Deadline; +import drake.tasks.Event; +import drake.tasks.Task; +import drake.tasks.Todo; + /** * Task list saving and loading functionalities. */ public class Storage { - List> tasks; private static final String TASK_FILE_PATH = "data/tasks.txt"; private static final String TASK_FILE_DIR = "data"; + private final List> tasks; private final File taskFile; /** @@ -95,6 +95,9 @@ public void updateTask(int taskNumber, CommandType command) throws DrakeExceptio case DELETE: tasks.remove(taskNumber - 1); + break; + default: + throw new UnknownCommandException(); } updateFile(); @@ -118,7 +121,7 @@ private void updateFile() throws DrakeException { fileWriter.write(listToCsv(task)); } fileWriter.close(); - } catch (IOException e){ + } catch (IOException e) { throw new DrakeException( "Higher powers taking a hold on me... I cannot save the task list. This might help: " + e); } diff --git a/src/main/java/drake/TaskList.java b/src/main/java/drake/TaskList.java index 25fa551bcc..2532489ce1 100644 --- a/src/main/java/drake/TaskList.java +++ b/src/main/java/drake/TaskList.java @@ -21,6 +21,13 @@ public TaskList(List list) { this.list = list; } + /** + * Constructor. Initialises the task list with an empty list. + */ + public TaskList() { + list = new ArrayList<>(); + } + /** * Checks if the given task number is valid. * diff --git a/src/main/java/drake/Ui.java b/src/main/java/drake/Ui.java index a03fa21417..2aed5aef82 100644 --- a/src/main/java/drake/Ui.java +++ b/src/main/java/drake/Ui.java @@ -1,12 +1,14 @@ package drake; +import java.util.ArrayList; +import java.util.List; import java.util.Scanner; /** * Interact with the user. */ public class Ui { - private final String DASH = "------------------------------------------------------"; + private static final String DASH = "------------------------------------------------------"; private static final String ANSI_RESET = "\u001B[0m"; private static final String ANSI_RED = "\u001B[31m"; private final Scanner sc; @@ -19,23 +21,38 @@ public Ui() { } /** - * Greets the user by printing the welcome message. + * Format a line of text according to the format for the chat bubble on the GUI. + * Do not use \n for multiline text - use the list version. + * + * @param text The text to output. */ - public void showWelcome() { - System.out.println(DASH); - System.out.println("You used to call me on my cellphone"); - StringBuilder logo = new StringBuilder(); - for (int i = 0; i < 5; i++) { - logo.append("DRAKE ".repeat(4)); - logo.append("DRAKE"); - if (i == 4) break; - logo.append("\n"); + public List chatBubbleText(String text) { + return chatBubbleText(List.of(text)); + } + + /** + * Format some multiline text according to the format for the chat bubble on the GUI. + * + * @param lines A list of lines of text to output. + */ + public List chatBubbleText(List lines) { + ArrayList result = new ArrayList<>(); + for (String line : lines) { + result.add(" " + line); } - System.out.println(logo); - System.out.println("!@#$%^&*()-+!@#$%^&*()`~`!@#$"); - System.out.println("drake.Drake's (me) the kind of guy to help you out uwu"); - System.out.println("Go ahead, make that hotline bling"); - System.out.println(DASH); + return result; + } + + /** + * Greets the user by printing the welcome message. + */ + public List replyWelcome() { + ArrayList reply = new ArrayList<>(); + reply.add("You used to call me on my cellphone"); + reply.add("!@#$%^&*()-+!@#$%^&*()`~`!@#$"); + reply.add("Drake's (me) the kind of guy to help you out uwu"); + reply.add("Go ahead, make that hotline bling"); + return reply; } /** @@ -60,8 +77,8 @@ public void printLine(Object line) { * Prints the exit message. * */ - public void printBye() { - System.out.println("I'm down for you always. See you " + ANSI_RED + "<3" + ANSI_RESET); + public String replyBye() { + return "I'm down for you always. See you " + ANSI_RED + "<3" + ANSI_RESET; } /** diff --git a/src/main/java/drake/commands/ByeCommand.java b/src/main/java/drake/commands/ByeCommand.java index 057d8f015c..b76d6601b1 100644 --- a/src/main/java/drake/commands/ByeCommand.java +++ b/src/main/java/drake/commands/ByeCommand.java @@ -1,16 +1,20 @@ package drake.commands; +import java.io.IOException; +import java.util.List; + import drake.DrakeException; import drake.Storage; import drake.TaskList; import drake.Ui; -import java.io.IOException; - +/** + * The bye command. + */ public class ByeCommand extends Command { @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { - ui.printBye(); + public List execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { + return List.of(ui.replyBye()); } @Override diff --git a/src/main/java/drake/commands/Command.java b/src/main/java/drake/commands/Command.java index b28eeadf54..c5691123b5 100644 --- a/src/main/java/drake/commands/Command.java +++ b/src/main/java/drake/commands/Command.java @@ -1,12 +1,13 @@ package drake.commands; +import java.io.IOException; +import java.util.List; + import drake.DrakeException; import drake.Storage; import drake.TaskList; import drake.Ui; -import java.io.IOException; - /** * Represents a command given by the user. */ @@ -15,13 +16,14 @@ public abstract class Command { /** * Executes the command. * - * @param tasks The task list before the command is executed. - * @param ui Gives access to the UI of the program. + * @param tasks The task list before the command is executed. + * @param ui Gives access to the UI of the program. * @param storage Gives access to local storage. - * @throws IOException when there is an issue with the IO. + * @return The list of replies + * @throws IOException when there is an issue with the IO. * @throws DrakeException when there is inappropriate input or save file issues. */ - public abstract void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException; + public abstract List execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException; /** * Checks whether the user has given the bye command. diff --git a/src/main/java/drake/commands/CreateTaskCommand.java b/src/main/java/drake/commands/CreateTaskCommand.java index 1787b6a392..fec2c8839c 100644 --- a/src/main/java/drake/commands/CreateTaskCommand.java +++ b/src/main/java/drake/commands/CreateTaskCommand.java @@ -1,12 +1,13 @@ package drake.commands; +import java.io.IOException; +import java.util.List; + import drake.DrakeException; import drake.Storage; import drake.TaskList; import drake.Ui; -import java.io.IOException; - /** * Represents a command given by the user to create a new task. */ @@ -26,14 +27,15 @@ public CreateTaskCommand(String fullInput) { /** * Executes the command to create a new task, printing the size of the task list after execution. * - * @param tasks The task list before the command is executed. - * @param ui Gives access to the UI of the program. + * @param tasks The task list before the command is executed. + * @param ui Gives access to the UI of the program. * @param storage Gives access to local storage. - * @throws IOException when there is an issue with the IO. + * @return The list of replies + * @throws IOException when there is an issue with the IO. * @throws DrakeException when there is inappropriate input or save file issues. */ @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { - ui.printLine(tasks.getSizeToString()); + public List execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { + return List.of(tasks.getSizeToString()); } } diff --git a/src/main/java/drake/commands/DeadlineCommand.java b/src/main/java/drake/commands/DeadlineCommand.java index b55caaac56..18aee13196 100644 --- a/src/main/java/drake/commands/DeadlineCommand.java +++ b/src/main/java/drake/commands/DeadlineCommand.java @@ -1,19 +1,25 @@ package drake.commands; -import drake.*; -import drake.tasks.Deadline; -import drake.tasks.Task; - import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import drake.DrakeException; +import drake.IncompatibleCommandException; +import drake.Storage; +import drake.TaskList; +import drake.Ui; +import drake.tasks.Deadline; +import drake.tasks.Task; + /** * Represents a command given by the user to create a new task with a deadline. */ public class DeadlineCommand extends CreateTaskCommand { - private final static Pattern descriptionPattern = Pattern.compile("(?.*) /by (?.*)"); + private static final Pattern descriptionPattern = Pattern.compile("(?.*) /by (?.*)"); /** * Constructor. @@ -28,22 +34,25 @@ public DeadlineCommand(String fullInput) { * Executes the command to create a new deadline task, saving the new task and * printing the size of the task list after execution. * - * @param tasks The task list before the command is executed. - * @param ui Gives access to the UI of the program. + * @param tasks The task list before the command is executed. + * @param ui Gives access to the UI of the program. * @param storage Gives access to local storage. - * @throws IOException when there is an issue with the IO. + * @return The list of replies + * @throws IOException when there is an issue with the IO. * @throws DrakeException when there is inappropriate input or save file issues. */ @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { + public List execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { + ArrayList reply = new ArrayList<>(); Matcher match = descriptionPattern.matcher(description); if (!match.matches()) { throw new IncompatibleCommandException("A deadline task without a deadline?"); } - ui.printLine("I've added this task:"); + reply.add("I've added this task:"); Task addedTask = tasks.addTask(new Deadline(match.group("taskName"), match.group("by"))); - ui.printLine(addedTask); + reply.add(addedTask.toString()); storage.addTask(addedTask); - super.execute(tasks, ui, storage); + reply.addAll(super.execute(tasks, ui, storage)); + return reply; } } diff --git a/src/main/java/drake/commands/DeleteCommand.java b/src/main/java/drake/commands/DeleteCommand.java index 79f8e3f155..91c66da205 100644 --- a/src/main/java/drake/commands/DeleteCommand.java +++ b/src/main/java/drake/commands/DeleteCommand.java @@ -1,9 +1,19 @@ package drake.commands; -import drake.*; - import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import drake.DrakeException; +import drake.IncompatibleCommandException; +import drake.InvalidTaskNumberException; +import drake.Storage; +import drake.TaskList; +import drake.Ui; +/** + * Represents a delete command. + */ public class DeleteCommand extends TaskOperationCommand { public DeleteCommand(String fullInput) throws IncompatibleCommandException { @@ -11,14 +21,16 @@ public DeleteCommand(String fullInput) throws IncompatibleCommandException { } @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { - if (tasks.isValidTaskNumber(taskNumber)) { - System.out.println("I've removed this task: "); - ui.printLine(tasks.getTaskToString(taskNumber)); - tasks.removeTask(taskNumber); - storage.updateTask(taskNumber, CommandType.DELETE); - } else { + public List execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { + if (!tasks.isValidTaskNumber(taskNumber)) { throw new InvalidTaskNumberException(); } + + ArrayList reply = new ArrayList<>(); + reply.add("I've removed this task: "); + reply.add(tasks.getTaskToString(taskNumber)); + tasks.removeTask(taskNumber); + storage.updateTask(taskNumber, CommandType.DELETE); + return reply; } } diff --git a/src/main/java/drake/commands/EventCommand.java b/src/main/java/drake/commands/EventCommand.java index f0ca913e6a..20362cb3ec 100644 --- a/src/main/java/drake/commands/EventCommand.java +++ b/src/main/java/drake/commands/EventCommand.java @@ -1,19 +1,24 @@ package drake.commands; -import drake.*; -import drake.tasks.Event; -import drake.tasks.Task; - import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import drake.DrakeException; +import drake.IncompatibleCommandException; +import drake.Storage; +import drake.TaskList; +import drake.Ui; +import drake.tasks.Event; +import drake.tasks.Task; /** * Represents a command given by the user to create a new event task. */ public class EventCommand extends CreateTaskCommand { - private final static Pattern descriptionPattern = Pattern.compile("(?.*) /at (?.*)"); + private static final Pattern descriptionPattern = Pattern.compile("(?.*) /at (?.*)"); /** * Constructor. @@ -28,22 +33,25 @@ public EventCommand(String fullInput) { * Executes the command to create a new event task, saving the new task and * printing the size of the task list after execution. * - * @param tasks The task list before the command is executed. - * @param ui Gives access to the UI of the program. + * @param tasks The task list before the command is executed. + * @param ui Gives access to the UI of the program. * @param storage Gives access to local storage. - * @throws IOException when there is an issue with the IO. + * @return The list of replies + * @throws IOException when there is an issue with the IO. * @throws DrakeException when there is inappropriate input or save file issues. */ @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { + public List execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { + ArrayList reply = new ArrayList<>(); Matcher match = descriptionPattern.matcher(description); if (!match.matches()) { throw new IncompatibleCommandException("An event task without an event time?"); } - ui.printLine("I've added this task:"); + reply.add("I've added this task:"); Task addedTask = tasks.addTask(new Event(match.group("taskName"), match.group("at"))); - ui.printLine(addedTask); + reply.add(addedTask.toString()); storage.addTask(addedTask); - super.execute(tasks, ui, storage); + reply.addAll(super.execute(tasks, ui, storage)); + return reply; } } diff --git a/src/main/java/drake/commands/FindCommand.java b/src/main/java/drake/commands/FindCommand.java index 924714d4bf..d824a6f413 100644 --- a/src/main/java/drake/commands/FindCommand.java +++ b/src/main/java/drake/commands/FindCommand.java @@ -1,14 +1,28 @@ package drake.commands; -import drake.*; - import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; + +import drake.DrakeException; +import drake.IncompatibleCommandException; +import drake.Storage; +import drake.TaskList; +import drake.Ui; +/** + * Represents a find command. + */ public class FindCommand extends Command { private final ArrayList searchKeywords; + + /** + * Constructor. + * @param fullInput The user's input + * @throws IncompatibleCommandException When the user does not enter the search keywords. + */ public FindCommand(String fullInput) throws IncompatibleCommandException { int firstSpace = fullInput.indexOf(" "); if (firstSpace == -1) { @@ -20,16 +34,16 @@ public FindCommand(String fullInput) throws IncompatibleCommandException { } @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { + public List execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { //Inspired by parnikkapore's PR TaskList matches = tasks.filter(searchKeywords); - - ui.printLine(String.format("Here are the tasks that match \"%s\":", + ArrayList reply = new ArrayList<>(); + reply.add(String.format("Here are the tasks that match \"%s\":", String.join("\", \"", searchKeywords))); for (int i = 1; matches.isValidTaskNumber(i); i++) { - ui.printLine(String.format("%d. %s", i + 1, matches.getTaskToString(i))); + reply.add(String.format("%d. %s", i + 1, matches.getTaskToString(i))); } - + return reply; } } diff --git a/src/main/java/drake/commands/ListCommand.java b/src/main/java/drake/commands/ListCommand.java index a9e67a2f51..a739d010d4 100644 --- a/src/main/java/drake/commands/ListCommand.java +++ b/src/main/java/drake/commands/ListCommand.java @@ -1,18 +1,25 @@ package drake.commands; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + import drake.DrakeException; import drake.Storage; import drake.TaskList; import drake.Ui; -import java.io.IOException; - +/** + * Represent a list command. + */ public class ListCommand extends Command { @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { - ui.printLine("Here are the tasks in your list:"); + public List execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DrakeException { + ArrayList reply = new ArrayList<>(); + reply.add("Here are the tasks in your list:"); for (int i = 1; tasks.isValidTaskNumber(i); i++) { - ui.printLine(i + ". " + tasks.getTaskToString(i)); + reply.add(i + ". " + tasks.getTaskToString(i)); } + return reply; } } diff --git a/src/main/java/drake/commands/MarkCommand.java b/src/main/java/drake/commands/MarkCommand.java index d74d004f1e..159d1ff048 100644 --- a/src/main/java/drake/commands/MarkCommand.java +++ b/src/main/java/drake/commands/MarkCommand.java @@ -1,9 +1,19 @@ package drake.commands; -import drake.*; - import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import drake.DrakeException; +import drake.IncompatibleCommandException; +import drake.InvalidTaskNumberException; +import drake.Storage; +import drake.TaskList; +import drake.Ui; +/** + * Represents the mark command. + */ public class MarkCommand extends TaskOperationCommand { public MarkCommand(String fullInput) throws IncompatibleCommandException { @@ -11,12 +21,14 @@ public MarkCommand(String fullInput) throws IncompatibleCommandException { } @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { + public List execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { if (tasks.isValidTaskNumber(taskNumber)) { - System.out.println("I've marked this task as done!"); + ArrayList reply = new ArrayList<>(); + reply.add("I've marked this task as done!"); tasks.markAsDone(taskNumber); storage.updateTask(taskNumber, CommandType.MARK); - ui.printLine(tasks.getTaskToString(taskNumber)); + reply.add(tasks.getTaskToString(taskNumber)); + return reply; } else { throw new InvalidTaskNumberException(); } diff --git a/src/main/java/drake/commands/TaskOperationCommand.java b/src/main/java/drake/commands/TaskOperationCommand.java index d71e4a792f..7fd5d6925a 100644 --- a/src/main/java/drake/commands/TaskOperationCommand.java +++ b/src/main/java/drake/commands/TaskOperationCommand.java @@ -1,8 +1,15 @@ package drake.commands; -import drake.*; - import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import drake.DrakeException; +import drake.IncompatibleCommandException; +import drake.InvalidTaskNumberException; +import drake.Storage; +import drake.TaskList; +import drake.Ui; /** * Represents a command given by the user to perform an operation on a task currently in the list. @@ -29,16 +36,19 @@ public TaskOperationCommand(String fullInput) throws IncompatibleCommandExceptio /** * Executes the command to perform an operation on a task currently in the list. * - * @param tasks The task list before the command is executed. - * @param ui Gives access to the UI of the program. + * @param tasks The task list before the command is executed. + * @param ui Gives access to the UI of the program. * @param storage Gives access to local storage. - * @throws IOException when there is an issue with the IO. + * @return The list of replies. + * @throws IOException when there is an issue with the IO. * @throws DrakeException when there is inappropriate input or save file issues. */ @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { + public List execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { if (tasks.isValidTaskNumber(taskNumber)) { - ui.printLine(tasks.getTaskToString(taskNumber)); + ArrayList reply = new ArrayList<>(); + reply.add(tasks.getTaskToString(taskNumber)); + return reply; } else { throw new InvalidTaskNumberException(); } diff --git a/src/main/java/drake/commands/TodoCommand.java b/src/main/java/drake/commands/TodoCommand.java index 17109b47d1..9a7c11fe76 100644 --- a/src/main/java/drake/commands/TodoCommand.java +++ b/src/main/java/drake/commands/TodoCommand.java @@ -1,5 +1,9 @@ package drake.commands; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + import drake.DrakeException; import drake.Storage; import drake.TaskList; @@ -7,8 +11,6 @@ import drake.tasks.Task; import drake.tasks.Todo; -import java.io.IOException; - /** * Represents a command given by the user to create a new to-do task. */ @@ -27,18 +29,21 @@ public TodoCommand(String fullInput) { * Executes the command to create a new to-do task, saving the new task and * printing the size of the task list after execution. * - * @param tasks The task list before the command is executed. - * @param ui Gives access to the UI of the program. + * @param tasks The task list before the command is executed. + * @param ui Gives access to the UI of the program. * @param storage Gives access to local storage. - * @throws IOException when there is an issue with the IO. + * @return The list of replies. + * @throws IOException when there is an issue with the IO. * @throws DrakeException when there is inappropriate input or save file issues. */ @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { - ui.printLine("I've added this task:"); + public List execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { + ArrayList reply = new ArrayList<>(); + reply.add("I've added this task:"); Task addedTask = tasks.addTask(new Todo(description)); - ui.printLine(addedTask); + reply.add(addedTask.toString()); storage.addTask(addedTask); - super.execute(tasks, ui, storage); + reply.addAll(super.execute(tasks, ui, storage)); + return reply; } } diff --git a/src/main/java/drake/commands/UnmarkCommand.java b/src/main/java/drake/commands/UnmarkCommand.java index 1d985b7bea..aa6440d166 100644 --- a/src/main/java/drake/commands/UnmarkCommand.java +++ b/src/main/java/drake/commands/UnmarkCommand.java @@ -1,9 +1,19 @@ package drake.commands; -import drake.*; - import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import drake.DrakeException; +import drake.IncompatibleCommandException; +import drake.InvalidTaskNumberException; +import drake.Storage; +import drake.TaskList; +import drake.Ui; +/** + * Represents the unmark command. + */ public class UnmarkCommand extends TaskOperationCommand { public UnmarkCommand(String fullInput) throws IncompatibleCommandException { @@ -11,12 +21,14 @@ public UnmarkCommand(String fullInput) throws IncompatibleCommandException { } @Override - public void execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { + public List execute(TaskList tasks, Ui ui, Storage storage) throws DrakeException, IOException { if (tasks.isValidTaskNumber(taskNumber)) { - System.out.println("I've marked this task as not done (yet ;))"); + ArrayList reply = new ArrayList<>(); + reply.add("I've marked this task as not done (yet ;))"); tasks.markAsNotDone(taskNumber); storage.updateTask(taskNumber, CommandType.UNMARK); - ui.printLine(tasks.getTaskToString(taskNumber)); + reply.add(tasks.getTaskToString(taskNumber)); + return reply; } else { throw new InvalidTaskNumberException(); } diff --git a/src/main/java/drake/gui/ChatMessage.java b/src/main/java/drake/gui/ChatMessage.java new file mode 100644 index 0000000000..a1cc773aad --- /dev/null +++ b/src/main/java/drake/gui/ChatMessage.java @@ -0,0 +1,69 @@ +package drake.gui; + +import java.io.IOException; +import java.util.Collections; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; + +/** + * A JavaFX component displaying a chat message. + */ +public class ChatMessage extends AnchorPane { + @FXML + private Label name; + + @FXML + private Label message; + + @FXML + private ImageView profilePicture; + + @FXML + private HBox header; + + private ChatMessage(String name, String text, Image img) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(Window.class.getResource("/view/ChatMessage.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + this.name.setText(name); + message.setText(text); + profilePicture.setImage(img); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + * Inspired by parnikkapore's PR + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(header.getChildren()); + Collections.reverse(tmp); + header.getChildren().setAll(tmp); + header.setAlignment(Pos.TOP_LEFT); + } + + public static ChatMessage getUserDialog(String text, Image img) { + return new ChatMessage("Lost Soul", text, img); + } + + public static ChatMessage getDrakeDialog(String text, Image img) { + var db = new ChatMessage("Drake", text, img); + db.flip(); + return db; + } +} diff --git a/src/main/java/drake/gui/Launcher.java b/src/main/java/drake/gui/Launcher.java new file mode 100644 index 0000000000..93f9b2167f --- /dev/null +++ b/src/main/java/drake/gui/Launcher.java @@ -0,0 +1,12 @@ +package drake.gui; + +import javafx.application.Application; + +/** + * A launcher class. + */ +public class Launcher { + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/drake/gui/Main.java b/src/main/java/drake/gui/Main.java new file mode 100644 index 0000000000..596fc24448 --- /dev/null +++ b/src/main/java/drake/gui/Main.java @@ -0,0 +1,33 @@ +package drake.gui; + +import java.io.IOException; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + + +/** + * A GUI for Drake. + */ +public class Main extends Application { + + @Override + public void start(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/Window.fxml")); + AnchorPane anchorPane = fxmlLoader.load(); + Scene scene = new Scene(anchorPane); + stage.setScene(scene); + stage.show(); + stage.setTitle("Drake"); + stage.setMinHeight(650.0); + stage.setMaxWidth(400.0); + stage.setMinWidth(400.0); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/drake/gui/Window.java b/src/main/java/drake/gui/Window.java new file mode 100644 index 0000000000..f988310b9e --- /dev/null +++ b/src/main/java/drake/gui/Window.java @@ -0,0 +1,94 @@ +package drake.gui; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import drake.DrakeException; +import drake.Parser; +import drake.Storage; +import drake.TaskList; +import drake.Ui; +import drake.commands.Command; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; + +/** + * Represents the chat window. + */ +public class Window extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private final Image userImage = new Image(this.getClass().getResourceAsStream("/media/lostsoul.png")); + private final Image drakeImage = new Image(this.getClass().getResourceAsStream("/media/drake.png")); + + // initialize plugins + + private TaskList taskList; + private Storage storage; + private final Ui ui = new Ui(); + + public Window() throws DrakeException, IOException { + } + + /** + * Initializes the GUI Window. + * @throws DrakeException When the user enters incorrect input. + * @throws IOException When IO encounters an issue. + */ + @FXML + public void initialize() throws DrakeException, IOException { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + + // intro string + List messages = new ArrayList<>(ui.chatBubbleText(ui.replyWelcome())); + + // initialize plugins + storage = new Storage(); + taskList = new TaskList(storage.fileToList()); + + for (String message : messages) { + dialogContainer.getChildren().add(ChatMessage.getDrakeDialog(message, drakeImage)); + } + } + + /** + * Handles an input from the user. + */ + @FXML + private void handleUserInput() throws DrakeException, IOException { + String input = userInput.getText(); + Command command = Parser.parse(input); + List reply = command.execute(taskList, ui, storage); + dialogContainer.getChildren().add(ChatMessage.getUserDialog(input, userImage)); + + // Split the reply into groups of 5 lines (the maximum that fits in a single message balloon) + List currentLines = new ArrayList<>(); + for (String line : reply) { + currentLines.add(line); + if (currentLines.size() >= 5) { + dialogContainer.getChildren().add( + ChatMessage.getDrakeDialog(String.join("\n", currentLines), drakeImage)); + currentLines.clear(); + } + } + if (currentLines.size() > 0) { + dialogContainer.getChildren().add( + ChatMessage.getDrakeDialog(String.join("\n", currentLines), drakeImage)); + } + + userInput.clear(); + } +} \ No newline at end of file diff --git a/src/main/resources/media/drake.png b/src/main/resources/media/drake.png new file mode 100644 index 0000000000000000000000000000000000000000..1b7f49e483951b6aabc282c91ed18b019318fcb3 GIT binary patch literal 23657 zcmV)bK&iipP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 UoNJQHI{Ng};;6x`7!C30-(niCMgRZ+ literal 0 HcmV?d00001 diff --git a/src/main/resources/view/ChatMessage.fxml b/src/main/resources/view/ChatMessage.fxml new file mode 100644 index 0000000000..ecd40408b6 --- /dev/null +++ b/src/main/resources/view/ChatMessage.fxml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/Window.fxml b/src/main/resources/view/Window.fxml new file mode 100644 index 0000000000..90a3559996 --- /dev/null +++ b/src/main/resources/view/Window.fxml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + +