diff --git a/README.md b/README.md index 8715d4d915..0f2208ab64 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# duke.Duke project template This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. @@ -13,7 +13,7 @@ Prerequisites: JDK 11, update Intellij to the most recent version. 1. If there are any further prompts, accept the defaults. 1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
In the same dialog, set the **Project language level** field to the `SDK default` option. -3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: +3. After that, locate the `src/main/java/duke.Duke.java` file, right-click it, and choose `Run duke.Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: ``` Hello from ____ _ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..a9549285a4 --- /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' + id 'org.openjfx.javafxplugin' version '0.0.13' +} + +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' + + 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 = "duke.Launcher" +} + +shadowJar { + archiveBaseName = "duke.Launcher" + archiveClassifier = null +} + +run{ + standardInput = System.in +} + +checkstyle{ + toolVersion = '10.2' +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..e8ee76467b --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,429 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..135ea49ee0 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 8077118ebe..b01cce1313 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,130 @@ -# User Guide +# Duke User Guide + +Duke is a chatbot that helps you to track your tasks via a **Command Line Interface (CLI)**. +Duke allows you to quickly perform tasks by typing commands into the chatbox. ## Features +- [X] Track Deadlines, Todos and Events +- [X] Mark tasks as done +- [X] Store data on your local device +- [X] Sort your task list for easy management +- [X] Search for relevant tasks -### Feature-ABC +## Installation Guide +1. Ensure that you have Java 11 installed on your computer. If you don't have it installed, you can get it +[here](https://www.oracle.com/java/technologies/downloads/#java11). +4. Download the latest release of Duke from [here](https://github.com/clydelhui/ip/releases) +5. Open a terminal in the directory which contains the `duke.Launcher.jar` file that you downloaded +6. Run the command `java -jar .\duke.Launcher.jar` -Description of the feature. +## Usage -### Feature-XYZ +### Listing items currently in the list: `list` -Description of the feature. +Lists all the items currently stored by Duke in the order it is stored. +
Format: `list` +
+
-## Usage +### Adding a todo: `todo` +Adds a todo to Duke's task list.
+Format: `todo [task]`

+Examples: +- `todo buy eggs` +- `todo feed cat` +
+
+ + +### Adding a deadline: `deadline` +Adds a deadline to Duke's task list.
+Format: `deadline [task] /[YYYY-MM-DD]`

+Examples: +- `deadline file taxes /2023-03-15` +- `deadline buy presents /2023-04-13` +
+
+ + +### Adding an event: `event` +Adds an event to Duke's task list.
+Format: `event [task] /[YYYY-MM-DD]/[YYYY-MM-DD]`

+- The first date is the start date of the event and the second is the end date +Examples: +- `event food fair /2018-03-10/2018-03-11` +- `event project showcase /2018-05-23/2018-05-26` +
+
+ +### Marking a task: `mark` +Marks an existing task as done.
+Format: `mark [task number]`
+- Marks the task as done +- The `task number` refers to the index number shown in the displayed task list. +- The index must be a positive integer starting from 1.

+ Examples: +- `mark 1` +- `mark 3` +
+
+ +### Un-marking a task: `unmark` +Un-marks an existing task as not done.
+Format: `unmark [task number]`
+- Un-marks the task +- The `task number` refers to the index number shown in the displayed task list. +- The index must be a positive integer starting from 1.

+ Examples: +- `unmark 1` +- `unmark 3` +
+
+ +### Locating a task: `find` +Find tasks whose description contain the given keyword.
+Format: `find [keyword]`
+- The search is case-sensitive.

+ Examples: +- `find assignment` returns `[T][X]todo math assignment` +- `find science` returns `[D][X]deadline science homework /2023-03-15` +
+
-### `Keyword` - Describe action -Describe the action and its outcome. +### Deleting a task: `delete` +Deletes the task at the specified task number from the task list.
+Format: `delete [task number]`
+- Deletes the task +- The `task number` refers to the index number shown in the displayed task list. +- The index must be a positive integer starting from 1.

+ Examples: +- `delete 1` +- `delete 2` +
+
-Example of usage: +### Sorting the task list: `sort` +Sorts the items in the task list by description.
+Format: `sort` +
+
-`keyword (optional arguments)` +### Exiting the program: `bye` +Exits the program after saving the data in the task list.
+Format: `bye` +
+
-Expected outcome: +### Exiting the program forcefully: `forcequit` +Exits the program even if the data cannot be saved.
+Format: `forcequit` +
+
-Description of the outcome. -``` -expected output -``` +### Saving the data +Duke's data is saved in the hard disk automatically in your local directory where your `duke.Launcher.jar` file +is saved in after any command that changes the data. You do not need to save the data manually. +
+
+*** \ No newline at end of file diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..5301158375 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/commands/AddCommand.java b/src/main/java/commands/AddCommand.java new file mode 100644 index 0000000000..cc62cc5727 --- /dev/null +++ b/src/main/java/commands/AddCommand.java @@ -0,0 +1,129 @@ +package commands; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; + +import dukeexceptions.DukeException; +import dukeexceptions.IllegalCommandException; +import dukeexceptions.IllegalInputException; +import elems.Storage; +import elems.TaskList; +import elems.Ui; +import items.Deadline; +import items.Event; +import items.Task; +import items.ToDo; + +/** + * Represents a command which adds a Task to a given TaskList + * @author clydelhui + */ +public class AddCommand extends Command { + + enum TaskType { + TODO, + DEADLINE, + EVENT + } + + private final TaskType taskType; + + /** + * Constructor that creates an AddCommand object given a keyword and + * an ArrayList of strings of parameters + * @param keyword The keyword for the AddCommand + * @param params An ArrayList containing the parameters of the command + * in String type + * @throws IllegalCommandException If the given keyword does not match any valid keywords + */ + public AddCommand(String keyword, ArrayList params) throws IllegalCommandException { + super(keyword, params); + + switch (keyword) { + case "todo": + this.taskType = TaskType.TODO; + break; + case "deadline": + this.taskType = TaskType.DEADLINE; + break; + case "event": + this.taskType = TaskType.EVENT; + break; + default: + throw new IllegalCommandException("Invalid keyword for AddCommand"); + } + } + + /** + * {@inheritDoc} + * @param tasks The TaskList to be acted on by the Command + * @param ui The Ui to be acted on by the Command + * @param storage The Storage to be acted on by the Command + * @throws DukeException When the Task is unable to be successfully added to the TaskList + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DukeException { + if (this.params.size() == 0 || this.params.size() > 3) { + throw new IllegalInputException("Wrong number of arguments to make a task!"); + } + Task addedTask; + switch (this.taskType) { + case TODO: + addedTask = makeTodo(); + break; + case DEADLINE: + addedTask = makeDeadline(); + break; + case EVENT: + addedTask = makeEvent(); + break; + default: + throw new IllegalCommandException("Unable to add task"); + } + + try { + tasks.addTask(addedTask); + storage.refreshStorage(tasks); + ui.dukeDisplay("I have successfully added the following task for you! \n" + addedTask); + } catch (IOException e) { + ui.dukeDisplay("Seems like there is something wrong with the storage file \n" + + "Any updates will not be saved!"); + ui.errorDisplay(e); + e.printStackTrace(); + } + } + + private Task makeTodo() throws IllegalInputException { + if (this.params.size() != 1) { + throw new IllegalInputException("Too many arguments for a todo!"); + } else if (this.params.get(0).isEmpty()) { + throw new IllegalInputException("Cannot make a todo without a description!"); + } + return new ToDo(this.params.get(0)); + } + + private Task makeDeadline() throws IllegalInputException { + if (this.params.size() != 2) { + throw new IllegalInputException("Wrong number of arguments for a deadline!"); + } + try { + return new Deadline(this.params.get(0), LocalDate.parse(this.params.get(1))); + } catch (DateTimeParseException e) { + throw new IllegalInputException("You have keyed in an invalid date"); + } + } + + private Task makeEvent() throws IllegalInputException { + if (this.params.size() != 3) { + throw new IllegalInputException("Wrong number of arguments for an Event!"); + } + try { + return new Event(this.params.get(0), + LocalDate.parse(this.params.get(1)), LocalDate.parse(this.params.get(2))); + } catch (DateTimeParseException e) { + throw new IllegalInputException("You have keyed in an invalid date"); + } + } +} diff --git a/src/main/java/commands/Command.java b/src/main/java/commands/Command.java new file mode 100644 index 0000000000..11378adec0 --- /dev/null +++ b/src/main/java/commands/Command.java @@ -0,0 +1,37 @@ +package commands; + +import java.util.ArrayList; + +import dukeexceptions.DukeException; +import elems.Storage; +import elems.TaskList; +import elems.Ui; + +/** + * Represents a command to be executed + * @author clydelhui + */ +public abstract class Command { + protected ArrayList params; + private final String keyword; + + /** + * Creates a command with the given keyword and parameters + * @param keyword The keyword for the intended command + * @param params An ArrayList of strings containing the parameters for the command + */ + public Command(String keyword, ArrayList params) { + this.keyword = keyword; + this.params = params; + } + + /** + * Executes the Command with the given TaskList, + * Ui and Storage + * @param tasks The TaskList to be acted on by the Command + * @param ui The Ui to be acted on by the Command + * @param storage The Storage to be acted on by the Command + * @throws DukeException when the Command is unable to be executed + */ + public abstract void execute(TaskList tasks, Ui ui, Storage storage) throws DukeException; +} diff --git a/src/main/java/commands/DeleteCommand.java b/src/main/java/commands/DeleteCommand.java new file mode 100644 index 0000000000..541a1a49ca --- /dev/null +++ b/src/main/java/commands/DeleteCommand.java @@ -0,0 +1,65 @@ +package commands; + +import java.io.IOException; +import java.util.ArrayList; + +import dukeexceptions.DukeException; +import dukeexceptions.IllegalCommandException; +import dukeexceptions.IllegalInputException; +import dukeexceptions.TaskListIndexOutOfBoundsException; +import elems.Storage; +import elems.TaskList; +import elems.Ui; +import items.Task; + +/** + * Represents a Command that deletes a task from the given TaskList + * @author clydelhui + */ +public class DeleteCommand extends Command { + + /** + * Constructor that creates a DeleteCommand object given a keyword and + * an ArrayList of strings of parameters + * @param keyword The keyword for the DeleteCommand + * @param params An ArrayList containing the parameters of the command + * in String type + * @throws IllegalCommandException If the given keyword does not match any valid keywords + */ + public DeleteCommand(String keyword, ArrayList params) throws IllegalCommandException { + super(keyword, params); + if (!"delete".equals(keyword)) { + throw new IllegalCommandException("Invalid DeleteCommand"); + } + } + + /** + * {@inheritDoc} + * @param tasks The TaskList to be acted on by the Command + * @param ui The Ui to be acted on by the Command + * @param storage The Storage to be acted on by the Command + * @throws DukeException when the Task cannot be successfully deleted from the TaskList + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DukeException { + if (this.params.size() != 1) { + throw new IllegalInputException("Wrong number of arguments for this command!"); + } + try { + int deleteIndex = Integer.parseInt(this.params.get(0)); + Task deleted = tasks.delete(deleteIndex); + storage.refreshStorage(tasks); + ui.dukeDisplay("Successfully deleted the following task! \n" + + deleted.toString()); + } catch (NumberFormatException e) { + throw new IllegalInputException("Seems like you didn't key in an integer?"); + } catch (TaskListIndexOutOfBoundsException e) { + ui.errorDisplay(e); + } catch (IOException e) { + ui.dukeDisplay("Seems like there is something wrong with the storage file \n" + + "Any updates will not be saved!"); + ui.errorDisplay(e); + e.printStackTrace(); + } + } +} diff --git a/src/main/java/commands/ListModifyCommand.java b/src/main/java/commands/ListModifyCommand.java new file mode 100644 index 0000000000..f2d4e81fb2 --- /dev/null +++ b/src/main/java/commands/ListModifyCommand.java @@ -0,0 +1,63 @@ +package commands; + +import java.io.IOException; +import java.util.ArrayList; + +import dukeexceptions.DukeException; +import dukeexceptions.EmptyTaskListException; +import dukeexceptions.IllegalCommandException; +import dukeexceptions.IllegalInputException; +import elems.Storage; +import elems.TaskList; +import elems.Ui; + +/** + * Represents a Command that modifies a TaskList without changing any of the Tasks + * within the TaskList + * @author clydelhui + */ +public class ListModifyCommand extends Command { + + /** + * Creates a new ListModifyCommand + * @param keyword The keyword for the command + * @param params The parameters for the command + * @throws IllegalCommandException when the command is not recognised as valid + */ + public ListModifyCommand(String keyword, ArrayList params) throws IllegalCommandException { + super(keyword, params); + if (!"sort".equals(keyword)) { + throw new IllegalCommandException("Invalid ListModifyCommand"); + } + } + + /** + * Executes the ListModifyCommand with the given TaskList, + * Ui and Storage + * + * @param tasks The TaskList to be acted on by the Command + * @param ui The Ui to be acted on by the Command + * @param storage The Storage to be acted on by the Command + * @throws dukeexceptions.DukeException when the Command is unable to be executed + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DukeException { + if (this.params.size() != 1 && !this.params.get(0).equals("")) { + throw new IllegalInputException("Wrong number of arguments!"); + } + if (tasks.getSize() == 0) { + throw new EmptyTaskListException("Trying to sort an empty task list"); + } + try { + tasks.sortByDescription(); + storage.refreshStorage(tasks); + ui.dukeDisplay("I have sorted the list by description!\nTry using the \"list\"" + + "command to see the new ordering of the list"); + } catch (IOException e) { + ui.dukeDisplay("Seems like there is something wrong with the storage file \n" + + "Any updates will not be saved!"); + ui.errorDisplay(e); + e.printStackTrace(); + } + } +} diff --git a/src/main/java/commands/ModifyCommand.java b/src/main/java/commands/ModifyCommand.java new file mode 100644 index 0000000000..80525ad8f6 --- /dev/null +++ b/src/main/java/commands/ModifyCommand.java @@ -0,0 +1,79 @@ +package commands; + +import java.io.IOException; +import java.util.ArrayList; + +import dukeexceptions.DukeException; +import dukeexceptions.IllegalCommandException; +import dukeexceptions.IllegalInputException; +import dukeexceptions.TaskListIndexOutOfBoundsException; +import elems.Storage; +import elems.TaskList; +import elems.Ui; + +/** + * Represents a command which modifies a Task in a given TaskList + * @author clydelhui + */ +public class ModifyCommand extends Command { + enum ModifyType { + MARK, + UNMARK + } + + private final ModifyType modifyType; + + /** + * Constructor that creates an AddCommand object given a keyword and + * an ArrayList of strings of parameters + * @param keyword The keyword for the AddCommand + * @param params An ArrayList containing the parameters of the command + * in String type + * @throws IllegalCommandException If the given keyword does not match any valid keywords + */ + public ModifyCommand(String keyword, ArrayList params) throws IllegalCommandException { + super(keyword, params); + + switch (keyword) { + case "mark": + this.modifyType = ModifyType.MARK; + break; + case "unmark": + this.modifyType = ModifyType.UNMARK; + break; + default: + throw new IllegalCommandException("Invalid keyword for ModifyCommand"); + } + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DukeException { + if (this.params.size() != 1) { + throw new IllegalInputException("Wrong number of parameters for this command!"); + } + try { + int taskIndex = Integer.parseInt(this.params.get(0)); + switch (this.modifyType) { + case MARK: + tasks.markTask(taskIndex); + break; + case UNMARK: + tasks.unmarkTask(taskIndex); + break; + default: + throw new IllegalCommandException("Invalid type of ModifyCommand"); + } + storage.refreshStorage(tasks); + ui.dukeDisplay("Task successfully modified! \n" + tasks.getTaskString(taskIndex)); + } catch (NumberFormatException e) { + throw new IllegalInputException("Invalid task item selected!"); + } catch (TaskListIndexOutOfBoundsException e) { + ui.errorDisplay(e); + } catch (IOException e) { + ui.dukeDisplay("Seems like there is something wrong with the storage file \n" + + "Any updates will not be saved!"); + ui.errorDisplay(e); + e.printStackTrace(); + } + } +} diff --git a/src/main/java/commands/VoidCommand.java b/src/main/java/commands/VoidCommand.java new file mode 100644 index 0000000000..50b0bb8ab0 --- /dev/null +++ b/src/main/java/commands/VoidCommand.java @@ -0,0 +1,122 @@ +package commands; + +import java.io.IOException; +import java.util.ArrayList; + +import dukeexceptions.DukeException; +import dukeexceptions.EmptyTaskListException; +import dukeexceptions.IllegalCommandException; +import dukeexceptions.IllegalInputException; +import elems.Storage; +import elems.TaskList; +import elems.Ui; + + +/** + * Represents a Command that does not influence the state of the TaskList + * @author clydelhui + */ +public class VoidCommand extends Command { + enum VoidType { + BYE, + FIND, + FORCEQUIT, + LIST + } + + private final VoidType voidType; + + /** + * Constructor to produce a new VoidCommand + * @param keyword The keyword for the given VoidCommand + * @param params The parameters associated with the given VoidCommand + * @throws IllegalCommandException When the given keyword is not valid + */ + public VoidCommand(String keyword, ArrayList params) throws IllegalCommandException { + super(keyword, params); + + switch (keyword) { + case "list": + this.voidType = VoidType.LIST; + break; + case "find": + this.voidType = VoidType.FIND; + break; + case "bye": + this.voidType = VoidType.BYE; + break; + case "forcequit": + this.voidType = VoidType.FORCEQUIT; + break; + default: + throw new IllegalCommandException("Invalid keyword for VoidCommand"); + } + + } + + /** + * {@inheritDoc} + * @param tasks The TaskList to be acted on by the Command + * @param ui The Ui to be acted on by the Command + * @param storage The Storage to be acted on by the Command + * @throws DukeException when unable to execute Command + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DukeException { + switch (voidType) { + case LIST: + if (tasks.getSize() == 0) { + throw new EmptyTaskListException("Trying to list an empty task list"); + } + String[] taskStringList = tasks.enumerate(); + int listSize = taskStringList.length; + StringBuilder listOutput = new StringBuilder(); + for (int i = 0; i < listSize; i++) { + listOutput.append(i + 1).append(":").append(taskStringList[i]).append("\n"); + } + ui.dukeDisplay(listOutput.toString()); + break; + case FIND: + if (this.params.size() > 2) { + throw new IllegalInputException("Only 1 word can be searched at a time"); + } + if (tasks.getSize() == 0) { + throw new EmptyTaskListException("Trying to find something in an empty list"); + } + String[] foundTasks = tasks.searchTaskDescription(this.params.get(0)); + if (foundTasks.length == 0) { + ui.dukeDisplay("Sorry, no tasks match the given search term :("); + } else { + StringBuilder findOutput = new StringBuilder("I have found the following tasks!\n"); + for (int i = 0; i < foundTasks.length; i++) { + findOutput.append(i + 1).append(":").append(foundTasks[i]).append("\n"); + } + ui.dukeDisplay(findOutput.toString()); + } + break; + case BYE: + try { + storage.refreshStorage(tasks); + System.exit(0); + } catch (IOException e) { + ui.errorDisplay(e); + e.printStackTrace(); + ui.dukeDisplay("Tasks were unable to be saved, if you still wish to quit without" + + "saving, try using \"forcequit\" "); + } + break; + case FORCEQUIT: + try { + storage.refreshStorage(tasks); + System.exit(0); + } catch (IOException e) { + ui.errorDisplay(e); + e.printStackTrace(); + System.exit(0); + } + break; + default: + throw new IllegalCommandException("No defined action to execute"); + } + } +} diff --git a/src/main/java/duke/DialogBox.java b/src/main/java/duke/DialogBox.java new file mode 100644 index 0000000000..6c72b5fbfc --- /dev/null +++ b/src/main/java/duke/DialogBox.java @@ -0,0 +1,53 @@ +package duke; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; + +/** + * Represents a Dialog Box in the Duke Ui + */ +public class DialogBox extends HBox { + private Label text; + private ImageView displayPicture; + + /** + * Creates a new dialog box + * @param l The given label for the dialog box + * @param iv The given image view for the dialog box + */ + public DialogBox(Label l, ImageView iv) { + text = l; + displayPicture = iv; + + text.setWrapText(true); + displayPicture.setFitWidth(100.0); + displayPicture.setFitHeight(100.0); + + this.setAlignment(Pos.TOP_RIGHT); + this.getChildren().addAll(text, displayPicture); + } + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + this.setAlignment(Pos.TOP_LEFT); + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + FXCollections.reverse(tmp); + this.getChildren().setAll(tmp); + } + + public static DialogBox getUserDialog(Label l, ImageView iv) { + return new DialogBox(l, iv); + } + + public static DialogBox getDukeDialog(Label l, ImageView iv) { + var db = new DialogBox(l, iv); + db.flip(); + return db; + } +} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java new file mode 100644 index 0000000000..00dd2667db --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,118 @@ +package duke; + +import commands.Command; +import dukeexceptions.DukeException; +import elems.Parser; +import elems.Storage; +import elems.TaskList; +import elems.Ui; +import javafx.application.Application; +import javafx.scene.Scene; +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.Region; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +/** + * The main program that runs the task list chatbot Duke + * @author clydelhui + */ +public class Duke extends Application { + + private ScrollPane scrollPane; + private VBox dialogContainer; + private TextField userInput; + private Button sendButton; + private Scene scene; + private Image user = new Image(this.getClass().getResourceAsStream("/images/DaUser.png")); + private Image duke = new Image(this.getClass().getResourceAsStream("/images/DaDuke.jpg")); + + @Override + public void start(Stage stage) { + //Step 1. Setting up required components + + //The container for the content of the chat to scroll. + scrollPane = new ScrollPane(); + dialogContainer = new VBox(); + scrollPane.setContent(dialogContainer); + + userInput = new TextField(); + sendButton = new Button("Send"); + + AnchorPane mainLayout = new AnchorPane(); + mainLayout.getChildren().addAll(scrollPane, userInput, sendButton); + + scene = new Scene(mainLayout); + + stage.setScene(scene); + stage.show(); + + //Step 2. Formatting the window to look as expected + stage.setTitle("Duke"); + stage.setResizable(false); + stage.setMinHeight(600.0); + stage.setMinWidth(400.0); + + mainLayout.setPrefSize(400.0, 600.0); + + scrollPane.setPrefSize(385, 535); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); + + scrollPane.setVvalue(1.0); + scrollPane.setFitToWidth(true); + + // You will need to import `javafx.scene.layout.Region` for this. + dialogContainer.setPrefHeight(Region.USE_COMPUTED_SIZE); + + userInput.setPrefWidth(325.0); + + sendButton.setPrefWidth(55.0); + + AnchorPane.setTopAnchor(scrollPane, 1.0); + + AnchorPane.setBottomAnchor(sendButton, 1.0); + AnchorPane.setRightAnchor(sendButton, 1.0); + + AnchorPane.setLeftAnchor(userInput , 1.0); + AnchorPane.setBottomAnchor(userInput, 1.0); + + Ui ui = new Ui(dialogContainer, user, duke, userInput); + ui.welcome(); + Storage storage = new Storage("data.txt", ui); + TaskList taskList = new TaskList(storage, ui); + Parser parser = new Parser(); + + + //Part 3. Add functionality to handle user input. + sendButton.setOnMouseClicked((event) -> { + handleUserInput(ui, storage, taskList, parser); + }); + + userInput.setOnAction((event) -> { + handleUserInput(ui, storage, taskList, parser); + }); + //Scroll down to the end every time dialogContainer's height changes. + dialogContainer.heightProperty().addListener((observable) -> scrollPane.setVvalue(1.0)); + } + + /** + * Iteration 2: + * 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. + */ + + private void handleUserInput(Ui ui, Storage storage, TaskList taskList, Parser parser) { + String input = ui.getInput(); + try { + Command command = parser.parse(input); + command.execute(taskList, ui, storage); + } catch (DukeException e) { + ui.errorDisplay(e); + } + } +} diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java new file mode 100644 index 0000000000..9438ec7d71 --- /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(Duke.class, args); + } +} diff --git a/src/main/java/dukeexceptions/DukeException.java b/src/main/java/dukeexceptions/DukeException.java new file mode 100644 index 0000000000..7c8cf24e23 --- /dev/null +++ b/src/main/java/dukeexceptions/DukeException.java @@ -0,0 +1,11 @@ +package dukeexceptions; + +/** + * An exception related to Duke + * @author clydelhui + */ +public class DukeException extends Exception { + public DukeException(String message) { + super(message); + } +} diff --git a/src/main/java/dukeexceptions/EmptyTaskListException.java b/src/main/java/dukeexceptions/EmptyTaskListException.java new file mode 100644 index 0000000000..1ea1b077f9 --- /dev/null +++ b/src/main/java/dukeexceptions/EmptyTaskListException.java @@ -0,0 +1,15 @@ +package dukeexceptions; + +/** + * An exception thrown when operations are performed on an empty TaskList. + */ +public class EmptyTaskListException extends DukeException { + public EmptyTaskListException(String s) { + super(s); + } + + @Override + public String toString() { + return "The Task List is currently empty!" + getMessage(); + } +} diff --git a/src/main/java/dukeexceptions/IllegalCommandException.java b/src/main/java/dukeexceptions/IllegalCommandException.java new file mode 100644 index 0000000000..b7df27c947 --- /dev/null +++ b/src/main/java/dukeexceptions/IllegalCommandException.java @@ -0,0 +1,16 @@ +package dukeexceptions; + +/** + * An exception thrown when an invalid command is created or issued by the user + * @author clydelhui + */ +public class IllegalCommandException extends DukeException { + public IllegalCommandException(String errorMessage) { + super(errorMessage); + } + + @Override + public String toString() { + return "Sorry, I do not understand your command." + getMessage(); + } +} diff --git a/src/main/java/dukeexceptions/IllegalInputException.java b/src/main/java/dukeexceptions/IllegalInputException.java new file mode 100644 index 0000000000..69c3fc0226 --- /dev/null +++ b/src/main/java/dukeexceptions/IllegalInputException.java @@ -0,0 +1,16 @@ +package dukeexceptions; + +/** + * An exception that is thrown when an Illegal input is keyed in by the user + * @author clydelhui + */ +public class IllegalInputException extends DukeException { + public IllegalInputException(String s) { + super(s); + } + + @Override + public String toString() { + return "Sorry, your input for the command is invalid:" + getMessage(); + } +} diff --git a/src/main/java/dukeexceptions/StorageException.java b/src/main/java/dukeexceptions/StorageException.java new file mode 100644 index 0000000000..ed66e3b336 --- /dev/null +++ b/src/main/java/dukeexceptions/StorageException.java @@ -0,0 +1,16 @@ +package dukeexceptions; + +/** + * An exception that is thrown when there is an issue with a Storage object. + */ +public class StorageException extends DukeException { + + public StorageException(String message) { + super(message); + } + + @Override + public String toString() { + return "There seems to be a problem with the Storage file..." + getMessage(); + } +} diff --git a/src/main/java/dukeexceptions/TaskListIndexOutOfBoundsException.java b/src/main/java/dukeexceptions/TaskListIndexOutOfBoundsException.java new file mode 100644 index 0000000000..be0edcbd54 --- /dev/null +++ b/src/main/java/dukeexceptions/TaskListIndexOutOfBoundsException.java @@ -0,0 +1,15 @@ +package dukeexceptions; + +/** + * An exception thrown when a Task outside the bounds of a TaskList is being accessed + */ +public class TaskListIndexOutOfBoundsException extends DukeException { + public TaskListIndexOutOfBoundsException(String s) { + super(s); + } + + @Override + public String toString() { + return "Sorry, the requested index is out of bounds of the TaskList" + getMessage(); + } +} diff --git a/src/main/java/elems/Parser.java b/src/main/java/elems/Parser.java new file mode 100644 index 0000000000..e3541a8d44 --- /dev/null +++ b/src/main/java/elems/Parser.java @@ -0,0 +1,66 @@ +package elems; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + +import commands.AddCommand; +import commands.Command; +import commands.DeleteCommand; +import commands.ListModifyCommand; +import commands.ModifyCommand; +import commands.VoidCommand; +import dukeexceptions.IllegalCommandException; + +/** + * A Pasrser that parses String input from the user and + * generates an appropriate Command + * @author clydelhui + */ +public class Parser { + + private static final HashSet addKeywords = new HashSet<>(Arrays.asList("todo", + "deadline", "event")); + private static final HashSet deleteKeywords = new HashSet<>(Arrays.asList("delete")); + private static final HashSet voidKeywords = new HashSet<>(Arrays.asList("list", + "bye", "forcequit", "find")); + private static final HashSet modifyKeywords = new HashSet<>(Arrays.asList("mark", "unmark")); + private static final HashSet listModifyKeywords = new HashSet<>(Arrays.asList("sort")); + private static final String COMMAND_SEPARATOR = " "; + private static final String PARAM_SEPARATOR = "/"; + + /** + * Returns an appropriate Command given the user's String input + * @param input The String input from the user + * @return The Command associated with the given String input + * @throws IllegalCommandException when an invalid input has been given + */ + public Command parse(String input) throws IllegalCommandException { + String[] commandSplit = input.split(COMMAND_SEPARATOR, 2); + String keyword = commandSplit[0]; + String paramChain = commandSplit.length == 1 ? "" : commandSplit[1]; + + String[] parsedParams = paramChain.split(PARAM_SEPARATOR); + + ArrayList params = new ArrayList<>(Arrays.asList(parsedParams)); + + Command parsedCommand; + + if (addKeywords.contains(keyword)) { + parsedCommand = new AddCommand(keyword, params); + } else if (deleteKeywords.contains(keyword)) { + parsedCommand = new DeleteCommand(keyword, params); + } else if (voidKeywords.contains(keyword)) { + parsedCommand = new VoidCommand(keyword, params); + } else if (modifyKeywords.contains(keyword)) { + parsedCommand = new ModifyCommand(keyword, params); + } else if (listModifyKeywords.contains(keyword)) { + parsedCommand = new ListModifyCommand(keyword, params); + } else { + throw new IllegalCommandException("You have entered an invalid command"); + } + + return parsedCommand; + + } +} diff --git a/src/main/java/elems/Storage.java b/src/main/java/elems/Storage.java new file mode 100644 index 0000000000..241aa6274c --- /dev/null +++ b/src/main/java/elems/Storage.java @@ -0,0 +1,69 @@ +package elems; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Represents the object that stores information in a data file + * @author clydelhui + */ +public class Storage { + private final File dataFile; + + /** + * Generates a Storage given the path to a data file. + * If the file does not exist, a new file will be created. + * @param filePathName The path to the data file + * @param ui A Ui object to display the messages depending on the status + * of the data file after construction + */ + public Storage(String filePathName, Ui ui) { + this.dataFile = new File(filePathName); + try { + boolean createdFile = dataFile.createNewFile(); + if (createdFile) { + ui.dukeDisplay("Data file successfully created!"); + } else { + ui.dukeDisplay("Data file already exists!"); + } + } catch (IOException e) { + ui.errorDisplay(e); + } + } + + /** + * Stores the TaskList into the Storage file + * @param taskList The TaskList that contains the tasks to store + * @throws IOException when there is a problem with reading or writing to the file + */ + public void refreshStorage(TaskList taskList) throws IOException { + FileWriter clearFile = new FileWriter(this.dataFile, false); + clearFile.close(); + FileWriter writer = new FileWriter(this.dataFile); + writer.write(taskList.getListStorageText()); + writer.close(); + + } + + /** + * Generates an ArrayList of String with the elements being the text storage format + * of the tasks in the TaskList + * @return An ArrayList of String with the elements being the text storage format + * * of the tasks in the TaskList + * @throws FileNotFoundException when the file to load the text from is not found + */ + public ArrayList load() throws FileNotFoundException { + ArrayList taskText = new ArrayList<>(); + Scanner scanner = new Scanner(dataFile); + while (scanner.hasNextLine()) { + taskText.add(scanner.nextLine()); + } + scanner.close(); + return taskText; + } + +} diff --git a/src/main/java/elems/TaskList.java b/src/main/java/elems/TaskList.java new file mode 100644 index 0000000000..49808d3104 --- /dev/null +++ b/src/main/java/elems/TaskList.java @@ -0,0 +1,176 @@ +package elems; + +import java.io.FileNotFoundException; +import java.time.LocalDate; +import java.util.ArrayList; + +import dukeexceptions.StorageException; +import dukeexceptions.TaskListIndexOutOfBoundsException; +import items.Deadline; +import items.Event; +import items.Task; +import items.ToDo; + +/** + * Represents the object that stores the Tasks + * @author clydelhui + */ +public class TaskList { + private ArrayList taskList; + + /** + * Generates a new TaskList object which loads from the given Storage + * @param storage The storage to load the data from + * @param ui The Ui to display error messages with + */ + public TaskList(Storage storage, Ui ui) { + try { + loadFromStorageText(storage.load()); + } catch (FileNotFoundException | StorageException e) { + ui.errorDisplay(e); + ui.dukeDisplay("Issue with the storage file, generating empty list!"); + this.taskList = new ArrayList<>(); + } + + } + + /** + * Deletes the Task at a given index (1 based indexing) from the TaskList + * @param index The index of the Task to be deleted + * @return The deleted Task + * @throws TaskListIndexOutOfBoundsException if the index given is out of bounds of the TaskList + */ + public Task delete(int index) throws TaskListIndexOutOfBoundsException { + assert this.taskList != null; + try { + return this.taskList.remove(index - 1); + } catch (IndexOutOfBoundsException e) { + throw new TaskListIndexOutOfBoundsException("Tried to delete a task that is out of task list bounds"); + } + + } + + /** + * Generates a string array containing the String representation of + * all the Tasks in the TaskList + * @return A string array containing the String representation of + * * all the Tasks in the TaskList + */ + public String[] enumerate() { + assert this.taskList != null; + int length = this.taskList.size(); + String[] taskStringList = new String[length]; + for (int i = 0; i < length; i++) { + taskStringList[i] = taskList.get(i).toString(); + } + return taskStringList; + } + + /** + * Adds a new Task to the TaskList + * @param newTask The Task to be added. + */ + public void addTask(Task newTask) { + assert this.taskList != null; + this.taskList.add(newTask); + } + + /** + * Marks the Task at a given index (1 based indexing) + * @param index The index of the Task + * @throws TaskListIndexOutOfBoundsException when the index is out of bounds of the TaskList + */ + public void markTask(int index) throws TaskListIndexOutOfBoundsException { + assert this.taskList != null; + try { + Task mark = this.taskList.get(index - 1); + mark.setDone(); + } catch (IndexOutOfBoundsException e) { + throw new TaskListIndexOutOfBoundsException("Tried to mark task that is out of task list bounds"); + } + } + + /** + * Unmarks the Task at a given index(1 based indexing) + * @param index The index of the Task to be unmarked + * @throws TaskListIndexOutOfBoundsException when an index is out of bounds of the given TaskList + */ + public void unmarkTask(int index) throws TaskListIndexOutOfBoundsException { + assert this.taskList != null; + try { + Task unmark = this.taskList.get(index - 1); + unmark.setNotDone(); + } catch (IndexOutOfBoundsException e) { + throw new TaskListIndexOutOfBoundsException("Tried to unmark task that is out of task list bounds"); + } + } + + public String getListStorageText() { + StringBuilder output = new StringBuilder(); + for (Task task : this.taskList) { + String taskText = task.generateStorageForm() + System.lineSeparator(); + output.append(taskText); + } + return output.toString(); + } + + public String getTaskString(int index) { + Task task = this.taskList.get(index - 1); + return task.toString(); + } + + /** + * Returns a String array which contains the String representations of the Tasks in the TaskList + * with descriptions that contain the given String search term + * @param searchTerm The search term to find in the descriptions of the Tasks + * @return A String array which contains the String representations of the Tasks in the TaskList + * with descriptions that contain the given String search term + */ + public String[] searchTaskDescription(String searchTerm) { + assert this.taskList != null; + ArrayList matchingItems = new ArrayList<>(); + for (Task current : this.taskList) { + String currentDescription = current.getDescription(); + String[] parsedDescription = currentDescription.split(" "); + for (String word : parsedDescription) { + if (word.equals(searchTerm)) { + matchingItems.add(current.toString()); + } + } + } + String[] result = new String[matchingItems.size()]; + matchingItems.toArray(result); + return result; + } + private void loadFromStorageText(ArrayList storageText) throws StorageException { + this.taskList = new ArrayList<>(); + String[] taskStringArray = storageText.toArray(new String[0]); + + for (String taskText : taskStringArray) { + String[] parsedInput = taskText.split("@"); + + boolean isDone = parsedInput[2].equals("X"); + switch (parsedInput[0]) { + case "T": + this.taskList.add(new ToDo(parsedInput[1], isDone)); + break; + case "D": + this.taskList.add(new Deadline(parsedInput[1], isDone, LocalDate.parse(parsedInput[3]))); + break; + case "E": + this.taskList.add(new Event(parsedInput[1], isDone, LocalDate.parse(parsedInput[3]), + LocalDate.parse(parsedInput[4]))); + break; + default: + throw new StorageException("Corrupted data file!"); + } + } + } + + public void sortByDescription() { + this.taskList.sort(null); + } + public int getSize() { + return this.taskList.size(); + } +} diff --git a/src/main/java/elems/Ui.java b/src/main/java/elems/Ui.java new file mode 100644 index 0000000000..0a76ccfc5c --- /dev/null +++ b/src/main/java/elems/Ui.java @@ -0,0 +1,70 @@ +package elems; + +import duke.DialogBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; + +/** + * Represents the User Interface that takes in input and displays output to the user + * @author clydelhui + */ +public class Ui { + + private static final String logo = " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n"; + + private final VBox dialogContainer; + private final Image userImage; + private final Image dukeImage; + private final TextField userInput; + + /** + * Creates a new Ui given the JavaFX elements + * @param dialogContainer A container to store the dialog elements + * @param userImage The image that represents the User's avatar + * @param dukeImage The image that represents Duke's avatar + * @param userInput The text box which the user uses to key in text to the program + */ + public Ui(VBox dialogContainer, Image userImage, Image dukeImage, TextField userInput) { + this.dialogContainer = dialogContainer; + this.userImage = userImage; + this.dukeImage = dukeImage; + this.userInput = userInput; + } + + /** + * Displays the given text as a message from Duke + * @param displayText The text to be displayed + */ + public void dukeDisplay(String displayText) { + Label dukeText = new Label(displayText); + dialogContainer.getChildren().addAll( + DialogBox.getDukeDialog(dukeText, new ImageView(dukeImage)) + ); + } + + public String getInput() { + String input = userInput.getText(); + Label userText = new Label(input); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(userText, new ImageView(userImage)) + ); + userInput.clear(); + return input; + } + + public void welcome() { + dukeDisplay("Hello from\n" + Ui.logo); + } + + public void errorDisplay(Exception exception) { + dukeDisplay(exception.getMessage()); + } + +} diff --git a/src/main/java/items/Deadline.java b/src/main/java/items/Deadline.java new file mode 100644 index 0000000000..ee058cc825 --- /dev/null +++ b/src/main/java/items/Deadline.java @@ -0,0 +1,44 @@ +package items; + +import java.time.LocalDate; + +/** + * Represents a Deadline task + * @author clydelhui + */ +public class Deadline extends Task { + private final LocalDate endDate; + + /** + * Creates a Deadline with the given description and end date + * @param description The description of the Deadline + * @param endDate the end date of the Deadline + */ + public Deadline(String description, LocalDate endDate) { + super(description, "D"); + this.endDate = endDate; + } + + /** + * Creates a Deadline with the given description, status and end date + * @param description The description of the Deadline + * @param isDone A boolean which indicates if the Deadline is done + * @param endDate + */ + public Deadline(String description, boolean isDone, LocalDate endDate) { + super(description, "D", isDone); + this.endDate = endDate; + } + + @Override + public String generateStorageForm() { + return this.getTaskType() + "@" + this.getDescription() + "@" + + this.getStatusIcon() + "@" + this.endDate; + } + + @Override + public String toString() { + return "[" + this.getTaskType() + "]" + "[" + this.getStatusIcon() + "]" + + this.description + "/" + this.endDate; + } +} diff --git a/src/main/java/items/Event.java b/src/main/java/items/Event.java new file mode 100644 index 0000000000..c4e8e9ab8d --- /dev/null +++ b/src/main/java/items/Event.java @@ -0,0 +1,49 @@ +package items; + +import java.time.LocalDate; + +/** + * Represents an Event task + * @author clydelhui + */ +public class Event extends Task { + private final LocalDate startDate; + private final LocalDate endDate; + + /** + * Creates a new Event with the given description, start date and end date + * @param description The description of the Event + * @param startDate The start date of the Event + * @param endDate The end date of the Event + */ + public Event(String description, LocalDate startDate, LocalDate endDate) { + super(description, "E"); + this.startDate = startDate; + this.endDate = endDate; + } + + /** + * Creates a new Event with the given description, status, start date and end date + * @param description The description of the Event + * @param isDone A boolean indicating the status of the Event + * @param startDate The start date of the Event + * @param endDate The end date of the Event + */ + public Event(String description, boolean isDone, LocalDate startDate, LocalDate endDate) { + super(description, "E", isDone); + this.startDate = startDate; + this.endDate = endDate; + } + + @Override + public String generateStorageForm() { + return this.getTaskType() + "@" + this.getDescription() + "@" + this.getStatusIcon() + "@" + + this.startDate + "@" + this.endDate; + } + + @Override + public String toString() { + return "[" + this.getTaskType() + "]" + "[" + this.getStatusIcon() + "]" + + this.description + "/" + this.startDate + "/" + this.endDate; + } +} diff --git a/src/main/java/items/Task.java b/src/main/java/items/Task.java new file mode 100644 index 0000000000..fea16a27a3 --- /dev/null +++ b/src/main/java/items/Task.java @@ -0,0 +1,66 @@ +package items; + +/** + * An abstract class the represents the types of tasks that can be added to the list + * @author clydelhui + */ +public abstract class Task implements Comparable { + protected String description; + protected boolean isDone; + protected String taskType; + + /** + * Creates a Task with the given description and type of task + * @param description The description of the Task + * @param taskType A string representing the type of Task + * it is included in the String representation of the Task + */ + public Task(String description, String taskType) { + this.description = description; + this.isDone = false; + this.taskType = taskType; + } + + /** + * Creates a Task with the given description, status and type of task + * @param description The description of the Task + * @param isDone A boolean indicating if the Task is done + * @param taskType A string representing the type of Task + * it is included in the String representation of the Task + */ + public Task(String description, String taskType, boolean isDone) { + this.description = description; + this.isDone = isDone; + this.taskType = taskType; + } + + public String getStatusIcon() { + return (isDone ? "X" : " "); // mark done task with X + } + + public String getTaskType() { + return this.taskType; + } + + public void setDone() { + this.isDone = true; + } + + public void setNotDone() { + this.isDone = false; + } + + public String getDescription() { + return this.description; + } + + public abstract String generateStorageForm(); + + @Override + public abstract String toString(); + + @Override + public int compareTo(Task o) { + return this.description.compareTo(o.getDescription()); + } +} diff --git a/src/main/java/items/ToDo.java b/src/main/java/items/ToDo.java new file mode 100644 index 0000000000..a1620467d8 --- /dev/null +++ b/src/main/java/items/ToDo.java @@ -0,0 +1,25 @@ +package items; + +/** + * Represents a to do task + * @author clydelhui + */ +public class ToDo extends Task { + public ToDo(String description) { + super(description, "T"); + } + + public ToDo(String description, boolean isDone) { + super(description, "T", isDone); + } + + @Override + public String generateStorageForm() { + return this.getTaskType() + "@" + this.getDescription() + "@" + this.getStatusIcon(); + } + + @Override + public String toString() { + return "[" + this.getTaskType() + "]" + "[" + this.getStatusIcon() + "]" + this.description; + } +} diff --git a/src/main/resources/images/DaDuke.jpg b/src/main/resources/images/DaDuke.jpg new file mode 100644 index 0000000000..0bb4811853 Binary files /dev/null and b/src/main/resources/images/DaDuke.jpg differ diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png new file mode 100644 index 0000000000..581e5be7ac Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/test/java/commands/AddCommandTest.java b/src/test/java/commands/AddCommandTest.java new file mode 100644 index 0000000000..fcfe52b3f4 --- /dev/null +++ b/src/test/java/commands/AddCommandTest.java @@ -0,0 +1,21 @@ +package commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; + +import org.junit.jupiter.api.Test; + +import dukeexceptions.IllegalCommandException; + +public class AddCommandTest { + + @Test + public void invalidKeywordTest() { + try { + AddCommand addCommand = new AddCommand("bob", new ArrayList<>()); + } catch (IllegalCommandException e) { + assertEquals("Invalid keyword for AddCommand", e.getMessage()); + } + } +} diff --git a/src/test/java/elems/ParserTest.java b/src/test/java/elems/ParserTest.java new file mode 100644 index 0000000000..e05cdc4444 --- /dev/null +++ b/src/test/java/elems/ParserTest.java @@ -0,0 +1,22 @@ +package elems; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import dukeexceptions.IllegalCommandException; + +public class ParserTest { + @Test + public void invalidKeywordTest() { + Parser parser = new Parser(); + String testInput = "what 1 2 3"; + + try { + parser.parse(testInput); + } catch (IllegalCommandException e) { + assertEquals("You have entered an invalid command", e.getMessage()); + } + + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..2b279af49f 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -5,3 +5,29 @@ Hello from | |_| | |_| | < __/ |____/ \__,_|_|\_\___| +added: +[T][ ]buy vegetables +added: +[T][ ]watch football +I have set the following task to done: +[T][X]watch football +added: +[D][ ]some homework / tomorrow +1:[T][ ]buy vegetables +2:[T][X]watch football +3:[D][ ]some homework / tomorrow +added: +[E][ ]some other homework / today / tomorrow +1:[T][ ]buy vegetables +2:[T][X]watch football +3:[D][ ]some homework / tomorrow +4:[E][ ]some other homework / today / tomorrow +I have set the following task to done: +[D][X]some homework / tomorrow +I have set the following task to not done: +[T][ ]watch football +1:[T][ ]buy vegetables +2:[T][ ]watch football +3:[D][X]some homework / tomorrow +4:[E][ ]some other homework / today / tomorrow +Goodbye! diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..714add8b70 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,11 @@ +todo buy vegetables +todo watch football +mark 2 +deadline some homework / tomorrow +list +event some other homework / today / tomorrow +list +mark 3 +unmark 2 +list +bye \ No newline at end of file diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 0873744649..320ae291da 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -8,6 +8,8 @@ if exist ACTUAL.TXT del ACTUAL.TXT REM compile the code into the bin folder javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\*.java +javac -cp ..\src\main\java\items -Xlint:none -d ..\bin ..\src\main\java\items\*.java +javac -cp ..\src\main\java\dukeexceptions -Xlint:none -d ..\bin ..\src\main\java\dukeexceptions\*.java IF ERRORLEVEL 1 ( echo ********** BUILD FAILURE ********** exit /b 1 @@ -15,7 +17,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 duke.Duke < 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