diff --git a/README.md b/README.md deleted file mode 100644 index 8715d4d915..0000000000 --- a/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# 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. - -## Setting up in Intellij - -Prerequisites: JDK 11, update Intellij to the most recent version. - -1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first) -1. Open the project into Intellij as follows: - 1. Click `Open`. - 1. Select the project directory, and click `OK`. - 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: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..93ac77cbd0 --- /dev/null +++ b/build.gradle @@ -0,0 +1,67 @@ +plugins { + id 'java' + id 'checkstyle' + id 'application' + id 'com.github.johnrengelman.shadow' version '7.1.2' + id 'org.openjfx.javafxplugin' version '0.0.13' +} + +checkstyle { + toolVersion = '10.2' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'com.google.code.gson:gson:2.10.1' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' + + // JavaFX 17.0.7 dependencies for macOS on ARM64 + implementation 'org.openjfx:javafx-base:17.0.7:mac-aarch64' + implementation 'org.openjfx:javafx-controls:17.0.7:mac-aarch64' + implementation 'org.openjfx:javafx-fxml:17.0.7:mac-aarch64' + implementation 'org.openjfx:javafx-graphics:17.0.7:mac-aarch64' + + // JavaFX 17.0.7 dependencies for Linux + implementation 'org.openjfx:javafx-base:17.0.7:linux' + implementation 'org.openjfx:javafx-controls:17.0.7:linux' + implementation 'org.openjfx:javafx-fxml:17.0.7:linux' + implementation 'org.openjfx:javafx-graphics:17.0.7:linux' + + // JavaFX 17.0.7 dependencies for Windows + implementation 'org.openjfx:javafx-base:17.0.7:win' + implementation 'org.openjfx:javafx-controls:17.0.7:win' + implementation 'org.openjfx:javafx-fxml:17.0.7:win' + implementation 'org.openjfx:javafx-graphics:17.0.7:win' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClass.set("woofwoof.Launcher") +} + +shadowJar { + archiveBaseName = "woofwoof" + archiveClassifier = null + dependsOn("distZip", "distTar") +} + +run{ + standardInput = System.in +} \ No newline at end of file diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..d732949de8 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,435 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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/GFMDPractice.md b/docs/GFMDPractice.md new file mode 100644 index 0000000000..d2eecaa2e8 --- /dev/null +++ b/docs/GFMDPractice.md @@ -0,0 +1,74 @@ +>THIS DOCUMENT WAS CREATED SOLELY FOR PRACTICE PURPOSES. + +# Duke project +## Hello from +``` + ██╗██╗███╗ ██╗ ██████╗ ███████╗██╗ ██╗███████╗███╗ ██╗ ██████╗ + ██║██║████╗ ██║██╔════╝ ██╔════╝██║ ██║██╔════╝████╗ ██║██╔════╝ + ██║██║██╔██╗ ██║██║ ███╗ ███████╗███████║█████╗ ██╔██╗ ██║██║ ███╗ + ██ ██║██║██║╚██╗██║██║ ██║ ╚════██║██╔══██║██╔══╝ ██║╚██╗██║██║ ██║ + ╚█████╔╝██║██║ ╚████║╚██████╔╝ ███████║██║ ██║███████╗██║ ╚████║╚██████╔╝ + ╚════╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═════╝ +``` +## Project Description +This is my project, created from a template for a greenfield Java project. +It's named after the Java mascot _Duke_. +This project is a Java application that aims to provide a user-friendly task management system. It allows users to manage their tasks efficiently and keep track of their schedules. + +## Features + +- **Task Creation**: Users can add various types of tasks, including todo, deadline, and event tasks. +- **Task List**: View a list of all tasks, including their descriptions and completion status. +- **Task Completion**: Mark tasks as complete when they are done. +- **Task Deletion**: Remove tasks that are no longer needed. +- **Task Searching**: Search for tasks based on keywords. +- **Data Storage**: Data is automatically saved and loaded for seamless usage. + +## Installation Option 1 + +1. Go to tjingsheng ip [releases](https://github.com/tjingsheng/ip/releases) +2. **Get Java**: Ensure you have a functioning version of Java. +3. **Copy the JAR File**: Copy the JAR file you want to run into an empty folder. +4. **Open a Command Window**: Open a command prompt (Windows) or terminal (macOS/Linux) in the same folder where you copied the JAR file. +5. **Run the Command**: Use the following command to run the JAR file: +6. **Enjoy**: Duke is very fun. + + ```shell + java -jar duke.jar + +## Installation Option 2 + +1. Clone the tjingsheng ip [repository](https://github.com/tjingshengg/ip) to your local machine. +2. Open the project in your preferred Java development environment. +3. Build and run the application. +4. Enjoy + +## Todo List + +- [x] Task 1 : Get woof +- [ ] Task 2 : Experiment with it +- [ ] Task 3 : Profit ??? +- [ ] Task 4 : ~~Cry~~ + +## Project Sample +``` + /** + * Runs the Duke application, allowing the user to interact with it via the command line. + */ + public static void runDuke() { + Scanner scanner = new Scanner(System.in); + Ui.helloWorld(); + boolean isConversing = true; + while (isConversing) { + String rawCommand = Ui.getUserInput(scanner); + TaskList taskList = TaskFileHandler.readFromFile(); + Ui.getBotMessage(); + Command command = Parser.parse(rawCommand); + command.execute(taskList); + TaskFileHandler.saveToFile(taskList); + isConversing = !command.isByeCommand(); + } + } +``` + +>>>>> "Duke hehe Duke hehe" - Jing Sheng diff --git a/docs/README.md b/docs/README.md index 8077118ebe..ee23528eb9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,262 @@ -# User Guide +# Woof Woof Bot User Guide -## Features +Welcome to WoofWoof Bot, your personal task manager! This bot can help you keep track of your to-do tasks, deadlines, events, and more. -### Feature-ABC +![woofwoof1.png](woofwoof1.png) +![woofwoof2.png](woofwoof2.png) +![woofwoof3.png](woofwoof3.png) +![woofwoof4.png](woofwoof4.png) -Description of the feature. +## Table of Contents +- [Purpose of the User Guide](#purpose-of-the-user-guide) +- [Getting Started](#getting-started) + - [Installation Option 1](#installation-option-1) + - [Installation Option 2](#installation-option-2) +- [Quick Start Commands](#quick-start-commands) + - [Todo](#1-todo) + - [Deadline](#2-deadline) + - [Event](#3-event) + - [Mark](#4-mark) + - [Unmark](#5-unmark) + - [Delete](#6-delete) + - [List](#7-list) + - [Find](#8-find) + - [Sort](#9-sort) + - [Bye](#10-bye) + - [Help](#11-help) +- [Notes](#notes) -### Feature-XYZ +## Purpose of the User Guide -Description of the feature. +This User Guide serves as your go-to resource for understanding and effectively using WoofWoof Bot. Whether you're new to the bot or just need a quick refresher, you'll find everything you need right here. -## Usage +## Getting Started -### `Keyword` - Describe action +Before you dive into the nitty-gritty of task management, it's essential to set up WoofWoof Bot correctly. You have two installation options to choose from: -Describe the action and its outcome. +### Installation Option 1 -Example of usage: +1. **Go to** tjingsheng ip [releases](https://github.com/tjingsheng/ip/releases) +2. **Get Java**: Ensure you have a functioning version of Java. +3. **Copy the JAR File**: Copy the JAR file you want to run into an empty folder. +4. **Open a Command Window**: Open a command prompt (Windows) or terminal (macOS/Linux) in the same folder where you copied the JAR file. +5. **Run the Command**: Use the following command to run the JAR file:\ + Run the GUI version: ```java -jar woofwoof```\ + Run the CLI version: ```java -jar woofwoof cli``` +6. **Enjoy**: WoofWoof is very fun. -`keyword (optional arguments)` +### Installation Option 2 -Expected outcome: +1. Clone the tjingsheng ip [repository](https://github.com/tjingsheng/ip) to your local machine. +2. Open the project in your preferred Java development environment. +3. Go to the target main directory:\ + GUI version: ```\main\java\WoofWoof\Launcher```\ + CLI version: ```\main\java\Woof``` +5. Run the main class +4. Enjoy -Description of the outcome. +## Quick Start Commands +### 1. Todo +Add a simple to-do task. +>Format: `todo `\ +>Example: `todo study` + +Expected Output: +``` +Got it. I've added this task: + [T][ ] study +You have 1 tasks in the task list. +``` + +### 2. Deadline +Add a task with a deadline. +>Format: `deadline /by `\ +>Example: `deadline assignment /by 2023-12-31` + +Expected Output: +``` +Doggo: +Got it. I've added this task: + [D][ ] assignment + ~By: 31 Dec 2023 +You have 2 tasks in the task list. +``` + +### 3. Event +Add an event with a start and end date. +>Format: `event /from /to `\ +>Example: `event enjoy /from 2023-11-25 /to 2024-01-14` + +Expected Output: +``` +Got it. I've added this task: + [E][ ] enjoy + ~From: 25 Nov 2023 + ~To : 14 Jan 2024 +You have 3 tasks in the task list. +``` + +### 4. Mark +Mark a task as completed. +>Format: `mark `\ +>Example: `mark 2` + +Expected Output: +``` +Ok! I've marked this task as done: + [D][X] assignment + ~By: 31 Dec 2023 +You have 3 tasks in the task list. +``` + + +### 5. Unmark +Unmark a completed task as incomplete. +>Format: `unmark `\ +>Example: `unmark 2` + +Expected Output: +``` +Ok! I've marked this task as undone: + [D][ ] assignment + ~By: 31 Dec 2023 +You have 3 tasks in the task list. +``` + +### 6. Delete +Delete a task. +>Format: `delete `\ +>Example: `delete 2` + +Expected Output: +``` +Ok! I've deleted this task: + [D][ ] assignment + ~By: 31 Dec 2023 +You have 1 tasks in the task list. +``` + +### 7. List +View all your tasks. +>Format: `list` +>Example: `list` + +Expected Output: +``` +Here are all the tasks in your list: + 1. [T][ ] study + 2. [E][ ] enjoy + ~From: 25 Nov 2023 + ~To : 14 Jan 2024 +You have 2 tasks in the task list. +``` + +### 8. Find +Search for tasks containing n keywords. +>Format: `find ... `\ +>Example: `find study` + +Expected Output: ``` -expected output +Here are your matching tasks in your list: + 1. [T][ ] study +You have 2 tasks in the task list. ``` + +### 9. Sort +Sort tasks by date and time. +>Format: `sort` +>Example: `sort` + +Expected Output +``` +Your tasks have been sorted: + 1. [E][ ] enjoy + ~From: 25 Nov 2023 + ~To : 14 Jan 2024 + 2. [T][ ] study +You have 2 tasks in the task list. +``` + +### 10. Bye +Say goodbye to WoofWoof Bot. +>Format: `bye` +>Example: `bye` + +Expected Output +``` +Bye. Hope to see you again soon! +Offing myself... woof :( +``` + +### 11. Help +Request help on how to use the bot. +>Format: `help` +>Example: `help` + +Expected Output +``` +Woof! Woof! Here to help: + +Available Commands: + +1. Todo : Add a simple to-do task. + Format : `todo ` + Example : `todo study` + +2. Deadline : Add a task with a deadline. + Format : `deadline /by ` + Example : `deadline assignment /by 2023-12-31` + +3. Event : Add an event with a start and end date. + Format : `event /from /to ` + Example : `event enjoy /from 2023-11-25 /to 2024-01-14` + +4. Mark : Mark a task as completed. + Format : `mark ` + Example : `mark 1` + +5. Unmark : Unmark a completed task as incomplete. + Format : `unmark ` + Example : `unmark 1` + +6. Delete : Delete a task. + Format : `delete ` + Example : `delete 2` + +7. List : View all your tasks. + Format : `list` + +8. Find : Search for tasks containing n keywords. + Format : `find ... ` + Example : `find meeting project` + +9. Sort : Sort tasks by date and time. + Format : `sort` + +10. Bye : Say goodbye to WoofWoof Bot. + Format : `bye` + +11. Help : Request help on how to use the bot. + Format : `help` + +Notes: +- Replace `` with a task description. +- `` should follow the yyyy-mm-dd date format. + e.g. 2023-12-31 +- `` should be the index of the task in the list + you want to manage. + +Feel free to ask for help using the `help` command if you +have any questions or encounter issues. +Woof Woof is here to make your life easier! +``` + +### Notes +- Replace `` with a brief task description. +- `` should follow the yyyy-mm-dd date format (e.g., 2023-12-31). +- `` is the index of the task in the list you want to manage. + +That's it! You're now ready to start using WoofWoof Bot to stay organised and manage your tasks efficiently. + diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..f3226de8a9 Binary files /dev/null and b/docs/Ui.png differ diff --git a/docs/woofwoof1.png b/docs/woofwoof1.png new file mode 100644 index 0000000000..7cf3b3baa5 Binary files /dev/null and b/docs/woofwoof1.png differ diff --git a/docs/woofwoof2.png b/docs/woofwoof2.png new file mode 100644 index 0000000000..53c85101fe Binary files /dev/null and b/docs/woofwoof2.png differ diff --git a/docs/woofwoof3.png b/docs/woofwoof3.png new file mode 100644 index 0000000000..f3e7b64a81 Binary files /dev/null and b/docs/woofwoof3.png differ diff --git a/docs/woofwoof4.png b/docs/woofwoof4.png new file mode 100644 index 0000000000..30c5544b2c Binary files /dev/null and b/docs/woofwoof4.png differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..62f495dfed --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..fcb6fca147 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..6689b85bee --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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=. +@rem This is normally unused +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% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +: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/ByeCommand.java b/src/main/java/command/ByeCommand.java new file mode 100644 index 0000000000..1ebe1b1aaa --- /dev/null +++ b/src/main/java/command/ByeCommand.java @@ -0,0 +1,58 @@ +package command; + +import enums.CommandWord; +import enums.WoofMessage; +import exceptions.WoofInvalidCommandException; +import parser.Parser; +import tasks.TaskList; + +/** + * The `ByeCommand` class represents a command to exit the application. + * When executed, it displays a bye message to the user. + */ +public class ByeCommand extends Command { + + /** + * Constructs a new `ByeCommand` with the specified raw command string. + * + * @param rawCommand The raw command string. + */ + public ByeCommand(String rawCommand) { + super(rawCommand); + } + + /** + * Validates the `ByeCommand`. + * It checks if the command is correctly formatted and if the specified task index is valid. + * + * @param rawCommand The raw command string. + * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an + * error message. + */ + public static void validate(String rawCommand) { + assert rawCommand != null : "raw command cannot be null"; + + String[] args = Parser.getArgs(rawCommand); + validateArgsLengthEquals(CommandWord.BYE, args, 1); + validateNotNullArgs(CommandWord.BYE, args); + validateNotEmptyArgs(CommandWord.BYE, args); + validateCommandWord(CommandWord.BYE, args[0]); + } + + /** + * Executes the `ByeCommand`. It shows the bye message to the user. + * + * @param taskList The task list (not used in this command). + */ + public String execute(TaskList taskList) { + assert taskList != null : "task list cannot be null"; + + try { + validate(super.getRawCommand()); + } catch (WoofInvalidCommandException e) { + return e.getMessage(); + } + + return WoofMessage.BYE.toFormattedValue(); + } +} diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java new file mode 100644 index 0000000000..246e602f72 --- /dev/null +++ b/src/main/java/command/Command.java @@ -0,0 +1,269 @@ +package command; + +import java.time.LocalDate; + +import enums.CommandWord; +import enums.ExceptionMessage; +import exceptions.WoofException; +import exceptions.WoofInvalidCommandException; +import parser.Parser; +import tasks.TaskList; +import woof.Woof; +/** + * The `Command` class is an abstract class representing a command in the application. + * All specific command classes should inherit from this class and implement the `execute` method. + */ +public abstract class Command { + /** + * The raw command entered by the user. + */ + private final String rawCommand; + + /** + * Constructs a new `Command` with the specified raw command string. + * + * @param rawCommand The raw command string. + */ + public Command(String rawCommand) { + assert rawCommand != null : "raw command cannot be null"; + + this.rawCommand = rawCommand; + } + + /** + * Executes the command. Subclasses must implement this method to define their behavior. + * + * @param taskList The task list to perform the command on. + */ + public abstract String execute(TaskList taskList); + + /** + * Parses a date string received by the application using the input formatter. + * + * @param string The date string to be parsed. + * @return The parsed LocalDate. + */ + public static LocalDate parseDateTimeIn(String string) { + return Woof.parseDateTimeIn(string); + } + + /** + * Gets the raw command entered by the user. + * + * @return The raw command string. + */ + public String getRawCommand() { + return this.rawCommand; + } + + /** + * Checks if the command is a "bye" command. + * + * @return `true` if it's a "bye" command, `false` otherwise. + */ + public boolean isByeCommand() { + return CommandWord.commandWordToValueMap(Parser.getArgs(rawCommand)[0]).equals(CommandWord.BYE); + } + + /** + * Validates the length of the input string array `args` against the specified `length`. If the length of the array + * does not match the expected length, a `WoofInvalidCommandException` is thrown with a message indicating the + * invalid number of arguments for the given `commandWord`. + * + * @param commandWord The command word associated with the operation for which the arguments are being validated. + * @param args The input string array to be validated. + * @param correctLength The expected length of the `args` array. + * + * @throws WoofInvalidCommandException If the length of `args` does not match the expected `length`. + */ + public static void validateArgsLengthEquals(CommandWord commandWord, String[] args, int correctLength) { + assert commandWord != null : "command word cannot be null"; + assert args != null : "args cannot be null"; + assert correctLength >= 0 : "correct length must more than or equals to 0"; + + if (args.length != correctLength) { + throw new WoofInvalidCommandException( + ExceptionMessage.INVALID_NUMBER_OF_ARGUMENTS.toFormattedValue( + commandWord.toValue() + ) + ); + } + } + + /** + * Validates the length of the input string array `args` against the specified `length`. If the length + * of the array matches the unexpected length, a `WoofInvalidCommandException` is thrown with + * a message indicating the invalid number of arguments for the given `commandWord`. + * + * @param commandWord The command word associated with the operation for which the arguments are being + * validated. + * @param args The input string array to be validated. + * @param unexpectedLength The expected length of the `args` array. + * + * @throws WoofInvalidCommandException If the length of `args` does not match the expected `length`. + */ + public static void validateArgsLengthNotEquals(CommandWord commandWord, String[] args, int unexpectedLength) { + assert commandWord != null : "command word cannot be null"; + assert args != null : "args cannot be null"; + + if (args.length == unexpectedLength) { + throw new WoofInvalidCommandException( + ExceptionMessage.INVALID_NUMBER_OF_ARGUMENTS.toFormattedValue( + commandWord.toValue() + ) + ); + } + } + + /** + * Validates the length of the input string array `args` against the specified `length`. If the length + * of the array less than (not more than equal) to the expected length, a `WoofInvalidCommandException` is thrown + * with a message indicating the invalid number of arguments for the given `commandWord`. + * + * @param commandWord The command word associated with the operation for which the arguments are being validated. + * @param args The input string array to be validated. + * @param minLength The minimium length of the `args` array. + * + * @throws WoofInvalidCommandException If the length of `args` does not match the expected `length`. + */ + public static void validateArgsLengthMoreThanEquals(CommandWord commandWord, String[] args, int minLength) { + assert commandWord != null : "command word cannot be null"; + assert args != null : "args cannot be null"; + + if (args.length < minLength) { + throw new WoofInvalidCommandException( + ExceptionMessage.INVALID_NUMBER_OF_ARGUMENTS.toFormattedValue( + commandWord.toValue() + ) + ); + } + } + + /** + * Validates that none of the provided arguments are null. If any of the arguments are null, + * a `WoofInvalidCommandException` is thrown with a message indicating null arguments for + * the given `commandWord`. + * + * @param commandWord The command word associated with the operation for which the arguments are being validated. + * @param args The arguments to check for null values. + * + * @throws WoofInvalidCommandException If any of the arguments are null. + */ + public static void validateNotNullArgs(CommandWord commandWord, String... args) { + assert commandWord != null : "command word cannot be null"; + assert args != null : "args cannot be null"; + + for (String arg : args) { + if (arg == null) { + throw new WoofInvalidCommandException( + ExceptionMessage.NULL_ARGUMENT.toFormattedValue( + commandWord.toValue() + ) + ); + } + } + } + + /** + * Validates that none of the provided arguments are empty (have zero length). If any of the arguments are empty, + * a `WoofInvalidCommandException` is thrown with a message indicating empty arguments for the given `commandWord`. + * + * @param commandWord The command word associated with the operation for which the arguments are being validated. + * @param args The arguments to check for empty values. + * + * @throws WoofInvalidCommandException If any of the arguments are empty. + */ + public static void validateNotEmptyArgs(CommandWord commandWord, String... args) { + assert commandWord != null : "command word cannot be null"; + assert args != null : "args cannot be null"; + + for (String arg : args) { + if (arg.isEmpty()) { + throw new WoofInvalidCommandException( + ExceptionMessage.EMPTY_ARGUMENT.toFormattedValue( + commandWord.toValue() + ) + ); + } + } + } + + /** + * Validates that the argument provided matches the expected `CommandWord`. If it doesn't match, + * a `WoofInvalidCommandException` is thrown with a message indicating an invalid command word for + * the expected `commandWord`. + * + * @param commandWord The command word associated with the operation for which the arguments are being validated. + * @param arg The argument to check for the command word. + * + * @throws WoofInvalidCommandException If the first argument doesn't match the expected `CommandWord`. + */ + public static void validateCommandWord(CommandWord commandWord, String arg) { + assert commandWord != null : "command word cannot be null"; + assert arg != null : "arg cannot be null"; + + if (!CommandWord.commandWordToValueMap(arg).equals(commandWord)) { + throw new WoofInvalidCommandException( + ExceptionMessage.INVALID_COMMAND_WORD.toFormattedValue( + commandWord.toValue() + ) + ); + } + } + + /** + * Validates a date and time string against a specific date and time format. The format is determined + * by the `getDateTimeFormatter()` method. + * + * @param string The date and time string to validate. + * + * @throws WoofInvalidCommandException If the string is null or doesn't match the expected date and time format. + */ + public static void validateDateTime(String string) { + assert string != null : "datetime string cannot be null"; + + try { + Woof.validateInDateTime(string); + } catch (WoofException e) { + throw new WoofInvalidCommandException(ExceptionMessage.INVALID_DATE_TIME_FORMAT.toFormattedValue(string)); + } + } + + /** + * Validates a task index string to ensure it is a valid index for tasks in the application. + * It delegates the validation to the `TaskList.validateTaskIndex` method and handles any exceptions + * by throwing a `WoofInvalidCommandException` with an appropriate error message. + * + * @param string The task index string to validate. + * + * @throws WoofInvalidCommandException If the task index is invalid, or if there's a problem during validation. + */ + public static void validateTaskIndex(TaskList taskList, String string) { + assert string != null : "task index string cannot be null"; + TaskList.validateTaskIndex(string, taskList); + } + + /** + * Validates that a start date is before an end date. + * + * @param startDateStr The start date string to validate. + * @param endDateStr The end date string to validate. + * + * @throws WoofInvalidCommandException If the start date is not before the end date. + */ + public static void validateStartDateBeforeEndDate(String startDateStr, String endDateStr) { + assert startDateStr != null : "Start date cannot be null"; + assert endDateStr != null : "End date cannot be null"; + + validateDateTime(startDateStr); + validateDateTime(endDateStr); + LocalDate startDate = parseDateTimeIn(startDateStr); + LocalDate endDate = parseDateTimeIn(endDateStr); + + if (endDate.isBefore(startDate)) { + throw new WoofInvalidCommandException( + ExceptionMessage.START_DATE_AFTER_END_DATE.toFormattedValue(startDateStr, endDateStr) + ); + } + } +} diff --git a/src/main/java/command/DeadlineCommand.java b/src/main/java/command/DeadlineCommand.java new file mode 100644 index 0000000000..c7682a285a --- /dev/null +++ b/src/main/java/command/DeadlineCommand.java @@ -0,0 +1,68 @@ +package command; + +import java.time.LocalDate; + +import enums.CommandWord; +import exceptions.WoofInvalidCommandException; +import parser.Parser; +import tasks.DeadlineTask; +import tasks.TaskList; + +/** + * The `DeadlineCommand` class represents a command to create a new deadline task. + * When executed, it parses the command and adds a new deadline task to the task list. + */ +public class DeadlineCommand extends Command { + + /** + * Constructs a new `DeadlineCommand` with the specified raw command string. + * + * @param rawCommand The raw command string. + */ + public DeadlineCommand(String rawCommand) { + super(rawCommand); + } + + /** + * Validates the `DeadlineCommand`. + * It checks if the command is correctly formatted and if the specified task index is valid. + * + * @param rawCommand The raw command string. + * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an + * error message. + */ + public static void validate(String rawCommand) throws WoofInvalidCommandException { + assert rawCommand != null : "raw command cannot be null"; + + String[] args = Parser.getArgs(rawCommand); + validateArgsLengthEquals(CommandWord.DEADLINE, args, 4); + validateNotNullArgs(CommandWord.DEADLINE, args); + validateNotEmptyArgs(CommandWord.DEADLINE, args); + validateCommandWord(CommandWord.DEADLINE, args[0]); + validateCommandWord(CommandWord.BY, args[2]); + validateDateTime(args[3]); + } + + /** + * Executes the `DeadlineCommand`. It parses the command, validates it, and adds a new + * deadline task to the task list if the command is valid. + * + * @param taskList The task list to which the deadline task is added. + */ + public String execute(TaskList taskList) { + assert taskList != null : "task list cannot be null"; + + String rawCommand = super.getRawCommand(); + try { + validate(rawCommand); + } catch (WoofInvalidCommandException e) { + return e.getMessage(); + } + + String[] args = Parser.getArgs(rawCommand); + String description = args[1]; + LocalDate endDate = parseDateTimeIn(args[3]); + return taskList.addTask(new DeadlineTask(description, endDate)); + } + +} diff --git a/src/main/java/command/DeleteCommand.java b/src/main/java/command/DeleteCommand.java new file mode 100644 index 0000000000..874bf8cfed --- /dev/null +++ b/src/main/java/command/DeleteCommand.java @@ -0,0 +1,67 @@ +package command; + +import enums.CommandWord; +import exceptions.WoofInvalidCommandException; +import parser.Parser; +import tasks.TaskList; + +/** + * The `DeleteCommand` class represents a command to delete a task. + * When executed, it parses the command, validates it, and deletes + * the specified task from the task list if the command is valid. + */ +public class DeleteCommand extends Command { + + /** + * Constructs a new `DeleteCommand` with the specified raw command string. + * + * @param rawCommand The raw command string. + */ + public DeleteCommand(String rawCommand) { + super(rawCommand); + } + + /** + * Validates the `DeleteCommand`. + * It checks if the command is correctly formatted and if the specified task index is valid. + * + * @param rawCommand The raw command string. + * @param taskList The task list against which to validate the task index. + * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an + * error message. + */ + public static void validate(String rawCommand, TaskList taskList) throws WoofInvalidCommandException { + assert rawCommand != null : "raw command cannot be null"; + assert taskList != null : "task list cannot be null"; + + String[] args = Parser.getArgs(rawCommand); + validateArgsLengthEquals(CommandWord.DELETE, args, 2); + validateNotNullArgs(CommandWord.DELETE, args); + validateNotEmptyArgs(CommandWord.DELETE, args); + validateCommandWord(CommandWord.DELETE, args[0]); + validateTaskIndex(taskList, args[1]); + } + + + + /** + * Executes the "delete" command. It parses the command, validates it, and deletes + * the specified task from the task list if the command is valid. + * + * @param taskList The task list from which the task is deleted. + */ + public String execute(TaskList taskList) { + assert taskList != null : "task list cannot be null"; + + String rawCommand = super.getRawCommand(); + try { + validate(rawCommand, taskList); + } catch (WoofInvalidCommandException e) { + return e.getMessage(); + } + + String[] args = Parser.getArgs(rawCommand); + return taskList.deleteTask(args[1]); + } + +} diff --git a/src/main/java/command/EventCommand.java b/src/main/java/command/EventCommand.java new file mode 100644 index 0000000000..7b976e1d69 --- /dev/null +++ b/src/main/java/command/EventCommand.java @@ -0,0 +1,73 @@ +package command; + +import java.time.LocalDate; + +import enums.CommandWord; +import exceptions.WoofInvalidCommandException; +import parser.Parser; +import tasks.EventTask; +import tasks.TaskList; +import woof.Woof; + +/** + * The `EventCommand` class represents a command to create a new event task. + * When executed, it parses the command, validates it, and adds a + * new event task to the task list if the command is valid. + */ +public class EventCommand extends Command { + + /** + * Constructs a new `EventCommand` with the specified raw command string. + * + * @param rawCommand The raw command string. + */ + public EventCommand(String rawCommand) { + super(rawCommand); + } + + /** + * Validates the `EventCommand`. + * It checks if the command is correctly formatted. + * + * @param rawCommand The raw command string. + * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an + * error message. + */ + public static void validate(String rawCommand) throws WoofInvalidCommandException { + assert rawCommand != null : "raw command cannot be null"; + + String[] args = Parser.getArgs(rawCommand); + validateArgsLengthEquals(CommandWord.EVENT, args, 6); + validateNotNullArgs(CommandWord.EVENT, args); + validateNotEmptyArgs(CommandWord.EVENT, args); + validateCommandWord(CommandWord.EVENT, args[0]); + validateCommandWord(CommandWord.FROM, args[2]); + validateDateTime(args[3]); + validateCommandWord(CommandWord.TO, args[4]); + validateDateTime(args[5]); + validateStartDateBeforeEndDate(args[3], args[5]); + } + + /** + * Executes the `EventCommand`. It parses the command, validates it, and adds a new + * event task to the task list if the command is valid. + * + * @param taskList The task list to which the event task is added. + */ + public String execute(TaskList taskList) { + assert taskList != null : "task list cannot be null"; + + String rawCommand = super.getRawCommand(); + try { + validate(rawCommand); + } catch (WoofInvalidCommandException e) { + return e.getMessage(); + } + String[] args = Parser.getArgs(rawCommand); + String description = args[1]; + LocalDate startDate = Woof.parseDateTimeIn(args[3]); + LocalDate endDate = Woof.parseDateTimeIn(args[5]); + return taskList.addTask(new EventTask(description, startDate, endDate)); + } + +} diff --git a/src/main/java/command/FindCommand.java b/src/main/java/command/FindCommand.java new file mode 100644 index 0000000000..cef36aed17 --- /dev/null +++ b/src/main/java/command/FindCommand.java @@ -0,0 +1,57 @@ +package command; + +import enums.CommandWord; +import exceptions.WoofInvalidCommandException; +import parser.Parser; +import tasks.TaskList; + +/** + * The `FindCommand` class represents a command to search for tasks based on a keyword in the Woof application. + */ +public class FindCommand extends Command { + /** + * Constructs a `FindCommand` with the given raw command. + * + * @param rawCommand The raw command input by the user. + */ + public FindCommand(String rawCommand) { + super(rawCommand); + } + + /** + * Validates the `FindCommand`. + * + * @param rawCommand The raw command input by the user. + * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an + * error message. + */ + public static void validate(String rawCommand) throws WoofInvalidCommandException { + assert rawCommand != null : "raw command cannot be null"; + + String[] args = Parser.getArgs(rawCommand); + validateArgsLengthMoreThanEquals(CommandWord.FIND, args, 2); + validateNotNullArgs(CommandWord.FIND, args); + validateNotEmptyArgs(CommandWord.FIND, args); + validateCommandWord(CommandWord.FIND, args[0]); + } + + + /** + * Executes the `FindCommand` to search for tasks based on the specified keyword. + * + * @param taskList The task list in which to search for tasks. + */ + public String execute(TaskList taskList) { + assert taskList != null : "task list cannot be null"; + + String rawCommand = super.getRawCommand(); + try { + validate(rawCommand); + } catch (WoofInvalidCommandException e) { + return e.getMessage(); + } + String[] args = Parser.getArgs(rawCommand); + String keySentence = args[1]; + return taskList.findTask(keySentence.split("\\s+")); + } +} diff --git a/src/main/java/command/HelpCommand.java b/src/main/java/command/HelpCommand.java new file mode 100644 index 0000000000..01b428b51c --- /dev/null +++ b/src/main/java/command/HelpCommand.java @@ -0,0 +1,52 @@ +package command; + +import enums.WoofMessage; +import exceptions.WoofInvalidCommandException; +import tasks.TaskList; + +/** + * The `HelpCommand` class represents a command request for help. + * When executed, it displays a help message to the user. + */ +public class HelpCommand extends Command { + + /** + * Constructs a new `HelpCommand` with the specified raw command string. + * + * @param rawCommand The raw command string. + */ + public HelpCommand(String rawCommand) { + super(rawCommand); + } + + /** + * Validates the `HelpCommand`. + * It checks if the command is correctly formatted and if the specified task index is valid. + * + * @param rawCommand The raw command string. + * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an + * error message. + */ + public static void validate(String rawCommand) { + assert rawCommand != null : "raw command cannot be null"; + + String[] args = parser.Parser.getArgs(rawCommand); + validateArgsLengthEquals(enums.CommandWord.HELP, args, 1); + validateNotNullArgs(enums.CommandWord.HELP, args); + validateNotEmptyArgs(enums.CommandWord.HELP, args); + validateCommandWord(enums.CommandWord.HELP, args[0]); + } + + /** + * Executes the `HelpCommand`. It shows the bye message to the user. + */ + public String execute(TaskList taskList) { + try { + validate(super.getRawCommand()); + } catch (exceptions.WoofInvalidCommandException e) { + return e.getMessage(); + } + + return WoofMessage.HELP.toFormattedValue(); + } +} diff --git a/src/main/java/command/ListCommand.java b/src/main/java/command/ListCommand.java new file mode 100644 index 0000000000..297bd50816 --- /dev/null +++ b/src/main/java/command/ListCommand.java @@ -0,0 +1,60 @@ +package command; + +import enums.CommandWord; +import exceptions.WoofInvalidCommandException; +import parser.Parser; +import tasks.TaskList; + +/** + * The `ListCommand` class represents a command to list all tasks. + * When executed, it validates the command and displays a list of all tasks in the task list. + */ +public class ListCommand extends Command { + + /** + * Constructs a new `ListCommand` with the specified raw command string. + * + * @param rawCommand The raw command string. + */ + public ListCommand(String rawCommand) { + super(rawCommand); + } + + /** + * Validates the `ListCommand`. + * It checks if the command is correctly formatted. + * + * @param rawCommand The raw command string. + * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an + * error message. + */ + public static void validate(String rawCommand) throws WoofInvalidCommandException { + assert rawCommand != null : "raw command cannot be null"; + + String[] args = Parser.getArgs(rawCommand); + validateArgsLengthEquals(CommandWord.LIST, args, 1); + validateNotNullArgs(CommandWord.LIST, args); + validateNotEmptyArgs(CommandWord.LIST, args); + validateCommandWord(CommandWord.LIST, args[0]); + } + + + /** + * Executes the `ListCommand`. It validates the command and displays + * a list of all tasks in the task list. + * + * @param taskList The task list from which tasks are listed. + */ + public String execute(TaskList taskList) { + assert taskList != null : "task list cannot be null"; + + String rawCommand = super.getRawCommand(); + try { + validate(rawCommand); + } catch (WoofInvalidCommandException e) { + return e.getMessage(); + } + return taskList.listAllTasks(); + } + +} diff --git a/src/main/java/command/MarkCommand.java b/src/main/java/command/MarkCommand.java new file mode 100644 index 0000000000..90686a8ab6 --- /dev/null +++ b/src/main/java/command/MarkCommand.java @@ -0,0 +1,66 @@ +package command; + +import enums.CommandWord; +import exceptions.WoofInvalidCommandException; +import parser.Parser; +import tasks.TaskList; + +/** + * The `MarkCommand` class represents a command to mark a task as done. + * When executed, it parses the command, validates it, and marks + * the specified task as done in the task list if the command is valid. + */ +public class MarkCommand extends Command { + + /** + * Constructs a new `MarkCommand` with the specified raw command string. + * + * @param rawCommand The raw command string. + */ + public MarkCommand(String rawCommand) { + super(rawCommand); + } + + /** + * Validates the `MarkCommand`. + * It checks if the command is correctly formatted and if the specified task index is valid. + * + * @param rawCommand The raw command string. + * @param taskList The task list against which to validate the task index. + * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an + * error message. + */ + public static void validate(String rawCommand, TaskList taskList) throws WoofInvalidCommandException { + assert rawCommand != null : "raw command cannot be null"; + assert taskList != null : "task list cannot be null"; + + String[] args = Parser.getArgs(rawCommand); + validateArgsLengthEquals(CommandWord.MARK, args, 2); + validateNotNullArgs(CommandWord.MARK, args); + validateNotEmptyArgs(CommandWord.MARK, args); + validateCommandWord(CommandWord.MARK, args[0]); + validateTaskIndex(taskList, args[1]); + } + + + /** + * Executes the `MarkCommand`. It parses the command, validates it, and marks + * the specified task as done in the task list if the command is valid. + * + * @param taskList The task list in which the task is marked as done. + */ + public String execute(TaskList taskList) { + assert taskList != null : "task list cannot be null"; + + String rawCommand = super.getRawCommand(); + try { + validate(rawCommand, taskList); + } catch (WoofInvalidCommandException e) { + return e.getMessage(); + } + String[] args = Parser.getArgs(super.getRawCommand()); + String taskIndex = args[1]; + return taskList.markTaskDone(taskIndex); + } + +} diff --git a/src/main/java/command/NullCommand.java b/src/main/java/command/NullCommand.java new file mode 100644 index 0000000000..749fa1c2b8 --- /dev/null +++ b/src/main/java/command/NullCommand.java @@ -0,0 +1,60 @@ +package command; + +import enums.CommandWord; +import enums.WoofMessage; +import exceptions.WoofInvalidCommandException; +import parser.Parser; +import tasks.TaskList; + +/** + * The `NullCommand` class represents a command that is not recognized or is not valid. + * When executed, it shows a "confused" message to the user. + */ +public class NullCommand extends Command { + + /** + * Constructs a new `NullCommand` with the specified raw command string. + * + * @param rawCommand The raw command string. + */ + public NullCommand(String rawCommand) { + super(rawCommand); + } + + /** + * Validates the `NullCommand`. + * It checks if the command is correctly formatted. + * + * @param rawCommand The raw command string. + * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an + * error message. + */ + public static void validate(String rawCommand) throws WoofInvalidCommandException { + assert rawCommand != null : "raw command cannot be null"; + + String[] args = Parser.getArgs(rawCommand); + validateArgsLengthNotEquals(CommandWord.NULL_COMMAND, args, 0); + validateNotNullArgs(CommandWord.NULL_COMMAND, args); + validateNotEmptyArgs(CommandWord.NULL_COMMAND, args); + validateCommandWord(CommandWord.NULL_COMMAND, args[0]); + } + + /** + * Executes the `NullCommand`. It validates the command and displays + * a "confused" message to the user if the command is not recognized or valid. + * + * @param taskList The task list (not used in this command). + */ + public String execute(TaskList taskList) { + assert taskList != null : "task list cannot be null"; + + String rawCommand = super.getRawCommand(); + try { + validate(rawCommand); + } catch (WoofInvalidCommandException e) { + return e.getMessage(); + } + return WoofMessage.CONFUSED.toFormattedValue(rawCommand); + } + +} diff --git a/src/main/java/command/SortCommand.java b/src/main/java/command/SortCommand.java new file mode 100644 index 0000000000..9814de0b0a --- /dev/null +++ b/src/main/java/command/SortCommand.java @@ -0,0 +1,58 @@ +package command; + +import enums.CommandWord; +import exceptions.WoofInvalidCommandException; +import parser.Parser; +import tasks.TaskList; + +/** + * The `SortCommand` class represents a command to list all tasks. + * When executed, it validates the command and displays a list of all tasks in the task list. + */ +public class SortCommand extends Command { + + /** + * Constructs a new `SortCommand` with the specified raw command string. + * + * @param rawCommand The raw command string. + */ + public SortCommand(String rawCommand) { + super(rawCommand); + } + + /** + * Validates the `SortCommand`. + * It checks if the command is correctly formatted. + * + * @param rawCommand The raw command string. + * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an + * error message. + */ + public static void validate(String rawCommand) throws WoofInvalidCommandException { + assert rawCommand != null : "raw command cannot be null"; + + String[] args = Parser.getArgs(rawCommand); + validateArgsLengthEquals(CommandWord.SORT, args, 1); + validateNotNullArgs(CommandWord.SORT, args); + validateNotEmptyArgs(CommandWord.SORT, args); + validateCommandWord(CommandWord.SORT, args[0]); + } + + /** + * Executes the `SortCommand`. It validates the command and displays + * a list of all tasks in the task list. + * + * @param taskList The task list from which tasks are listed. + */ + public String execute(TaskList taskList) { + assert taskList != null : "task list cannot be null"; + + String rawCommand = super.getRawCommand(); + try { + validate(rawCommand); + } catch (WoofInvalidCommandException e) { + return e.getMessage(); + } + return taskList.sortTasks(); + } +} diff --git a/src/main/java/command/TodoCommand.java b/src/main/java/command/TodoCommand.java new file mode 100644 index 0000000000..717218ce83 --- /dev/null +++ b/src/main/java/command/TodoCommand.java @@ -0,0 +1,61 @@ +package command; + +import enums.CommandWord; +import exceptions.WoofInvalidCommandException; +import parser.Parser; +import tasks.TaskList; +import tasks.TodoTask; + +/** + * The `TodoCommand` class represents a command to create a new todo task. + * When executed, it parses the command, validates it, and adds + * a new todo task to the task list if the command is valid. + */ +public class TodoCommand extends Command { + /** + * Constructs a new `TodoCommand` with the specified raw command string. + * + * @param rawCommand The raw command string. + */ + public TodoCommand(String rawCommand) { + super(rawCommand); + } + + /** + * Validates the `TodoCommand`. + * It checks if the command is correctly formatted. + * + * @param rawCommand The raw command string. + * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an + * error message. + */ + public static void validate(String rawCommand) throws WoofInvalidCommandException { + assert rawCommand != null : "raw command cannot be null"; + + String[] args = Parser.getArgs(rawCommand); + validateArgsLengthEquals(CommandWord.TODO, args, 2); + validateNotNullArgs(CommandWord.TODO, args); + validateNotEmptyArgs(CommandWord.TODO, args); + validateCommandWord(CommandWord.TODO, args[0]); + } + + /** + * Executes the `TodoCommand`. It parses the command, validates it, and adds a new + * todo task to the task list if the command is valid. + * + * @param taskList The task list to which the todo task is added. + */ + public String execute(TaskList taskList) { + assert taskList != null : "task list cannot be null"; + + String rawCommand = super.getRawCommand(); + try { + validate(rawCommand); + } catch (WoofInvalidCommandException e) { + return e.getMessage(); + } + String[] args = Parser.getArgs(rawCommand); + String description = args[1]; + return taskList.addTask(new TodoTask(description)); + } +} diff --git a/src/main/java/command/UnmarkCommand.java b/src/main/java/command/UnmarkCommand.java new file mode 100644 index 0000000000..d5167b1664 --- /dev/null +++ b/src/main/java/command/UnmarkCommand.java @@ -0,0 +1,65 @@ +package command; + +import enums.CommandWord; +import exceptions.WoofInvalidCommandException; +import parser.Parser; +import tasks.TaskList; + +/** + * The `UnmarkCommand` class represents a command to unmark a task as done. + * When executed, it parses the command, validates it, and marks the specified task as + * undone in the task list if the command is valid. + */ +public class UnmarkCommand extends Command { + + /** + * Constructs a new `UnmarkCommand` with the specified raw command string. + * + * @param rawCommand The raw command string. + */ + public UnmarkCommand(String rawCommand) { + super(rawCommand); + } + + /** + * Validates the `UnmarkCommand`. + * It checks if the command is correctly formatted and if the specified task index is valid. + * + * @param rawCommand The raw command string. + * @param taskList The task list against which to validate the task index. + * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an + * error message. + */ + public static void validate(String rawCommand, TaskList taskList) throws WoofInvalidCommandException { + assert rawCommand != null : "raw command cannot be null"; + assert taskList != null : "task list cannot be null"; + + String[] args = Parser.getArgs(rawCommand); + validateArgsLengthEquals(CommandWord.UNMARK, args, 2); + validateNotNullArgs(CommandWord.UNMARK, args); + validateNotEmptyArgs(CommandWord.UNMARK, args); + validateCommandWord(CommandWord.UNMARK, args[0]); + validateTaskIndex(taskList, args[1]); + } + + /** + * Executes the `UnmarkCommand`. It parses the command, validates it, and marks the specified task as + * undone in the task list if the command is valid. + * + * @param taskList The task list in which the task is marked as undone. + */ + public String execute(TaskList taskList) { + assert taskList != null : "task list cannot be null"; + + String rawCommand = super.getRawCommand(); + try { + validate(rawCommand, taskList); + } catch (WoofInvalidCommandException e) { + return e.getMessage(); + } + + String[] args = Parser.getArgs(super.getRawCommand()); + String taskIndex = args[1]; + return taskList.markTaskUndone(taskIndex); + } +} diff --git a/src/main/java/enums/CommandWord.java b/src/main/java/enums/CommandWord.java new file mode 100644 index 0000000000..63b5d0f3b0 --- /dev/null +++ b/src/main/java/enums/CommandWord.java @@ -0,0 +1,59 @@ +package enums; + +/** + * The `CommandWord` enum represents the possible command words used in the Woof application. + */ +public enum CommandWord { + BY("/by"), + BYE("bye"), + DEADLINE("deadline"), + DELETE("delete"), + EVENT("event"), + EXIT("exit"), + FIND("find"), + FROM("/from"), + HELP("help"), + LIST("list"), + MARK("mark"), + NULL_COMMAND(""), + SORT("sort"), + TO("/to"), + TODO("todo"), + UNMARK("unmark"); + + private final String value; + + /** + * Constructs a `CommandWord` enum with the given value. + * + * @param value The string representation of the command word. + */ + CommandWord(String value) { + this.value = value; + } + + /** + * Maps a string value to the corresponding `CommandWord` enum. + * If no match is found, it returns the `NULL_COMMAND` enum. + * + * @param value The string value to map to a command word. + * @return The corresponding `CommandWord` enum, or `NULL_COMMAND` if no match is found. + */ + public static CommandWord commandWordToValueMap(String value) { + for (CommandWord e : values()) { + if (e.toValue().equals(value)) { + return e; + } + } + return NULL_COMMAND; + } + + /** + * Gets the string representation of the `CommandWord`. + * + * @return The string representation of the `CommandWord`. + */ + public String toValue() { + return this.value; + } +} diff --git a/src/main/java/enums/ExceptionMessage.java b/src/main/java/enums/ExceptionMessage.java new file mode 100644 index 0000000000..169fab2a57 --- /dev/null +++ b/src/main/java/enums/ExceptionMessage.java @@ -0,0 +1,58 @@ +package enums; + +/** + * The `ExceptionMessage` enum represents the possible exception messages used in the Woof application. + */ +public enum ExceptionMessage { + // INVALID COMMAND EXCEPTION MESSAGES + INVALID_NUMBER_OF_ARGUMENTS( + "wrong number of arguments for '%s'!"), + NULL_ARGUMENT( + "null argument not allowed for '%s'!"), + EMPTY_ARGUMENT( + "I don't want empty arguments for '%s' :("), + INVALID_COMMAND_WORD( + "bad command word for '%s'..."), + INVALID_DATE_TIME_FORMAT( + "me no understand your datetime format '%s'"), + START_DATE_AFTER_END_DATE( + "just like all things in life, you can't end, before you start.'%s' is before '%s'."), + + // STORAGE EXCEPTION MESSAGES + UNABLE_TO_CREATE_FILE( + "me can't create file. Computer go '%s'"), + UNABLE_TO_READ_FILE( + "me can't read file. Computer go '%s'"), + UNABLE_TO_UPDATE_FILE( + "me can't update file. Computer go '%s'"), + UNABLE_TO_CREATE_DIRECTORY( + "me can't create folder"), + + // TASK INDEX EXCEPTION MESSAGE + NON_INTEGER_TASK_INDEX("bad task index '%s', give me integer!"), + UNABLE_TO_PARSE_INDEX("idk how to parse '%s', Computer go '%s'"), + TASK_INDEX_NOT_IN_LIST("'%s' not in my task list"); + + private final String value; + + /** + * Constructs an `ExceptionMessage` enum with the given value. + * + * @param value The string representation of the command word. + */ + ExceptionMessage(String value) { + assert value != null : "value cannot be null"; + + this.value = value; + } + + /** + * Gets the string representation of the `ExceptionMessage` with the appropriate args word. + * + * @param args The arguments to replace placeholders in the exception message. + * @return The string representation of the `ExceptionMessage` with the appropriate command word. + */ + public String toFormattedValue(String ... args) { + return String.format(this.value, (Object[]) args); + } +} diff --git a/src/main/java/enums/FilePath.java b/src/main/java/enums/FilePath.java new file mode 100644 index 0000000000..c1384a2280 --- /dev/null +++ b/src/main/java/enums/FilePath.java @@ -0,0 +1,40 @@ +package enums; + +/** + * The `FilePath` enum represents file paths used in the Woof application. + */ +public enum FilePath { + CUSTOM_FONT("/fonts/sono/static/Sono-Light.ttf"), + USER_DISPLAY_PICTURE("/images/userDisplayPicture.jpeg"), + BOT_DISPLAY_PICTURE("/images/botDisplayPicture.jpeg"), + CUSTOM_CURSOR("/images/paw.png"), + DIALOG_AREA_CSS("/styles/dialogArea.css"), + ROOT_CSS("/styles/root.css"), + SCROLL_PANE_CSS("/styles/scrollPane.css"), + SEND_BUTTON_CSS("/styles/sendButton.css"), + USER_INPUT_CSS("/styles/userInput.css"), + CLEAR_BUTTON_CSS("/styles/clearButton.css"), + DEFAULT_STORAGE_PATH(".data/task.json"), + FXML_VIEW_PATH("/views/WoofWoof.fxml"); + + private final String value; + + /** + * Constructs a `FilePath` enum with the given value. + * + * @param value The string representation of the `FilePath`. + */ + FilePath(String value) { + this.value = value; + } + + /** + * Gets the string representation of the `FilePath`. + * + * @return The string representation of the `FilePath`. + */ + public String toValue() { + return this.value; + } +} + diff --git a/src/main/java/enums/TaskType.java b/src/main/java/enums/TaskType.java new file mode 100644 index 0000000000..f73ec0f3f4 --- /dev/null +++ b/src/main/java/enums/TaskType.java @@ -0,0 +1,30 @@ +package enums; + +/** + * The `TaskType` enum represents the different types of tasks in the Woof application. + */ +public enum TaskType { + TODO("[T]"), + DEADLINE("[D]"), + EVENT("[E]"); + + private final String symbol; + + /** + * Constructs a `TaskType` enum with the given symbol. + * + * @param symbol The symbol associated with the task type. + */ + TaskType(String symbol) { + this.symbol = symbol; + } + + /** + * Returns the symbol associated with the `TaskType`. + * + * @return The symbol associated with the `TaskType`. + */ + public String toSymbol() { + return this.symbol; + } +} diff --git a/src/main/java/enums/WoofMessage.java b/src/main/java/enums/WoofMessage.java new file mode 100644 index 0000000000..925c1c4fd1 --- /dev/null +++ b/src/main/java/enums/WoofMessage.java @@ -0,0 +1,84 @@ +package enums; + +/** + * The `WoofMessage` enum represents the possible woof messages used in the Woof application. + */ +public enum WoofMessage { + WOOF_TITLE("Woof Woof"), + HI("Woof Woof! I'm Oreo!!!\nWhat can I do for you?\nsend `help` for help."), + BYE("Bye. Hope to see you again soon!\nOffing myself... woof :("), + CONFUSED("OOPS!!! I'm sorry, I don't know what is\n'%s'\nsend `help` for help."), + TASK_LIST_COUNT("You have %s tasks in the task list."), + TASK_ADDED("Got it. I've added this task:\n%s%s%s"), + NO_MATCHING_TASKS("No tasks matched your keyword(s)!\n"), + TASKS_FOUND("Here are your matching tasks in your list:\n%s%s"), + EMPTY_TASK_LIST("There's nothing to sort... really\n"), + TASKS_LISTED("Here are all the tasks in your list:\n%s%s"), + TASK_DONE("Ok! I've marked this task as done:\n%s%s%s"), + TASKS_SORTED("Your tasks have been sorted:\n%s%s"), + TASK_UNDONE("Ok! I've marked this task as undone:\n%s%s%s"), + TASK_DELETED("Ok! I've deleted this task:\n%s%s%s"), + HELP("Woof! Woof! Here to help:\n\n" + + "Available Commands:\n\n" + + "1. Todo : Add a simple to-do task.\n" + + " Format : `todo `\n" + + " Example : `todo study`\n\n" + + "2. Deadline : Add a task with a deadline.\n" + + " Format : `deadline /by `\n" + + " Example : `deadline assignment /by 2023-12-31`\n\n" + + "3. Event : Add an event with a start and end date.\n" + + " Format : `event /from /to `\n" + + " Example : `event enjoy /from 2023-11-25 /to 2024-01-14`\n\n" + + "4. Mark : Mark a task as completed.\n" + + " Format : `mark `\n" + + " Example : `mark 1`\n\n" + + "5. Unmark : Unmark a completed task as incomplete.\n" + + " Format : `unmark `\n" + + " Example : `unmark 1`\n\n" + + "6. Delete : Delete a task.\n" + + " Format : `delete `\n" + + " Example : `delete 2`\n\n" + + "7. List : View all your tasks.\n" + + " Format : `list`\n\n" + + "8. Find : Search for tasks containing n keywords.\n" + + " Format : `find ... `\n" + + " Example : `find meeting project`\n\n" + + "9. Sort : Sort tasks by date and time.\n" + + " Format : `sort`\n\n" + + "10. Bye : Say goodbye to WoofWoof Bot.\n" + + " Format : `bye`\n\n" + + "11. Help : Request help on how to use the bot.\n" + + " Format : `help`\n\n" + + "Notes:\n" + + "- Replace `` with a task description.\n" + + "- `` should follow the yyyy-mm-dd date format.\n" + + " e.g. 2023-12-31\n" + + "- `` should be the index of the task in the list\n" + + " you want to manage.\n\n" + + "Feel free to ask for help using the `help` command if you\n" + + "have any questions or encounter issues.\n" + + "Woof Woof is here to make your life easier!"); + + private final String value; + + /** + * Constructs a `Exception Message` enum with the given value. + * + * @param value The string representation of the command word. + */ + WoofMessage(String value) { + assert value != null : "value cannot be null"; + + this.value = value; + } + + /** + * Gets the string representation of the woof message with the appropriate args word. + * + * @param args The arguments to replace placeholders in the exception message. + * @return The string representation of the exception message with the appropriate command word. + */ + public String toFormattedValue(String... args) { + return String.format(this.value, (Object[]) args); + } +} diff --git a/src/main/java/exceptions/WoofException.java b/src/main/java/exceptions/WoofException.java new file mode 100644 index 0000000000..99a67562ca --- /dev/null +++ b/src/main/java/exceptions/WoofException.java @@ -0,0 +1,16 @@ +package exceptions; + +/** + * The `WoofException` class represents a custom exception specific to the Woof application. + * It is used to indicate exceptional situations or errors within the application. + */ +public class WoofException extends RuntimeException { + /** + * Constructs a new `WoofException` with the specified error message. + * + * @param message The error message describing the exception. + */ + public WoofException(String message) { + super(String.format("Woof! %s", message)); + } +} diff --git a/src/main/java/exceptions/WoofInvalidCommandException.java b/src/main/java/exceptions/WoofInvalidCommandException.java new file mode 100644 index 0000000000..84323e69ab --- /dev/null +++ b/src/main/java/exceptions/WoofInvalidCommandException.java @@ -0,0 +1,16 @@ +package exceptions; + +/** + * The `WoofInvalidCommandException` class represents a custom exception specific to the Woof application. + * It is used to indicate that an invalid command has been encountered. + */ +public class WoofInvalidCommandException extends WoofException { + /** + * Constructs a new `WoofInvalidCommandException` with the specified error message. + * + * @param message The error message describing the invalid command. + */ + public WoofInvalidCommandException(String message) { + super(String.format("command bad ... %s", message)); + } +} diff --git a/src/main/java/exceptions/WoofStorageException.java b/src/main/java/exceptions/WoofStorageException.java new file mode 100644 index 0000000000..470593daa4 --- /dev/null +++ b/src/main/java/exceptions/WoofStorageException.java @@ -0,0 +1,17 @@ +package exceptions; + +/** + * The `WoofStorageException` class represents a custom exception specific to the Woof application. + * It is used to indicate exceptions related to storage operations, + * such as reading from or writing to storage. + */ +public class WoofStorageException extends WoofException { + /** + * Constructs a new `WoofStorageException` with the specified error message. + * + * @param message The error message describing the storage-related exception. + */ + public WoofStorageException(String message) { + super(String.format("storage... %s", message)); + } +} diff --git a/src/main/java/parser/Parser.java b/src/main/java/parser/Parser.java new file mode 100644 index 0000000000..2d8bd8cde3 --- /dev/null +++ b/src/main/java/parser/Parser.java @@ -0,0 +1,100 @@ +package parser; + +import java.util.ArrayList; + +import command.ByeCommand; +import command.Command; +import command.DeadlineCommand; +import command.DeleteCommand; +import command.EventCommand; +import command.FindCommand; +import command.HelpCommand; +import command.ListCommand; +import command.MarkCommand; +import command.NullCommand; +import command.SortCommand; +import command.TodoCommand; +import command.UnmarkCommand; +import enums.CommandWord; + +/** + * The `Parser` class is responsible for parsing user commands and creating corresponding `Command` objects. + */ +public class Parser { + /** + * Parses a raw user command and returns the corresponding `Command` object. + * + * @param rawCommand The raw user command. + * @return A `Command` object representing the parsed command. + */ + public static Command parse(String rawCommand) { + switch (CommandWord.commandWordToValueMap(getArgs(rawCommand)[0])) { + case BYE: + return new ByeCommand(rawCommand); + case DEADLINE: + return new DeadlineCommand(rawCommand); + case DELETE: + return new DeleteCommand(rawCommand); + case EVENT: + return new EventCommand(rawCommand); + case FIND: + return new FindCommand(rawCommand); + case HELP: + return new HelpCommand(rawCommand); + case LIST: + return new ListCommand(rawCommand); + case MARK: + return new MarkCommand(rawCommand); + case UNMARK: + return new UnmarkCommand(rawCommand); + case SORT: + return new SortCommand(rawCommand); + case TODO: + return new TodoCommand(rawCommand); + default: + return new NullCommand(rawCommand); + } + } + + /** + * Splits a raw user command into individual arguments. + * + * @param rawCommand The raw user command. + * @return An array of individual arguments. + */ + public static String[] getArgs(String rawCommand) { + assert rawCommand != null : "raw command cannot be null"; + + ArrayList result = new ArrayList<>(); + + String[] words = rawCommand.trim().split(" "); + if (words.length == 0) { + return result.toArray(new String[0]); + } + + String mainCommand = words[0]; + assert mainCommand != null : "main command cannot be null"; + + StringBuilder subCommand = new StringBuilder(); + + result.add(mainCommand); + for (int i = 1; i < words.length; i++) { + String currentWord = words[i]; + if (currentWord.startsWith("/")) { + if (!subCommand.toString().trim().isEmpty()) { + result.add(subCommand.toString().trim()); + } + result.add(currentWord); + subCommand = new StringBuilder(); + } else { + subCommand.append(" ").append(currentWord); + } + } + + if (!subCommand.toString().trim().isEmpty()) { + result.add(subCommand.toString().trim()); + } + + return result.toArray(new String[0]); + } +} diff --git a/src/main/java/storage/LocalDateAdapter.java b/src/main/java/storage/LocalDateAdapter.java new file mode 100644 index 0000000000..d4bde7fcda --- /dev/null +++ b/src/main/java/storage/LocalDateAdapter.java @@ -0,0 +1,52 @@ +package storage; + +import java.lang.reflect.Type; +import java.time.LocalDate; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import woof.Woof; + +/** + * The `LocalDateAdapter` class is a Gson adapter for serializing and deserializing `LocalDate` objects. + */ +public class LocalDateAdapter implements JsonSerializer, JsonDeserializer { + /** + * Deserialize a `LocalDate` object from a JSON element. + * + * @param json The JSON element containing the date as a string. + * @param typeOfT The type of the object to deserialize. + * @param context The deserialization context. + * @return A `LocalDate` object deserialized from the JSON element. + * + * @throws JsonParseException If there is an issue with JSON parsing. + */ + @Override + public LocalDate deserialize( + JsonElement json, + Type typeOfT, + JsonDeserializationContext context + ) throws JsonParseException { + String dateStr = json.getAsString(); + return LocalDate.parse(dateStr, Woof.getDateTimeOutFormatter()); + } + + /** + * Serialize a `LocalDate` object to a JSON element. + * + * @param src The `LocalDate` object to serialize. + * @param typeOfSrc The type of the source object. + * @param context The serialization context. + * @return A JSON element representing the serialized `LocalDate` object. + */ + @Override + public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(Woof.getDateTimeOutFormatter().format(src)); + } +} diff --git a/src/main/java/storage/TaskAdapter.java b/src/main/java/storage/TaskAdapter.java new file mode 100644 index 0000000000..eb17840035 --- /dev/null +++ b/src/main/java/storage/TaskAdapter.java @@ -0,0 +1,60 @@ +package storage; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import tasks.Task; + +/** + * The `TaskAdapter` class is a Gson adapter for serializing and deserializing `Task` objects. + */ +public class TaskAdapter implements JsonDeserializer, JsonSerializer { + /** + * Serialize a `Task` object to a JSON element. + * + * @param src The `Task` object to serialize. + * @param typeOfSrc The type of the source object. + * @param context The serialization context. + * @return A JSON element representing the serialized `Task` object. + */ + @Override + public JsonElement serialize(Task src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("type", src.getClass().getName()); + jsonObject.add("data", context.serialize(src)); + return jsonObject; + } + + /** + * Deserialize a `Task` object from a JSON element. + * + * @param json The JSON element containing the serialized `Task` object. + * @param typeOfT The type of the object to deserialize. + * @param context The deserialization context. + * @return A `Task` object deserialized from the JSON element. + * @throws JsonParseException If there is an issue with JSON parsing. + */ + @Override + public Task deserialize( + JsonElement json, + Type typeOfT, + JsonDeserializationContext context + ) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String type = jsonObject.get("type").getAsString(); + JsonElement data = jsonObject.get("data"); + try { + Class clazz = Class.forName(type); + return context.deserialize(data, clazz); + } catch (ClassNotFoundException e) { + throw new JsonParseException(e); + } + } +} diff --git a/src/main/java/storage/TaskFileHandler.java b/src/main/java/storage/TaskFileHandler.java new file mode 100644 index 0000000000..67f6047005 --- /dev/null +++ b/src/main/java/storage/TaskFileHandler.java @@ -0,0 +1,118 @@ +package storage; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.time.DateTimeException; +import java.time.LocalDate; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; + +import enums.ExceptionMessage; +import enums.FilePath; +import exceptions.WoofStorageException; +import tasks.Task; +import tasks.TaskList; + + +/** + * The `TaskFileHandler` class is responsible for reading and writing tasks to a JSON file. + */ +public class TaskFileHandler { + private static final String storageLocation = FilePath.DEFAULT_STORAGE_PATH.toValue(); + /** + * Reads tasks from the JSON file and returns a `TaskList` object. + * + * @return A `TaskList` containing the tasks read from the file. + * @throws WoofStorageException If there is an issue with reading or parsing the file. + */ + public static TaskList readFromFile() { + createFileIfNotExists(); + Gson gson = new GsonBuilder() + .registerTypeAdapter(Task.class, new TaskAdapter()) + .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) + .setPrettyPrinting() + .create(); + Task[] tasks; + try (FileReader r = new FileReader(storageLocation)) { + tasks = gson.fromJson(r, Task[].class); + } catch (IOException e) { + throw new WoofStorageException(ExceptionMessage.UNABLE_TO_READ_FILE.toFormattedValue(e.getMessage())); + } catch (JsonSyntaxException | DateTimeException e) { + return destroyFileAndRetry(); + } + + return new TaskList(tasks); + } + + /** + * Destroys the file content and retries reading tasks from the JSON file. + * + * @return A `TaskList` containing the tasks after destroying the file content and retrying. + * @throws WoofStorageException If there is an issue with destroying the file content or reading tasks. + */ + private static TaskList destroyFileAndRetry() { + try (FileWriter w = new FileWriter(storageLocation)) { + w.write(""); + } catch (IOException e) { + throw new WoofStorageException(ExceptionMessage.UNABLE_TO_UPDATE_FILE.toFormattedValue(e.getMessage())); + } + + try (FileReader r = new FileReader(storageLocation)) { + Task[] tasks = new Gson().fromJson(r, Task[].class); + return new TaskList(tasks); + } catch (IOException e) { + throw new WoofStorageException(ExceptionMessage.UNABLE_TO_UPDATE_FILE.toFormattedValue(e.getMessage())); + } + } + + /** + * Saves tasks to the JSON file. + * + * @param taskList The `TaskList` containing tasks to be saved. + * @throws WoofStorageException If there is an issue with writing to the file. + */ + public static void saveToFile(TaskList taskList) { + Task[] tasks = taskList.getTasks(); + Gson gson = new GsonBuilder() + .registerTypeAdapter(Task.class, new TaskAdapter()) + .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) + .setPrettyPrinting() + .create(); + try (FileWriter w = new FileWriter(storageLocation)) { + gson.toJson(tasks, w); + } catch (IOException e) { + throw new WoofStorageException(ExceptionMessage.UNABLE_TO_UPDATE_FILE.toFormattedValue(e.getMessage())); + } + } + + /** + * Creates the JSON file if it does not exist. + * + * @throws WoofStorageException If there is an issue with file creation. + */ + private static void createFileIfNotExists() { + File file = new File(storageLocation); + if (!file.exists()) { + try { + if (!file.getParentFile().mkdirs()) { + throw new WoofStorageException( + ExceptionMessage.UNABLE_TO_CREATE_DIRECTORY.toFormattedValue() + ); + } + if (!file.createNewFile()) { + throw new WoofStorageException( + ExceptionMessage.UNABLE_TO_CREATE_FILE.toFormattedValue() + ); + } + } catch (IOException e) { + throw new WoofStorageException( + ExceptionMessage.UNABLE_TO_CREATE_FILE.toFormattedValue(e.getMessage()) + ); + } + } + } +} diff --git a/src/main/java/tasks/DeadlineTask.java b/src/main/java/tasks/DeadlineTask.java new file mode 100644 index 0000000000..b27374fdc3 --- /dev/null +++ b/src/main/java/tasks/DeadlineTask.java @@ -0,0 +1,110 @@ +package tasks; + +import java.time.LocalDate; +import java.util.Objects; + +import enums.TaskType; + +/** + * The `DeadlineTask` class represents a task with a specific deadline in the Woof application. + * It extends the `Task` class and includes the deadline information. + */ +public class DeadlineTask extends Task { + /** + * The deadline date of the task. + */ + private final LocalDate endDate; + + /** + * Constructs a `DeadlineTask` with the given description and deadline date. + * + * @param description The description of the task. + * @param endDate The deadline date of the task. + */ + public DeadlineTask(String description, LocalDate endDate) { + super(description); + this.endDate = endDate; + } + + /** + * Constructs a `DeadlineTask` with the given description and deadline date. + * + * @param description The description of the task. + * @param endDate The deadline date of the task. + * @param isDone The optional is done to mark a task as per is done. + */ + public DeadlineTask(String description, LocalDate endDate, Boolean isDone) { + super(description, isDone); + this.endDate = endDate; + } + + /** + * Gets the deadline date formatted as a string. + * + * @return A string representation of the deadline date. + */ + public String getDeadline() { + return String.format("%s~By: %s\n", getTabStopTwo(), parseDateTimeOut(endDate)); + } + + /** + * Retrieves the type of task associated with this object, which is "DEADLINE." + * + * @return The task type, which is "DEADLINE" for objects of this class. + */ + @Override + public TaskType getTaskType() { + return TaskType.DEADLINE; + } + + /** + * Retrieves the datetime associated with deadlines, the end date and time, represented as a long value. + * + * @return The end date and time as a long value. + */ + @Override + public long getDateTimeLong() { + return this.endDate.toEpochDay(); + } + + /** + * Generates a string representation of the `DeadlineTask`. + * + * @return A string representation of the task, including its symbol, description, and deadline. + */ + @Override + public String toString() { + return String.format("%s%s%s", TaskType.DEADLINE.toSymbol(), super.toString() , getDeadline()); + } + + /** + * Checks if this `DeadlineTask` is equal to another object. + * + * @param o The object to compare to. + * @return `true` if the objects are equal, `false` otherwise. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof DeadlineTask)) { + return false; + } + if (!super.equals(o)) { + return false; + } + DeadlineTask otherDeadlineTask = (DeadlineTask) o; + return this.endDate.equals(otherDeadlineTask.endDate); + } + + /** + * Generates a hash code for this `DeadlineTask`. + * + * @return A hash code for the task, including its description and deadline. + */ + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), this.endDate); + } +} diff --git a/src/main/java/tasks/EventTask.java b/src/main/java/tasks/EventTask.java new file mode 100644 index 0000000000..5a71c2580e --- /dev/null +++ b/src/main/java/tasks/EventTask.java @@ -0,0 +1,124 @@ +package tasks; + +import java.time.LocalDate; +import java.util.Objects; + +import enums.TaskType; + +/** + * The `EventTask` class represents an event task with a specific start and end date in the Woof application. + * It extends the `Task` class and includes the event date range information. + */ +public class EventTask extends Task { + /** + * The start date of the event task. + */ + private final LocalDate startDate; + + /** + * The end date of the event task. + */ + private final LocalDate endDate; + + /** + * Constructs an `EventTask` with the given description, start date, and end date. + * + * @param description The description of the event task. + * @param startDate The start date of the event task. + * @param endDate The end date of the event task. + */ + public EventTask(String description, LocalDate startDate, LocalDate endDate) { + super(description); + this.startDate = startDate; + this.endDate = endDate; + } + + /** + * Constructs an `EventTask` with the given description, start date, and end date. + * + * @param description The description of the event task. + * @param startDate The start date of the event task. + * @param endDate The end date of the event task. + * @param isDone The optional is done to mark a task as per is done. + */ + public EventTask(String description, LocalDate startDate, LocalDate endDate, Boolean isDone) { + super(description, isDone); + this.startDate = startDate; + this.endDate = endDate; + } + + /** + * Gets the event date range formatted as a string. + * + * @return A string representation of the event date range. + */ + public String getEventRange() { + return String.format("%s~From: %s\n%s~To : %s", + getTabStopTwo(), + parseDateTimeOut(startDate), + getTabStopTwo(), + parseDateTimeOut(endDate) + ); + } + + /** + * Retrieves the type of task associated with this object, which is "DEADLINE." + * + * @return The task type, which is "EVENT" for objects of this class. + */ + @Override + public TaskType getTaskType() { + return TaskType.EVENT; + } + + /** + * Retrieves the datetime associated with events, the start date and time, represented as a long value. + * + * @return The start date and time as a long value. + */ + @Override + public long getDateTimeLong() { + return this.startDate.toEpochDay(); + } + + /** + * Generates a string representation of the `EventTask`. + * + * @return A string representation of the task, including its symbol, description, and event date range. + */ + @Override + public String toString() { + return String.format("%s%s%s\n", TaskType.EVENT.toSymbol() , super.toString() , getEventRange()); + } + + /** + * Checks if this `EventTask` is equal to another object. + * + * @param o The object to compare to. + * @return `true` if the objects are equal, `false` otherwise. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof EventTask)) { + return false; + } + if (!super.equals(o)) { + return false; + } + EventTask eventTask = (EventTask) o; + return this.startDate.equals(eventTask.startDate) && this.endDate.equals(eventTask.endDate); + } + + /** + * Generates a hash code for this `EventTask`. + * + * @return A hash code for the task, including its description and event date range. + */ + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), this.startDate, this.endDate); + } +} diff --git a/src/main/java/tasks/Task.java b/src/main/java/tasks/Task.java new file mode 100644 index 0000000000..815cd24c08 --- /dev/null +++ b/src/main/java/tasks/Task.java @@ -0,0 +1,224 @@ +package tasks; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; + +import enums.TaskType; +import woof.Woof; + +/** + * The `Task` class represents a task in the Woof application. + */ +public abstract class Task implements Comparable { + /** + * The first tab stop position, typically at column 5. + * Adjust this constant to change the position of the first tab stop. + */ + static final int TAB_STOP_ONE = 5; + + /** + * The second tab stop position, typically at column 12. + * Adjust this constant to change the position of the second tab stop. + */ + static final int TAB_STOP_TWO = 12; + + /** + * The description of the task. + */ + protected String description; + + /** + * A flag indicating whether the task is marked as done. + */ + private boolean isDone; + + /** + * Constructs a `Task` with the given description and initializes it as not done. + * + * @param description The description of the task. + */ + public Task(String description) { + assert description != null : "description cannot be null"; + + this.description = Woof.wrapText( + description, + getTabStopTwo(), + Woof.getChatWidth() - TAB_STOP_TWO); + this.isDone = false; + } + + /** + * Constructs a `Task` with the given description and initializes it as not done. + * + * @param description The description of the task. + * @param isDone The optional is done to mark a task as per is done. + */ + public Task(String description, Boolean isDone) { + assert description != null : "description cannot be null"; + assert isDone != null : "is done cannot be null"; + + this.description = Woof.wrapText( + description, + getTabStopTwo(), + Woof.getChatWidth() - TAB_STOP_TWO); + this.isDone = isDone; + } + + /** + * Formats a LocalDate into a user-friendly date string for presentation. + * + * @param localDate The LocalDate to format. + * @return A formatted date string. + */ + public static String parseDateTimeOut(LocalDate localDate) { + return Woof.parseDateTimeOut(localDate); + } + + /** + * Gets the status icon representing the task's completion status. + * + * @return The status icon ("[X]" for done, "[ ]" for not done). + */ + private String getStatusIcon() { + return (isDone ? "[X]" : "[ ]"); + } + + /** + * Gets a tab stop to the first level for indenting a task to the same level as in a list. + * + * @return The tab space " ". + */ + protected String getTabStopOne() { + return " ".repeat(TAB_STOP_ONE); + } + + /** + * Gets a tab stop to the second level for indenting to the description of a task. + * + * @return The tab space " ". + */ + protected String getTabStopTwo() { + return " ".repeat(TAB_STOP_TWO); + } + + /** + * Generates a string representation of the `Task`. + * + * @return A string representation of the task, including its status icon and description. + */ + public String toString() { + return String.format("%s %s\n", this.getStatusIcon(), this.description); + } + + /** + * Marks the task as done. + */ + public void markDone() { + this.isDone = true; + } + + /** + * Marks the task as not done. + */ + public void markUndone() { + this.isDone = false; + } + + /** + * Checks if the task is marked as done. + * + * @return `true` if the task is done, `false` otherwise. + */ + public boolean isDone() { + return this.isDone; + } + + /** + * Checks if the task description contains a specified keyword. + * + * @param keyword The keyword to search for within the task description. + * @return `true` if the task description contains the keyword, `false` otherwise. + */ + public boolean hasKeyWord(String keyword) { + return this.description.contains(keyword); + } + + public abstract TaskType getTaskType(); + + public abstract long getDateTimeLong(); + + /** + * Compares this task to another task for ordering based on specific criteria. + * The comparison is primarily based on the following criteria, in order of priority: + * 1. Task completion status (done or not done). + * 2. Task type (Deadline, Event, Todo). + * 3. Date and time associated with the task (if applicable). + * 4. Task description (in case of equal status, type, and date/time). + * + * @param otherTask The task to compare to. + * @return A negative integer, zero, or a positive integer as this task is less than, + * equal to, or greater than the specified task. + */ + @Override + public int compareTo(Task otherTask) { + final ArrayList taskPriority = new ArrayList<>(Arrays.asList( + TaskType.DEADLINE, + TaskType.EVENT, + TaskType.TODO) + ); + + int doneCompare = Boolean.compare( + this.isDone(), + otherTask.isDone()); + if (doneCompare != 0) { + return doneCompare; + } + + int priorityCompare = Integer.compare( + taskPriority.indexOf(this.getTaskType()), + taskPriority.indexOf(otherTask.getTaskType()) + ); + if (priorityCompare != 0) { + return priorityCompare; + } + + int dateTimeCompare = Long.compare( + this.getDateTimeLong(), + otherTask.getDateTimeLong()); + if (dateTimeCompare != 0) { + return dateTimeCompare; + } + + return this.description.compareTo(otherTask.description); + } + + /** + * Checks if this `Task` is equal to another object. + * + * @param o The object to compare to. + * @return `true` if the objects are equal, `false` otherwise. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Task)) { + return false; + } + Task otherTask = (Task) o; + return this.isDone == otherTask.isDone && this.description.equals(otherTask.description); + } + + /** + * Generates a hash code for this `Task`. + * + * @return A hash code for the task, including its description and completion status. + */ + @Override + public int hashCode() { + return Objects.hash(description, isDone); + } +} diff --git a/src/main/java/tasks/TaskList.java b/src/main/java/tasks/TaskList.java new file mode 100644 index 0000000000..1fa4599120 --- /dev/null +++ b/src/main/java/tasks/TaskList.java @@ -0,0 +1,224 @@ +package tasks; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import enums.ExceptionMessage; +import enums.WoofMessage; +import exceptions.WoofInvalidCommandException; + +/** + * The `TaskList` class represents a collection of tasks in the Woof application. + * It provides methods for adding, marking tasks as done/undone, deleting tasks, + * and listing all tasks in the list. + */ +public class TaskList { + /** + * The list of tasks managed by this `TaskList`. + */ + protected ArrayList tasks; + + /** + * Constructs a `TaskList` with an initial array of tasks. + * + * @param tasks The initial array of tasks to populate the list. + */ + public TaskList(Task[] tasks) { + if (tasks == null) { + this.tasks = new ArrayList<>(1); + } else { + this.tasks = new ArrayList<>(Arrays.asList(tasks)); + } + } + + /** + * Gets a task from the list by its index. + * + * @param taskIndex The index of the task to retrieve. + * @return The task at the specified index, or `null` if the index is out of bounds. + */ + public Task getTask(int taskIndex) { + if (taskIndex < 0 || taskIndex >= tasks.size()) { + return null; + } + return this.tasks.get(taskIndex); + } + + /** + * Gets the number of tasks in the list. + * + * @return The number of tasks in the list. + */ + public int size() { + return this.tasks.size(); + } + + /** + * Retrieves all tasks in the list as an array. + * + * @return An array containing all the tasks in the list. + */ + public Task[] getTasks() { + Task[] taskArray = new Task[this.size()]; + return this.tasks.toArray(taskArray); + } + + /** + * Adds a task to the list and returns a confirmation message. + * + * @param task The task to be added. + * @return A confirmation message. + */ + public String addTask(Task task) { + this.tasks.add(task); + return WoofMessage.TASK_ADDED.toFormattedValue(task.getTabStopOne(), task.toString(), getTaskCountMessage()); + } + + /** + * Marks a task as done and returns a confirmation message. + * + * @param text The text representing the task to be marked as done. + * @return A confirmation message. + */ + public String markTaskDone(String text) { + int taskIndex = Integer.parseInt(text) - 1; + Task task = this.tasks.get(taskIndex); + task.markDone(); + return WoofMessage.TASK_DONE.toFormattedValue(task.getTabStopOne(), task.toString(), getTaskCountMessage()); + } + + /** + * Marks a task as undone and returns a confirmation message. + * + * @param text The text representing the task to be marked as undone. + * @return A confirmation message. + */ + public String markTaskUndone(String text) { + int taskIndex = Integer.parseInt(text) - 1; + Task task = this.tasks.get(taskIndex); + task.markUndone(); + return WoofMessage.TASK_UNDONE.toFormattedValue(task.getTabStopOne(), task.toString(), getTaskCountMessage()); + } + + /** + * Deletes a task from the list and returns a confirmation message. + * + * @param text The text representing the task to be deleted. + * @return A confirmation message. + */ + public String deleteTask(String text) { + int taskIndex = Integer.parseInt(text) - 1; + Task task = this.tasks.get(taskIndex); + this.tasks.remove(taskIndex); + return WoofMessage.TASK_DELETED.toFormattedValue(task.getTabStopOne(), task.toString(), getTaskCountMessage()); + } + + /** + * Lists all tasks in the list and returns them along with task numbers as a formatted string. + * + * @return A formatted string containing the list of tasks with task numbers. + */ + public String listAllTasks() { + String taskList = IntStream.range(0, tasks.size()) + .mapToObj(i -> String.format("%3d. %s", i + 1, tasks.get(i).toString())) + .collect(Collectors.joining("")); + return WoofMessage.TASKS_LISTED.toFormattedValue(taskList, getTaskCountMessage()); + } + + /** + * Sorts the tasks in the task list based on their natural order (using compareTo). + * + * @return A message indicating the sorting result or an error message if the task list is empty. + */ + public String sortTasks() { + Collections.sort(this.tasks); + String sortedTaskList = IntStream.range(0, tasks.size()) + .mapToObj(i -> { + String taskNumber = String.format("%3d.", i + 1); + return String.format("%s %s", taskNumber, tasks.get(i).toString()); + }) + .collect(Collectors.joining("")); + if (this.size() == 0) { + return WoofMessage.TASKS_SORTED.toFormattedValue( + WoofMessage.EMPTY_TASK_LIST.toFormattedValue(), + getTaskCountMessage()); + } + return WoofMessage.TASKS_SORTED.toFormattedValue(sortedTaskList, getTaskCountMessage()); + } + + /** + * Searches for tasks that contain any of the specified keywords in their descriptions. + * + * @param keywords The keywords to search for within task descriptions. + * @return A string containing the matching tasks or a message if no matches are found. + */ + public String findTask(String ...keywords) { + + String matchedTasks = IntStream.range(0, tasks.size()) + .filter(i -> containsKeywords(tasks.get(i), keywords)) + .mapToObj(i -> String.format("%3d. %s", i + 1, tasks.get(i).toString())) + .collect(Collectors.joining("")); + + if (matchedTasks.isEmpty()) { + return WoofMessage.TASKS_FOUND.toFormattedValue( + WoofMessage.NO_MATCHING_TASKS.toFormattedValue(), + getTaskCountMessage()); + } + + return WoofMessage.TASKS_FOUND.toFormattedValue(matchedTasks, getTaskCountMessage()); + } + + /** + * Checks if a task contains any of the specified keywords within its description. + * + * @param task The task to check for keyword matches. + * @param keywords The keywords to search for within the task's description. + * @return true if the task contains any of the specified keywords, false otherwise. + */ + private boolean containsKeywords(Task task, String[] keywords) { + for (String keyword : keywords) { + if (task.hasKeyWord(keyword)) { + return true; + } + } + return false; + } + + + /** + * Returns a string indicating the number of tasks in the task list. + * + * @return A string mentioning the number of tasks in the task list. + */ + public String getTaskCountMessage() { + return WoofMessage.TASK_LIST_COUNT.toFormattedValue(String.valueOf(this.size())); + } + + /** + * Validates whether a task index is valid within the list. + * + * @param text The text representing the task index to validate. + */ + public static void validateTaskIndex(String text, TaskList taskList) { + int index; + try { + index = Integer.parseInt(text); + } catch (NumberFormatException e) { + throw new WoofInvalidCommandException( + ExceptionMessage.NON_INTEGER_TASK_INDEX.toFormattedValue(text) + ); + } catch (Exception e) { + throw new WoofInvalidCommandException( + ExceptionMessage.UNABLE_TO_PARSE_INDEX.toFormattedValue(text, e.getMessage()) + ); + } + if (index < 1 || index > taskList.size()) { + throw new WoofInvalidCommandException( + ExceptionMessage.TASK_INDEX_NOT_IN_LIST.toFormattedValue(text) + ); + } + } +} diff --git a/src/main/java/tasks/TodoTask.java b/src/main/java/tasks/TodoTask.java new file mode 100644 index 0000000000..9bf60a449f --- /dev/null +++ b/src/main/java/tasks/TodoTask.java @@ -0,0 +1,88 @@ +package tasks; + +import java.util.Objects; + +import enums.TaskType; + +/** + * The `TodoTask` class represents a "TODO" task in the Woof application. + * It is a specific type of task that has no specific due date or event range. + */ +public class TodoTask extends Task { + /** + * Constructs a `TodoTask` with the given description. + * + * @param description The description of the "TODO" task. + */ + public TodoTask(String description) { + super(description); + } + + /** + * Constructs a `TodoTask` with the given description. + * + * @param description The description of the "TODO" task. + * @param isDone The optional is done to mark a task as per is done. + */ + public TodoTask(String description, Boolean isDone) { + super(description, isDone); + } + + /** + * Retrieves the type of task associated with this object, which is "DEADLINE." + * + * @return The task type, which is "TODO" for objects of this class. + */ + @Override + public TaskType getTaskType() { + return TaskType.TODO; + } + + /** + * Retrieves the datetime associated with "TODO", 0 since todo has no datetime associated. + * + * @return The start date and time as a long value. + */ + @Override + public long getDateTimeLong() { + return 0; + } + + + /** + * Generates a string representation of the `TodoTask`. + * + * @return A string representation of the "TODO" task, including its status icon and description. + */ + @Override + public String toString() { + return String.format("%s%s", TaskType.TODO.toSymbol(), super.toString()); + } + + /** + * Checks if this `TodoTask` is equal to another object. + * + * @param o The object to compare to. + * @return `true` if the objects are equal, `false` otherwise. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TodoTask)) { + return false; + } + return super.equals(o); + } + + /** + * Generates a hash code for this `TodoTask`. + * + * @return A hash code for the "TODO" task, including its description and completion status. + */ + @Override + public int hashCode() { + return Objects.hash(super.hashCode()); + } +} diff --git a/src/main/java/ui/Ui.java b/src/main/java/ui/Ui.java new file mode 100644 index 0000000000..0474e07304 --- /dev/null +++ b/src/main/java/ui/Ui.java @@ -0,0 +1,58 @@ +package ui; + +import java.util.Scanner; + +import enums.WoofMessage; +import woof.Woof; + +/** + * The `Ui` class provides user interface-related functionality for the Woof application. + * It includes methods for generating and returning messages, getting user input, and printing divider lines. + */ +public class Ui { + /** + * Generates and returns a welcome message when the Woof application starts. + * + * @return A welcome message. + */ + public static String getHelloWorldMessage() { + return WoofMessage.HI.toFormattedValue(); + } + + /** + * Retrieves user input using a specified `Scanner` object and returns a user prompt. + * + * @param scanner The `Scanner` object for reading user input. + * @return A user prompt. + */ + public static String getUserInput(Scanner scanner) { + return scanner.nextLine(); + } + + /** + * Generates and returns a title header for the bot's responses. + * + * @return A message prompt for the bot. + */ + public static String getBotTitle() { + return getDividerLine() + "\nOreo: "; + } + + /** + * Generates and returns a title header for the user's responses. + * + * @return A message prompt for the bot. + */ + public static String getUserTitle() { + return getDividerLine() + "\nYou: "; + } + + /** + * Generates and returns a horizontal divider line as a string to separate messages in the chat window. + * + * @return A string representing a horizontal divider line. + */ + public static String getDividerLine() { + return '\n' + "═".repeat(Woof.getChatWidth()); + } +} diff --git a/src/main/java/woof/Woof.java b/src/main/java/woof/Woof.java new file mode 100644 index 0000000000..6b689c7283 --- /dev/null +++ b/src/main/java/woof/Woof.java @@ -0,0 +1,185 @@ +package woof; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Scanner; + +import command.Command; +import command.NullCommand; +import enums.ExceptionMessage; +import exceptions.WoofException; +import parser.Parser; +import storage.TaskFileHandler; +import tasks.TaskList; +import ui.Ui; + +/** + * The `Woof` class is the main class for the Woof CLI application. + * It handles user interactions and the core functionality of the application. + */ +public class Woof { + private static final int CHAT_WIDTH = 60; + + /** + * The main entry point for the application. + * + * @param args Command-line arguments (not used in this application). + */ + public static void main(String[] args) { + runWoofCli(); + } + + /** + * Runs the Woof CLI application, allowing the user to interact with it via the command line. + */ + public static void runWoofCli() { + Scanner scanner = new Scanner(System.in); + System.out.print(Ui.getHelloWorldMessage()); + converse(scanner); + scanner.close(); + } + + /** + * Handles the conversation loop of the Woof CLI application. + * + * @param scanner The Scanner object for user input. + */ + public static void converse(Scanner scanner) { + Command command = new NullCommand(""); + while (!command.isByeCommand()) { + TaskList taskList = TaskFileHandler.readFromFile(); + + System.out.println(Ui.getUserTitle()); + String rawCommand = Ui.getUserInput(scanner); + + System.out.println(Ui.getBotTitle()); + command = Parser.parse(rawCommand); + String response = command.execute(taskList); + String wrappedResponse = wrapText(response, "" , getChatWidth()); + System.out.println(wrappedResponse); + + TaskFileHandler.saveToFile(taskList); + } + } + + /** + * Validates input date-time string using the application date-time formatter. + * + * @param string The date-time string to validate. + */ + public static void validateInDateTime(String string) { + assert string != null : "datetime string cannot be null"; + + try { + parseDateTimeIn(string); + } catch (DateTimeParseException e) { + throw new WoofException(ExceptionMessage.INVALID_DATE_TIME_FORMAT.toFormattedValue(string)); + } + } + + /** + * Updates the task list by executing a command, saving the file, and returning the result. + * + * @param command The command to be executed. + * @return The result message of executing the command. + */ + public static String updateFileAndExecute(Command command) { + assert command != null : "command cannot be null"; + + TaskList taskList = TaskFileHandler.readFromFile(); + String result = command.execute(taskList); + TaskFileHandler.saveToFile(taskList); + return result; + } + + /** + * Adds a separator every specified length to a text, wrapping it. + * + * @param message The text to be wrapped. + * @param separator The separator to be inserted when the text wraps. + * @param length The maximum length of each line before wrapping. + * @return The wrapped text with separators. + */ + public static String wrapText(String message, String separator, int length) { + assert message != null : "text cannot be null"; + assert separator != null : "separator cannot be null"; + assert length > 0 : "length has to be more than 0"; + + int currentLineLength = 0; + StringBuilder result = new StringBuilder(); + + for (int i = 0; i < message.length(); i++) { + char currentChar = message.charAt(i); + if (currentChar == '\n') { + result.append(currentChar); + result.append(separator); + currentLineLength = -1; + } else if (currentLineLength >= length) { + result.append('\n'); + result.append(separator); + result.append(currentChar); + currentLineLength = 0; + } else { + result.append(currentChar); + } + currentLineLength++; + } + return result.toString(); + } + + /** + * Provides the DateTimeFormatter for parsing and formatting input dates. + * + * @return The DateTimeFormatter for input date strings in ISO_LOCAL_DATE format. + */ + public static DateTimeFormatter getDateTimeInFormatter() { + return DateTimeFormatter.ISO_LOCAL_DATE; + } + + /** + * Provides the DateTimeFormatter for formatting dates to be presented to the user. + * + * @return The DateTimeFormatter for output date strings in "dd MMM yyyy" format. + */ + public static DateTimeFormatter getDateTimeOutFormatter() { + return DateTimeFormatter.ofPattern("dd MMM yyyy"); + } + + /** + * Parses a date string received by the application using the input formatter. + * + * @param dateString The date string to be parsed. + * @return The parsed LocalDate. + * @throws WoofException If the date string is invalid and cannot be parsed. + */ + public static LocalDate parseDateTimeIn(String dateString) { + assert dateString != null : "Date string cannot be null"; + + try { + return LocalDate.parse(dateString, getDateTimeInFormatter()); + } catch (DateTimeParseException e) { + throw new WoofException(ExceptionMessage.INVALID_DATE_TIME_FORMAT.toFormattedValue(dateString)); + } + } + + /** + * Get the chat width for chat formatting. + */ + public static int getChatWidth() { + return Woof.CHAT_WIDTH; + } + + /** + * Formats a LocalDate into a user-friendly date string for presentation. + * + * @param date The LocalDate to format. + * @return A formatted date string. + * @throws IllegalArgumentException If the input LocalDate is null. + */ + public static String parseDateTimeOut(LocalDate date) { + assert date != null : "Date cannot be null"; + + return date.format(getDateTimeOutFormatter()); + } +} diff --git a/src/main/java/woofwoof/DialogBox.java b/src/main/java/woofwoof/DialogBox.java new file mode 100644 index 0000000000..d66c644283 --- /dev/null +++ b/src/main/java/woofwoof/DialogBox.java @@ -0,0 +1,165 @@ +package woofwoof; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.effect.DropShadow; +import javafx.scene.image.Image; +import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.paint.ImagePattern; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; +import javafx.scene.text.Text; + + +/** + * The `DialogBox` class represents a graphical dialog box for displaying messages in the GUI application. + * It provides methods for creating user and application dialog boxes, flipping their alignment, + * and setting text wrapping. + */ +public class DialogBox extends HBox { + /** + * Private constructor to create a new `DialogBox`. + * + * @param message The text content to be displayed in the dialog box. + * @param img The image to be displayed in the dialog box. + */ + private DialogBox(String message, Image img) { + assert message != null : "message cannot be null"; + assert img != null : "image cannot be null"; + + this.setPadding(new Insets(10.0, 0.0, 10.0, 0.0)); + DropShadow dropShadow = createDropShadow(); + Circle displayPicture = createDisplayPicture(img); + Text text = createText(message); + Rectangle messageBubble = createMessageBubble(text); + + displayPicture.setEffect(dropShadow); + messageBubble.setEffect(dropShadow); + StackPane bubbleDialog = createBubbleDialog(messageBubble, text); + this.getChildren().addAll(bubbleDialog, displayPicture); + } + + /** + * Create and configure the drop shadow effect for elements. + * + * @return The configured DropShadow effect. + */ + private DropShadow createDropShadow() { + DropShadow dropShadow = new DropShadow(); + dropShadow.setRadius(5.0); + dropShadow.setColor(new Color(0.0, 0.0, 0.0, 0.5)); + return dropShadow; + } + + /** + * Create and configure the display picture (circle with an image fill). + * + * @param img The display image to be displayed. + * @return The configured Circle element for the display picture. + */ + private Circle createDisplayPicture(Image img) { + assert img != null : "image cannot be null"; + + Circle displayPicture = new Circle(0, 0, 50); + displayPicture.setFill(new ImagePattern(img)); + displayPicture.setEffect(createDropShadow()); + return displayPicture; + } + + /** + * Create a Text element with the given message content. + * + * @param message The text content to be displayed. + * @return The configured Text element. + */ + private Text createText(String message) { + assert message != null : "message cannot be null"; + + return new Text(message); + } + + /** + * Create and configure the message bubble (rounded rectangle). + * + * @param text The Text element to calculate the height of the bubble. + * @return The configured Rectangle element for the message bubble. + */ + private Rectangle createMessageBubble(Text text) { + assert text != null : "text cannot be null"; + + text.setFont(WoofWoof.getFont()); + double messageBubbleHeight = text.getBoundsInLocal().getHeight() + 40; + Rectangle messageBubble = new Rectangle(560, messageBubbleHeight); + messageBubble.setFill(Color.WHITE); + messageBubble.setArcWidth(40); + messageBubble.setArcHeight(40); + messageBubble.setEffect(createDropShadow()); + return messageBubble; + } + + /** + * Create a StackPane to combine the message bubble and text message. + * + * @param messageBubble The message bubble element. + * @param text The text message element. + * @return The configured StackPane containing the message bubble and text message. + */ + private StackPane createBubbleDialog(Rectangle messageBubble, Text text) { + assert text != null : "message cannot be null"; + + Insets bubbleMargin = new Insets(20.0); // top, right, bottom, left + StackPane bubbleDialog = new StackPane(messageBubble, text); + StackPane.setMargin(text, bubbleMargin); + bubbleDialog.setAlignment(Pos.TOP_LEFT); + bubbleDialog.setStyle( + "-fx-padding: 0px 20px 0px 20px;" + ); + return bubbleDialog; + } + + /** + * Flips the dialog box, changing the alignment to place the display picture on the left and message bubble + * on the right. + */ + private void flip() { + this.setAlignment(Pos.TOP_LEFT); + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + FXCollections.reverse(tmp); + this.getChildren().setAll(tmp); + } + + /** + * Creates a new user dialog box. + * + * @param message The message content to be displayed in the user dialog box. + * @param img The display image to be displayed in the user dialog box. + * @return A new `DialogBox` representing the user's dialog. + */ + public static DialogBox getUserDialog(String message, Image img) { + assert message != null : "message cannot be null"; + assert img != null : "image cannot be null"; + + return new DialogBox(message, img); + } + + /** + * Creates a new dialog box and flips it to place the ImageView on the left and message on the right. + * + * @param message The message content to be displayed in the application dialog box. + * @param img The display image to be displayed in the application dialog box. + * @return A new `DialogBox` representing the application's dialog. + */ + public static DialogBox getBotDialog(String message, Image img) { + assert message != null : "message cannot be null"; + assert img != null : "image cannot be null"; + + var db = new DialogBox(message, img); + db.flip(); + return db; + } +} diff --git a/src/main/java/woofwoof/Launcher.java b/src/main/java/woofwoof/Launcher.java new file mode 100644 index 0000000000..5024d20e7b --- /dev/null +++ b/src/main/java/woofwoof/Launcher.java @@ -0,0 +1,25 @@ +package woofwoof; + +import java.util.Objects; + +import javafx.application.Application; +import woof.Woof; + +/** + * The Launcher class is responsible for launching the WoofWoof application. + */ +public class Launcher { + /** + * The main method of the Launcher class. + * + * @param args The command-line arguments. If 'cli' is provided as an argument, it launches the CLI mode; + * otherwise, it launches the GUI mode. + */ + public static void main(String[] args) { + if (args.length != 1 || !Objects.equals(args[0], "cli")) { + Application.launch(WoofWoof.class, args); + } else { + Woof.main(null); + } + } +} diff --git a/src/main/java/woofwoof/WoofWoof.java b/src/main/java/woofwoof/WoofWoof.java new file mode 100644 index 0000000000..deb6a6fa9c --- /dev/null +++ b/src/main/java/woofwoof/WoofWoof.java @@ -0,0 +1,256 @@ +package woofwoof; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import command.Command; +import enums.FilePath; +import enums.WoofMessage; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.ImageCursor; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextArea; +import javafx.scene.image.Image; +import javafx.scene.image.PixelWriter; +import javafx.scene.image.WritableImage; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.stage.Stage; +import parser.Parser; +import woof.Woof; + +/** + * The `WoofWoof` class is the main class for the Woof GUI application. + * The `WoofWoof` class relies on the existence of the `Woof` class, as it only extends `Woof` to support GUI. + */ +public class WoofWoof extends Application { + private static final Font FONT = loadCustomFont(); + + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogArea; + @FXML + private Button clearButton; + @FXML + private TextArea userInput; + @FXML + private Button sendButton; + private Scene scene; + + private Woof woof; + private final Image user = getImageFromFilePath(FilePath.USER_DISPLAY_PICTURE); + private final Image bot = getImageFromFilePath(FilePath.BOT_DISPLAY_PICTURE); + + + /** + * Retrieves an image from a file path. If the file path is invalid or the resource is not found, + * it returns an empty image. + * + * @param filePath The path to the image file. + * @return An Image object loaded from the specified file path or an empty image if not found. + */ + public Image getImageFromFilePath(FilePath filePath) { + InputStream inputStream; + try { + inputStream = Objects.requireNonNull(this.getClass().getResourceAsStream(filePath.toValue())); + } catch (NullPointerException e) { + return getEmptyImage(); + } + return new Image(inputStream); + } + + /** + * Generates an empty image with a single pixel of gray color (0.5, 0.5, 0.5, 1). + * + * @return An empty Image object with a single gray pixel. + */ + public Image getEmptyImage() { + WritableImage img = new WritableImage(1, 1); + PixelWriter pw = img.getPixelWriter(); + pw.setColor(0, 0, new Color(0.5, 0.5, 0.5, 1)); + return img; + } + + /** + * Initializes the WoofWoof application by reading the task list from a file + * and loading the font. + */ + @FXML + public void initialize() { + this.userInput.setOnKeyPressed(event -> { + if (event.getCode() == KeyCode.ENTER) { + handleUserSubmit(); + } + }); + this.sendButton.setOnMouseEntered(e -> this.sendButton.setStyle("-fx-base: #C1E1C1")); + this.sendButton.setOnMouseExited(e -> this.sendButton.setStyle("-fx-base: #C5CBEC")); + this.clearButton.setOnMouseEntered(e -> this.clearButton.setStyle("-fx-base: #FAA0A0")); + this.clearButton.setOnMouseExited(e -> this.clearButton.setStyle("-fx-base: #C5CBEC")); + this.sendButton.setOnMouseClicked(event -> handleUserSubmit()); + + this.userInput.setFont(getFont()); + this.sendButton.setFont(getFont()); + this.clearButton.setFont(getFont()); + + this.dialogArea.getChildren().addAll( + DialogBox.getBotDialog( + Woof.wrapText(WoofMessage.HI.toFormattedValue(), "", Woof.getChatWidth()), this.bot + ) + ); + this.dialogArea.prefHeightProperty().bind(this.scrollPane.heightProperty()); + } + + /** + * Sets the Woof instance for the application. + * + * @param w The Woof instance to set. + */ + public void setWoof(Woof w) { + this.woof = w; + } + + /** + * Starts the WoofWoof application. + * + * @param primaryStage The primary stage for the application. + */ + @Override + public void start(Stage primaryStage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(WoofWoof.class.getResource(FilePath.FXML_VIEW_PATH.toValue())); + this.scene = new Scene(fxmlLoader.load()); + setCustomCursorOnApplication(); + loadCssStyles(); + primaryStage.setScene(this.scene); + primaryStage.setTitle(WoofMessage.WOOF_TITLE.toFormattedValue()); + primaryStage.setMinHeight(290.0); + fxmlLoader.getController().setWoof(this.woof); + primaryStage.show(); + primaryStage.maxWidthProperty().bind(primaryStage.widthProperty()); + primaryStage.minWidthProperty().bind(primaryStage.widthProperty()); + } catch (IOException e) { + System.out.printf("really oh no\n%s\n", e.getMessage()); + } + } + + /** + * Loads the font and sets the custom font for the class. + */ + private static Font loadCustomFont() { + URL fontResourceUrl = WoofWoof.class.getResource(FilePath.CUSTOM_FONT.toValue()); + return Font.loadFont(Objects.requireNonNull(fontResourceUrl).toExternalForm(), 14); + } + + /** + * Gets the custom font. + */ + public static Font getFont() { + return WoofWoof.FONT; + } + + /** + * Sets up and applies a custom cursor when entering a JavaFX scene and handles cursor reset on click. + */ + private void setCustomCursorOnApplication() { + String imagePath = FilePath.CUSTOM_CURSOR.toValue(); + Image defaultCursorImage = new Image(imagePath, 40, 40, false, true); + ImageCursor defaultCursor = new ImageCursor(defaultCursorImage, 20, 20); + this.scene.setCursor(defaultCursor); + } + + /** + * Load CSS styles from file paths and apply them to the scene. + */ + private void loadCssStyles() { + String[] cssFilePaths = { + FilePath.ROOT_CSS.toValue(), + FilePath.SCROLL_PANE_CSS.toValue(), + FilePath.DIALOG_AREA_CSS.toValue(), + FilePath.CLEAR_BUTTON_CSS.toValue(), + FilePath.USER_INPUT_CSS.toValue(), + FilePath.SEND_BUTTON_CSS.toValue(), + }; + for (String cssFilePath : cssFilePaths) { + String css = Objects.requireNonNull(getClass().getResource(cssFilePath)).toExternalForm(); + this.scene.getStylesheets().add(css); + } + } + + /** + * Handles user submit, generates responses, and updates the dialog. + */ + @FXML + private void handleUserSubmit() { + String message = this.userInput.getText().trim(); + if (!message.isEmpty()) { + String response = processMessage(message); + this.dialogArea.getChildren().addAll( + DialogBox.getUserDialog(Woof.wrapText(message, "", Woof.getChatWidth()), this.user), + DialogBox.getBotDialog(Woof.wrapText(response, "", Woof.getChatWidth()), this.bot) + ); + this.userInput.clear(); + } + Platform.runLater(() -> Platform.runLater(()->this.scrollPane.setVvalue(1.0))); + } + + /** + * Handles user clear, clears the dialog area and text area. + */ + @FXML + private void handleUserClear() { + this.dialogArea.getChildren().clear(); + this.userInput.clear(); + } + + /** + * Schedule the closing of the JavaFX stage after a 1-second delay. + */ + private void scheduleCloseAfterDelay() { + ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + executorService.schedule(() -> Platform.runLater(() -> { + Stage currentStage = (Stage) this.dialogArea.getScene().getWindow(); + currentStage.close(); + executorService.shutdown(); + }), + 1, TimeUnit.SECONDS + ); + } + + /** + * Processes the message and generates a response to user input. + * + * @param message The user's message. + * @return The response message. + */ + private String processMessage(String message) { + assert message != null : "message cannot be null"; + + Command command = Parser.parse(message); + if (command.isByeCommand()) { + scheduleCloseAfterDelay(); + } + return Woof.updateFileAndExecute(command); + } + + /** + * The main entry point of the WoofWoof application. + * + * @param args The command-line arguments (not used in JavaFX applications). + */ + public static void main(String[] args) { + launch(args); + } +} diff --git a/src/main/resources/fonts/sono/OFL.txt b/src/main/resources/fonts/sono/OFL.txt new file mode 100644 index 0000000000..0ed8abb251 --- /dev/null +++ b/src/main/resources/fonts/sono/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Sono Project Authors (https://github.com/sursly/sono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/main/resources/fonts/sono/README.txt b/src/main/resources/fonts/sono/README.txt new file mode 100644 index 0000000000..261ee8dcfd --- /dev/null +++ b/src/main/resources/fonts/sono/README.txt @@ -0,0 +1,77 @@ +Sono Variable Font +================== + +This download contains Sono as both a variable font and static fonts. + +Sono is a variable font with these axes: + MONO + wght + +This means all the styles are contained in a single file: + Sono-VariableFont_MONO,wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Sono: + static/Sono_Proportional-ExtraLight.ttf + static/Sono_Proportional-Light.ttf + static/Sono_Proportional-Regular.ttf + static/Sono_Proportional-Medium.ttf + static/Sono_Proportional-SemiBold.ttf + static/Sono_Proportional-Bold.ttf + static/Sono_Proportional-ExtraBold.ttf + static/Sono-ExtraLight.ttf + static/Sono-Light.ttf + static/Sono-Regular.ttf + static/Sono-Medium.ttf + static/Sono-SemiBold.ttf + static/Sono-Bold.ttf + static/Sono-ExtraBold.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/src/main/resources/fonts/sono/Sono-VariableFont_MONO,wght.ttf b/src/main/resources/fonts/sono/Sono-VariableFont_MONO,wght.ttf new file mode 100644 index 0000000000..4793a34b80 Binary files /dev/null and b/src/main/resources/fonts/sono/Sono-VariableFont_MONO,wght.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono-Bold.ttf b/src/main/resources/fonts/sono/static/Sono-Bold.ttf new file mode 100644 index 0000000000..e403d1cc40 Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-Bold.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono-ExtraBold.ttf b/src/main/resources/fonts/sono/static/Sono-ExtraBold.ttf new file mode 100644 index 0000000000..841477d983 Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-ExtraBold.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono-ExtraLight.ttf b/src/main/resources/fonts/sono/static/Sono-ExtraLight.ttf new file mode 100644 index 0000000000..a4501c601b Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-ExtraLight.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono-Light.ttf b/src/main/resources/fonts/sono/static/Sono-Light.ttf new file mode 100644 index 0000000000..1a893377ee Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-Light.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono-Medium.ttf b/src/main/resources/fonts/sono/static/Sono-Medium.ttf new file mode 100644 index 0000000000..ee40d6c1df Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-Medium.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono-Regular.ttf b/src/main/resources/fonts/sono/static/Sono-Regular.ttf new file mode 100644 index 0000000000..82eb78f5e1 Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-Regular.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono-SemiBold.ttf b/src/main/resources/fonts/sono/static/Sono-SemiBold.ttf new file mode 100644 index 0000000000..b994cdfcaa Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-SemiBold.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-Bold.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-Bold.ttf new file mode 100644 index 0000000000..42ce09ed8c Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-Bold.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-ExtraBold.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-ExtraBold.ttf new file mode 100644 index 0000000000..11088fa0e0 Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-ExtraBold.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-ExtraLight.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-ExtraLight.ttf new file mode 100644 index 0000000000..6bd23dd93e Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-ExtraLight.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-Light.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-Light.ttf new file mode 100644 index 0000000000..ed31dfb925 Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-Light.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-Medium.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-Medium.ttf new file mode 100644 index 0000000000..10c8bc26ef Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-Medium.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-Regular.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-Regular.ttf new file mode 100644 index 0000000000..99367c3878 Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-Regular.ttf differ diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-SemiBold.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-SemiBold.ttf new file mode 100644 index 0000000000..32ee6416cb Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-SemiBold.ttf differ diff --git a/src/main/resources/images/botDisplayPicture.jpeg b/src/main/resources/images/botDisplayPicture.jpeg new file mode 100644 index 0000000000..c4e5e19df8 Binary files /dev/null and b/src/main/resources/images/botDisplayPicture.jpeg differ diff --git a/src/main/resources/images/paw.png b/src/main/resources/images/paw.png new file mode 100644 index 0000000000..a5c37be4c2 Binary files /dev/null and b/src/main/resources/images/paw.png differ diff --git a/src/main/resources/images/paws.png b/src/main/resources/images/paws.png new file mode 100644 index 0000000000..1f6d667dab Binary files /dev/null and b/src/main/resources/images/paws.png differ diff --git a/src/main/resources/images/userDisplayPicture.jpeg b/src/main/resources/images/userDisplayPicture.jpeg new file mode 100644 index 0000000000..294da37a50 Binary files /dev/null and b/src/main/resources/images/userDisplayPicture.jpeg differ diff --git a/src/main/resources/styles/clearButton.css b/src/main/resources/styles/clearButton.css new file mode 100644 index 0000000000..d28eab081b --- /dev/null +++ b/src/main/resources/styles/clearButton.css @@ -0,0 +1,5 @@ +#clearButton { + -fx-base: #C5CBEC; + -fx-pref-height: 60px; + -fx-pref-width: 100px; +} diff --git a/src/main/resources/styles/dialogArea.css b/src/main/resources/styles/dialogArea.css new file mode 100644 index 0000000000..6612b8331d --- /dev/null +++ b/src/main/resources/styles/dialogArea.css @@ -0,0 +1,5 @@ +#dialogArea { + -fx-background-color: #C5CBEC; + -fx-pref-width: 740.0; + -fx-padding: 10px 20px 0px 20px; +} diff --git a/src/main/resources/styles/root.css b/src/main/resources/styles/root.css new file mode 100644 index 0000000000..437a6463a0 --- /dev/null +++ b/src/main/resources/styles/root.css @@ -0,0 +1,5 @@ +.root { + -fx-pref-height: 800px; + -fx-pref-width: 800px; + -fx-background: #AFBAEA; +} diff --git a/src/main/resources/styles/scrollPane.css b/src/main/resources/styles/scrollPane.css new file mode 100644 index 0000000000..bc81bb4d8f --- /dev/null +++ b/src/main/resources/styles/scrollPane.css @@ -0,0 +1,23 @@ +#scrollPane { + -fx-background-color: #C5CBEC; + -fx-base: #C5CBEC; +} + +/* Hide the arrow buttons for the vertical scroll bar */ +#scrollPane .scroll-bar:vertical .increment-button, +#scrollPane .scroll-bar:vertical .decrement-button { + -fx-padding: 0; + -fx-opacity: 0; +} + +/* Adjust the width of the vertical scroll bar */ +#scrollPane .scroll-bar:vertical { + -fx-background-color: #C5CBEC; + -fx-pref-width: 20px; +} + +/* Customize the thumb of the vertical scroll bar */ +#scrollPane .scroll-bar:vertical .thumb { + -fx-background-color: #8E9CE2; + -fx-border-radius: 20px; +} diff --git a/src/main/resources/styles/sendButton.css b/src/main/resources/styles/sendButton.css new file mode 100644 index 0000000000..8ec640218a --- /dev/null +++ b/src/main/resources/styles/sendButton.css @@ -0,0 +1,5 @@ +#sendButton { + -fx-base: #C5CBEC; + -fx-pref-height: 60px; + -fx-pref-width: 100px; +} diff --git a/src/main/resources/styles/userInput.css b/src/main/resources/styles/userInput.css new file mode 100644 index 0000000000..66d2cff5b5 --- /dev/null +++ b/src/main/resources/styles/userInput.css @@ -0,0 +1,23 @@ +#userInput { + -fx-control-inner-background: #C5CBEC; + -fx-max-height: 60.0; +} + +/* Hide the arrow buttons for the vertical scroll bar */ +#userInput .scroll-bar:vertical .increment-button, +#userInput .scroll-bar:vertical .decrement-button { + -fx-padding: 0; + -fx-opacity: 0; +} + +/* Adjust the width of the vertical scroll bar */ +#userInput .scroll-bar:vertical { + -fx-background-color: #C5CBEC; + -fx-pref-width: 20px; +} + +/* Customize the thumb of the vertical scroll bar */ +#userInput .scroll-bar:vertical .thumb { + -fx-background-color: #8E9CE2; + -fx-border-radius: 20px; +} diff --git a/src/main/resources/views/WoofWoof.fxml b/src/main/resources/views/WoofWoof.fxml new file mode 100644 index 0000000000..1b6cb5381f --- /dev/null +++ b/src/main/resources/views/WoofWoof.fxml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + +