From 556af3f47a96b32898ab4cdbd65b16486a4871e8 Mon Sep 17 00:00:00 2001 From: damithc Date: Mon, 25 May 2020 00:58:18 +0800 Subject: [PATCH 01/35] 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 43932c72dea12d4eaff491a27574d6f8f2a88693 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Thu, 18 Aug 2022 08:06:22 +0800 Subject: [PATCH 02/35] Implement Level-1 - Add basic greeting and exit phrases - Implement basic input reading --- src/main/java/Duke.java | 44 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5d313334cc..8f06b0657b 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,46 @@ +import java.util.Locale; +import java.util.Scanner; + public class Duke { + private static String botName = "Duke"; + private static int lineLength = 80; + public static void main(String[] args) { String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n"; System.out.println("Hello from\n" + logo); + + System.out.printf("Hello, I'm %s\n", botName); + System.out.println("What can I do for you?"); + + Scanner scanner = new Scanner(System.in); + + boolean done = false; + while (!done) { + GenerateLine(); + String input = scanner.nextLine(); + GenerateLine(); + + switch (input.toLowerCase()) { + case "bye": + done = true; + break; + default: + Echo(input); + } + } + + System.out.println("Goodbye."); + } + + public static void GenerateLine() { + System.out.println(String.format("%" + lineLength + "s", "").replace(" ", "-")); + } + + public static void Echo(String message) { + System.out.println(message); } } From c8bc26e5f0288c399d61c66f31aaa239e31c710f Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Thu, 18 Aug 2022 10:38:52 +0800 Subject: [PATCH 03/35] Implement Level-2 - Create ParseInput method to parse commands to the bot and call the relevant methods - Create AddToList and DisplayList methods to support text list feature > AddToList stores a given text string to a list > DisplayList prints all the text strings stored so far in order - Remove unused Echo method from Level-1 --- src/main/java/Duke.java | 68 +++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 8f06b0657b..8af4a8211b 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,9 +1,12 @@ import java.util.Locale; import java.util.Scanner; +import java.util.ArrayList; public class Duke { private static String botName = "Duke"; private static int lineLength = 80; + private static ArrayList textList = new ArrayList<>(); + private static boolean isDone = false; public static void main(String[] args) { String logo = " ____ _ \n" @@ -18,29 +21,62 @@ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); - boolean done = false; - while (!done) { - GenerateLine(); + isDone = false; + while (!isDone) { + GenerateLine(lineLength); String input = scanner.nextLine(); - GenerateLine(); - - switch (input.toLowerCase()) { - case "bye": - done = true; - break; - default: - Echo(input); - } + GenerateLine(lineLength); + + ParseInput(input); } System.out.println("Goodbye."); } - public static void GenerateLine() { - System.out.println(String.format("%" + lineLength + "s", "").replace(" ", "-")); + /** + * Parses an input string and calls the relevant method (if any) + * Calls AddToList by default + * + * @param input - the input string to be parsed + */ + public static void ParseInput(String input) { + switch (input.toLowerCase()) { + case "bye": + isDone = true; + break; + case "list": + DisplayList(); + break; + default: + AddToList(input); + } + } + + /** + * Prints a line of a specified character length + * + * @param length - the character length of the line to print + */ + public static void GenerateLine(int length) { + System.out.println(String.format("%" + length + "s", "").replace(" ", "-")); } - public static void Echo(String message) { - System.out.println(message); + /** + * Adds a text string to the list, which is displayed when DisplayList is called + * + * @param text - the text to add to the list + */ + public static void AddToList(String text) { + textList.add(text); + System.out.printf("added: %s\n", text); + } + + /** + * Displays the list of texts added to the list so far in order + */ + public static void DisplayList() { + for (int i = 0; i < textList.size(); i++) { + System.out.printf("%d. %s\n", i + 1, textList.get(i)); + } } } From 162e54a0f87061943f70b6047bc091db1259d34a Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Thu, 18 Aug 2022 12:12:35 +0800 Subject: [PATCH 04/35] Implement Level-3 - Rework text list feature to a to-do list > Create Task class to represent tasks that can be added to the to-do list > Implement methods to support marking of Tasks as completed - Implement ParseArgs method to support chatbot commands with arguments --- src/main/java/Duke.java | 91 +++++++++++++++++++++++++++++++++++------ src/main/java/Task.java | 27 ++++++++++++ 2 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 src/main/java/Task.java diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 8af4a8211b..b5395a46a1 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,12 +1,12 @@ -import java.util.Locale; import java.util.Scanner; import java.util.ArrayList; +import java.util.List; public class Duke { private static String botName = "Duke"; private static int lineLength = 80; - private static ArrayList textList = new ArrayList<>(); - private static boolean isDone = false; + private static List taskList = new ArrayList<>(); + private static boolean isRunning; public static void main(String[] args) { String logo = " ____ _ \n" @@ -21,8 +21,8 @@ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); - isDone = false; - while (!isDone) { + isRunning = true; + while (isRunning) { GenerateLine(lineLength); String input = scanner.nextLine(); GenerateLine(lineLength); @@ -40,18 +40,47 @@ public static void main(String[] args) { * @param input - the input string to be parsed */ public static void ParseInput(String input) { - switch (input.toLowerCase()) { + if (input == "") { + System.out.println("Invalid Input"); + return; + } + + String[] words = input.toLowerCase().split(" "); + String command = words[0]; + List args = ParseArgs(words); + + switch (command) { case "bye": - isDone = true; + isRunning = false; break; case "list": DisplayList(); break; + case "mark": + TryMark(true, args); + break; + case "unmark": + TryMark(false, args); + break; default: AddToList(input); } } + /** + * Parses an array of words into a list of arguments, omitting the first word as the command + * + * @param words - the array of string to parse + * @return a list of strings that represent the arguments + */ + public static List ParseArgs(String[] words) { + List args = new ArrayList<>(); + for (int i = 1; i < words.length; i++) { + args.add(words[i]); + } + return args; + } + /** * Prints a line of a specified character length * @@ -62,21 +91,59 @@ public static void GenerateLine(int length) { } /** - * Adds a text string to the list, which is displayed when DisplayList is called + * Creates a task from the given string and adds it to the list, which is displayed when DisplayList is called * * @param text - the text to add to the list */ public static void AddToList(String text) { - textList.add(text); + taskList.add(new Task(text, false)); System.out.printf("added: %s\n", text); } /** - * Displays the list of texts added to the list so far in order + * Displays the list of tasks added to the list so far in order */ public static void DisplayList() { - for (int i = 0; i < textList.size(); i++) { - System.out.printf("%d. %s\n", i + 1, textList.get(i)); + for (int i = 0; i < taskList.size(); i++) { + System.out.printf("%d. %s\n", i + 1, taskList.get(i)); + } + } + + /** + * Try to mark or unmark a task based on the parsed task index + * + * @param marked - if true, attempt to mark the task, otherwise attempt to unmark + * @param args - a list of string inputs, the first of which will be parsed as the task index to mark/unmark + */ + public static void TryMark(boolean marked, List args) { + if (args.size() == 0) { + System.out.println("Mark failed, no index given"); + return; + } + + String indexString = args.get(0); + int index; + try { + index = Integer.parseInt(indexString); + } catch (NumberFormatException e) { + System.out.println("Mark failed, invalid index"); + return; + } + index--; + + if (index < 0 || index >= taskList.size()) { + System.out.println("Mark failed, index out of range"); + return; + } + + Task task = taskList.get(index); + if (marked) { + System.out.println("Task marked as done"); + task.mark(); + } else { + System.out.println("Task marked as not done"); + task.unmark(); } + System.out.println(task); } } diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 0000000000..d5c030d6b8 --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,27 @@ +/** + * A class used to represent a task. A task has a name and completion status. + */ +public class Task { + protected String taskName = ""; + protected boolean isDone = false; + + protected static final String MARK_CHARACTER = "X"; + + public Task(String taskName, boolean isDone) { + this.taskName = taskName; + this.isDone = isDone; + } + + public void mark() { + this.isDone = true; + } + + public void unmark() { + this.isDone = false; + } + + @Override + public String toString() { + return String.format("[%s] %s", isDone ? MARK_CHARACTER : " ", this.taskName); + } +} From 76c13a69aa80e0027e4d6eb513eddc53fdcc1d06 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Thu, 18 Aug 2022 13:30:32 +0800 Subject: [PATCH 05/35] Implement Level-4 - Create Deadline, Event and ToDo classes that inherit from Task to support different types of Task creation - Refactor input parsing logic to support creation of different types of tasks --- src/main/java/Deadline.java | 16 ++++++ src/main/java/Duke.java | 106 +++++++++++++++++++++++------------- src/main/java/Event.java | 16 ++++++ src/main/java/ToDo.java | 13 +++++ 4 files changed, 114 insertions(+), 37 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..c1b0cf7b8e --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,16 @@ +/** + * Class used to represent a Deadline type task that has a due date. + */ +public class Deadline extends Task { + protected String dueDate; + + public Deadline(String taskName, boolean isDone, String dueDate) { + super(taskName, isDone); + this.dueDate = dueDate; + } + + @Override + public String toString() { + return String.format("[D]%s (by: %s)", super.toString(), dueDate); + } +} diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index b5395a46a1..4d32dc647d 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -40,14 +40,12 @@ public static void main(String[] args) { * @param input - the input string to be parsed */ public static void ParseInput(String input) { - if (input == "") { - System.out.println("Invalid Input"); - return; - } - - String[] words = input.toLowerCase().split(" "); + String[] words = input.toLowerCase().split(" ", 2); String command = words[0]; - List args = ParseArgs(words); + String args = ""; + if (words.length > 1) { + args = words[1]; + } switch (command) { case "bye": @@ -62,42 +60,81 @@ public static void ParseInput(String input) { case "unmark": TryMark(false, args); break; + case "todo": + TryAddToDo(args); + break; + case "deadline": + TryAddDeadline(args); + break; + case "event": + TryAddEvent(args); + break; default: - AddToList(input); + System.out.println("Command not recognised"); } } /** - * Parses an array of words into a list of arguments, omitting the first word as the command + * Prints a line of a specified character length * - * @param words - the array of string to parse - * @return a list of strings that represent the arguments + * @param length - the character length of the line to print */ - public static List ParseArgs(String[] words) { - List args = new ArrayList<>(); - for (int i = 1; i < words.length; i++) { - args.add(words[i]); - } - return args; + public static void GenerateLine(int length) { + System.out.println(String.format("%" + length + "s", "").replace(" ", "-")); } /** - * Prints a line of a specified character length + * Adds a task to the list which is displayed when DisplayList is called * - * @param length - the character length of the line to print + * @param task - the task to add to the list */ - public static void GenerateLine(int length) { - System.out.println(String.format("%" + length + "s", "").replace(" ", "-")); + public static void AddToList(Task task) { + taskList.add(task); + System.out.printf("Task added: %s\n", task); + System.out.printf("You now have %d task(s) in the list\n", taskList.size()); + } + + /** + * Creates a ToDo task from the given argument string + * + * @param args - the argument string to be parsed + */ + public static void TryAddToDo(String args) { + AddToList(new ToDo(args, false)); + } + + /** + * Creates a Deadline task from the given argument string + * + * @param args - the argument string to be parsed + */ + public static void TryAddDeadline(String args) { + String[] argsArr = args.split(" /by ", 2); + if (argsArr.length < 2) { + System.out.println("Failed to create deadline: Invalid number of arguments"); + return; + } + + String name = argsArr[0].strip(); + String date = argsArr[1].strip(); + AddToList(new Deadline(name, false, date)); } /** - * Creates a task from the given string and adds it to the list, which is displayed when DisplayList is called + * Creates an Event task from the given argument string * - * @param text - the text to add to the list + * @param args - the argument string to be parsed */ - public static void AddToList(String text) { - taskList.add(new Task(text, false)); - System.out.printf("added: %s\n", text); + public static void TryAddEvent(String args) { + String[] argsArr = args.split(" /at ", 2); + if (argsArr.length < 2) { + System.out.println("Failed to create event: Invalid number of arguments"); + return; + } + + String name = argsArr[0].strip(); + String date = argsArr[1].strip(); + AddToList(new Event(name, false, date)); } /** @@ -107,6 +144,8 @@ public static void DisplayList() { for (int i = 0; i < taskList.size(); i++) { System.out.printf("%d. %s\n", i + 1, taskList.get(i)); } + + System.out.printf("You have %d task(s) in your list\n", taskList.size()); } /** @@ -115,16 +154,10 @@ public static void DisplayList() { * @param marked - if true, attempt to mark the task, otherwise attempt to unmark * @param args - a list of string inputs, the first of which will be parsed as the task index to mark/unmark */ - public static void TryMark(boolean marked, List args) { - if (args.size() == 0) { - System.out.println("Mark failed, no index given"); - return; - } - - String indexString = args.get(0); + public static void TryMark(boolean marked, String args) { int index; try { - index = Integer.parseInt(indexString); + index = Integer.parseInt(args); } catch (NumberFormatException e) { System.out.println("Mark failed, invalid index"); return; @@ -138,12 +171,11 @@ public static void TryMark(boolean marked, List args) { Task task = taskList.get(index); if (marked) { - System.out.println("Task marked as done"); task.mark(); + System.out.printf("Task marked as done: %s\n", task); } else { - System.out.println("Task marked as not done"); task.unmark(); + System.out.printf("Task marked as not done: %s\n", task); } - System.out.println(task); } } diff --git a/src/main/java/Event.java b/src/main/java/Event.java new file mode 100644 index 0000000000..f8713c5633 --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,16 @@ +/** + * Class used to represent an Event type task that has a start and end datetime. + */ +public class Event extends Task { + protected String eventDate; + + public Event(String taskName, boolean isDone, String eventDate) { + super(taskName, isDone); + this.eventDate = eventDate; + } + + @Override + public String toString() { + return String.format("[E]%s (at: %s)", super.toString(), eventDate); + } +} diff --git a/src/main/java/ToDo.java b/src/main/java/ToDo.java new file mode 100644 index 0000000000..686184ff68 --- /dev/null +++ b/src/main/java/ToDo.java @@ -0,0 +1,13 @@ +/** + * Class used to represent a ToDo type task that has no date. + */ +public class ToDo extends Task { + public ToDo(String taskName, boolean isDone) { + super(taskName, isDone); + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } +} From 1dd0b26c5aa3099ceac5d7119e43fba8b364d09d Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Thu, 18 Aug 2022 13:48:57 +0800 Subject: [PATCH 06/35] Implement A-TextUiTesting - Add test input to input.txt file - Performed testing using Runtest.bat - Fix bug where tasks could be created with empty name or date --- src/main/java/Duke.java | 27 ++++++++++++++++ text-ui-test/EXPECTED.TXT | 68 +++++++++++++++++++++++++++++++++++++++ text-ui-test/input.txt | 17 ++++++++++ 3 files changed, 112 insertions(+) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 4d32dc647d..ba7b9373e3 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -100,6 +100,11 @@ public static void AddToList(Task task) { * @param args - the argument string to be parsed */ public static void TryAddToDo(String args) { + if (args == "") { + System.out.println("Failed to create todo: No task name given"); + return; + } + AddToList(new ToDo(args, false)); } @@ -117,6 +122,17 @@ public static void TryAddDeadline(String args) { String name = argsArr[0].strip(); String date = argsArr[1].strip(); + + if (name == "") { + System.out.println("Failed to create deadline: No task name given"); + return; + } + + if (date == "") { + System.out.println("Failed to create deadline: No date given"); + return; + } + AddToList(new Deadline(name, false, date)); } @@ -134,6 +150,17 @@ public static void TryAddEvent(String args) { String name = argsArr[0].strip(); String date = argsArr[1].strip(); + + if (name == "") { + System.out.println("Failed to create event: No task name given"); + return; + } + + if (date == "") { + System.out.println("Failed to create event: No date given"); + return; + } + AddToList(new Event(name, false, date)); } diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..a7ec958800 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -5,3 +5,71 @@ Hello from | |_| | |_| | < __/ |____/ \__,_|_|\_\___| +Hello, I'm Duke +What can I do for you? +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Mark failed, index out of range +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Task added: [T][ ] asdf +You now have 1 task(s) in the list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Task added: [D][ ] ghjk (by: 19:00) +You now have 2 task(s) in the list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Task added: [E][ ] tyui (at: aug 8, 2pm - 4pm) +You now have 3 task(s) in the list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +1. [T][ ] asdf +2. [D][ ] ghjk (by: 19:00) +3. [E][ ] tyui (at: aug 8, 2pm - 4pm) +You have 3 task(s) in your list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Task marked as done: [T][X] asdf +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +1. [T][X] asdf +2. [D][ ] ghjk (by: 19:00) +3. [E][ ] tyui (at: aug 8, 2pm - 4pm) +You have 3 task(s) in your list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Task marked as not done: [T][ ] asdf +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Mark failed, index out of range +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Failed to create deadline: Invalid number of arguments +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Failed to create event: Invalid number of arguments +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Failed to create todo: No task name given +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +1. [T][ ] asdf +2. [D][ ] ghjk (by: 19:00) +3. [E][ ] tyui (at: aug 8, 2pm - 4pm) +You have 3 task(s) in your list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Failed to create deadline: Invalid number of arguments +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Failed to create event: Invalid number of arguments +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +1. [T][ ] asdf +2. [D][ ] ghjk (by: 19:00) +3. [E][ ] tyui (at: aug 8, 2pm - 4pm) +You have 3 task(s) in your list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Goodbye. diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..7a7a38dd5a 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,17 @@ +mark 1 +todo asdf +deadline ghjk /by 19:00 +event tyui /at Aug 8, 2pm - 4pm +list +mark 1 +list +unmark 1 +mark 5 +deadline a +event b +todo +list +deadline /by +event /at +list +bye \ No newline at end of file From 896077421a726b8882d0b81840b850967ee17a96 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Thu, 18 Aug 2022 16:01:23 +0800 Subject: [PATCH 07/35] Implement Level-5 - Create DukeException class to be thrown by and handled by methods in Duke - Refactor methods to use DukeException class in error handling --- src/main/java/Duke.java | 50 +++++++++++++++----------------- src/main/java/DukeException.java | 8 +++++ 2 files changed, 31 insertions(+), 27 deletions(-) create mode 100644 src/main/java/DukeException.java diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index ba7b9373e3..ea0ab6a11f 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -27,7 +27,11 @@ public static void main(String[] args) { String input = scanner.nextLine(); GenerateLine(lineLength); - ParseInput(input); + try { + ParseInput(input); + } catch (DukeException e) { + System.out.println(e.getMessage()); + } } System.out.println("Goodbye."); @@ -39,7 +43,7 @@ public static void main(String[] args) { * * @param input - the input string to be parsed */ - public static void ParseInput(String input) { + public static void ParseInput(String input) throws DukeException { String[] words = input.toLowerCase().split(" ", 2); String command = words[0]; String args = ""; @@ -70,7 +74,7 @@ public static void ParseInput(String input) { TryAddEvent(args); break; default: - System.out.println("Command not recognised"); + throw new DukeException("Command not recognised"); } } @@ -99,13 +103,13 @@ public static void AddToList(Task task) { * * @param args - the argument string to be parsed */ - public static void TryAddToDo(String args) { - if (args == "") { - System.out.println("Failed to create todo: No task name given"); - return; + public static void TryAddToDo(String args) throws DukeException { + String name = args.strip(); + if (name == "") { + throw new DukeException("Failed to create todo: No task name given"); } - AddToList(new ToDo(args, false)); + AddToList(new ToDo(name, false)); } /** @@ -113,24 +117,21 @@ public static void TryAddToDo(String args) { * * @param args - the argument string to be parsed */ - public static void TryAddDeadline(String args) { + public static void TryAddDeadline(String args) throws DukeException { String[] argsArr = args.split(" /by ", 2); if (argsArr.length < 2) { - System.out.println("Failed to create deadline: Invalid number of arguments"); - return; + throw new DukeException("Failed to create deadline: Invalid number of arguments"); } String name = argsArr[0].strip(); String date = argsArr[1].strip(); if (name == "") { - System.out.println("Failed to create deadline: No task name given"); - return; + throw new DukeException("Failed to create deadline: No task name given"); } if (date == "") { - System.out.println("Failed to create deadline: No date given"); - return; + throw new DukeException("Failed to create deadline: No date given"); } AddToList(new Deadline(name, false, date)); @@ -141,24 +142,21 @@ public static void TryAddDeadline(String args) { * * @param args - the argument string to be parsed */ - public static void TryAddEvent(String args) { + public static void TryAddEvent(String args) throws DukeException { String[] argsArr = args.split(" /at ", 2); if (argsArr.length < 2) { - System.out.println("Failed to create event: Invalid number of arguments"); - return; + throw new DukeException("Failed to create event: Invalid number of arguments"); } String name = argsArr[0].strip(); String date = argsArr[1].strip(); if (name == "") { - System.out.println("Failed to create event: No task name given"); - return; + throw new DukeException("Failed to create event: No task name given"); } if (date == "") { - System.out.println("Failed to create event: No date given"); - return; + throw new DukeException("Failed to create event: No date given"); } AddToList(new Event(name, false, date)); @@ -181,19 +179,17 @@ public static void DisplayList() { * @param marked - if true, attempt to mark the task, otherwise attempt to unmark * @param args - a list of string inputs, the first of which will be parsed as the task index to mark/unmark */ - public static void TryMark(boolean marked, String args) { + public static void TryMark(boolean marked, String args) throws DukeException { int index; try { index = Integer.parseInt(args); } catch (NumberFormatException e) { - System.out.println("Mark failed, invalid index"); - return; + throw new DukeException("Mark failed, invalid index"); } index--; if (index < 0 || index >= taskList.size()) { - System.out.println("Mark failed, index out of range"); - return; + throw new DukeException("Mark failed, index out of range"); } Task task = taskList.get(index); diff --git a/src/main/java/DukeException.java b/src/main/java/DukeException.java new file mode 100644 index 0000000000..21a4f8c77c --- /dev/null +++ b/src/main/java/DukeException.java @@ -0,0 +1,8 @@ +/** + * A class used to represent Exceptions specific to Duke + */ +public class DukeException extends Exception { + public DukeException(String message) { + super(message); + } +} From ecd6e8bfdb38f546f45de50f49409556ea8c3ccd Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Thu, 18 Aug 2022 16:12:27 +0800 Subject: [PATCH 08/35] Implement Level-6 - Implement delete command > Tasks can now be deleted from the list by index - Modify test input to test deletion feature --- src/main/java/Duke.java | 24 ++++++++++++++++++++++- text-ui-test/EXPECTED.TXT | 40 +++++++++++++++++++++++++++++++++++++++ text-ui-test/input.txt | 11 +++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index ea0ab6a11f..7ea1ca42dc 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -73,6 +73,9 @@ public static void ParseInput(String input) throws DukeException { case "event": TryAddEvent(args); break; + case "delete": + TryDelete(args); + break; default: throw new DukeException("Command not recognised"); } @@ -182,7 +185,7 @@ public static void DisplayList() { public static void TryMark(boolean marked, String args) throws DukeException { int index; try { - index = Integer.parseInt(args); + index = Integer.parseInt(args.strip()); } catch (NumberFormatException e) { throw new DukeException("Mark failed, invalid index"); } @@ -201,4 +204,23 @@ public static void TryMark(boolean marked, String args) throws DukeException { System.out.printf("Task marked as not done: %s\n", task); } } + + public static void TryDelete(String args) throws DukeException { + int index; + try { + index = Integer.parseInt(args.strip()); + } catch (NumberFormatException e) { + throw new DukeException("Delete failed, invalid index"); + } + index--; + + if (index < 0 || index >= taskList.size()) { + throw new DukeException("Delete failed, index out of range"); + } + + Task task = taskList.get(index); + taskList.remove(index); + System.out.printf("Task deleted: %s\n", task); + System.out.printf("You now have %d task(s) in the list\n", taskList.size()); + } } diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index a7ec958800..2e16033193 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -72,4 +72,44 @@ Failed to create event: Invalid number of arguments You have 3 task(s) in your list -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- +Task deleted: [T][ ] asdf +You now have 2 task(s) in the list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Task deleted: [D][ ] ghjk (by: 19:00) +You now have 1 task(s) in the list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Delete failed, invalid index +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Delete failed, invalid index +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Delete failed, index out of range +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Task added: [T][ ] a +You now have 2 task(s) in the list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Task added: [T][ ] b +You now have 3 task(s) in the list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Task added: [T][ ] c +You now have 4 task(s) in the list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Task deleted: [T][ ] c +You now have 3 task(s) in the list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Task marked as done: [E][X] tyui (at: aug 8, 2pm - 4pm) +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Task deleted: [E][X] tyui (at: aug 8, 2pm - 4pm) +You now have 2 task(s) in the list +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- Goodbye. diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index 7a7a38dd5a..2020912686 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -14,4 +14,15 @@ list deadline /by event /at list +delete 1 +delete 1 +delete +delete +delete 3 +todo a +todo b +todo c +delete 4 +mark 1 +delete 1 bye \ No newline at end of file From acfd4da6a09a7de598afc304708ba4b78658fff9 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Sun, 28 Aug 2022 14:36:00 +0800 Subject: [PATCH 09/35] Implement Level-7 - Chat history with Duke is now saved between runs and loaded on start --- .gitignore | 1 + src/main/java/Duke.java | 69 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index f69985ef1f..862d164975 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT +data/duke.txt diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 7ea1ca42dc..fa72ced28b 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,3 +1,9 @@ +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Scanner; import java.util.ArrayList; import java.util.List; @@ -7,6 +13,9 @@ public class Duke { private static int lineLength = 80; private static List taskList = new ArrayList<>(); private static boolean isRunning; + private static final String DATA_FILE_PATH = "data"; + private static final String DATA_FILE_NAME = "duke.txt"; + private static FileWriter dataFileWriter; public static void main(String[] args) { String logo = " ____ _ \n" @@ -19,6 +28,14 @@ public static void main(String[] args) { System.out.printf("Hello, I'm %s\n", botName); System.out.println("What can I do for you?"); + try { + loadData(); + } catch (IOException e) { + e.printStackTrace(); + System.out.println("Error: Failed to access data"); + return; + } + Scanner scanner = new Scanner(System.in); isRunning = true; @@ -28,13 +45,57 @@ public static void main(String[] args) { GenerateLine(lineLength); try { - ParseInput(input); + if (ParseInput(input)) { + dataFileWriter.append(input+"\n"); + } } catch (DukeException e) { System.out.println(e.getMessage()); + } catch (IOException e) { + System.out.println("Error: Failed to write to save file"); } } System.out.println("Goodbye."); + try { + dataFileWriter.close(); + } catch (IOException e) { + System.out.println("Error: Failed to close file writer"); + } + } + + /** + * Attempts to load saved tasks from the hard disk. + * Creates the save file and directory if missing. + * + * @throws IOException + */ + public static void loadData() throws IOException { + Path parentDir = Paths.get(DATA_FILE_PATH); + if (!Files.exists(parentDir)) { + Files.createDirectories(parentDir); + } + + File dataFile = new File(Paths.get(parentDir.toString(), DATA_FILE_NAME).toString()); + if (!dataFile.exists()) { + dataFile.createNewFile(); + } + + Scanner scanner = new Scanner(dataFile); + while (scanner.hasNextLine()) { + GenerateLine(lineLength); + String input = scanner.nextLine(); + System.out.println(input); + GenerateLine(lineLength); + + try { + ParseInput(input); + } catch (DukeException e) { + System.out.println(e.getMessage()); + } + } + scanner.close(); + + dataFileWriter = new FileWriter(dataFile, true); } /** @@ -43,7 +104,7 @@ public static void main(String[] args) { * * @param input - the input string to be parsed */ - public static void ParseInput(String input) throws DukeException { + public static boolean ParseInput(String input) throws DukeException { String[] words = input.toLowerCase().split(" ", 2); String command = words[0]; String args = ""; @@ -54,7 +115,7 @@ public static void ParseInput(String input) throws DukeException { switch (command) { case "bye": isRunning = false; - break; + return false; case "list": DisplayList(); break; @@ -79,6 +140,8 @@ public static void ParseInput(String input) throws DukeException { default: throw new DukeException("Command not recognised"); } + + return true; } /** From 5e3049b64469a14115dd17980f40fd78bb1670d7 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Tue, 30 Aug 2022 15:50:01 +0800 Subject: [PATCH 10/35] Implement Level-8 - Modify parsing of input to parse date arguments - Modify Deadline and Event class to store date as LocalDate instead of String --- src/main/java/Deadline.java | 6 ++++-- src/main/java/Duke.java | 26 +++++++++++++++++++++----- src/main/java/Event.java | 6 ++++-- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index c1b0cf7b8e..4fff0fcebe 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -1,10 +1,12 @@ +import java.time.LocalDate; + /** * Class used to represent a Deadline type task that has a due date. */ public class Deadline extends Task { - protected String dueDate; + protected LocalDate dueDate; - public Deadline(String taskName, boolean isDone, String dueDate) { + public Deadline(String taskName, boolean isDone, LocalDate dueDate) { super(taskName, isDone); this.dueDate = dueDate; } diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 7ea1ca42dc..0dcc50f402 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,3 +1,5 @@ +import java.time.LocalDate; +import java.time.format.DateTimeParseException; import java.util.Scanner; import java.util.ArrayList; import java.util.List; @@ -127,16 +129,23 @@ public static void TryAddDeadline(String args) throws DukeException { } String name = argsArr[0].strip(); - String date = argsArr[1].strip(); + String dateStr = argsArr[1].strip(); if (name == "") { throw new DukeException("Failed to create deadline: No task name given"); } - if (date == "") { + if (dateStr == "") { throw new DukeException("Failed to create deadline: No date given"); } + LocalDate date; + try { + date = LocalDate.parse(dateStr); + } catch (DateTimeParseException e) { + throw new DukeException("Failed to create deadline: Invalid date given"); + } + AddToList(new Deadline(name, false, date)); } @@ -152,17 +161,24 @@ public static void TryAddEvent(String args) throws DukeException { } String name = argsArr[0].strip(); - String date = argsArr[1].strip(); + String dateStr = argsArr[1].strip(); if (name == "") { throw new DukeException("Failed to create event: No task name given"); } - if (date == "") { + if (dateStr == "") { throw new DukeException("Failed to create event: No date given"); } - AddToList(new Event(name, false, date)); + LocalDate date; + try { + date = LocalDate.parse(dateStr); + } catch (DateTimeParseException e) { + throw new DukeException("Failed to create event: Invalid date given"); + } + + AddToList(new Deadline(name, false, date)); } /** diff --git a/src/main/java/Event.java b/src/main/java/Event.java index f8713c5633..7ff0a35c7c 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -1,10 +1,12 @@ +import java.time.LocalDate; + /** * Class used to represent an Event type task that has a start and end datetime. */ public class Event extends Task { - protected String eventDate; + protected LocalDate eventDate; - public Event(String taskName, boolean isDone, String eventDate) { + public Event(String taskName, boolean isDone, LocalDate eventDate) { super(taskName, isDone); this.eventDate = eventDate; } From b469e7b91196123542fa81984103f09b658d7463 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Wed, 31 Aug 2022 13:17:10 +0800 Subject: [PATCH 11/35] Clean up and refactor code - Modify all code to follow coding style - Refactor task saving logic to be cleaner - Update javadocs --- src/main/java/Deadline.java | 5 + src/main/java/Duke.java | 210 +++++++++++++++++++++--------------- src/main/java/Event.java | 5 + src/main/java/Task.java | 8 +- src/main/java/ToDo.java | 5 + 5 files changed, 146 insertions(+), 87 deletions(-) diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 4fff0fcebe..4c99677bc8 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -11,6 +11,11 @@ public Deadline(String taskName, boolean isDone, LocalDate dueDate) { this.dueDate = dueDate; } + @Override + public String toSaveFormatString() { + return String.format("D|%d|%s|%s", isDone ? 1 : 0, taskName, dueDate); + } + @Override public String toString() { return String.format("[D]%s (by: %s)", super.toString(), dueDate); diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index ed7ac2face..45b94b7ffb 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -10,13 +10,17 @@ import java.util.ArrayList; import java.util.List; +/** + * The main class for the Duke program + */ public class Duke { + private static final String DATA_FILE_PATH = "data"; + private static final String DATA_FILE_NAME = "duke.txt"; + private static String botName = "Duke"; private static int lineLength = 80; - private static List taskList = new ArrayList<>(); + private static List tasks = new ArrayList<>(); private static boolean isRunning; - private static final String DATA_FILE_PATH = "data"; - private static final String DATA_FILE_NAME = "duke.txt"; private static FileWriter dataFileWriter; public static void main(String[] args) { @@ -27,9 +31,6 @@ public static void main(String[] args) { + "|____/ \\__,_|_|\\_\\___|\n"; System.out.println("Hello from\n" + logo); - System.out.printf("Hello, I'm %s\n", botName); - System.out.println("What can I do for you?"); - try { loadData(); } catch (IOException e) { @@ -38,30 +39,33 @@ public static void main(String[] args) { return; } + System.out.printf("Hello, I'm %s\n", botName); + System.out.println("What can I do for you?"); + Scanner scanner = new Scanner(System.in); isRunning = true; while (isRunning) { - GenerateLine(lineLength); + generateLine(lineLength); String input = scanner.nextLine(); - GenerateLine(lineLength); + generateLine(lineLength); try { - if (ParseInput(input)) { - dataFileWriter.append(input+"\n"); - } + parseInput(input); } catch (DukeException e) { System.out.println(e.getMessage()); - } catch (IOException e) { - System.out.println("Error: Failed to write to save file"); } } System.out.println("Goodbye."); try { + for (Task task: tasks) { + dataFileWriter.write(task.toSaveFormatString() + "\n"); + } + dataFileWriter.close(); } catch (IOException e) { - System.out.println("Error: Failed to close file writer"); + System.out.println("Error: Failed to save tasks"); } } @@ -71,7 +75,7 @@ public static void main(String[] args) { * * @throws IOException */ - public static void loadData() throws IOException { + private static void loadData() throws IOException { Path parentDir = Paths.get(DATA_FILE_PATH); if (!Files.exists(parentDir)) { Files.createDirectories(parentDir); @@ -83,30 +87,64 @@ public static void loadData() throws IOException { } Scanner scanner = new Scanner(dataFile); + int taskCount = 0; while (scanner.hasNextLine()) { - GenerateLine(lineLength); - String input = scanner.nextLine(); - System.out.println(input); - GenerateLine(lineLength); + String saveFormatString = scanner.nextLine(); + String[] args = saveFormatString.split("\\|", 4); + if (args.length < 3) { + System.out.println("Invalid task save string, task not added"); + continue; + } - try { - ParseInput(input); - } catch (DukeException e) { - System.out.println(e.getMessage()); + String taskType = args[0].strip(); + boolean marked = args[1].strip() == "1" ? true : false; + String taskName = args[2].strip(); + if (taskName == "") { + System.out.println("Invalid task save string, task not added"); + continue; + } + + LocalDate date = null; + if (args.length >= 4) { + try { + date = LocalDate.parse(args[3].strip()); + } catch (DateTimeParseException e) { + System.out.println("Invalid date in task save string, task not added"); + } + } + + switch (taskType) { + case "T": + tasks.add(new ToDo(taskName, marked)); + taskCount++; + break; + case "D": + if (date != null) { + tasks.add(new Deadline(taskName, marked, date)); + taskCount++; + } + break; + case "E": + if (date != null) { + tasks.add(new Event(taskName, marked, date)); + taskCount++; + } + break; } } scanner.close(); - dataFileWriter = new FileWriter(dataFile, true); + System.out.printf("%d tasks loaded from save\n", taskCount); + + dataFileWriter = new FileWriter(dataFile); } /** * Parses an input string and calls the relevant method (if any) - * Calls AddToList by default * * @param input - the input string to be parsed */ - public static boolean ParseInput(String input) throws DukeException { + private static void parseInput(String input) throws DukeException { String[] words = input.toLowerCase().split(" ", 2); String command = words[0]; String args = ""; @@ -115,35 +153,33 @@ public static boolean ParseInput(String input) throws DukeException { } switch (command) { - case "bye": - isRunning = false; - return false; - case "list": - DisplayList(); - break; - case "mark": - TryMark(true, args); - break; - case "unmark": - TryMark(false, args); - break; - case "todo": - TryAddToDo(args); - break; - case "deadline": - TryAddDeadline(args); - break; - case "event": - TryAddEvent(args); - break; - case "delete": - TryDelete(args); - break; - default: - throw new DukeException("Command not recognised"); + case "bye": + isRunning = false; + break; + case "list": + displayTasks(); + break; + case "mark": + markTask(true, args); + break; + case "unmark": + markTask(false, args); + break; + case "todo": + addToDo(args); + break; + case "deadline": + addDeadline(args); + break; + case "event": + addEvent(args); + break; + case "delete": + deleteTask(args); + break; + default: + throw new DukeException("Command not recognised"); } - - return true; } /** @@ -151,19 +187,19 @@ public static boolean ParseInput(String input) throws DukeException { * * @param length - the character length of the line to print */ - public static void GenerateLine(int length) { + private static void generateLine(int length) { System.out.println(String.format("%" + length + "s", "").replace(" ", "-")); } /** - * Adds a task to the list which is displayed when DisplayList is called + * Adds the given task to the list * * @param task - the task to add to the list */ - public static void AddToList(Task task) { - taskList.add(task); + private static void addToList(Task task) { + tasks.add(task); System.out.printf("Task added: %s\n", task); - System.out.printf("You now have %d task(s) in the list\n", taskList.size()); + System.out.printf("You now have %d task(s) in the list\n", tasks.size()); } /** @@ -171,13 +207,13 @@ public static void AddToList(Task task) { * * @param args - the argument string to be parsed */ - public static void TryAddToDo(String args) throws DukeException { + private static void addToDo(String args) throws DukeException { String name = args.strip(); if (name == "") { throw new DukeException("Failed to create todo: No task name given"); } - AddToList(new ToDo(name, false)); + addToList(new ToDo(name, false)); } /** @@ -185,7 +221,7 @@ public static void TryAddToDo(String args) throws DukeException { * * @param args - the argument string to be parsed */ - public static void TryAddDeadline(String args) throws DukeException { + private static void addDeadline(String args) throws DukeException { String[] argsArr = args.split(" /by ", 2); if (argsArr.length < 2) { throw new DukeException("Failed to create deadline: Invalid number of arguments"); @@ -209,7 +245,7 @@ public static void TryAddDeadline(String args) throws DukeException { throw new DukeException("Failed to create deadline: Invalid date given"); } - AddToList(new Deadline(name, false, date)); + addToList(new Deadline(name, false, date)); } /** @@ -217,7 +253,7 @@ public static void TryAddDeadline(String args) throws DukeException { * * @param args - the argument string to be parsed */ - public static void TryAddEvent(String args) throws DukeException { + private static void addEvent(String args) throws DukeException { String[] argsArr = args.split(" /at ", 2); if (argsArr.length < 2) { throw new DukeException("Failed to create event: Invalid number of arguments"); @@ -241,41 +277,41 @@ public static void TryAddEvent(String args) throws DukeException { throw new DukeException("Failed to create event: Invalid date given"); } - AddToList(new Deadline(name, false, date)); + addToList(new Deadline(name, false, date)); } /** - * Displays the list of tasks added to the list so far in order + * Displays all tasks in the list */ - public static void DisplayList() { - for (int i = 0; i < taskList.size(); i++) { - System.out.printf("%d. %s\n", i + 1, taskList.get(i)); + private static void displayTasks() { + for (int i = 0; i < tasks.size(); i++) { + System.out.printf("%d. %s\n", i + 1, tasks.get(i)); } - System.out.printf("You have %d task(s) in your list\n", taskList.size()); + System.out.printf("You have %d task(s) in your list\n", tasks.size()); } /** - * Try to mark or unmark a task based on the parsed task index + * Marks or unmarks a task based on the given task index string * - * @param marked - if true, attempt to mark the task, otherwise attempt to unmark - * @param args - a list of string inputs, the first of which will be parsed as the task index to mark/unmark + * @param isMarked - if true, attempt to mark the task, otherwise attempt to unmark + * @param indexString - the index of the task to mark */ - public static void TryMark(boolean marked, String args) throws DukeException { + private static void markTask(boolean isMarked, String indexString) throws DukeException { int index; try { - index = Integer.parseInt(args.strip()); + index = Integer.parseInt(indexString.strip()); } catch (NumberFormatException e) { throw new DukeException("Mark failed, invalid index"); } index--; - if (index < 0 || index >= taskList.size()) { + if (index < 0 || index >= tasks.size()) { throw new DukeException("Mark failed, index out of range"); } - Task task = taskList.get(index); - if (marked) { + Task task = tasks.get(index); + if (isMarked) { task.mark(); System.out.printf("Task marked as done: %s\n", task); } else { @@ -284,22 +320,28 @@ public static void TryMark(boolean marked, String args) throws DukeException { } } - public static void TryDelete(String args) throws DukeException { + /** + * Deletes a task based on the given task index string + * + * @param indexString - the index of the task to delete + * @throws DukeException + */ + private static void deleteTask(String indexString) throws DukeException { int index; try { - index = Integer.parseInt(args.strip()); + index = Integer.parseInt(indexString.strip()); } catch (NumberFormatException e) { throw new DukeException("Delete failed, invalid index"); } index--; - if (index < 0 || index >= taskList.size()) { + if (index < 0 || index >= tasks.size()) { throw new DukeException("Delete failed, index out of range"); } - Task task = taskList.get(index); - taskList.remove(index); + Task task = tasks.get(index); + tasks.remove(index); System.out.printf("Task deleted: %s\n", task); - System.out.printf("You now have %d task(s) in the list\n", taskList.size()); + System.out.printf("You now have %d task(s) in the list\n", tasks.size()); } } diff --git a/src/main/java/Event.java b/src/main/java/Event.java index 7ff0a35c7c..32e2e6a731 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -11,6 +11,11 @@ public Event(String taskName, boolean isDone, LocalDate eventDate) { this.eventDate = eventDate; } + @Override + public String toSaveFormatString() { + return String.format("E|%d|%s|%s", isDone ? 1 : 0, taskName, eventDate); + } + @Override public String toString() { return String.format("[E]%s (at: %s)", super.toString(), eventDate); diff --git a/src/main/java/Task.java b/src/main/java/Task.java index d5c030d6b8..d2d0abf176 100644 --- a/src/main/java/Task.java +++ b/src/main/java/Task.java @@ -1,12 +1,12 @@ /** * A class used to represent a task. A task has a name and completion status. */ -public class Task { +public abstract class Task { + protected static final String MARK_CHARACTER = "X"; + protected String taskName = ""; protected boolean isDone = false; - protected static final String MARK_CHARACTER = "X"; - public Task(String taskName, boolean isDone) { this.taskName = taskName; this.isDone = isDone; @@ -20,6 +20,8 @@ public void unmark() { this.isDone = false; } + public abstract String toSaveFormatString(); + @Override public String toString() { return String.format("[%s] %s", isDone ? MARK_CHARACTER : " ", this.taskName); diff --git a/src/main/java/ToDo.java b/src/main/java/ToDo.java index 686184ff68..6455d19a2d 100644 --- a/src/main/java/ToDo.java +++ b/src/main/java/ToDo.java @@ -6,6 +6,11 @@ public ToDo(String taskName, boolean isDone) { super(taskName, isDone); } + @Override + public String toSaveFormatString() { + return String.format("T|%d|%s", isDone ? 1 : 0, taskName); + } + @Override public String toString() { return "[T]" + super.toString(); From 094719aef38d74dd18effa8fdbe36c19a1ff3c3d Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Wed, 31 Aug 2022 13:57:58 +0800 Subject: [PATCH 12/35] Implement A-Gradle - Set up gradle --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 885198fcfa..69bf40c771 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ test { } application { - mainClassName = "seedu.duke.Duke" + mainClassName = "Duke" } shadowJar { From 77977644014994134c2156f29ea9273c677daaed Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Wed, 31 Aug 2022 15:32:25 +0800 Subject: [PATCH 13/35] Implement A-MoreOOP - Create Ui, TaskList, Storage, and Parser classes to handle different operations of Duke - Refactor code to these new classes --- src/main/java/Duke.java | 323 +----------------------------------- src/main/java/Parser.java | 50 ++++++ src/main/java/Storage.java | 95 +++++++++++ src/main/java/TaskList.java | 185 +++++++++++++++++++++ src/main/java/Ui.java | 65 ++++++++ 5 files changed, 403 insertions(+), 315 deletions(-) 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/Ui.java diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 45b94b7ffb..4e43c6c266 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,347 +1,40 @@ -import java.io.File; -import java.io.FileWriter; import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.time.LocalDate; -import java.time.format.DateTimeParseException; import java.util.Scanner; -import java.util.ArrayList; -import java.util.List; /** - * The main class for the Duke program + * The main class for the Duke program. */ public class Duke { - private static final String DATA_FILE_PATH = "data"; - private static final String DATA_FILE_NAME = "duke.txt"; - - private static String botName = "Duke"; - private static int lineLength = 80; - private static List tasks = new ArrayList<>(); private static boolean isRunning; - private static FileWriter dataFileWriter; public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - try { - loadData(); + Storage.loadData(); } catch (IOException e) { e.printStackTrace(); System.out.println("Error: Failed to access data"); return; } - System.out.printf("Hello, I'm %s\n", botName); - System.out.println("What can I do for you?"); + Ui.displayStartUpText(); Scanner scanner = new Scanner(System.in); isRunning = true; while (isRunning) { - generateLine(lineLength); + Ui.generateLine(); String input = scanner.nextLine(); - generateLine(lineLength); + Ui.generateLine(); try { - parseInput(input); + isRunning = Parser.parseInput(input); } catch (DukeException e) { System.out.println(e.getMessage()); } } - System.out.println("Goodbye."); - try { - for (Task task: tasks) { - dataFileWriter.write(task.toSaveFormatString() + "\n"); - } - - dataFileWriter.close(); - } catch (IOException e) { - System.out.println("Error: Failed to save tasks"); - } - } - - /** - * Attempts to load saved tasks from the hard disk. - * Creates the save file and directory if missing. - * - * @throws IOException - */ - private static void loadData() throws IOException { - Path parentDir = Paths.get(DATA_FILE_PATH); - if (!Files.exists(parentDir)) { - Files.createDirectories(parentDir); - } - - File dataFile = new File(Paths.get(parentDir.toString(), DATA_FILE_NAME).toString()); - if (!dataFile.exists()) { - dataFile.createNewFile(); - } - - Scanner scanner = new Scanner(dataFile); - int taskCount = 0; - while (scanner.hasNextLine()) { - String saveFormatString = scanner.nextLine(); - String[] args = saveFormatString.split("\\|", 4); - if (args.length < 3) { - System.out.println("Invalid task save string, task not added"); - continue; - } - - String taskType = args[0].strip(); - boolean marked = args[1].strip() == "1" ? true : false; - String taskName = args[2].strip(); - if (taskName == "") { - System.out.println("Invalid task save string, task not added"); - continue; - } - - LocalDate date = null; - if (args.length >= 4) { - try { - date = LocalDate.parse(args[3].strip()); - } catch (DateTimeParseException e) { - System.out.println("Invalid date in task save string, task not added"); - } - } - - switch (taskType) { - case "T": - tasks.add(new ToDo(taskName, marked)); - taskCount++; - break; - case "D": - if (date != null) { - tasks.add(new Deadline(taskName, marked, date)); - taskCount++; - } - break; - case "E": - if (date != null) { - tasks.add(new Event(taskName, marked, date)); - taskCount++; - } - break; - } - } - scanner.close(); - - System.out.printf("%d tasks loaded from save\n", taskCount); - - dataFileWriter = new FileWriter(dataFile); - } - - /** - * Parses an input string and calls the relevant method (if any) - * - * @param input - the input string to be parsed - */ - private static void parseInput(String input) throws DukeException { - String[] words = input.toLowerCase().split(" ", 2); - String command = words[0]; - String args = ""; - if (words.length > 1) { - args = words[1]; - } - - switch (command) { - case "bye": - isRunning = false; - break; - case "list": - displayTasks(); - break; - case "mark": - markTask(true, args); - break; - case "unmark": - markTask(false, args); - break; - case "todo": - addToDo(args); - break; - case "deadline": - addDeadline(args); - break; - case "event": - addEvent(args); - break; - case "delete": - deleteTask(args); - break; - default: - throw new DukeException("Command not recognised"); - } - } - - /** - * Prints a line of a specified character length - * - * @param length - the character length of the line to print - */ - private static void generateLine(int length) { - System.out.println(String.format("%" + length + "s", "").replace(" ", "-")); - } - - /** - * Adds the given task to the list - * - * @param task - the task to add to the list - */ - private static void addToList(Task task) { - tasks.add(task); - System.out.printf("Task added: %s\n", task); - System.out.printf("You now have %d task(s) in the list\n", tasks.size()); - } - - /** - * Creates a ToDo task from the given argument string - * - * @param args - the argument string to be parsed - */ - private static void addToDo(String args) throws DukeException { - String name = args.strip(); - if (name == "") { - throw new DukeException("Failed to create todo: No task name given"); - } - - addToList(new ToDo(name, false)); - } - - /** - * Creates a Deadline task from the given argument string - * - * @param args - the argument string to be parsed - */ - private static void addDeadline(String args) throws DukeException { - String[] argsArr = args.split(" /by ", 2); - if (argsArr.length < 2) { - throw new DukeException("Failed to create deadline: Invalid number of arguments"); - } - - String name = argsArr[0].strip(); - String dateStr = argsArr[1].strip(); - - if (name == "") { - throw new DukeException("Failed to create deadline: No task name given"); - } - - if (dateStr == "") { - throw new DukeException("Failed to create deadline: No date given"); - } - - LocalDate date; - try { - date = LocalDate.parse(dateStr); - } catch (DateTimeParseException e) { - throw new DukeException("Failed to create deadline: Invalid date given"); - } - - addToList(new Deadline(name, false, date)); - } - - /** - * Creates an Event task from the given argument string - * - * @param args - the argument string to be parsed - */ - private static void addEvent(String args) throws DukeException { - String[] argsArr = args.split(" /at ", 2); - if (argsArr.length < 2) { - throw new DukeException("Failed to create event: Invalid number of arguments"); - } - - String name = argsArr[0].strip(); - String dateStr = argsArr[1].strip(); - - if (name == "") { - throw new DukeException("Failed to create event: No task name given"); - } - - if (dateStr == "") { - throw new DukeException("Failed to create event: No date given"); - } - - LocalDate date; - try { - date = LocalDate.parse(dateStr); - } catch (DateTimeParseException e) { - throw new DukeException("Failed to create event: Invalid date given"); - } - - addToList(new Deadline(name, false, date)); - } - - /** - * Displays all tasks in the list - */ - private static void displayTasks() { - for (int i = 0; i < tasks.size(); i++) { - System.out.printf("%d. %s\n", i + 1, tasks.get(i)); - } - - System.out.printf("You have %d task(s) in your list\n", tasks.size()); - } - - /** - * Marks or unmarks a task based on the given task index string - * - * @param isMarked - if true, attempt to mark the task, otherwise attempt to unmark - * @param indexString - the index of the task to mark - */ - private static void markTask(boolean isMarked, String indexString) throws DukeException { - int index; - try { - index = Integer.parseInt(indexString.strip()); - } catch (NumberFormatException e) { - throw new DukeException("Mark failed, invalid index"); - } - index--; - - if (index < 0 || index >= tasks.size()) { - throw new DukeException("Mark failed, index out of range"); - } - - Task task = tasks.get(index); - if (isMarked) { - task.mark(); - System.out.printf("Task marked as done: %s\n", task); - } else { - task.unmark(); - System.out.printf("Task marked as not done: %s\n", task); - } - } - - /** - * Deletes a task based on the given task index string - * - * @param indexString - the index of the task to delete - * @throws DukeException - */ - private static void deleteTask(String indexString) throws DukeException { - int index; - try { - index = Integer.parseInt(indexString.strip()); - } catch (NumberFormatException e) { - throw new DukeException("Delete failed, invalid index"); - } - index--; - - if (index < 0 || index >= tasks.size()) { - throw new DukeException("Delete failed, index out of range"); - } + Ui.displayExitText(); - Task task = tasks.get(index); - tasks.remove(index); - System.out.printf("Task deleted: %s\n", task); - System.out.printf("You now have %d task(s) in the list\n", tasks.size()); + Storage.saveData(); } } diff --git a/src/main/java/Parser.java b/src/main/java/Parser.java new file mode 100644 index 0000000000..3e3bec0420 --- /dev/null +++ b/src/main/java/Parser.java @@ -0,0 +1,50 @@ +/** + * A class that handles user input. + */ +public class Parser { + /** + * Parses an input string and calls the relevant method (if any). + * + * @param input The input string to be parsed. + */ + public static boolean parseInput(String input) throws DukeException { + String[] words = input.toLowerCase().split(" ", 2); + String command = words[0]; + String args = ""; + if (words.length > 1) { + args = words[1]; + } + + switch (command) { + case "hello": + Ui.displayGreeting(); + break; + case "bye": + return false; + case "list": + Ui.displayTasks(); + break; + case "mark": + case "unmark": + boolean isDone = command.equals("mark"); + Ui.displayMarkTaskMessage(TaskList.markTask(isDone, args), isDone); + break; + case "todo": + Ui.displayAddTaskMessage(TaskList.addToDo(args)); + break; + case "deadline": + Ui.displayAddTaskMessage(TaskList.addDeadline(args)); + break; + case "event": + Ui.displayAddTaskMessage(TaskList.addEvent(args)); + break; + case "delete": + Ui.displayDeleteTaskMessage(TaskList.deleteTask(args)); + break; + default: + throw new DukeException("Command not recognised"); + } + + return true; + } +} diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java new file mode 100644 index 0000000000..2341b12c94 --- /dev/null +++ b/src/main/java/Storage.java @@ -0,0 +1,95 @@ +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.Scanner; + +/** + * A class that handles loading and saving tasks in the file. + */ +public class Storage { + private static final String DATA_FILE_PATH = "data"; + private static final String DATA_FILE_NAME = "duke.txt"; + + private static FileWriter dataFileWriter; + + /** + * Attempts to load saved tasks from the hard disk. + * Creates the save file and directory if missing. + * + * @throws IOException If the save file or file path could not be accessed. + */ + public static void loadData() throws IOException { + Path parentDir = Paths.get(DATA_FILE_PATH); + if (!Files.exists(parentDir)) { + Files.createDirectories(parentDir); + } + + File dataFile = new File(Paths.get(parentDir.toString(), DATA_FILE_NAME).toString()); + if (!dataFile.exists()) { + dataFile.createNewFile(); + } + + Scanner scanner = new Scanner(dataFile); + while (scanner.hasNextLine()) { + String saveFormatString = scanner.nextLine(); + String[] args = saveFormatString.split("\\|", 4); + if (args.length < 3) { + System.out.println("Invalid task save string, task not added"); + continue; + } + + String taskType = args[0].strip(); + boolean marked = args[1].strip().equals("1") ? true : false; + String taskName = args[2].strip(); + if (taskName == "") { + System.out.println("Invalid task save string, task not added"); + continue; + } + + LocalDate date = null; + if (args.length >= 4) { + try { + date = LocalDate.parse(args[3].strip()); + } catch (DateTimeParseException e) { + System.out.println("Invalid date in task save string, task not added"); + } + } + + switch (taskType) { + case "T": + TaskList.addToList(new ToDo(taskName, marked)); + break; + case "D": + if (date != null) { + TaskList.addToList(new Deadline(taskName, marked, date)); + } + break; + case "E": + if (date != null) { + TaskList.addToList(new Event(taskName, marked, date)); + } + break; + } + } + scanner.close(); + + dataFileWriter = new FileWriter(dataFile); + } + + public static void saveData() { + try { + for (String saveString: TaskList.getTasksSaveStrings()) { + dataFileWriter.write(saveString + "\n"); + } + + dataFileWriter.close(); + } catch (IOException e) { + System.out.println("Error: Failed to save tasks"); + } + } +} diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java new file mode 100644 index 0000000000..168e4d56af --- /dev/null +++ b/src/main/java/TaskList.java @@ -0,0 +1,185 @@ +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.List; + +/** + * A class that represents the task list and handles add/delete/update operations. + */ +public class TaskList { + private static List tasks = new ArrayList<>(); + + public static int getTaskCount() { + return tasks.size(); + } + + public static List getTasksStrings() { + ArrayList tasksStrings = new ArrayList<>(); + + for (int i = 0; i < tasks.size(); i++) { + tasksStrings.add(tasks.get(i).toString()); + } + + return tasksStrings; + } + + /** + * Adds the given task to the list. + * + * @param task The task to add to the list. + * @return The task that was added. + */ + public static Task addToList(Task task) { + tasks.add(task); + + return task; + } + + /** + * Creates a ToDo task from the given argument string. + * + * @param args The argument string to be parsed. + * @return The task that was added. + */ + public static Task addToDo(String args) throws DukeException { + String name = args.strip(); + if (name == "") { + throw new DukeException("Failed to create todo: No task name given"); + } + + return addToList(new ToDo(name, false)); + } + + /** + * Creates a Deadline task from the given argument string. + * + * @param args The argument string to be parsed. + * @return The task that was added. + */ + public static Task addDeadline(String args) throws DukeException { + String[] argsArr = args.split(" /by ", 2); + if (argsArr.length < 2) { + throw new DukeException("Failed to create deadline: Invalid number of arguments"); + } + + String name = argsArr[0].strip(); + String dateStr = argsArr[1].strip(); + + if (name == "") { + throw new DukeException("Failed to create deadline: No task name given"); + } + + if (dateStr == "") { + throw new DukeException("Failed to create deadline: No date given"); + } + + LocalDate date; + try { + date = LocalDate.parse(dateStr); + } catch (DateTimeParseException e) { + throw new DukeException("Failed to create deadline: Invalid date given"); + } + + return addToList(new Deadline(name, false, date)); + } + + /** + * Creates an Event task from the given argument string. + * + * @param args The argument string to be parsed. + * @return The task that was added. + */ + public static Task addEvent(String args) throws DukeException { + String[] argsArr = args.split(" /at ", 2); + if (argsArr.length < 2) { + throw new DukeException("Failed to create event: Invalid number of arguments"); + } + + String name = argsArr[0].strip(); + String dateStr = argsArr[1].strip(); + + if (name == "") { + throw new DukeException("Failed to create event: No task name given"); + } + + if (dateStr == "") { + throw new DukeException("Failed to create event: No date given"); + } + + LocalDate date; + try { + date = LocalDate.parse(dateStr); + } catch (DateTimeParseException e) { + throw new DukeException("Failed to create event: Invalid date given"); + } + + return addToList(new Deadline(name, false, date)); + } + + /** + * Marks/unmarks a task based on the given task index string. + * + * @param isDone Whether to mark/unmark the task. + * @param indexString The index of the task to mark/unmark. + * @return The task that was marked/unmarked. + * @throws DukeException If indexString is not an integer or out of range. + */ + public static Task markTask(boolean isDone, String indexString) throws DukeException { + int index; + try { + index = Integer.parseInt(indexString.strip()); + } catch (NumberFormatException e) { + throw new DukeException("Mark failed, invalid index"); + } + index--; + + if (index < 0 || index >= tasks.size()) { + throw new DukeException("Mark failed, index out of range"); + } + + Task task = tasks.get(index); + if (isDone) { + task.mark(); + } else { + task.unmark(); + } + + return task; + } + + /** + * Deletes a task based on the given task index string. + * + * @param indexString The index of the task to delete. + * @return The task that was deleted. + * @throws DukeException If indexString is not an integer or out of range. + */ + public static Task deleteTask(String indexString) throws DukeException { + int index; + try { + index = Integer.parseInt(indexString.strip()); + } catch (NumberFormatException e) { + throw new DukeException("Delete failed, invalid index"); + } + index--; + + if (index < 0 || index >= tasks.size()) { + throw new DukeException("Delete failed, index out of range"); + } + + Task task = tasks.get(index); + tasks.remove(index); + + return task; + } + + public static List getTasksSaveStrings() { + ArrayList saveStrings = new ArrayList<>(); + + for (Task task: tasks) { + saveStrings.add(task.toSaveFormatString()); + } + + return saveStrings; + } +} diff --git a/src/main/java/Ui.java b/src/main/java/Ui.java new file mode 100644 index 0000000000..0ebd1a017d --- /dev/null +++ b/src/main/java/Ui.java @@ -0,0 +1,65 @@ +import java.util.List; + +/** + * A class that handles displaying of information to the user. + */ +public class Ui { + private static final String LOGO = " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n"; + private static final String BOT_NAME = "Duke"; + private static final int LINE_LENGTH = 80; + + public static void displayStartUpText() { + System.out.println("Hello from\n" + LOGO); + displayGreeting(); + } + + public static void displayGreeting() { + generateLine(); + displayTasks(); + generateLine(); + + System.out.printf("Hello, I'm %s\n", BOT_NAME); + System.out.println("What can I do for you?"); + } + + /** + * Prints a line of length LINE_LENGTH. + */ + public static void generateLine() { + System.out.println(String.format("%" + LINE_LENGTH + "s", "").replace(" ", "-")); + } + + public static void displayExitText() { + System.out.println("Goodbye."); + } + + public static void displayAddTaskMessage(Task task) { + System.out.printf("Task added: %s\n", task); + System.out.printf("You now have %d task(s) in your list\n", TaskList.getTaskCount()); + } + + public static void displayDeleteTaskMessage(Task task) { + System.out.printf("Task deleted: %s\n", task); + System.out.printf("You now have %d task(s) in your list\n", TaskList.getTaskCount()); + } + + public static void displayMarkTaskMessage(Task task, boolean isDone) { + System.out.printf("Task marked as %s: %s\n", isDone ? "complete" : "incomplete", task); + } + + /** + * Displays all tasks in the list. + */ + public static void displayTasks() { + List tasksStrings = TaskList.getTasksStrings(); + for (int i = 0; i < tasksStrings.size(); i++) { + System.out.printf("%d. %s\n", i + 1, tasksStrings.get(i)); + } + + System.out.printf("You have %d task(s) in your list\n", TaskList.getTaskCount()); + } +} From 07b4d5f0ed960e1fd188b1aa06daf6d8ad56e5c2 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Wed, 31 Aug 2022 15:38:12 +0800 Subject: [PATCH 14/35] Implement A-Packages - Packaged all classes into one package Duke --- build.gradle | 2 +- src/main/java/{ => Duke}/Deadline.java | 2 ++ src/main/java/{ => Duke}/Duke.java | 2 ++ src/main/java/{ => Duke}/DukeException.java | 2 ++ src/main/java/{ => Duke}/Event.java | 2 ++ src/main/java/{ => Duke}/Parser.java | 2 ++ src/main/java/{ => Duke}/Storage.java | 2 ++ src/main/java/{ => Duke}/Task.java | 2 ++ src/main/java/{ => Duke}/TaskList.java | 2 ++ src/main/java/{ => Duke}/ToDo.java | 2 ++ src/main/java/{ => Duke}/Ui.java | 2 ++ 11 files changed, 21 insertions(+), 1 deletion(-) rename src/main/java/{ => Duke}/Deadline.java (97%) rename src/main/java/{ => Duke}/Duke.java (98%) rename src/main/java/{ => Duke}/DukeException.java (92%) rename src/main/java/{ => Duke}/Event.java (97%) rename src/main/java/{ => Duke}/Parser.java (98%) rename src/main/java/{ => Duke}/Storage.java (99%) rename src/main/java/{ => Duke}/Task.java (97%) rename src/main/java/{ => Duke}/TaskList.java (99%) rename src/main/java/{ => Duke}/ToDo.java (96%) rename src/main/java/{ => Duke}/Ui.java (99%) diff --git a/build.gradle b/build.gradle index 69bf40c771..0da478eaee 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ test { } application { - mainClassName = "Duke" + mainClassName = "Duke.Duke" } shadowJar { diff --git a/src/main/java/Deadline.java b/src/main/java/Duke/Deadline.java similarity index 97% rename from src/main/java/Deadline.java rename to src/main/java/Duke/Deadline.java index 4c99677bc8..f8d5c94b8d 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Duke/Deadline.java @@ -1,3 +1,5 @@ +package Duke; + import java.time.LocalDate; /** diff --git a/src/main/java/Duke.java b/src/main/java/Duke/Duke.java similarity index 98% rename from src/main/java/Duke.java rename to src/main/java/Duke/Duke.java index 4e43c6c266..4c52a6bff9 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke/Duke.java @@ -1,3 +1,5 @@ +package Duke; + import java.io.IOException; import java.util.Scanner; diff --git a/src/main/java/DukeException.java b/src/main/java/Duke/DukeException.java similarity index 92% rename from src/main/java/DukeException.java rename to src/main/java/Duke/DukeException.java index 21a4f8c77c..633eceb12a 100644 --- a/src/main/java/DukeException.java +++ b/src/main/java/Duke/DukeException.java @@ -1,3 +1,5 @@ +package Duke; + /** * A class used to represent Exceptions specific to Duke */ diff --git a/src/main/java/Event.java b/src/main/java/Duke/Event.java similarity index 97% rename from src/main/java/Event.java rename to src/main/java/Duke/Event.java index 32e2e6a731..c549a77ab5 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Duke/Event.java @@ -1,3 +1,5 @@ +package Duke; + import java.time.LocalDate; /** diff --git a/src/main/java/Parser.java b/src/main/java/Duke/Parser.java similarity index 98% rename from src/main/java/Parser.java rename to src/main/java/Duke/Parser.java index 3e3bec0420..1647feda4b 100644 --- a/src/main/java/Parser.java +++ b/src/main/java/Duke/Parser.java @@ -1,3 +1,5 @@ +package Duke; + /** * A class that handles user input. */ diff --git a/src/main/java/Storage.java b/src/main/java/Duke/Storage.java similarity index 99% rename from src/main/java/Storage.java rename to src/main/java/Duke/Storage.java index 2341b12c94..def7c691d8 100644 --- a/src/main/java/Storage.java +++ b/src/main/java/Duke/Storage.java @@ -1,3 +1,5 @@ +package Duke; + import java.io.File; import java.io.FileWriter; import java.io.IOException; diff --git a/src/main/java/Task.java b/src/main/java/Duke/Task.java similarity index 97% rename from src/main/java/Task.java rename to src/main/java/Duke/Task.java index d2d0abf176..84e17b694b 100644 --- a/src/main/java/Task.java +++ b/src/main/java/Duke/Task.java @@ -1,3 +1,5 @@ +package Duke; + /** * A class used to represent a task. A task has a name and completion status. */ diff --git a/src/main/java/TaskList.java b/src/main/java/Duke/TaskList.java similarity index 99% rename from src/main/java/TaskList.java rename to src/main/java/Duke/TaskList.java index 168e4d56af..7496b3581e 100644 --- a/src/main/java/TaskList.java +++ b/src/main/java/Duke/TaskList.java @@ -1,3 +1,5 @@ +package Duke; + import java.time.LocalDate; import java.time.format.DateTimeParseException; import java.util.ArrayList; diff --git a/src/main/java/ToDo.java b/src/main/java/Duke/ToDo.java similarity index 96% rename from src/main/java/ToDo.java rename to src/main/java/Duke/ToDo.java index 6455d19a2d..e4e819ac48 100644 --- a/src/main/java/ToDo.java +++ b/src/main/java/Duke/ToDo.java @@ -1,3 +1,5 @@ +package Duke; + /** * Class used to represent a ToDo type task that has no date. */ diff --git a/src/main/java/Ui.java b/src/main/java/Duke/Ui.java similarity index 99% rename from src/main/java/Ui.java rename to src/main/java/Duke/Ui.java index 0ebd1a017d..fc8cd41f77 100644 --- a/src/main/java/Ui.java +++ b/src/main/java/Duke/Ui.java @@ -1,3 +1,5 @@ +package Duke; + import java.util.List; /** From 0689b3f25f1db5aee6bcd76aa48a7796db5e6a2e Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Wed, 31 Aug 2022 16:51:38 +0800 Subject: [PATCH 15/35] Implement A-JUnit - Added JUnit tests for adding tasks, loading and saving tasks, and parsing input commands --- build.gradle | 2 +- src/main/java/{Duke => duke}/Deadline.java | 2 +- src/main/java/{Duke => duke}/Duke.java | 10 ++- .../java/{Duke => duke}/DukeException.java | 2 +- src/main/java/{Duke => duke}/Event.java | 2 +- src/main/java/{Duke => duke}/Parser.java | 2 +- src/main/java/{Duke => duke}/Storage.java | 28 ++++++-- src/main/java/{Duke => duke}/Task.java | 2 +- src/main/java/{Duke => duke}/TaskList.java | 8 ++- src/main/java/{Duke => duke}/ToDo.java | 2 +- src/main/java/{Duke => duke}/Ui.java | 4 +- src/test/java/duke/DukeTest.java | 67 +++++++++++++++++++ 12 files changed, 112 insertions(+), 19 deletions(-) rename src/main/java/{Duke => duke}/Deadline.java (97%) rename src/main/java/{Duke => duke}/Duke.java (82%) rename src/main/java/{Duke => duke}/DukeException.java (93%) rename src/main/java/{Duke => duke}/Event.java (97%) rename src/main/java/{Duke => duke}/Parser.java (99%) rename src/main/java/{Duke => duke}/Storage.java (80%) rename src/main/java/{Duke => duke}/Task.java (97%) rename src/main/java/{Duke => duke}/TaskList.java (97%) rename src/main/java/{Duke => duke}/ToDo.java (96%) rename src/main/java/{Duke => duke}/Ui.java (97%) create mode 100644 src/test/java/duke/DukeTest.java diff --git a/build.gradle b/build.gradle index 0da478eaee..0bdc254568 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ test { } application { - mainClassName = "Duke.Duke" + mainClassName = "duke.Duke" } shadowJar { diff --git a/src/main/java/Duke/Deadline.java b/src/main/java/duke/Deadline.java similarity index 97% rename from src/main/java/Duke/Deadline.java rename to src/main/java/duke/Deadline.java index f8d5c94b8d..6d62bfba67 100644 --- a/src/main/java/Duke/Deadline.java +++ b/src/main/java/duke/Deadline.java @@ -1,4 +1,4 @@ -package Duke; +package duke; import java.time.LocalDate; diff --git a/src/main/java/Duke/Duke.java b/src/main/java/duke/Duke.java similarity index 82% rename from src/main/java/Duke/Duke.java rename to src/main/java/duke/Duke.java index 4c52a6bff9..c733352154 100644 --- a/src/main/java/Duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -1,4 +1,4 @@ -package Duke; +package duke; import java.io.IOException; import java.util.Scanner; @@ -13,7 +13,6 @@ public static void main(String[] args) { try { Storage.loadData(); } catch (IOException e) { - e.printStackTrace(); System.out.println("Error: Failed to access data"); return; } @@ -37,6 +36,11 @@ public static void main(String[] args) { Ui.displayExitText(); - Storage.saveData(); + try { + Storage.saveData(); + } catch (IOException e) { + System.out.println("Error: Failed to access data"); + return; + } } } diff --git a/src/main/java/Duke/DukeException.java b/src/main/java/duke/DukeException.java similarity index 93% rename from src/main/java/Duke/DukeException.java rename to src/main/java/duke/DukeException.java index 633eceb12a..1f394b45e5 100644 --- a/src/main/java/Duke/DukeException.java +++ b/src/main/java/duke/DukeException.java @@ -1,4 +1,4 @@ -package Duke; +package duke; /** * A class used to represent Exceptions specific to Duke diff --git a/src/main/java/Duke/Event.java b/src/main/java/duke/Event.java similarity index 97% rename from src/main/java/Duke/Event.java rename to src/main/java/duke/Event.java index c549a77ab5..9f93faffd0 100644 --- a/src/main/java/Duke/Event.java +++ b/src/main/java/duke/Event.java @@ -1,4 +1,4 @@ -package Duke; +package duke; import java.time.LocalDate; diff --git a/src/main/java/Duke/Parser.java b/src/main/java/duke/Parser.java similarity index 99% rename from src/main/java/Duke/Parser.java rename to src/main/java/duke/Parser.java index 1647feda4b..524f987b45 100644 --- a/src/main/java/Duke/Parser.java +++ b/src/main/java/duke/Parser.java @@ -1,4 +1,4 @@ -package Duke; +package duke; /** * A class that handles user input. diff --git a/src/main/java/Duke/Storage.java b/src/main/java/duke/Storage.java similarity index 80% rename from src/main/java/Duke/Storage.java rename to src/main/java/duke/Storage.java index def7c691d8..4c40876385 100644 --- a/src/main/java/Duke/Storage.java +++ b/src/main/java/duke/Storage.java @@ -1,4 +1,4 @@ -package Duke; +package duke; import java.io.File; import java.io.FileWriter; @@ -17,8 +17,6 @@ public class Storage { private static final String DATA_FILE_PATH = "data"; private static final String DATA_FILE_NAME = "duke.txt"; - private static FileWriter dataFileWriter; - /** * Attempts to load saved tasks from the hard disk. * Creates the save file and directory if missing. @@ -36,6 +34,8 @@ public static void loadData() throws IOException { dataFile.createNewFile(); } + TaskList.clearTaskList(); + Scanner scanner = new Scanner(dataFile); while (scanner.hasNextLine()) { String saveFormatString = scanner.nextLine(); @@ -80,10 +80,28 @@ public static void loadData() throws IOException { } scanner.close(); - dataFileWriter = new FileWriter(dataFile); + } - public static void saveData() { + /** + * Attempts to save tasks from TaskList to the hard disk. + * Creates the save file and directory if missing. + * + * @throws IOException If the save file or file path could not be accessed. + */ + public static void saveData() throws IOException{ + Path parentDir = Paths.get(DATA_FILE_PATH); + if (!Files.exists(parentDir)) { + Files.createDirectories(parentDir); + } + + File dataFile = new File(Paths.get(parentDir.toString(), DATA_FILE_NAME).toString()); + if (!dataFile.exists()) { + dataFile.createNewFile(); + } + + FileWriter dataFileWriter = new FileWriter(dataFile); + try { for (String saveString: TaskList.getTasksSaveStrings()) { dataFileWriter.write(saveString + "\n"); diff --git a/src/main/java/Duke/Task.java b/src/main/java/duke/Task.java similarity index 97% rename from src/main/java/Duke/Task.java rename to src/main/java/duke/Task.java index 84e17b694b..1c31bb341e 100644 --- a/src/main/java/Duke/Task.java +++ b/src/main/java/duke/Task.java @@ -1,4 +1,4 @@ -package Duke; +package duke; /** * A class used to represent a task. A task has a name and completion status. diff --git a/src/main/java/Duke/TaskList.java b/src/main/java/duke/TaskList.java similarity index 97% rename from src/main/java/Duke/TaskList.java rename to src/main/java/duke/TaskList.java index 7496b3581e..0c35932ba4 100644 --- a/src/main/java/Duke/TaskList.java +++ b/src/main/java/duke/TaskList.java @@ -1,4 +1,4 @@ -package Duke; +package duke; import java.time.LocalDate; import java.time.format.DateTimeParseException; @@ -115,7 +115,7 @@ public static Task addEvent(String args) throws DukeException { throw new DukeException("Failed to create event: Invalid date given"); } - return addToList(new Deadline(name, false, date)); + return addToList(new Event(name, false, date)); } /** @@ -184,4 +184,8 @@ public static List getTasksSaveStrings() { return saveStrings; } + + public static void clearTaskList() { + tasks.clear(); + } } diff --git a/src/main/java/Duke/ToDo.java b/src/main/java/duke/ToDo.java similarity index 96% rename from src/main/java/Duke/ToDo.java rename to src/main/java/duke/ToDo.java index e4e819ac48..e7498fa043 100644 --- a/src/main/java/Duke/ToDo.java +++ b/src/main/java/duke/ToDo.java @@ -1,4 +1,4 @@ -package Duke; +package duke; /** * Class used to represent a ToDo type task that has no date. diff --git a/src/main/java/Duke/Ui.java b/src/main/java/duke/Ui.java similarity index 97% rename from src/main/java/Duke/Ui.java rename to src/main/java/duke/Ui.java index fc8cd41f77..66ae7a00f9 100644 --- a/src/main/java/Duke/Ui.java +++ b/src/main/java/duke/Ui.java @@ -1,4 +1,4 @@ -package Duke; +package duke; import java.util.List; @@ -11,7 +11,7 @@ public class Ui { + "| | | | | | | |/ / _ \\\n" + "| |_| | |_| | < __/\n" + "|____/ \\__,_|_|\\_\\___|\n"; - private static final String BOT_NAME = "Duke"; + private static final String BOT_NAME = "duke"; private static final int LINE_LENGTH = 80; public static void displayStartUpText() { diff --git a/src/test/java/duke/DukeTest.java b/src/test/java/duke/DukeTest.java new file mode 100644 index 0000000000..4ae1f91cab --- /dev/null +++ b/src/test/java/duke/DukeTest.java @@ -0,0 +1,67 @@ +package duke; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.PrintStream; +import java.util.List; + +public class DukeTest { + @Test + public void addInvalidTaskTest() { + DukeException noTaskNameException = Assertions.assertThrows(DukeException.class, () -> { + TaskList.addToDo(""); + }); + + Assertions.assertEquals("Failed to create todo: No task name given", noTaskNameException.getMessage()); + + DukeException invalidDateException = Assertions.assertThrows(DukeException.class, () -> { + TaskList.addDeadline("test /by abc"); + }); + + Assertions.assertEquals("Failed to create deadline: Invalid date given", invalidDateException.getMessage()); + + DukeException invalidArgumentsException = Assertions.assertThrows(DukeException.class, () -> { + TaskList.addEvent("123456789/at bcdefg"); + }); + + Assertions.assertEquals("Failed to create event: Invalid number of arguments", invalidArgumentsException.getMessage()); + } + + @Test + public void loadAndSaveTest() { + Assertions.assertDoesNotThrow(Storage::loadData); + List tasksStringsAfterFirstLoad = TaskList.getTasksStrings(); + + Assertions.assertDoesNotThrow(Storage::loadData); + List tasksStringsAfterSecondLoad = TaskList.getTasksStrings(); + + Assertions.assertEquals(tasksStringsAfterFirstLoad, tasksStringsAfterSecondLoad); + + Assertions.assertDoesNotThrow(Storage::saveData); + Assertions.assertDoesNotThrow(Storage::saveData); + + Assertions.assertDoesNotThrow(Storage::loadData); + List tasksStringsAfterThirdLoad = TaskList.getTasksStrings(); + + Assertions.assertEquals(tasksStringsAfterSecondLoad, tasksStringsAfterThirdLoad); + } + + @Test + public void parseInputTest() { + DukeException invalidCommandException = Assertions.assertThrows(DukeException.class, () -> { + Parser.parseInput("asdfg"); + }); + + Assertions.assertEquals("Command not recognised", invalidCommandException.getMessage()); + + DukeException blankCommandException = Assertions.assertThrows(DukeException.class, () -> { + Parser.parseInput(""); + }); + + Assertions.assertEquals("Command not recognised", blankCommandException.getMessage()); + + Assertions.assertDoesNotThrow(() -> Parser.parseInput("deadline test1/by /by 2020-01-01")); + Assertions.assertDoesNotThrow(() -> Parser.parseInput("event test2/at asdf /at 2020-01-01")); + } +} From c123f90581488dbfbe6c203132e9af3fe3155473 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Wed, 31 Aug 2022 17:01:28 +0800 Subject: [PATCH 16/35] Implement A-Jar --- src/main/java/duke/Ui.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java index 66ae7a00f9..235f2a7acd 100644 --- a/src/main/java/duke/Ui.java +++ b/src/main/java/duke/Ui.java @@ -16,14 +16,15 @@ public class Ui { public static void displayStartUpText() { System.out.println("Hello from\n" + LOGO); - displayGreeting(); - } - public static void displayGreeting() { generateLine(); displayTasks(); generateLine(); + displayGreeting(); + } + + public static void displayGreeting() { System.out.printf("Hello, I'm %s\n", BOT_NAME); System.out.println("What can I do for you?"); } From e55211e7fbd77a619631c387d60d5acb9594c778 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Wed, 31 Aug 2022 17:29:47 +0800 Subject: [PATCH 17/35] Implement A-JavaDoc - Added JavaDocs to more methods --- src/main/java/duke/Task.java | 17 +++++++++++++++++ src/main/java/duke/TaskList.java | 20 ++++++++++++++++++++ src/main/java/duke/Ui.java | 27 ++++++++++++++++++++++++++- src/test/java/duke/DukeTest.java | 13 ++++++++++++- 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/main/java/duke/Task.java b/src/main/java/duke/Task.java index 1c31bb341e..b1e340ddcf 100644 --- a/src/main/java/duke/Task.java +++ b/src/main/java/duke/Task.java @@ -14,16 +14,33 @@ public Task(String taskName, boolean isDone) { this.isDone = isDone; } + /** + * Marks the task as done. + */ public void mark() { this.isDone = true; } + /** + * Marks the task as not done. + */ public void unmark() { this.isDone = false; } + /** + * Generates a string that can be used to reconstruct the task. + * Intended to be used by Storage to save and load tasks to the hard disk. + * + * @return A string that can be used to reconstruct the task. + */ public abstract String toSaveFormatString(); + /** + * Generates a string representation of the task meant for display. + * + * @return A string representation of the task meant for display. + */ @Override public String toString() { return String.format("[%s] %s", isDone ? MARK_CHARACTER : " ", this.taskName); diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java index 0c35932ba4..4f71eac4ce 100644 --- a/src/main/java/duke/TaskList.java +++ b/src/main/java/duke/TaskList.java @@ -11,10 +11,21 @@ public class TaskList { private static List tasks = new ArrayList<>(); + /** + * Gets the total number of tasks in the task list. + * + * @return The total number of tasks in the task list. + */ public static int getTaskCount() { return tasks.size(); } + /** + * Generates a list of string representations of tasks meant for display. + * Intended to be used by Ui for displaying all tasks. + * + * @return A list of strings that represent task information to be displayed. + */ public static List getTasksStrings() { ArrayList tasksStrings = new ArrayList<>(); @@ -175,6 +186,12 @@ public static Task deleteTask(String indexString) throws DukeException { return task; } + /** + * Converts all tasks to their save string format and returns it as a list. + * Intended to be used by Storage to save tasks. + * + * @return A list of strings which can be used to reconstruct tasks. + */ public static List getTasksSaveStrings() { ArrayList saveStrings = new ArrayList<>(); @@ -185,6 +202,9 @@ public static List getTasksSaveStrings() { return saveStrings; } + /** + * Removes all tasks from the task list. + */ public static void clearTaskList() { tasks.clear(); } diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java index 235f2a7acd..c06e602092 100644 --- a/src/main/java/duke/Ui.java +++ b/src/main/java/duke/Ui.java @@ -3,7 +3,7 @@ import java.util.List; /** - * A class that handles displaying of information to the user. + * A class that handles the displaying of information to the user. */ public class Ui { private static final String LOGO = " ____ _ \n" @@ -14,6 +14,9 @@ public class Ui { private static final String BOT_NAME = "duke"; private static final int LINE_LENGTH = 80; + /** + * Displays the text to be shown on starting up Duke. + */ public static void displayStartUpText() { System.out.println("Hello from\n" + LOGO); @@ -24,6 +27,10 @@ public static void displayStartUpText() { displayGreeting(); } + /** + * Displays the text to be shown when the user greets Duke with hello. + * This is also used in displayStartUpText. + */ public static void displayGreeting() { System.out.printf("Hello, I'm %s\n", BOT_NAME); System.out.println("What can I do for you?"); @@ -36,20 +43,38 @@ public static void generateLine() { System.out.println(String.format("%" + LINE_LENGTH + "s", "").replace(" ", "-")); } + /** + * Displays the text to be shown when the user exits the program. + */ public static void displayExitText() { System.out.println("Goodbye."); } + /** + * Displays the text to be shown when the user adds a task to the list. + * + * @param task The task that was added to the list. + */ public static void displayAddTaskMessage(Task task) { System.out.printf("Task added: %s\n", task); System.out.printf("You now have %d task(s) in your list\n", TaskList.getTaskCount()); } + /** + * Displays the text to be shown when the user removes a task to the list. + * + * @param task The task that was removed from the list. + */ public static void displayDeleteTaskMessage(Task task) { System.out.printf("Task deleted: %s\n", task); System.out.printf("You now have %d task(s) in your list\n", TaskList.getTaskCount()); } + /** + * Displays the text to be shown when the user marks a task on the list. + * + * @param task The task that was marked on the list. + */ public static void displayMarkTaskMessage(Task task, boolean isDone) { System.out.printf("Task marked as %s: %s\n", isDone ? "complete" : "incomplete", task); } diff --git a/src/test/java/duke/DukeTest.java b/src/test/java/duke/DukeTest.java index 4ae1f91cab..9c7806503f 100644 --- a/src/test/java/duke/DukeTest.java +++ b/src/test/java/duke/DukeTest.java @@ -3,10 +3,15 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.io.PrintStream; import java.util.List; +/** + * A class for testing Duke's functionalities. + */ public class DukeTest { + /** + * Tests whether TaskList handles invalid task creation correctly. + */ @Test public void addInvalidTaskTest() { DukeException noTaskNameException = Assertions.assertThrows(DukeException.class, () -> { @@ -28,6 +33,9 @@ public void addInvalidTaskTest() { Assertions.assertEquals("Failed to create event: Invalid number of arguments", invalidArgumentsException.getMessage()); } + /** + * Tests whether Storage loads and saves tasks correctly (no duplicate/lost data between consecutive saves/loads). + */ @Test public void loadAndSaveTest() { Assertions.assertDoesNotThrow(Storage::loadData); @@ -47,6 +55,9 @@ public void loadAndSaveTest() { Assertions.assertEquals(tasksStringsAfterSecondLoad, tasksStringsAfterThirdLoad); } + /** + * Tests whether Parser handles invalid input, and uncommon but valid input correctly. + */ @Test public void parseInputTest() { DukeException invalidCommandException = Assertions.assertThrows(DukeException.class, () -> { From 5c007e4cb8bc155c9b476b1a443ea878015ceb2c Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Wed, 31 Aug 2022 17:37:26 +0800 Subject: [PATCH 18/35] Implement A-CodingStandard - Adjust code to follow coding standard where not done previously --- src/main/java/duke/Duke.java | 1 + src/main/java/duke/Storage.java | 3 +++ src/main/java/duke/TaskList.java | 1 + src/main/java/duke/Ui.java | 11 ++++++----- src/test/java/duke/DukeTest.java | 9 ++++++--- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java index c733352154..3e738281a6 100644 --- a/src/main/java/duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -1,6 +1,7 @@ package duke; import java.io.IOException; + import java.util.Scanner; /** diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java index 4c40876385..b8dbe04182 100644 --- a/src/main/java/duke/Storage.java +++ b/src/main/java/duke/Storage.java @@ -3,11 +3,14 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; + import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; + import java.time.LocalDate; import java.time.format.DateTimeParseException; + import java.util.Scanner; /** diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java index 0c35932ba4..c6ed6aeea5 100644 --- a/src/main/java/duke/TaskList.java +++ b/src/main/java/duke/TaskList.java @@ -2,6 +2,7 @@ import java.time.LocalDate; import java.time.format.DateTimeParseException; + import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java index 235f2a7acd..b07ed601f5 100644 --- a/src/main/java/duke/Ui.java +++ b/src/main/java/duke/Ui.java @@ -6,11 +6,12 @@ * A class that handles displaying of information to the user. */ public class Ui { - private static final String LOGO = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; + private static final String LOGO = + " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n"; private static final String BOT_NAME = "duke"; private static final int LINE_LENGTH = 80; diff --git a/src/test/java/duke/DukeTest.java b/src/test/java/duke/DukeTest.java index 4ae1f91cab..2fb33e77ef 100644 --- a/src/test/java/duke/DukeTest.java +++ b/src/test/java/duke/DukeTest.java @@ -13,19 +13,22 @@ public void addInvalidTaskTest() { TaskList.addToDo(""); }); - Assertions.assertEquals("Failed to create todo: No task name given", noTaskNameException.getMessage()); + Assertions.assertEquals( + "Failed to create todo: No task name given", noTaskNameException.getMessage()); DukeException invalidDateException = Assertions.assertThrows(DukeException.class, () -> { TaskList.addDeadline("test /by abc"); }); - Assertions.assertEquals("Failed to create deadline: Invalid date given", invalidDateException.getMessage()); + Assertions.assertEquals( + "Failed to create deadline: Invalid date given", invalidDateException.getMessage()); DukeException invalidArgumentsException = Assertions.assertThrows(DukeException.class, () -> { TaskList.addEvent("123456789/at bcdefg"); }); - Assertions.assertEquals("Failed to create event: Invalid number of arguments", invalidArgumentsException.getMessage()); + Assertions.assertEquals( + "Failed to create event: Invalid number of arguments", invalidArgumentsException.getMessage()); } @Test From 508ff58ec0abe0e1f58935eee99ad7096d242123 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Wed, 31 Aug 2022 18:13:29 +0800 Subject: [PATCH 19/35] Implement Level-9 - Add getter for taskName to Task class - Add search task by taskName functionality to TaskList class - Modify Parser class to accept search command - Add display text function for search command to Ui class --- src/main/java/duke/Parser.java | 3 +++ src/main/java/duke/Task.java | 4 ++++ src/main/java/duke/TaskList.java | 13 +++++++++++++ src/main/java/duke/Ui.java | 11 +++++++++++ 4 files changed, 31 insertions(+) diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java index 524f987b45..03d5d11104 100644 --- a/src/main/java/duke/Parser.java +++ b/src/main/java/duke/Parser.java @@ -43,6 +43,9 @@ public static boolean parseInput(String input) throws DukeException { case "delete": Ui.displayDeleteTaskMessage(TaskList.deleteTask(args)); break; + case "find": + Ui.displaySearchTasksMessage(TaskList.searchTasks(args), args); + break; default: throw new DukeException("Command not recognised"); } diff --git a/src/main/java/duke/Task.java b/src/main/java/duke/Task.java index 1c31bb341e..97852a12e5 100644 --- a/src/main/java/duke/Task.java +++ b/src/main/java/duke/Task.java @@ -14,6 +14,10 @@ public Task(String taskName, boolean isDone) { this.isDone = isDone; } + public String getTaskName() { + return this.taskName; + } + public void mark() { this.isDone = true; } diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java index 0c35932ba4..b98ce77a04 100644 --- a/src/main/java/duke/TaskList.java +++ b/src/main/java/duke/TaskList.java @@ -175,6 +175,19 @@ public static Task deleteTask(String indexString) throws DukeException { return task; } + public static List searchTasks(String searchString) { + searchString = searchString.strip(); + ArrayList foundTasks = new ArrayList<>(); + + for (Task task: tasks) { + if (task.getTaskName().contains(searchString)) { + foundTasks.add(task); + } + } + + return foundTasks; + } + public static List getTasksSaveStrings() { ArrayList saveStrings = new ArrayList<>(); diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java index 235f2a7acd..775f4bb868 100644 --- a/src/main/java/duke/Ui.java +++ b/src/main/java/duke/Ui.java @@ -54,6 +54,17 @@ public static void displayMarkTaskMessage(Task task, boolean isDone) { System.out.printf("Task marked as %s: %s\n", isDone ? "complete" : "incomplete", task); } + public static void displaySearchTasksMessage(List foundTasks, String searchString) { + if (foundTasks.size() == 0) { + System.out.printf("Search results for \"%s\": No tasks found\n", searchString); + } else { + System.out.printf("Search results for \"%s\": %d task(s) found\n", searchString, foundTasks.size()); + for (int i = 0; i < foundTasks.size(); i++) { + System.out.printf("%d. %s\n", i + 1, foundTasks.get(i)); + } + } + } + /** * Displays all tasks in the list. */ From 19d17faf229e0826937aec93728af44df3bc1ba8 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Wed, 31 Aug 2022 18:23:00 +0800 Subject: [PATCH 20/35] Add Javadocs headers to Level-9 methods --- src/main/java/duke/TaskList.java | 7 +++++++ src/main/java/duke/Ui.java | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java index f0a680a920..70ae5e772c 100644 --- a/src/main/java/duke/TaskList.java +++ b/src/main/java/duke/TaskList.java @@ -187,6 +187,13 @@ public static Task deleteTask(String indexString) throws DukeException { return task; } + /** + * Searches for tasks based on the given search string, and returns a list of tasks found. + * If the taskName of a task contains the search string, it is added to the search results. + * + * @param searchString The string to be used to search for tasks. + * @return The list of tasks found in the search. + */ public static List searchTasks(String searchString) { searchString = searchString.strip(); ArrayList foundTasks = new ArrayList<>(); diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java index 141751595f..da3e31595a 100644 --- a/src/main/java/duke/Ui.java +++ b/src/main/java/duke/Ui.java @@ -80,6 +80,12 @@ public static void displayMarkTaskMessage(Task task, boolean isDone) { System.out.printf("Task marked as %s: %s\n", isDone ? "complete" : "incomplete", task); } + /** + * Displays the text to be shown when the user searches for tasks. + * + * @param foundTasks The tasks that were found in the search. + * @param searchString The string that was used to perform the search. + */ public static void displaySearchTasksMessage(List foundTasks, String searchString) { if (foundTasks.size() == 0) { System.out.printf("Search results for \"%s\": No tasks found\n", searchString); From 5b097c09bd7cf6db4deb9d72538fbb14baef1df9 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Wed, 31 Aug 2022 18:54:46 +0800 Subject: [PATCH 21/35] Implement A-Checkstyle - Add checkstyle plugin to gradle - Use chcekstyle to find coding style violations - Resolve coding style violations --- build.gradle | 5 + config/checkstyle/checkstyle.xml | 434 +++++++++++++++++++++++++++++ config/checkstyle/suppressions.xml | 10 + src/main/java/duke/Deadline.java | 9 +- src/main/java/duke/Duke.java | 6 +- src/main/java/duke/Event.java | 9 +- src/main/java/duke/Storage.java | 6 +- src/main/java/duke/Task.java | 11 + src/main/java/duke/TaskList.java | 1 - src/test/java/duke/DukeTest.java | 4 +- 10 files changed, 485 insertions(+), 10 deletions(-) create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 config/checkstyle/suppressions.xml diff --git a/build.gradle b/build.gradle index 0bdc254568..5cc08e2a9c 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,11 @@ plugins { id 'java' id 'application' id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'checkstyle' +} + +checkstyle { + toolVersion = '10.2' } repositories { diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..62281d3553 --- /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..dcaa1af3c3 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/java/duke/Deadline.java b/src/main/java/duke/Deadline.java index 6d62bfba67..8701826fe8 100644 --- a/src/main/java/duke/Deadline.java +++ b/src/main/java/duke/Deadline.java @@ -3,11 +3,18 @@ import java.time.LocalDate; /** - * Class used to represent a Deadline type task that has a due date. + * Class used to represent a task that has a due date. */ public class Deadline extends Task { protected LocalDate dueDate; + /** + * The constructor for a Deadline task. + * + * @param taskName A string that is the name of the task. + * @param isDone A boolean that represents whether this task is complete. + * @param dueDate A LocalDate that contains information about when this task is due. + */ public Deadline(String taskName, boolean isDone, LocalDate dueDate) { super(taskName, isDone); this.dueDate = dueDate; diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java index 3e738281a6..f04af45ede 100644 --- a/src/main/java/duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -1,7 +1,6 @@ package duke; import java.io.IOException; - import java.util.Scanner; /** @@ -10,6 +9,11 @@ public class Duke { private static boolean isRunning; + /** + * The method used to run the Duke program. + * + * @param args The command line arguments. + */ public static void main(String[] args) { try { Storage.loadData(); diff --git a/src/main/java/duke/Event.java b/src/main/java/duke/Event.java index 9f93faffd0..b064530a9d 100644 --- a/src/main/java/duke/Event.java +++ b/src/main/java/duke/Event.java @@ -3,11 +3,18 @@ import java.time.LocalDate; /** - * Class used to represent an Event type task that has a start and end datetime. + * Class used to represent a task that has a start date. */ public class Event extends Task { protected LocalDate eventDate; + /** + * The constructor for an Event. + * + * @param taskName A string that is the name of the task. + * @param isDone A boolean that represents whether this task is complete. + * @param eventDate A LocalDate that contains information about the start date of this task. + */ public Event(String taskName, boolean isDone, LocalDate eventDate) { super(taskName, isDone); this.eventDate = eventDate; diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java index b8dbe04182..20331020aa 100644 --- a/src/main/java/duke/Storage.java +++ b/src/main/java/duke/Storage.java @@ -3,14 +3,11 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; - import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; - import java.time.LocalDate; import java.time.format.DateTimeParseException; - import java.util.Scanner; /** @@ -79,6 +76,7 @@ public static void loadData() throws IOException { TaskList.addToList(new Event(taskName, marked, date)); } break; + default: } } scanner.close(); @@ -92,7 +90,7 @@ public static void loadData() throws IOException { * * @throws IOException If the save file or file path could not be accessed. */ - public static void saveData() throws IOException{ + public static void saveData() throws IOException { Path parentDir = Paths.get(DATA_FILE_PATH); if (!Files.exists(parentDir)) { Files.createDirectories(parentDir); diff --git a/src/main/java/duke/Task.java b/src/main/java/duke/Task.java index 30a60c4cac..31bc359cee 100644 --- a/src/main/java/duke/Task.java +++ b/src/main/java/duke/Task.java @@ -9,11 +9,22 @@ public abstract class Task { protected String taskName = ""; protected boolean isDone = false; + /** + * The constructor for a Task. + * + * @param taskName A string that is the name of the task. + * @param isDone A boolean that represents whether this task is complete. + */ public Task(String taskName, boolean isDone) { this.taskName = taskName; this.isDone = isDone; } + /** + * Gets the name of the task. + * + * @return A string that is name of the task. + */ public String getTaskName() { return this.taskName; } diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java index 70ae5e772c..40998ac15c 100644 --- a/src/main/java/duke/TaskList.java +++ b/src/main/java/duke/TaskList.java @@ -2,7 +2,6 @@ import java.time.LocalDate; import java.time.format.DateTimeParseException; - import java.util.ArrayList; import java.util.List; diff --git a/src/test/java/duke/DukeTest.java b/src/test/java/duke/DukeTest.java index 953fa8f7b0..9ef68f00b1 100644 --- a/src/test/java/duke/DukeTest.java +++ b/src/test/java/duke/DukeTest.java @@ -1,10 +1,10 @@ package duke; +import java.util.List; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.List; - /** * A class for testing Duke's functionalities. */ From 0d77585e37d00d259c53db544e6007738e797a09 Mon Sep 17 00:00:00 2001 From: nqt230 <76938663+nqt230@users.noreply.github.com> Date: Thu, 1 Sep 2022 11:16:40 +0800 Subject: [PATCH 22/35] Implement Level-10 - Create GUI for Duke - User input is now received from the TextField component in MainWindow - handleUserInput function in MainWindow passes the user input to Duke - Duke takes the input and passes it to Parser - Duke now also stores the response to the user generated by Ui to be displayed by MainWindow --- build.gradle | 17 +++- src/main/java/duke/DialogueBox.java | 60 ++++++++++++++ src/main/java/duke/Duke.java | 100 +++++++++++++++++++---- src/main/java/duke/Launcher.java | 12 +++ src/main/java/duke/Main.java | 32 ++++++++ src/main/java/duke/MainWindow.java | 66 +++++++++++++++ src/main/java/duke/Parser.java | 7 +- src/main/java/duke/Storage.java | 2 - src/main/java/duke/Ui.java | 51 +++++++++--- src/main/resources/images/DukeIcon.png | Bin 0 -> 35819 bytes src/main/resources/images/UserIcon.png | Bin 0 -> 20300 bytes src/main/resources/view/DialogueBox.fxml | 20 +++++ src/main/resources/view/MainWindow.fxml | 19 +++++ 13 files changed, 350 insertions(+), 36 deletions(-) create mode 100644 src/main/java/duke/DialogueBox.java create mode 100644 src/main/java/duke/Launcher.java create mode 100644 src/main/java/duke/Main.java create mode 100644 src/main/java/duke/MainWindow.java create mode 100644 src/main/resources/images/DukeIcon.png create mode 100644 src/main/resources/images/UserIcon.png create mode 100644 src/main/resources/view/DialogueBox.fxml create mode 100644 src/main/resources/view/MainWindow.fxml diff --git a/build.gradle b/build.gradle index 5cc08e2a9c..98e03a44da 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,21 @@ repositories { } dependencies { + 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' + 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' } @@ -33,7 +48,7 @@ test { } application { - mainClassName = "duke.Duke" + mainClassName = "duke.Launcher" } shadowJar { diff --git a/src/main/java/duke/DialogueBox.java b/src/main/java/duke/DialogueBox.java new file mode 100644 index 0000000000..bcb02302e9 --- /dev/null +++ b/src/main/java/duke/DialogueBox.java @@ -0,0 +1,60 @@ +package duke; + +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.HBox; + +/** + * This control represents a dialogue box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ +public class DialogueBox extends HBox { + @FXML + private Label dialogue; + @FXML + private ImageView displayPicture; + + private DialogueBox(String text, Image img) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogueBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialogue.setText(text); + displayPicture.setImage(img); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + public static DialogueBox getUserDialogue(String text, Image img) { + return new DialogueBox(text, img); + } + + public static DialogueBox getDukeDialogue(String text, Image img) { + var db = new DialogueBox(text, img); + db.flip(); + return db; + } +} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java index f04af45ede..1d0c687fe7 100644 --- a/src/main/java/duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -8,44 +8,112 @@ */ public class Duke { private static boolean isRunning; + private static StringBuilder response = new StringBuilder(); /** - * The method used to run the Duke program. - * - * @param args The command line arguments. + * Starts the Duke program and performs the necessary start up operations. + * This includes displaying the greeting response and loading the task list from file. */ - public static void main(String[] args) { + public static void initialize() { try { Storage.loadData(); } catch (IOException e) { System.out.println("Error: Failed to access data"); - return; + + addToResponse("Error: Failed to access data"); } Ui.displayStartUpText(); + isRunning = true; + } + + /** + * Stops the Duke program and performs the necessary exit operations. + * This includes displaying the exit response and saving the task list to file. + */ + public static void exit() { + try { + Storage.saveData(); + } catch (IOException e) { + System.out.println("Error: Failed to access data"); + + addToResponse("Error: Failed to access data"); + } + + Ui.displayExitText(); + + isRunning = false; + } + + /** + * Gives Duke an input string to process using Parser. + * Parser will generate a command based on this input, and perform operations. + * Ui then creates a response to the user and passes it to Duke via addToResponse. + * + * @param input The input string to be processed by Parser. + */ + public static void giveInput(String input) { + Ui.generateLine(); + try { + Parser.parseInput(input); + } catch (DukeException e) { + System.out.println(e.getMessage()); + + addToResponse(e.getMessage() + "\n"); + } + Ui.generateLine(); + } + + public static void addToResponse(String output) { + response.append(output); + } + + /** + * Gets a string that is a response to the user's last command. + * + * @return A string that is a response to the user's last command. + */ + public static String getResponse() { + return response.toString(); + } + + /** + * Clears the response to be displayed to the user when getResponse is called. + */ + public static void clearResponse() { + response = new StringBuilder(); + } + + /** + * Gets whether Duke is still running. + * + * @return True if Duke is running, otherwise false. + */ + public static boolean getIsRunning() { + return isRunning; + } + + /** + * The method used to run the Duke program. + * + * @param args The command line arguments. + */ + public static void main(String[] args) { + initialize(); + Scanner scanner = new Scanner(System.in); - isRunning = true; while (isRunning) { Ui.generateLine(); String input = scanner.nextLine(); Ui.generateLine(); try { - isRunning = Parser.parseInput(input); + Parser.parseInput(input); } catch (DukeException e) { System.out.println(e.getMessage()); } } - - Ui.displayExitText(); - - try { - Storage.saveData(); - } catch (IOException e) { - System.out.println("Error: Failed to access data"); - return; - } } } diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java new file mode 100644 index 0000000000..e4ef6b4628 --- /dev/null +++ b/src/main/java/duke/Launcher.java @@ -0,0 +1,12 @@ +package duke; + +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class Launcher { + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java new file mode 100644 index 0000000000..a5f196de5f --- /dev/null +++ b/src/main/java/duke/Main.java @@ -0,0 +1,32 @@ +package duke; + +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 Duke using FXML. + */ +public class Main extends Application { + @Override + public void start(Stage stage) { + try { + Duke.initialize(); + + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + MainWindow mainWindow = fxmlLoader.getController(); + mainWindow.setStage(stage); + mainWindow.getResponse(); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/duke/MainWindow.java b/src/main/java/duke/MainWindow.java new file mode 100644 index 0000000000..db54c1224a --- /dev/null +++ b/src/main/java/duke/MainWindow.java @@ -0,0 +1,66 @@ +package duke; + +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; +import javafx.stage.Stage; + +/** + * Controller for MainWindow. Provides the layout for the other controls. + */ +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogueContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private Stage stage; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images/UserIcon.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DukeIcon.png")); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogueContainer.heightProperty()); + } + + public void setStage(Stage stage) { + this.stage = stage; + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to + * the dialog container. Clears the user input after processing. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + dialogueContainer.getChildren().addAll( + DialogueBox.getUserDialogue(input, userImage) + ); + + if (!Duke.getIsRunning()) { + stage.close(); + } + + Duke.giveInput(input); + getResponse(); + + userInput.clear(); + } + + public void getResponse() { + dialogueContainer.getChildren().add( + DialogueBox.getDukeDialogue(Duke.getResponse(), dukeImage) + ); + Duke.clearResponse(); + } +} diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java index 03d5d11104..145d191076 100644 --- a/src/main/java/duke/Parser.java +++ b/src/main/java/duke/Parser.java @@ -9,7 +9,7 @@ public class Parser { * * @param input The input string to be parsed. */ - public static boolean parseInput(String input) throws DukeException { + public static void parseInput(String input) throws DukeException { String[] words = input.toLowerCase().split(" ", 2); String command = words[0]; String args = ""; @@ -22,7 +22,8 @@ public static boolean parseInput(String input) throws DukeException { Ui.displayGreeting(); break; case "bye": - return false; + Duke.exit(); + break; case "list": Ui.displayTasks(); break; @@ -49,7 +50,5 @@ public static boolean parseInput(String input) throws DukeException { default: throw new DukeException("Command not recognised"); } - - return true; } } diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java index 20331020aa..63aa80edeb 100644 --- a/src/main/java/duke/Storage.java +++ b/src/main/java/duke/Storage.java @@ -80,8 +80,6 @@ public static void loadData() throws IOException { } } scanner.close(); - - } /** diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java index da3e31595a..6608463ccc 100644 --- a/src/main/java/duke/Ui.java +++ b/src/main/java/duke/Ui.java @@ -12,8 +12,8 @@ public class Ui { + "| | | | | | | |/ / _ \\\n" + "| |_| | |_| | < __/\n" + "|____/ \\__,_|_|\\_\\___|\n"; - private static final String BOT_NAME = "duke"; - private static final int LINE_LENGTH = 80; + private static final String BOT_NAME = "Duke"; + private static final int LINE_LENGTH = 50; /** * Displays the text to be shown on starting up Duke. @@ -35,6 +35,9 @@ public static void displayStartUpText() { public static void displayGreeting() { System.out.printf("Hello, I'm %s\n", BOT_NAME); System.out.println("What can I do for you?"); + + Duke.addToResponse(String.format("Hello, I'm %s\n", BOT_NAME)); + Duke.addToResponse(String.format("What can I do for you?\n")); } /** @@ -42,6 +45,8 @@ public static void displayGreeting() { */ public static void generateLine() { System.out.println(String.format("%" + LINE_LENGTH + "s", "").replace(" ", "-")); + + Duke.addToResponse(String.format("%" + LINE_LENGTH + "s", "").replace(" ", "-") + "\n"); } /** @@ -49,6 +54,8 @@ public static void generateLine() { */ public static void displayExitText() { System.out.println("Goodbye."); + + Duke.addToResponse("Goodbye.\n"); } /** @@ -57,8 +64,11 @@ public static void displayExitText() { * @param task The task that was added to the list. */ public static void displayAddTaskMessage(Task task) { - System.out.printf("Task added: %s\n", task); + System.out.printf("Task added:\n%s\n", task); System.out.printf("You now have %d task(s) in your list\n", TaskList.getTaskCount()); + + Duke.addToResponse(String.format("Task added:\n%s\n", task)); + Duke.addToResponse(String.format("You now have %d task(s) in your list\n", TaskList.getTaskCount())); } /** @@ -67,8 +77,11 @@ public static void displayAddTaskMessage(Task task) { * @param task The task that was removed from the list. */ public static void displayDeleteTaskMessage(Task task) { - System.out.printf("Task deleted: %s\n", task); + System.out.printf("Task deleted:\n%s\n", task); System.out.printf("You now have %d task(s) in your list\n", TaskList.getTaskCount()); + + Duke.addToResponse(String.format("Task deleted:\n%s\n", task)); + Duke.addToResponse(String.format("You now have %d task(s) in your list\n", TaskList.getTaskCount())); } /** @@ -77,7 +90,9 @@ public static void displayDeleteTaskMessage(Task task) { * @param task The task that was marked on the list. */ public static void displayMarkTaskMessage(Task task, boolean isDone) { - System.out.printf("Task marked as %s: %s\n", isDone ? "complete" : "incomplete", task); + System.out.printf("Task marked as %s:\n%s\n", isDone ? "complete" : "incomplete", task); + + Duke.addToResponse(String.format("Task marked as %s:\n%s\n", isDone ? "complete" : "incomplete", task)); } /** @@ -87,14 +102,20 @@ public static void displayMarkTaskMessage(Task task, boolean isDone) { * @param searchString The string that was used to perform the search. */ public static void displaySearchTasksMessage(List foundTasks, String searchString) { - if (foundTasks.size() == 0) { - System.out.printf("Search results for \"%s\": No tasks found\n", searchString); - } else { - System.out.printf("Search results for \"%s\": %d task(s) found\n", searchString, foundTasks.size()); - for (int i = 0; i < foundTasks.size(); i++) { - System.out.printf("%d. %s\n", i + 1, foundTasks.get(i)); - } + System.out.printf("Displaying search results for \"%s\":\n", + searchString, foundTasks.size()); + + Duke.addToResponse(String.format("Displaying search results for \"%s\":\n", + searchString, foundTasks.size())); + + for (int i = 0; i < foundTasks.size(); i++) { + System.out.printf("%d. %s\n", i + 1, foundTasks.get(i)); + + Duke.addToResponse(String.format("%d. %s\n", i + 1, foundTasks.get(i))); } + System.out.printf("\n%d task(s) found\n", foundTasks.size()); + + Duke.addToResponse(String.format("\n%d task(s) found\n", foundTasks.size())); } /** @@ -104,8 +125,12 @@ public static void displayTasks() { List tasksStrings = TaskList.getTasksStrings(); for (int i = 0; i < tasksStrings.size(); i++) { System.out.printf("%d. %s\n", i + 1, tasksStrings.get(i)); + + Duke.addToResponse(String.format("%d. %s\n", i + 1, tasksStrings.get(i))); } - System.out.printf("You have %d task(s) in your list\n", TaskList.getTaskCount()); + System.out.printf("\nYou have %d task(s) in your list\n", TaskList.getTaskCount()); + + Duke.addToResponse(String.format("\nYou have %d task(s) in your list\n", TaskList.getTaskCount())); } } diff --git a/src/main/resources/images/DukeIcon.png b/src/main/resources/images/DukeIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..53ef4b8095520382eb43ed9824aa549f7562e6e7 GIT binary patch literal 35819 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 aAn|{rfL`Xmy&}H=00007|Op)OC{L7}0c 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/view/DialogueBox.fxml b/src/main/resources/view/DialogueBox.fxml new file mode 100644 index 0000000000..d9ffc92a4c --- /dev/null +++ b/src/main/resources/view/DialogueBox.fxml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..86cac0f44f --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + +