diff --git a/README.md b/README.md
index 9d95025bce..3e769f7d81 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ Prerequisites: JDK 11, update Intellij to the most recent version.
1. Click `Open or Import`.
1. Select the project directory, and click `OK`
1. If there are any further prompts, accept the defaults.
-1. After the importing is complete, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below:
+1. After the importing is complete, locate the `src/dukemain/java/Duke.java` file, right-click it, and choose `Run Duke.dukemain()`. If the setup is correct, you should see something like the below:
```
Hello from
____ _
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..4bb8956801
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,62 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'checkstyle'
+ id 'com.github.johnrengelman.shadow' version '5.1.0'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'org.junit.jupiter:junit-jupiter:5.4.2'
+ testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0'
+ testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0'
+
+ String javaFxVersion = '11'
+
+ implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux'
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed"
+
+ showExceptions true
+ exceptionFormat "full"
+ showCauses true
+ showStackTraces true
+ showStandardStreams = false
+ }
+}
+
+application {
+ mainClassName = "dukemain.Launcher"
+}
+
+shadowJar {
+ archiveBaseName = "duke"
+ archiveClassifier = null
+}
+
+checkstyle {
+ toolVersion = '8.36.1'
+}
+
+run{
+ standardInput = System.in
+}
diff --git a/data.txt b/data.txt
new file mode 100644
index 0000000000..825f1840ba
--- /dev/null
+++ b/data.txt
@@ -0,0 +1,5 @@
+done t buy groceries
+incomplete d math homework timeOfTask: 22/09/2020 2359
+done e David's birthday timeOfTask: 26/09/2020
+incomplete t journal entry
+incomplete d science hw assignment 3 timeOfTask: 23/09/2020 2359
diff --git a/docs/README.md b/docs/README.md
index fd44069597..eaea167e82 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,20 +1,226 @@
-# User Guide
+# Duke User Guide
## Features
-### Feature 1
-Description of feature.
+### ToDo
+Adds a ToDo task to your list.
-## Usage
+#### Usage
-### `Keyword` - Describe action
+### `todo [task description]`
-Describe action and its outcome.
+Creates a ToDo task with specified description and adds
+it to your list.
Example of usage:
-`keyword (optional arguments)`
+`todo homework`
Expected outcome:
-`outcome`
+`Got it! Task added to your list.`
+ `[T][X] homework`
+`Now you have x tasks in your list.`
+
+***
+
+### Deadline
+Adds a Deadline task to your list.
+
+#### Usage
+
+### `deadline [task description] /by [date]`
+
+Creates a Deadline task with specified description and time
+and adds it to your list.
+
+Example of usage:
+
+`deadline assignment /by 19/04/2020 2359`
+
+Expected outcome:
+
+`Got it! Task added to your list.`
+ `[D][X] assignment (by: 19 Apr 2020 11:59pm)`
+`Now you have x tasks in your list.`
+
+Note:
+Valid date formats : `dd-mm-yyyy`, `dd-mm-yyyy hhhh`,
+`dd/mm/yyyy`, `dd/mm/yyyy hhhh`.
+Any other formats will be read as String.
+
+***
+
+### Event
+Adds an Event task to your list.
+
+#### Usage
+
+### `event [task description] /at [date]`
+
+Creates an Event task with specified description and time
+and adds it to your list.
+
+Example of usage:
+
+`event concert /at 19/04/2020 1159`
+
+Expected outcome:
+
+`Got it! Task added to your list.`
+ `[E][X] concert (at: 19 Apr 2020 11:59am)`
+`Now you have x tasks in your list.`
+
+Note:
+Valid date formats : `dd-mm-yyyy`, `dd-mm-yyyy hhhh`,
+`dd/mm/yyyy`, `dd/mm/yyyy hhhh`.
+Any other formats will be read as String.
+
+***
+
+### List
+Shows a list of all your tasks.
+
+#### Usage
+
+### `list`
+
+Lists all tasks.
+
+Example of usage:
+
+`list`
+
+Expected outcome:
+
+`Here are your tasks:`
+`1. [T][X] homework`
+`2. [D][X] assignment (by: 19 Apr 2020 11:59pm)`
+`3. [E][X] concert (at: 19 Apr 2020 11:59am)`
+
+***
+
+### Done
+Marks task as completed with a [ / ].
+
+#### Usage
+
+### `done [index]`
+
+Marks task at the specified index as done.
+
+Example of usage:
+
+`done 1`
+
+Expected outcome:
+
+`Nice! I have marked this task as done:`
+ `[T][/] homework`
+
+Note:
+Index is as specified by `list` function.
+
+***
+
+### Delete
+Removes specified task from list.
+
+#### Usage
+
+### `delete [index]`
+
+Deletes task at the specified index.
+
+Example of usage:
+
+`delete 1`
+
+Expected outcome:
+
+`Okay! I have removed this task:`
+ `[T] homework`
+`Now you have x tasks in your list`
+
+Note:
+Index is as specified by `list` function.
+
+***
+
+### Clear
+Deletes task list.
+
+#### Usage
+
+### `clear`
+
+Deletes entire task list.
+
+Example of usage:
+
+`clear`
+
+Expected outcome:
+
+`Task list cleared!`
+
+***
+
+### Find
+Finds task list using keyword/phrase.
+
+#### Usage
+
+### `find [keyword/phrase]`
+
+Prints a sublist of tasks that contain the specified
+keyword/phrase.
+
+Example of usage:
+
+`find math`
+
+Expected outcome:
+
+`Here are your matching tasks:`
+`1. [D][X] math homework (by: Sep 28 2020)`
+`2. [D][X] math assignment (by: Sep 30 2020)`
+`3. [E][X] math lecture (at: Sep 25 2020, 03:00pm)`
+
+If unsuccessful find:
+`Sorry! There are no tasks that match that description.`
+
+***
+
+### Edit
+Edits a specified task with a new field.
+
+#### Usage
+
+### `edit 1 [/d or /t] [new field]`
+
+Edits a task at the specified index with a new field.
+Tag "/d" to edit description.
+Tag "/t" to edit time.
+
+Example of usage:
+
+`edit 1 /d math hw 1 section b`
+
+Expected outcome:
+
+`Okay! I have edited this task:`
+ `===> [D][X] math homework (by: Sep 28 2020)`
+ `<=== [D][X] math hw 1 section b (by: Sep 28 2020)`
+
+Example of usage:
+
+`edit 1 /t 29/09/2020 2359`
+
+Expected outcome:
+
+`Okay! I have edited this task:`
+ `===> [D][X] math homework (by: Sep 28 2020)`
+ `<=== [D][X] math homework (by: Sep 29 2020, 11:59pm)`
+
+***
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..6b0da7bd4c
Binary files /dev/null and b/docs/Ui.png differ
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..f3d88b1c2f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 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/src/main/java/Duke.java b/src/main/java/Duke.java
deleted file mode 100644
index 5d313334cc..0000000000
--- a/src/main/java/Duke.java
+++ /dev/null
@@ -1,10 +0,0 @@
-public class Duke {
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- }
-}
diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java
new file mode 100644
index 0000000000..07a7e1bf03
--- /dev/null
+++ b/src/main/java/command/Command.java
@@ -0,0 +1,324 @@
+package command;
+
+import exception.InvalidInputException;
+import task.Deadline;
+import task.Event;
+import task.Task;
+import task.ToDo;
+import ui.Ui;
+
+import java.util.ArrayList;
+
+/**
+ * Command object has a CommandType to determine which specific command to execute
+ * and a description which contains the user input String. Command objects to be executed
+ * are always valid as input validation is done by the Parser object.
+ * Command object also handles manipulation of list of Task objects.
+ *
+ * @author Hakiem Rasid
+ */
+public class Command {
+
+ public CommandType type;
+ public String description;
+
+ /**
+ * Constructor for Command object.
+ *
+ * @param type Type of command to be executed as CommandType.
+ * @param description User input instruction as String.
+ */
+ public Command(CommandType type, String description) {
+ this.type = type;
+ this.description = description;
+ }
+
+ /**
+ * Returns list of Task objects after executing command.
+ *
+ * @param tasks List of Task objects to be manipulated.
+ * @param sb StringBuilder to append messages.
+ * @return List of Task objects after changes made from executing command.
+ * @throws IndexOutOfBoundsException If index specified in DONE or DELETE command
+ * does not lie within range of list of Task objects.
+ * @throws InvalidInputException If use requests to edit time field of ToDo.
+ */
+ public ArrayList executeCommand(ArrayList tasks, StringBuilder sb) throws
+ IndexOutOfBoundsException, InvalidInputException {
+
+ switch (this.type) {
+ case BYE:
+ sb.append(Ui.byeMessage());
+ return tasks;
+ case CLEAR:
+ sb.append(Ui.clearedListMessage());
+ return new ArrayList<>();
+
+ /*
+ *****Don't know how to implement in GUI*****
+ if (Ui.promptConfirm(new Scanner(System.in))) {
+ // Reference to empty ArrayList
+ Ui.clearedListMessage();
+ return new ArrayList<>();
+ } else {
+ // do nothing
+ Ui.didNotClearListMessage();
+ return tasks;
+ }
+ */
+ case LIST:
+ if (tasks.size() == 0) {
+ // do nothing
+ sb.append("There are no tasks in your list!");
+ return new ArrayList<>();
+ } else {
+ sb.append(Ui.printList(tasks, "print"));
+ return tasks;
+ }
+ case DONE:
+ return markDone(tasks, this.description, sb);
+ case DELETE:
+ return deleteTask(tasks, this.description, sb);
+ case TODO:
+ case DEADLINE:
+ case EVENT:
+ return addTask(tasks, this.description, sb);
+ case FIND:
+ return findTask(tasks, this.description, sb);
+ case EDIT:
+ return editTask(tasks, this.description, sb);
+ case UNKNOWN:
+ sb.append(this.description);
+ return tasks;
+ }
+ return tasks;
+ }
+
+ /**
+ * Returns a list of task objects with specified task edited.
+ * @param tasks List of tasks.
+ * @param description User input for edit command.
+ * @param sb StringBuilder to append messages.
+ * @return Lists of task objects after editing.
+ * @throws IndexOutOfBoundsException If index specified does not lie within
+ * range of list of Task objects.
+ * @throws InvalidInputException If user requests to edit time field of ToDo object.
+ */
+ public ArrayList editTask(ArrayList tasks, String description, StringBuilder sb)
+ throws IndexOutOfBoundsException, InvalidInputException {
+
+ // Format eg: 1 /d newName
+ String[] descriptionArr = description.split(" ");
+ int index = Integer.parseInt(descriptionArr[0]);
+ String editType = descriptionArr[1].trim();
+ String newField;
+
+ // initialize newField
+ StringBuilder newFieldBuilder = new StringBuilder();
+ for (int i = 2; i < descriptionArr.length; i++) {
+ newFieldBuilder.append(descriptionArr[i]);
+ newFieldBuilder.append(" ");
+ }
+ newField = newFieldBuilder.toString().trim();
+
+ // initialize oldTask with correct type and handle editing
+ Task current = tasks.get(index - 1);
+ if (current.printTask().startsWith("[T]")) {
+ // case: edit ToDo
+ if (editType.equals("/d")) {
+ // case: edit ToDo description
+ ToDo newTask = new ToDo(newField);
+
+ // mark done
+ if (current.isDone()) {
+ newTask.completeTask();
+ }
+
+ // replace element in list
+ tasks.set(index - 1, newTask);
+ } else {
+ // throw exception: ToDo task does not have field for time
+ throw new InvalidInputException("Cannot edit time for todo task!");
+ }
+ } else if (current.printTask().startsWith("[D]")) {
+ // case: edit Deadline
+ Deadline oldTask = (Deadline) current;
+ Deadline newTask;
+ if (editType.equals("/d")) {
+ // case: edit Deadline description
+ newTask = new Deadline(newField, oldTask.getDeadline());
+
+ // mark done
+
+ // replace element in list
+ } else {
+ // case: edit Deadline time (deadline)
+ newTask = new Deadline(oldTask.getName(), newField);
+
+ // mark done
+
+ // replace element in list
+ }
+ if (current.isDone()) {
+ newTask.completeTask();
+ }
+ tasks.set(index - 1, newTask);
+ } else {
+ // case; edit Event
+ Event oldTask = (Event) current;
+ Event newTask;
+ if (editType.equals("/d")) {
+ // case: edit Event description
+ newTask = new Event(newField, oldTask.getName());
+
+ // mark done
+
+ // replace element in list
+ } else {
+ // case: edit Event time
+ newTask = new Event(oldTask.getName(), newField);
+
+ // mark done
+
+ // replace element in list
+ }
+ if (current.isDone()) {
+ newTask.completeTask();
+ }
+ tasks.set(index - 1, newTask);
+ }
+
+ sb.append(Ui.editMessage(current, tasks.get(index - 1)));
+ return tasks;
+
+ }
+
+ /**
+ * Returns a list of Task objects with a description that contains the key.
+ *
+ * @param tasks List of Task objects of which to find matching Tasks.
+ * @param key Key used to find matching Task objects.
+ * @param sb StringBuilder to append message.
+ * @return List of matching Task objects.
+ */
+ public ArrayList findTask(ArrayList tasks, String key, StringBuilder sb) {
+ assert !key.equals(" ");
+ ArrayList matchedTasks = new ArrayList<>();
+ for (Task task : tasks) {
+ if (task.getName().toLowerCase().contains(key.toLowerCase())) {
+ matchedTasks.add(task);
+ }
+ }
+ if (matchedTasks.size() == 0) {
+ sb.append(Ui.noMatchMessage());
+ } else {
+ sb.append(Ui.printList(matchedTasks, "find"));
+ }
+ return tasks;
+ }
+
+ /**
+ * Returns list of Task objects with specified Task object marked as done.
+ *
+ * @param tasks List of Task objects.
+ * @param input Keyword "done" followed by index of Task object to be marked done.
+ * @param sb StringBuilder to append message.
+ * @return List of updated Task objects.
+ * @throws IndexOutOfBoundsException If index specified in DONE Command
+ * does not lie within range of list of Task objects.
+ */
+ public ArrayList markDone(ArrayList tasks, String input, StringBuilder sb) throws
+ IndexOutOfBoundsException {
+
+ // parse int for index of task to be marked as done
+ int index = Integer.valueOf(input.split(" ")[1]);
+
+ Task current = tasks.get(index - 1);
+ current.completeTask();
+ sb.append(Ui.markDoneMessage(current.printTask()));
+ return tasks;
+ }
+
+ /**
+ * Returns updated list of Task objects after deleting Task at specified index.
+ *
+ * @param tasks List of Task objects to be manipulated.
+ * @param input Keyword "delete" followed by index of Task to be deleted.
+ * @param sb StringBuilder used to append message.
+ * @return Updated list of Task objects.
+ * @throws IndexOutOfBoundsException If index specified in DELETE Command
+ * does not lie within range of list of Task objects.
+ */
+ public ArrayList deleteTask(ArrayList tasks, String input, StringBuilder sb) throws
+ IndexOutOfBoundsException {
+
+ // parse int for index of task to be deleted
+ int index = Integer.valueOf(input.split(" ")[1]);
+
+ Task current = tasks.remove(index - 1);
+ sb.append(Ui.deleteTaskMessage(current.printTask(), tasks.size()));
+ return tasks;
+ }
+
+ // Adds Task to list. Checks inputs and throws exceptions for invalid inputs
+
+ /**
+ * Returns updated list of Task objects after adding the specified Task.
+ *
+ * @param tasks List of Task objects to be manipulated.
+ * @param input Keyword of specified Task type followed by details of the Task.
+ * @param sb StringBuilder to append message.
+ * @return Updated List of Task objects.
+ */
+ public ArrayList addTask(ArrayList tasks, String input, StringBuilder sb) {
+
+ StringBuilder todoDescription = new StringBuilder();
+ String[] splitSpace = input.split(" ");
+ Task task;
+
+ if (this.type.equals(CommandType.TODO)) {
+ // case: todo
+ for (String str : splitSpace) {
+ if (str.toLowerCase().equals("todo")) {
+ continue;
+ } else {
+ todoDescription.append(str + " ");
+ }
+ } // end for loop
+ task = new ToDo(todoDescription.toString().trim());
+ } else if (this.type.equals(CommandType.DEADLINE)) {
+ // case: deadline
+
+ String name = input.split("/by")[0].trim().substring(9);
+ String deadline = input.split("/by")[1].trim();
+ task = new Deadline(name, deadline);
+ } else {
+ // case: event
+ String name = input.split("/at")[0].trim().substring(6);
+ String time = input.split("/at")[1].trim();
+ task = new Event(name, time);
+ }
+
+ tasks.add(task);
+ sb.append(Ui.addTaskMessage(task.printTask(), tasks.size()));
+ return tasks;
+ }
+
+ /**
+ * Returns the type of this Command object.
+ *
+ * @return Type of this Command object as CommandType.
+ */
+ public CommandType getType() {
+ return this.type;
+ }
+
+ /**
+ * Returns description(user input instruction) of this Command object.
+ * @return Description of Command object as String.
+ */
+ public String getDescription() {
+ return this.description;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/command/CommandType.java b/src/main/java/command/CommandType.java
new file mode 100644
index 0000000000..751cb2c6c1
--- /dev/null
+++ b/src/main/java/command/CommandType.java
@@ -0,0 +1,5 @@
+package command;
+
+public enum CommandType {
+ TODO, DEADLINE, EVENT, LIST, DONE, BYE, DELETE, CLEAR, UNKNOWN, FIND, EDIT
+}
diff --git a/src/main/java/data/DateManager.java b/src/main/java/data/DateManager.java
new file mode 100644
index 0000000000..d58fdd1ec6
--- /dev/null
+++ b/src/main/java/data/DateManager.java
@@ -0,0 +1,181 @@
+package data;
+
+import java.util.Date;
+import java.util.Optional;
+import java.text.SimpleDateFormat;
+import java.text.ParseException;
+
+/**
+ * data.DateManager object parses valid String inputs from Deadline and Event
+ * objects to be stored as Date objects.
+ *
+ * @author Hakiem Rasid
+ */
+public class DateManager {
+
+ private static final String[] DATE_INPUT_FORMATS =
+ {"invalid", "dd-MM-yyyy", "dd/MM/yyyy", "dd-MM-yyyy kkmm", "dd/MM/yyyy kkmm"};
+ private static final String[] DATE_OUTPUT_FORMATS = {"MMM dd yyyy", "MMM dd yyyy',' hh:mma"};
+
+ /**
+ * Returns Optional containing Date if String is valid.
+ *
+ * @param str Input String to be parsed as Date object.
+ * @return Optional containing Date if String is valid. Empty Optional otherwise.
+ */
+ public Optional getDate(String str) {
+ try {
+ if (getDateFormat(str).equals(DATE_INPUT_FORMATS[0])) {
+ // returns empty Optional if str is not of valid format
+ return Optional.empty();
+ } else {
+ SimpleDateFormat formatter = new SimpleDateFormat(getDateFormat(str));
+ return Optional.of(formatter.parse(str));
+ }
+ } catch (ParseException e) {
+ e.printStackTrace();
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Returns String representation of valid Date object.
+ *
+ * @param str Valid input String to be parsed as Date.
+ * @return String representation of Date.
+ */
+ public String getDateAsString(String str) {
+ // assumes that str input has valid date format
+ // input checks done in Deadline and Event
+
+ SimpleDateFormat sdf;
+
+ if (getDateFormat(str).equals(DATE_INPUT_FORMATS[1]) ||
+ getDateFormat(str).equals(DATE_INPUT_FORMATS[2])) {
+
+ sdf = new SimpleDateFormat(DATE_OUTPUT_FORMATS[0]);
+ } else {
+ sdf = new SimpleDateFormat(DATE_OUTPUT_FORMATS[1]);
+ }
+ return sdf.format(getDate(str).get());
+ }
+
+ /**
+ * Returns format of String input of a date.
+ *
+ * @param str String representation of a date/
+ * @return Format of Date object if input is valid. Returns "invalid" otherwise.
+ */
+ public String getDateFormat(String str) {
+ if (str.length() != DATE_INPUT_FORMATS[1].length() &&
+ str.length() != DATE_INPUT_FORMATS[3].length()) {
+ // input string has invalid format if it is not of correct length
+ return DATE_INPUT_FORMATS[0];
+ }
+
+ if (str.length() == DATE_INPUT_FORMATS[1].length()) {
+ // either of format dd-mm-yyyy or dd/mm/yyyy
+ if (str.substring(2, 3).equals("-")) {
+ // case: dd-mm-yyyy
+ String[] date = str.split("-");
+ return (isValidDateNumerals(date))
+ ? DATE_INPUT_FORMATS[1]
+ : DATE_INPUT_FORMATS[0];
+ } else if (str.substring(2, 3).equals("/")) {
+ // case: dd/mm/yyyy
+ String[] date = str.split("/");
+ return (isValidDateNumerals(date))
+ ? DATE_INPUT_FORMATS[2]
+ : DATE_INPUT_FORMATS[0];
+ } else {
+ // invalid format
+ return DATE_INPUT_FORMATS[0];
+ }
+ } else {
+ // case: dd-mm-yyyy hhhh or dd/mm/yyyy hhhh
+ if (str.substring(2, 3).equals("-")) {
+ // case: dd-mm-yyyy hhhh
+ String hrs = str.split(" ")[1];
+ String[] date = str.split(" ")[0].split("-");
+ String[] dateTime = new String[4];
+
+ // assign values to dateTime array
+ for (int i = 0; i < 3; i++) {
+ dateTime[i] = date[i];
+ }
+ dateTime[3] = hrs;
+ return (isValidDateNumerals(dateTime))
+ ? DATE_INPUT_FORMATS[3]
+ : DATE_INPUT_FORMATS[0];
+ } else if (str.substring(2, 3).equals("/")) {
+ // case: dd/mm/yyyy hhhh
+ String hrs = str.split(" ")[1];
+ String[] date = str.split(" ")[0].split("/");
+ String[] dateTime = new String[4];
+
+ // assign values to dateTime array
+ for (int i = 0; i < 3; i++) {
+ dateTime[i] = date[i];
+ }
+ dateTime[3] = hrs;
+ return (isValidDateNumerals(dateTime))
+ ? DATE_INPUT_FORMATS[4]
+ : DATE_INPUT_FORMATS[0];
+ } else {
+ // invalid format
+ return DATE_INPUT_FORMATS[0];
+ }
+ }
+
+ }
+
+ /**
+ * Checks validity of integers in String representation of a date.
+ *
+ * @param arr Array containing integers for day, month and year.
+ * @return True if integers form a valid date. False otherwise.
+ */
+ private boolean isValidDateNumerals(String[] arr) {
+ assert arr.length <= 4;
+ assert canParseToInt(arr[0]);
+ assert canParseToInt(arr[1]);
+ assert canParseToInt(arr[2]);
+
+ boolean isValid = true;
+ if (Integer.parseInt(arr[0]) > 31) {
+ // invalid if day > 31
+ isValid = false;
+ }
+ if (Integer.parseInt(arr[1]) > 12) {
+ // invalid if month > 12
+ isValid = false;
+ }
+ if (Integer.parseInt(arr[2]) < 2020) {
+ // invalid if year < 2020
+ isValid = false;
+ }
+
+ if (arr.length == 4) {
+ // case dd mm yyyy hhhh
+ if (Integer.parseInt(arr[3]) > 2359 || Integer.parseInt(arr[3]) < 0) {
+ isValid = false;
+ }
+ }
+
+ return isValid;
+ }
+
+ /**
+ * Checks if String can be parsed to Integer.
+ * @param str Input String.
+ * @return True if String can be parsed as Integer, false otherwise.
+ */
+ private boolean canParseToInt(String str) {
+ try {
+ Integer.parseInt(str);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/data/Parser.java b/src/main/java/data/Parser.java
new file mode 100644
index 0000000000..a6bf69dcd6
--- /dev/null
+++ b/src/main/java/data/Parser.java
@@ -0,0 +1,267 @@
+package data;
+
+import command.Command;
+import command.CommandType;
+import exception.InvalidInputException;
+
+/**
+ * data.Parser object processes and makes sense of user input to be executed.
+ * Input validation is also done as inputs are parsed to ensure command
+ * Strings follow a specific format before they can be executed.
+ *
+ * @author Hakiem Rasid
+ */
+public class Parser {
+
+ public static final String[] COMMANDS = {"todo", "deadline", "event",
+ "list", "done", "bye", "delete", "clear", "unknown", "find", "edit"};
+
+ /**
+ * Returns Command object by processing user input.
+ *
+ * @param input User inputs as String.
+ * @return Command object.
+ * @throws InvalidInputException If inputs are of incorrect format
+ * and cannot be parsed.
+ */
+ public Command parseCommand(String input) throws InvalidInputException {
+ String[] strings = input.split(" ");
+ Command command;
+
+ if (input.trim().toLowerCase().equals(Parser.COMMANDS[5])) {
+ // returns BYE Command
+ command = new Command(CommandType.BYE, input);
+ } else if (input.trim().toLowerCase().equals(Parser.COMMANDS[7])) {
+ // returns CLEAR Command
+ command = new Command(CommandType.CLEAR, input);
+ } else if (input.trim().toLowerCase().equals(Parser.COMMANDS[3])) {
+ // return LIST Command
+ command = new Command(CommandType.LIST, input);
+ } else if (strings[0].toLowerCase().equals(Parser.COMMANDS[4])) {
+ // returns DONE Command
+ command = new Command(CommandType.DONE, input);
+ } else if (strings[0].toLowerCase().equals(Parser.COMMANDS[6])) {
+ // returns DELETE Command
+ command = new Command(CommandType.DELETE, input);
+ } else if (strings[0].toLowerCase().equals(Parser.COMMANDS[0])) {
+ // returns TODO Command
+ command = new Command(CommandType.TODO, input);
+ } else if (strings[0].toLowerCase().equals(Parser.COMMANDS[1])) {
+ // returns DEADLINE Command
+ command = new Command(CommandType.DEADLINE, input);
+ } else if (strings[0].toLowerCase().equals(Parser.COMMANDS[2])) {
+ // returns EVENT Command
+ command = new Command(CommandType.EVENT, input);
+ } else if (strings[0].toLowerCase().equals(Parser.COMMANDS[9])) {
+ // returns FIND Command
+ command = new Command(CommandType.FIND, input);
+ } else if (strings[0].toLowerCase().equals(Parser.COMMANDS[10])) {
+ // returns EDIT Command
+ command = new Command(CommandType.EDIT, input);
+ } else {
+ // returns UNKNOWN Command
+ command = new Command(CommandType.UNKNOWN,
+ "Sorry, I don't understand!");
+ }
+ return validateCommand(command);
+ }
+
+ /**
+ * Returns Command object after validating its contents.
+ *
+ * @param cmd Command object to be validated.
+ * @return Valid Command object that can be executed.
+ * @throws InvalidInputException If description of input Command
+ * is of incorrect format and cannot be parsed.
+ */
+ public Command validateCommand(Command cmd) throws InvalidInputException {
+ Command validCommand = new Command(CommandType.UNKNOWN,
+ "Sorry, I don't understand!");
+ switch(cmd.getType()) {
+ case BYE:
+ case CLEAR:
+ case LIST:
+ case UNKNOWN:
+ // no need to validate these Command types
+ validCommand = cmd;
+ break;
+ case DONE:
+ case DELETE:
+ validCommand = checkDoneAndDeleteValidity(cmd);
+ break;
+ case TODO:
+ validCommand = checkToDoValidity(cmd);
+ break;
+ case DEADLINE:
+ case EVENT:
+ validCommand = checkDeadlineAndEventValidity(cmd);
+ break;
+ case FIND:
+ validCommand = checkFindValidity(cmd);
+ break;
+ case EDIT:
+ validCommand = checkEditValidity(cmd);
+ }
+ return validCommand;
+ }
+
+ /**
+ * Returns valid Command object after input validity checks of
+ * EDIT Command.
+ *
+ * @param cmd Command object with CommandType EDIT.
+ * @return Input Command object if description passes input validity checks.
+ * @throws InvalidInputException If description of Command object is of
+ * incorrect format.
+ */
+ public Command checkEditValidity(Command cmd) throws InvalidInputException {
+ // Correct format eg:
+ // - edit 1 /d newName (edits name)
+ // - edit 1 /t 12/12/2020 1230 (edits time for Deadline/Event)
+ String commandString = cmd.getDescription().trim();
+ String[] commandStringArr = commandString.split("\\s+");
+
+ if (!commandString.contains("/d") && !commandString.contains("/t")) {
+ // throws Exception for missing identifier
+ throw new InvalidInputException("Command is missing \"/d\" or \"/t\" keyword.");
+ }
+
+ if (commandStringArr[commandStringArr.length - 1].equals("/t") ||
+ commandStringArr[commandStringArr.length - 1].equals("/d")) {
+ // throws Exception if missing fields after /d or /t identifier
+ throw new InvalidInputException("Incomplete edit command.");
+ }
+
+ try {
+ // throws Exception if an integer does not follow "edit"
+ Integer.parseInt(commandStringArr[1]);
+ } catch (NumberFormatException e) {
+ throw new InvalidInputException("Please enter a valid integer index.");
+ }
+
+ StringBuilder editCommand = new StringBuilder();
+ for (int i = 1; i < commandStringArr.length; i++) {
+ editCommand.append(commandStringArr[i]);
+ editCommand.append(" ");
+ }
+ return new Command(CommandType.EDIT, editCommand.toString().trim());
+ }
+
+ /**
+ * Returns valid Command object after input validity checks of
+ * FIND Command.
+ *
+ * @param cmd Command object with CommandType FIND.
+ * @return Input Command object if description passes input validity checks.
+ * @throws InvalidInputException If description of Command object is of
+ * incorrect format.
+ */
+ public Command checkFindValidity(Command cmd) throws InvalidInputException {
+ String description = cmd.getDescription();
+ String key;
+
+ if (description.trim().equals("find")) {
+ // throws exception for invalid input i.e. "find", "find "
+ throw new InvalidInputException("Incomplete find command");
+ }
+
+ key = description.trim().substring(5).trim();
+ return new Command(CommandType.FIND, key);
+ }
+
+ /**
+ * Returns valid Command object after input validity checks of
+ * DEADLINE and EVENT Commands.
+ *
+ * @param cmd Command object with CommandType DEADLINE or EVENT.
+ * @return Input Command object if description passes input validity checks.
+ * @throws InvalidInputException If description of Command object is of
+ * incorrect format.
+ */
+ public Command checkDeadlineAndEventValidity(Command cmd) throws InvalidInputException {
+ String cmdIdentifier;
+
+ String description = cmd.getDescription();
+ if (cmd.getType().equals(CommandType.DEADLINE)) {
+ // case: DEADLINE
+ cmdIdentifier = "by";
+ } else {
+ // case: EVENT
+ cmdIdentifier = "at";
+ }
+
+ if (!description.contains(" /" + cmdIdentifier)) {
+ // throws exception if invalid input format: does not contain "/by" or "/at"
+ // must have space before /by or /at keywords
+ throw new InvalidInputException("Command is missing \"/" + cmdIdentifier +
+ "\" keyword");
+ }
+
+ if (description.split(" /")[0].toLowerCase().equals("deadline") ||
+ description.split(" /")[0].toLowerCase().equals("event")) {
+
+ // throws exception if invalid input format: "deadline /by taskDeadline"
+ // throws exception if invalid input format: "event /by eventDate"
+ throw new InvalidInputException("Missing task description");
+ }
+
+ if (description.split(" ")[(description.split(" ").length - 1)]
+ .equals("/" + cmdIdentifier)) {
+ // throws exception if invalid input format: "deadline taskName /by"
+ throw new InvalidInputException("Missing task deadline/time");
+ }
+ return cmd;
+ }
+
+ /**
+ * Returns valid Command object after input validity checks of TODO Command.
+ *
+ * @param cmd Command object with CommandType TODO.
+ * @return Input Command object if description passes input validity checks.
+ * @throws InvalidInputException If description of Command object is of incorrect format.
+ */
+ public Command checkToDoValidity(Command cmd) throws InvalidInputException {
+ if (cmd.getDescription().split(" ").length == 1) {
+ // throws exception if invalid input format: "todo" (missing task name)
+ throw new InvalidInputException("Todo command incomplete");
+ }
+ return cmd;
+ }
+
+ /**
+ * Returns valid Command object after input validity checks of DONE or
+ * DELETE Commands.
+ *
+ * @param cmd Command object with CommandType DONE or DELETE.
+ * @return Input Command object if description passes input validity checks.
+ * @throws InvalidInputException If description of Command object is of
+ * incorrect format.
+ */
+ public Command checkDoneAndDeleteValidity(Command cmd) throws InvalidInputException {
+ String description = cmd.getDescription();
+ if (description.split(" ").length == 1) {
+ // throws exception if invalid input format: "done"/"delete" (missing index)
+ throw new InvalidInputException("Task index not specified");
+ }
+
+ if (description.split(" ").length > 2) {
+ // throws exception if invalid input format: > 2 strings separated by " "
+ // e.g "done/delete 1 2 3", "done/delete 12 text"
+ throw new InvalidInputException("Sorry, command unclear!" +
+ " Please specify only one index");
+ }
+
+ int index;
+ try {
+ // parse int for index of task to be marked as done/deleted
+ index = Integer.valueOf(description.split(" ")[1]);
+ } catch (NumberFormatException e) {
+ // throws exception if invalid input format: Invalid integer
+ // e.g "done/delete abc"
+ throw new InvalidInputException("Please enter a valid integer");
+ }
+
+ return cmd;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/data/Storage.java b/src/main/java/data/Storage.java
new file mode 100644
index 0000000000..59f63db222
--- /dev/null
+++ b/src/main/java/data/Storage.java
@@ -0,0 +1,147 @@
+package data;
+
+import task.Deadline;
+import task.Event;
+import task.Task;
+import task.ToDo;
+
+import java.util.ArrayList;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Storage object handles saving and loading of data for list of Task objects.
+ * Task objects are stored as String representations in specified .txt file.
+ *
+ * @author Hakiem Rasid
+ */
+public class Storage {
+
+ private String filePath;
+
+ /**
+ * Constructor of Storage object.
+ *
+ * @param filePath Target .txt file for saving and loading of data.
+ */
+ public Storage(String filePath) {
+ this.filePath = filePath;
+ }
+
+ /**
+ * Sends data of list of Task objects as String to be saved onto
+ * target .txt file.
+ * Format of each line in txt file: done/incomplete t/d/e description [time: time]
+ *
+ * @param taskList List of Task objects to be saved.
+ */
+ public void saveData(ArrayList taskList) {
+
+ try {
+ File file = new File(this.filePath);
+
+ // if file doesn't exists, then create it
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+
+ FileWriter writer = new FileWriter(file, false);
+
+ for (Task task : taskList) {
+ StringBuilder sb = new StringBuilder();
+ if (task.isDone()) {
+ sb.append("done ");
+ } else {
+ sb.append("incomplete ");
+ }
+
+ if (task instanceof ToDo) {
+ ToDo currentTask = (ToDo) task;
+ sb.append("t " + currentTask.getName());
+ } else if (task instanceof Deadline) {
+ Deadline currentTask = (Deadline) task;
+ sb.append("d " + currentTask.getName() + " timeOfTask: " +
+ currentTask.getDeadline());
+ } else if (task instanceof Event) {
+ Event currentTask = (Event) task;
+ sb.append("e " + currentTask.getName() + " timeOfTask: " +
+ currentTask.getTime());
+ } else {
+ // do nothing
+ }
+ sb.append("\n");
+ writer.write(sb.toString());
+ } // end for loop
+ writer.flush();
+ writer.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Returns list of Task objects after loading data from .txt file.
+ * Returns empty list if no data is found.
+ *
+ * @return List of Task objects if data is found and loaded. Empty list
+ * if no data is found.
+ */
+ public ArrayList loadData() {
+ ArrayList list = new ArrayList<>();
+ try {
+ BufferedReader br = new BufferedReader(new FileReader(filePath));
+ String currentLine;
+
+ while ((currentLine = br.readLine()) != null) {
+ StringBuilder sb = new StringBuilder();
+ String[] lineArray = currentLine.split(" ");
+ boolean isDoneTask = lineArray[0].equals("done");
+ String type = lineArray[1];
+ String description;
+ String time;
+ Task newTask;
+
+ // eg. done d this is a description timeOfTask: 12-12-2020 1455
+ if (type.equals("t")) {
+ // case ToDo
+ for (int i = 2; i < lineArray.length; i++) {
+ sb.append(lineArray[i] + " ");
+ }
+ description = sb.toString().trim();
+ newTask = new ToDo(description);
+ } else {
+ // case Deadline/Event
+ for (int i = 2; i < lineArray.length; i++) {
+ if (lineArray[i].equals("timeOfTask:")) {
+ break;
+ }
+ sb.append(lineArray[i] + " ");
+ }
+ description = sb.toString().trim();
+ time = currentLine.split("timeOfTask:")[1].trim();
+ newTask = (type.equals("d"))
+ ? new Deadline(description, time)
+ : new Event(description, time);
+ }
+
+ if (isDoneTask) {
+ newTask.completeTask();
+ }
+ list.add(newTask);
+ } // end while loop
+
+ } catch (FileNotFoundException e) {
+ // returns empty ArrayList if savedata text file not found
+ return new ArrayList<>();
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+
+ return list;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/data/TaskList.java b/src/main/java/data/TaskList.java
new file mode 100644
index 0000000000..70eef88ec5
--- /dev/null
+++ b/src/main/java/data/TaskList.java
@@ -0,0 +1,100 @@
+package data;
+
+import command.Command;
+import command.CommandType;
+import exception.InvalidInputException;
+import ui.Ui;
+import task.Task;
+
+import java.util.ArrayList;
+import java.util.Scanner;
+
+/**
+ * TaskList object contains a list of Task objects that are added and edited
+ * by the user. This object has a Storage object to manage saving and
+ * loading of data.
+ *
+ * @author Hakiem Rasid
+ */
+public class TaskList {
+
+ private final Storage storage;
+ private ArrayList list;
+
+ /**
+ * Constructor for TaskList object.
+ * @param filePath Destination file for saving and loading of data.
+ */
+ public TaskList(String filePath) {
+ this.storage = new Storage(filePath);
+ this.list = storage.loadData();
+ }
+
+ /**
+ * Reads input from user and executes the appropriate commands to manipulate
+ * the list of Task objects or provide instructions to the program.
+ */
+ public void runCommands() {
+ Parser parser = new Parser();
+ Scanner sc = new Scanner(System.in);
+
+ while (true) {
+ StringBuilder sb = new StringBuilder();
+ System.out.println(Ui.horizontalLine());
+ try {
+ String input = sc.nextLine();
+ System.out.println(Ui.horizontalLine());
+ Command cmd = parser.parseCommand(input);
+ this.list = cmd.executeCommand(this.list, sb);
+ if (cmd.getType().equals(CommandType.BYE)) {
+ // exit program if user inputs "bye"
+ break;
+ }
+ } catch (InvalidInputException e) {
+ sb.append(e.getMessage() + "\n");
+ sb.append(Ui.invalidInputMessage());
+ } catch (IndexOutOfBoundsException obe) {
+ sb.append(Ui.invalidIndexMessage());
+ } finally {
+ System.out.println(sb.toString());
+ }
+ } // end while loop
+ }
+
+ /**
+ * Returns a message after executing a Command.
+ *
+ * @param input User input as String.
+ * @return Message as String.
+ */
+ public String runSingleCommand(String input) {
+ Parser parser = new Parser();
+ StringBuilder sb = new StringBuilder();
+ try {
+ Command cmd = parser.parseCommand(input);
+ this.list = cmd.executeCommand(this.list, sb);
+ } catch (InvalidInputException e) {
+ sb.append(e.getMessage());
+ sb.append("\n");
+ sb.append(Ui.invalidInputMessage());
+ } catch (IndexOutOfBoundsException obe) {
+ sb.append(Ui.invalidIndexMessage());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns List of Task objects.
+ * @return List of Task objects.
+ */
+ public ArrayList getList() {
+ return this.list;
+ }
+
+ /**
+ * Saves list of Task objects onto specified txt file.
+ */
+ public void save() {
+ storage.saveData(this.list);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/dukemain/DukeMain.java b/src/main/java/dukemain/DukeMain.java
new file mode 100644
index 0000000000..b6e9103e2e
--- /dev/null
+++ b/src/main/java/dukemain/DukeMain.java
@@ -0,0 +1,32 @@
+package dukemain;
+
+import data.TaskList;
+import ui.Ui;
+
+public class DukeMain {
+
+ private TaskList list;
+
+ public DukeMain() {
+ // data.txt will be created in same directory as duke.jar
+ // if it does not already exist
+ this.list = new TaskList("data.txt");
+ }
+
+ // For CLI Duke. But does not run anymore not sure why??
+ public static void main(String[] args) {
+ TaskList taskList = new TaskList("src/savedata/data.txt");
+ Ui.startUpMessage();
+ taskList.runCommands();
+ taskList.save();
+ }
+
+ public String getResponse(String input) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(this.list.runSingleCommand(input));
+ this.list.save();
+ return sb.toString();
+ }
+
+}
+
diff --git a/src/main/java/dukemain/Launcher.java b/src/main/java/dukemain/Launcher.java
new file mode 100644
index 0000000000..cdb68fd943
--- /dev/null
+++ b/src/main/java/dukemain/Launcher.java
@@ -0,0 +1,14 @@
+package dukemain;
+
+import javafx.application.Application;
+import ui.Main;
+
+/**
+ * A launcher class to workaround classpath issues.
+ * Credits: CS2103 JavaFX Tutorial
+ */
+public class Launcher {
+ public static void main(String[] args) {
+ Application.launch(Main.class, args);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/exception/InvalidInputException.java b/src/main/java/exception/InvalidInputException.java
new file mode 100644
index 0000000000..100107961c
--- /dev/null
+++ b/src/main/java/exception/InvalidInputException.java
@@ -0,0 +1,7 @@
+package exception;
+
+public class InvalidInputException extends Exception {
+ public InvalidInputException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/task/Deadline.java b/src/main/java/task/Deadline.java
new file mode 100644
index 0000000000..9e478ca7d3
--- /dev/null
+++ b/src/main/java/task/Deadline.java
@@ -0,0 +1,66 @@
+package task;
+
+import data.DateManager;
+
+import java.util.Date;
+import java.util.Optional;
+
+/**
+ * Deadline object is a subclass of Task object. It contains a deadline String,
+ * data.DateManager to process the deadline and an Optional to store a Date object
+ * if deadline is of a valid format.
+ *
+ * @author Hakiem Rasid
+ *
+ */
+public class Deadline extends Task {
+
+ private final String deadline;
+ private final Optional optDate;
+ private final DateManager dateManager;
+
+ /**
+ * Constructor for Deadline object.
+ *
+ * @param name Description of task.
+ * @param deadline Description of deadline for this task.
+ */
+ public Deadline(String name, String deadline) {
+ super(name);
+ this.deadline = deadline;
+ this.dateManager = new DateManager();
+ this.optDate = dateManager.getDate(deadline);
+ }
+
+ /**
+ * Returns Deadline for this task to be completed.
+ *
+ * @return Deadline as a String.
+ */
+ public String getDeadline() {
+ return this.deadline;
+ }
+
+ /**
+ * Prints a String representation of Deadline object and processes validity of deadline
+ * format to determine format of output.
+ * Clearly labels the Deadline object to be easily distinguishable from other
+ * Task objects.
+ *
+ * @return String representation of Deadline.
+ */
+ @Override
+ public String printTask() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[D]");
+ sb.append(super.printTask());
+ if (!optDate.isPresent()) {
+ sb.append(" (by: " + this.deadline + ")");
+ } else {
+ sb.append(" (by: " + dateManager.getDateAsString(deadline) + ")");
+ }
+
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/task/Event.java b/src/main/java/task/Event.java
new file mode 100644
index 0000000000..d384ca53e0
--- /dev/null
+++ b/src/main/java/task/Event.java
@@ -0,0 +1,65 @@
+package task;
+
+import data.DateManager;
+
+import java.util.Date;
+import java.util.Optional;
+
+/**
+ * Event object is a subclass of Task object. It contains a time as String,
+ * data.DateManager to process the time and an Optional to store a Date object
+ * if time is of a valid format.
+ *
+ * @author Hakiem Rasid
+ *
+ */
+public class Event extends Task {
+
+ private final String time;
+ private final Optional optTime;
+ private final DateManager dateManager;
+
+ /**
+ * Constructor for Event object.
+ *
+ * @param name Description of event.
+ * @param time Description of time of this event.
+ */
+ public Event(String name, String time) {
+ super(name);
+ this.time = time;
+ this.dateManager = new DateManager();
+ this.optTime = dateManager.getDate(time);
+ }
+
+ /**
+ * Returns time of this event.
+ *
+ * @return Time as a String.
+ */
+ public String getTime() {
+ return this.time;
+ }
+
+ /**
+ * Prints a String representation of Event object and processes validity of time
+ * format to determine format of output.
+ * Clearly labels the Deadline object to be easily distinguishable from other
+ * Task objects.
+ *
+ * @return String representation of Event.
+ */
+ @Override
+ public String printTask() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[E]");
+ sb.append(super.printTask());
+ if (!optTime.isPresent()) {
+ sb.append(" (at: " + this.time + ")");
+ } else {
+ sb.append(" (at: " + dateManager.getDateAsString(time) + ")");
+ }
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/task/Task.java b/src/main/java/task/Task.java
new file mode 100644
index 0000000000..27c936b47d
--- /dev/null
+++ b/src/main/java/task/Task.java
@@ -0,0 +1,67 @@
+package task;
+
+/**
+ * Task object contains a name or description of the task to be done and
+ * an indicator if it has been completed.
+ *
+ * @author Hakiem Rasid
+ */
+
+public class Task {
+ private final String name;
+ private boolean done;
+
+ /**
+ * Constructor for Task object
+ *
+ * @param name Description or name of the task.
+ */
+ public Task(String name) {
+ this.name = name;
+ this.done = false;
+
+ }
+
+ /**
+ * Marks the task as completed.
+ */
+ public void completeTask() {
+ this.done = true;
+ }
+
+ /**
+ * Returns name/description of the task.
+ *
+ * @return
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Returns boolean value to determine if task has been completed.
+ *
+ * @return True if task is completed. False otherwise.
+ */
+ public boolean isDone() {
+ return this.done;
+ }
+
+ /**
+ * Prints a string representation of the Task object with a tick if the
+ * task has been competed and a cross otherwise.
+ *
+ * @return String representation of Task object.
+ */
+ public String printTask() {
+ StringBuilder out = new StringBuilder();
+
+ if (this.isDone()) {
+ out.append("[/] ");
+ } else {
+ out.append("[X] ");
+ }
+ out.append(this.getName());
+ return out.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/task/ToDo.java b/src/main/java/task/ToDo.java
new file mode 100644
index 0000000000..eda3d7800c
--- /dev/null
+++ b/src/main/java/task/ToDo.java
@@ -0,0 +1,33 @@
+package task;
+
+/**
+ * ToDo object is a subclass of Task object but does not contain any extra information.
+ *
+ * @author Hakiem Rasid
+ */
+public class ToDo extends Task {
+
+ /**
+ * Constructor for ToDo object.
+ *
+ * @param name Description of this task.
+ */
+ public ToDo(String name) {
+ super(name);
+ }
+
+ /**
+ * Returns a String representation of ToDo object. Clearly labels
+ * the ToDo object to be distinguishable from other Task objects.
+ *
+ * @return String representation of ToDo object.
+ */
+ @Override
+ public String printTask() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[T]");
+ sb.append(super.printTask());
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/ui/DialogBox.java b/src/main/java/ui/DialogBox.java
new file mode 100644
index 0000000000..5fa5869e28
--- /dev/null
+++ b/src/main/java/ui/DialogBox.java
@@ -0,0 +1,79 @@
+package ui;
+
+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.Insets;
+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.Background;
+import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.CornerRadii;
+import javafx.scene.layout.HBox;
+import javafx.scene.paint.Color;
+
+/**
+ * An example of a custom control using FXML.
+ * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label
+ * containing text from the speaker.
+ * Credits: CS2103 JavaFX Tutorial.
+ */
+public class DialogBox extends HBox {
+ @FXML
+ private Label dialog;
+ @FXML
+ private ImageView displayPicture;
+
+ private DialogBox(String text, Image img) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml"));
+ fxmlLoader.setController(this);
+ fxmlLoader.setRoot(this);
+ fxmlLoader.load();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ dialog.setText(text);
+ displayPicture.setImage(img);
+
+ // set background colour
+ this.setBackground(new Background(new BackgroundFill(Color.LAVENDER,
+ new CornerRadii(8), Insets.EMPTY)));
+
+ // set padding and spacing
+ this.setPadding(new Insets(15, 12, 15, 12));
+ this.setSpacing(10);
+ }
+
+ /**
+ * 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 DialogBox getUserDialog(String text, Image img) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("\n");
+ sb.append(text);
+ sb.append("\n");
+ return new DialogBox(sb.toString(), img);
+ }
+
+ public static DialogBox getDukeDialog(String text, Image img) {
+ var db = new DialogBox(text, img);
+ db.flip();
+ return db;
+ }
+}
diff --git a/src/main/java/ui/Main.java b/src/main/java/ui/Main.java
new file mode 100644
index 0000000000..6f8762d41d
--- /dev/null
+++ b/src/main/java/ui/Main.java
@@ -0,0 +1,35 @@
+package ui;
+
+import java.io.IOException;
+
+import dukemain.DukeMain;
+
+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.
+ * Credits: CS2103 JavaFX Tutorial.
+ */
+public class Main extends Application {
+
+ private final DukeMain duke = new DukeMain();
+
+ @Override
+ public void start(Stage stage) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
+ AnchorPane ap = fxmlLoader.load();
+ Scene scene = new Scene(ap);
+ stage.setScene(scene);
+ stage.setTitle("Duke");
+ fxmlLoader.getController().setDuke(duke);
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ui/MainWindow.java b/src/main/java/ui/MainWindow.java
new file mode 100644
index 0000000000..c74c532e1e
--- /dev/null
+++ b/src/main/java/ui/MainWindow.java
@@ -0,0 +1,70 @@
+package ui;
+
+import dukemain.DukeMain;
+
+import javafx.fxml.FXML;
+import javafx.geometry.Insets;
+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.Background;
+import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.CornerRadii;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+
+/**
+ * Controller for MainWindow. Provides the layout for the other controls.
+ * Credits: CS2103 JavaFX Tutorial.
+ */
+public class MainWindow extends AnchorPane {
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
+ @FXML
+ private TextField userInput;
+ @FXML
+ private Button sendButton;
+
+ private DukeMain duke;
+
+ // Credit: https://www.pexels.com/photo/person-wearing-vr-goggles-2007647/
+ private final Image userImage = new Image(this.getClass()
+ .getResourceAsStream("/images/DaVrGuy.jpeg"));
+ // Credit: https://www.pexels.com/photo/selective-focus-photography-of-black-cat-2071881/
+ private final Image dukeImage = new Image(this.getClass()
+ .getResourceAsStream("/images/DaCat.jpeg"));
+
+ @FXML
+ public void initialize() {
+ dialogContainer.getChildren().addAll(DialogBox.getDukeDialog(Ui.chatStartMessage(),
+ dukeImage));
+ dialogContainer.setSpacing(15);
+ dialogContainer.setBackground(new Background(new BackgroundFill(Color.BEIGE,
+ CornerRadii.EMPTY, Insets.EMPTY)));
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ }
+
+ public void setDuke(DukeMain d) {
+ duke = d;
+ }
+
+ /**
+ * 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();
+ String response = duke.getResponse(input);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getDukeDialog(response, dukeImage)
+ );
+ userInput.clear();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ui/Ui.java b/src/main/java/ui/Ui.java
new file mode 100644
index 0000000000..9d42fd9cc7
--- /dev/null
+++ b/src/main/java/ui/Ui.java
@@ -0,0 +1,213 @@
+package ui;
+
+import task.Task;
+
+import java.util.Scanner;
+import java.util.ArrayList;
+
+/**
+ * Ui class handles any text or String representations to be viewed by the user.
+ *
+ * @author Hakiem Rasid
+ */
+public class Ui {
+
+ private static final String HORIZONTAL_LINE =
+ "______________________________________________________";
+
+
+ /**
+ * Returns and prints start-up message upon program execution.
+ * @return Start-up message.
+ */
+ public static String startUpMessage() {
+ StringBuilder sb = new StringBuilder();
+ String logo = " ____ _ \n"
+ + "| _ \\ _ _| | _____ \n"
+ + "| | | | | | | |/ / _ \\\n"
+ + "| |_| | |_| | < __/\n"
+ + "|____/ \\__,_|_|\\_\\___|\n";
+ sb.append("Hello from\n" + logo);
+ sb.append(Ui.HORIZONTAL_LINE + "\n");
+ sb.append("Hello I'm Duke\nWhat can I do for you?");
+ System.out.println(sb.toString());
+ return sb.toString();
+ }
+
+ public static String chatStartMessage() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Hello I'm Duke! How may I assist you?\n");
+ sb.append("Here are a list of commands you can try:\n\n");
+ sb.append("1. [todo] (description) - ");
+ sb.append("creates new todo task\n");
+ sb.append("2. [deadline] (description) [/by] (date) - ");
+ sb.append("creates new deadline task\n");
+ sb.append("3. [event] (description) [/at] (date) - ");
+ sb.append("creates new event task\n");
+ sb.append("4. [list] - shows list of tasks\n");
+ sb.append("5. [done] (index) - marks specified task as done\n");
+ sb.append("6. [delete] (index) - deletes specified task\n");
+ sb.append("7. [clear] - deletes all tasks\n");
+ sb.append("8. [find] [key] - prints sublist of tasks that match specified key\n");
+ sb.append("9. [edit] (index) [/d or /t] (new field) - ");
+ sb.append("edits task at specified index with the new field\n\n");
+ sb.append("Format: [command] (user input)");
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns horizontal line
+ *
+ * @return Horizontal line.
+ */
+ public static String horizontalLine() {
+ return Ui.HORIZONTAL_LINE;
+ }
+
+ /**
+ *
+ */
+ /**
+ * Returns goodbye message upon exiting program.
+ *
+ * @return Goodbye message as String;
+ */
+ public static String byeMessage() {
+ return "Bye. Hope to see you again soon!";
+ }
+
+ /**
+ * Prints message upon clearing list of Task objects.
+ */
+ public static String clearedListMessage() {
+ return "Task list cleared!";
+ }
+
+ /**
+ * Prints message upon failing to confirm clearing of list.
+ */
+ public static void didNotClearListMessage() {
+ System.out.println("Did NOT clear your task list! " +
+ "Is there anything else?");
+ }
+
+ /**
+ * Returns message upon successful marking of Task as done.
+ *
+ * @param task String representation of Task marked as done.
+ * @return Message as String.
+ */
+ public static String markDoneMessage(String task) {
+ return ("Nice! I have marked this task as done:\n\t" + task);
+ }
+
+ /**
+ * Returns message upon successful deletion of specified task and current size
+ * of list of Task objects.
+ *
+ * @param task String representation of Task deleted.
+ * @param size Size of list of Task objects after deletion.
+ * @return Message as String.
+ */
+ public static String deleteTaskMessage(String task, int size) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Okay! I have removed this task:\n\t" + task + "\n");
+ sb.append("Now you have " + size + " tasks in your list");
+ return sb.toString();
+ }
+
+ /**
+ * Returns message upon sucecssful editing of task.
+ * @param oldTask Old Task before editing.
+ * @param newTask New Task after editing.
+ * @return Message as String.
+ */
+ public static String editMessage(Task oldTask, Task newTask) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Okay! I have edited this task:\n\t ===>");
+ sb.append(oldTask.printTask());
+ sb.append("\n\t<=== ");
+ sb.append(newTask.printTask());
+ return sb.toString();
+ }
+
+ /**
+ * Returns message upon successful adding of new Task object to list and current
+ * size of list of Task objects.
+ *
+ * @param task String representation of Task added to list.
+ * @param size Size of list of Task objects after adding new Task.
+ * @return Message as String.
+ */
+ public static String addTaskMessage(String task, int size) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Got it! Task added to list.\n");
+ sb.append("\t" + task + "\n");
+ sb.append("Now you have " + size + " tasks in your list.");
+ return sb.toString();
+ }
+
+ /**
+ * Returns String representation of all Task objects in the input list.
+ * @param tasks List of Task objects.
+ * @param printOrFind Indicator if caller is a print or find method.
+ * @return List of all Task objects as String.
+ */
+ public static String printList(ArrayList tasks, String printOrFind) {
+ StringBuilder sb = new StringBuilder();
+ if (printOrFind.equals("print")) {
+ sb.append("Here are your tasks:\n");
+ } else {
+ sb.append("Here are your matching tasks:\n");
+ }
+
+ for (int i = 0; i < tasks.size(); i++) {
+ sb.append(i + 1 + ". " + tasks.get(i).printTask());
+ if (i != tasks.size() - 1) {
+ sb.append("\n");
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns message if FIND command does not return any matching Task objects.
+ *
+ * @return Message as String.
+ */
+ public static String noMatchMessage() {
+ return ("Sorry! There are no tasks " +
+ "that match that description.\n");
+ }
+
+ /**
+ * Prints message upon reading input of invalid format.
+ */
+ public static String invalidInputMessage() {
+ return "Please enter valid input";
+ }
+
+ /**
+ * Prints message upon reading DONE, DELETE input command
+ * of invalid format.
+ */
+ public static String invalidIndexMessage() {
+ return "Please enter valid index";
+ }
+
+ /**
+ * Returns a boolean value after user has confirmed or denied a previous
+ * instruction.
+ *
+ * @param sc Scanner object to read inputs.
+ * @return True if user confirms previous instruction. False if otherwise.
+ */
+ public static boolean promptConfirm(Scanner sc) {
+ System.out.println("Are you sure? (Y/N)");
+ System.out.println(horizontalLine());
+ String input = sc.nextLine();
+ System.out.println(horizontalLine());
+ return input.toLowerCase().equals("y");
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/images/DaCat.jpeg b/src/main/resources/images/DaCat.jpeg
new file mode 100644
index 0000000000..ab34b55024
Binary files /dev/null and b/src/main/resources/images/DaCat.jpeg differ
diff --git a/src/main/resources/images/DaVrGuy.jpeg b/src/main/resources/images/DaVrGuy.jpeg
new file mode 100644
index 0000000000..bacb27dcac
Binary files /dev/null and b/src/main/resources/images/DaVrGuy.jpeg differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..91011d3660
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..71424885f5
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/savedata/data.txt b/src/savedata/data.txt
new file mode 100644
index 0000000000..338ea9d668
--- /dev/null
+++ b/src/savedata/data.txt
@@ -0,0 +1,2 @@
+t one two three four incomplete incomplete
+e 1 2 3 4 time: 12/12/2020 1445 incomplete
diff --git a/src/test/java/ParserTest.java b/src/test/java/ParserTest.java
new file mode 100644
index 0000000000..49e8b52a6e
--- /dev/null
+++ b/src/test/java/ParserTest.java
@@ -0,0 +1,51 @@
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import command.Command;
+import command.CommandType;
+import data.Parser;
+import exception.InvalidInputException;
+import org.junit.jupiter.api.Test;
+
+public class ParserTest {
+
+ @Test
+ public void checkDeadlineAndEventValidity_validCommand_success () {
+ Command deadlineCommand = new Command(CommandType.DEADLINE,
+ "deadline hw /by 12/10/2020 1330");
+ Command eventCommand = new Command(CommandType.EVENT,
+ "event concert /at 12/12/2020 1445");
+ try {
+ assertEquals(deadlineCommand, new Parser().checkDeadlineAndEventValidity(deadlineCommand));
+ assertEquals(deadlineCommand, new Parser().checkDeadlineAndEventValidity(deadlineCommand));
+ } catch (InvalidInputException e) {
+ fail();
+ }
+
+ }
+
+ @Test
+ public void checkDeadlineAndEventValidity_invalidDeadlineMissingKeyword_exceptionThrown() {
+ Command deadlineCommand = new Command(CommandType.DEADLINE,
+ "deadline hw 12/10/2020 1330");
+ try {
+ assertEquals(deadlineCommand, new Parser().checkDeadlineAndEventValidity(deadlineCommand));
+ fail();
+ } catch (InvalidInputException e) {
+ assertEquals("Command is missing \"/by\" keyword", e.getMessage());
+ }
+ }
+
+ @Test
+ public void checkDeadlineAndEventValidity_invalidEventMissingKeyword_exceptionThrown() {
+ Command eventCommand = new Command(CommandType.EVENT,
+ "event concert 12/10/2020 1330");
+ try {
+ assertEquals(eventCommand, new Parser().checkDeadlineAndEventValidity(eventCommand));
+ fail();
+ } catch (InvalidInputException e) {
+ assertEquals("Command is missing \"/at\" keyword", e.getMessage());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..d078d9fd95 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,7 +1,119 @@
Hello from
- ____ _
-| _ \ _ _| | _____
+ ____ _
+| _ \ _ _| | _____
| | | | | | | |/ / _ \
| |_| | |_| | < __/
|____/ \__,_|_|\_\___|
+_________________________________________________________________________________________
+Hello I'm Duke
+What can I do for you?
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Got it! task.Task added to list.
+ [T][X] borrow book
+Now you have 1 tasks in your list.
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Got it! task.Task added to list.
+ [D][X] return book (by: Dec 12 2020, 02:00pm)
+Now you have 2 tasks in your list.
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Got it! task.Task added to list.
+ [E][X] group meeting (by: Dec 11 2020)
+Now you have 3 tasks in your list.
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Got it! task.Task added to list.
+ [T][X] join a club
+Now you have 4 tasks in your list.
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Here are your tasks:
+1. [T][X] borrow book
+2. [D][X] return book (by: Dec 12 2020, 02:00pm)
+3. [E][X] group meeting (by: Dec 11 2020)
+4. [T][X] join a club
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Nice! I have marked this task as done:
+ [T][/] join a club
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Nice! I have marked this task as done:
+ [T][/] borrow book
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Please enter valid index
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Nice! I have marked this task as done:
+ [D][/] return book (by: Dec 12 2020, 02:00pm)
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Here are your tasks:
+1. [T][/] borrow book
+2. [D][/] return book (by: Dec 12 2020, 02:00pm)
+3. [E][X] group meeting (by: Dec 11 2020)
+4. [T][/] join a club
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Todo command incomplete
+Please enter valid input
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+task.Event command is missing "/at" keyword
+Please enter valid input
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Missing event task description
+Please enter valid input
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Missing event date
+Please enter valid input
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Got it! task.Task added to list.
+ [E][X] watch netflix (by: Dec 12 2020, 03:45pm)
+Now you have 5 tasks in your list.
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+task.Deadline command is missing "/by" keyword
+Please enter valid input
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Missing deadline task description
+Please enter valid input
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Missing task deadline
+Please enter valid input
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Got it! task.Task added to list.
+ [D][X] assignment (by: Monday)
+Now you have 6 tasks in your list.
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Here are your tasks:
+1. [T][/] borrow book
+2. [D][/] return book (by: Dec 12 2020, 02:00pm)
+3. [E][X] group meeting (by: Dec 11 2020)
+4. [T][/] join a club
+5. [E][X] watch netflix (by: Dec 12 2020, 03:45pm)
+6. [D][X] assignment (by: Monday)
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Nice! I have marked this task as done:
+ [E][/] watch netflix (by: Dec 12 2020, 03:45pm)
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Are you sure? (Y/N)
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+task.Task list cleared!
+_________________________________________________________________________________________
+_________________________________________________________________________________________
+Bye. Hope to see you again soon!
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb2..c9ea143c98 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,24 @@
+todo borrow book
+deadline return book /by 12/12/2020 1400
+event group meeting /at 11-12-2020
+todo join a club
+list
+done 4
+done 1
+done 5
+done 2
+list
+todo
+event
+event /at 12-11-2020 1545
+event watch netflix /at
+event watch netflix /at 12/12/2020 1545
+deadline
+deadline /by Monday
+deadline assignment /by
+deadline assignment /by Monday
+list
+done 5
+clear
+Y
+bye
\ No newline at end of file
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index d0facc6310..7e9b058696 100644
--- a/text-ui-test/runtest.bat
+++ b/text-ui-test/runtest.bat
@@ -7,7 +7,7 @@ REM delete output from previous run
del ACTUAL.TXT
REM compile the code into the bin folder
-javac -cp ..\src -Xlint:none -d ..\bin ..\src\main\java\Duke.java
+javac -cp ..\src -Xlint:none -d ..\bin ..\src\dukemain\java\*.java
IF ERRORLEVEL 1 (
echo ********** BUILD FAILURE **********
exit /b 1
@@ -15,7 +15,7 @@ IF ERRORLEVEL 1 (
REM no error here, errorlevel == 0
REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT
-java -classpath ..\bin Duke < input.txt > ACTUAL.TXT
+java -classpath ..\bin dukemain.DukeMain < input.txt > ACTUAL.TXT
REM compare the output to the expected output
FC ACTUAL.TXT EXPECTED.TXT
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
old mode 100644
new mode 100755