diff --git a/.gitignore b/.gitignore index 2873e189e..405640353 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,9 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +#Compiled class file +*.class + +# userdata +/data/ diff --git a/README.md b/README.md index af0309a9e..35c032d3b 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,177 @@ -# 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 17, 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 17** (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. -1. 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 - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` - -**Warning:** Keep the `src\main\java` folder as the root folder for Java files (i.e., don't rename those folders or move Java files to another folder outside of this folder path), as this is the default location some tools (e.g., Gradle) expect to find Java files. +# BobChungus - User Guide + +## Overview + +BobChungus is a command-line based application intended as a Task Management System. It aims to help users manage their active tasks in the form of **todos**, **deadlines** and **events**. + +--- + +## Commands + +#### Notes about the Command Format + +
+ +- Words in UPPER_CASE are the parameters to be supplied by the user. + e.g. in TASK_DESCRIPTION is a parameter which can be replaced with the description of the task to be added +
+ +- Parameters must be in the specified order. + e.g. if the command specifies TASK_DESCRIPTION followed by TASK_DATE, + TASK_DATE followed by TASK_DESCRIPTION is NOT acceptable and will cause issues. +
+ +- Extraneous parameters for commands that do not take in parameters (such as list and bye) will cause errors. + e.g. if the command specifies bye 123, it will be create an error. +
+ +- If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. + +
+ +--- + +### List of Commnads + +--- + +### **Exiting the program:** `bye` + +**Description:** Exits the program. Saves all Tasks within `TaskList` before exitting. + +- **Use Case:** When the user is done, they can type `bye` to exit the program. +- **Format:** `bye` + +### **Display all Tasks:** `list` + +**Description:** Displays the list of tasks. + +- **Use Case:** When the user wants to view all tasks in their list. +- **Format:** `list` + +### **Create new `Todo` task:** `todo` + +**Description:** Adds a new to-do task. + +- **Use Case:** The user provides a description for the to-do task. +- **Arguments:** Task description (e.g., `todo Buy groceries`) +- **Format:** `todo TASK_DESCRIPTION` + +### **Create new `deadline` task:** `deadline` + +**Description:** Adds a new deadline task. + +- **Use Case:** The user provides a task description and a deadline date. +- **Arguments:** Task description followed by `/by` and the deadline date (e.g., `deadline Finish assignment /by 15-03-2025`). +- **Format:** `deadline TASK_DESCRIPTION /by DD-MM-YYYY` + +### **Create new `event` task:** `event` + +**Description:** Adds a new event task. + +- **Use Case:** The user provides an event description and start and end date. +- **Arguments:** Event description followed by `/from` (start date) and `/to` (end date) (e.g., `event Conference /from 20-03-2025 /to 22-03-2025`). +- **Format:** `event TASK_DESCRIPTION /from TASK_START_DATE(dd-MM-yyyy) /to TASK_END_DATE(dd-MM-yyyy)` + +--- + +
+ Note: Tasks are stored within the program 0-indexed, but are displayed and interacted with by users 1-indexed. +
+ +--- + +### **Delete a Task:** `delete` + +**Description:** Deletes a task by index. + +- **Use Case:** The user provides the index of the task to be deleted. +- **Arguments:** Task index (e.g., `delete 2` to delete the second task in the list). +- **Format:** `delete TASK_INDEX` + +### **Mark a task as complete:** `mark` + +**Description:** Marks a task as completed. + +- **Use Case:** The user provides the index of the task to mark as done. +- **Arguments:** Task index (e.g., `mark 3` to mark the third task as done). +- **Format:** `mark TASK_INDEX` + +### **Mark a task as not complete:** `unmark` + +**Description:** Marks a task as not completed. + +- **Use Case:** The user provides the index of the task to mark as not done. +- **Arguments:** Task index (e.g., `unmark 1` to unmark the first task). +- **Format:** `unmark TASK_INDEX` + +### **Find a task:** `find` + +**Description:** Finds tasks with a specific keyword in their description. + +- **Use Case:** The user provides a keyword to search for in the task descriptions. +- **Arguments:** Keyword to search for (e.g., `find meeting`). +- **Format:** `find TASK_DESCRIPTION` + +### **Saving your data** + +BobChungus' `TaskList` data is saved in the hard disk automatically after exiting with the `bye` command. +There is no need to save manually. + +### **Editing Saved data** + +BobChungus' `TaskList` Data is automatically saved as a `.txt` file at `[JAR file location]/data/userTasks.txt`. Advanced users are welcome to update data directly by editing that data file. + +
+ Caution: If your changes to the data file makes its format invalid, BobChungus will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it. + +Furthermore, certain edits can cause BobChungus to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly. + +
+ +--- + +## Command Summary + +| Command | Description | Arguments | Format | +| ---------- | ----------------------------------------- | ------------------------------------- | -------------------------------------------------- | +| `bye` | Exits the program | None | `bye` | +| `list` | Lists all tasks | None | `list` | +| `todo` | Adds a to-do task | Task description | `todo Buy groceries` | +| `deadline` | Adds a deadline task | Task description /by date | `deadline Submit report /by 18-03-2025` | +| `event` | Adds an event task | Event description /from date /to date | `event Conference /from 20-03-2025 /to 22-03-2025` | +| `delete` | Deletes a task by index | Task index | `delete 2` | +| `mark` | Marks a task as completed | Task index | `mark 3` | +| `unmark` | Marks a task as not completed | Task index | `unmark 1` | +| `find` | Finds tasks by keyword in the description | Search keyword | `find meeting` | + +--- + +## FAQs + +### 1. **What happens if I enter an invalid command?** + +- If you enter a command that is not recognized, the program will throw an exception and display an error message indicating that the command is invalid. + +### 2. **What if I forget to include the arguments for a command like `todo` or `deadline`?** + +- The program will throw an exception (`MissingTodoArgumentException`, `MissingDeadlineArgumentException`, etc.) and inform you that you are missing required arguments. + +### 3. **What format should the dates be in?** + +- All dates should be in the format `dd-MM-yyyy`, for Format, example `15-03-2025`. + +--- + +## Notes on Editing Data + +- The commands are case-insensitive, meaning you can type commands in any letter case (e.g., `todo`, `TODO`, `ToDo`). +- Ensure that the date format for commands like `deadline` and `event` is strictly followed (`dd-MM-yyyy`). +- You can modify the task list and storage handling by editing the `TaskList` and `Storage` classes. + +--- + +## Known Issues + +- **Date parsing errors**: If a date is not in the correct format (`dd-MM-yyyy`), a `DateTimeParseException` will be thrown. +- **Index errors**: Deleting or marking tasks by index requires the index to be valid; otherwise, an exception may occur if the user enters an invalid index. diff --git a/data/userTasks.txt b/data/userTasks.txt new file mode 100644 index 000000000..81a72365c --- /dev/null +++ b/data/userTasks.txt @@ -0,0 +1 @@ +todo|done|testing diff --git a/docs/README.md b/docs/README.md index 47b9f984f..22c65ccb8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,186 @@ -# Duke User Guide +# BobChungus - User Guide -// Update the title above to match the actual product name +## Overview -// Product screenshot goes here +BobChungus is a command-line based application intended as a Task Management System. It aims to help users manage their active tasks in the form of **todos**, **deadlines** and **events**. -// Product intro goes here +--- -## Adding deadlines +## Commands -// Describe the action and its outcome. -// Give examples of usage -Example: `keyword (optional arguments)` +
-// A description of the expected outcome goes here + Notes about the Command Format +
+
-``` -expected output -``` +- Words in UPPER_CASE are the parameters to be supplied by the user. + e.g. in TASK_DESCRIPTION is a parameter which can be replaced with the description of the task to be added +
+
-## Feature ABC +- Parameters must be in the specified order. + e.g. if the command specifies TASK_DESCRIPTION followed by TASK_DATE, + TASK_DATE followed by TASK_DESCRIPTION is NOT acceptable and will cause issues. +
+
-// Feature details +- Extraneous parameters for commands that do not take in parameters (such as list and bye) will cause errors. + e.g. if the command specifies bye 123, it will be create an error. +
+
+- If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. +
-## Feature XYZ -// Feature details \ No newline at end of file +
+ +--- + +### List of Commands + +--- + +### **Exiting the program:** `bye` + +**Description:** Exits the program. Saves all Tasks within `TaskList` before exitting. + +- **Use Case:** When the user is done, they can type `bye` to exit the program. +- **Format:** `bye` + +### **Display all Tasks:** `list` + +**Description:** Displays the list of tasks. + +- **Use Case:** When the user wants to view all tasks in their list. +- **Format:** `list` + +### **Create new `Todo` task:** `todo` + +**Description:** Adds a new to-do task. + +- **Use Case:** The user provides a description for the to-do task. +- **Arguments:** Task description (e.g., `todo Buy groceries`) +- **Format:** `todo TASK_DESCRIPTION` + +### **Create new `deadline` task:** `deadline` + +**Description:** Adds a new deadline task. + +- **Use Case:** The user provides a task description and a deadline date. +- **Arguments:** Task description followed by `/by` and the deadline date (e.g., `deadline Finish assignment /by 15-03-2025`). +- **Format:** `deadline TASK_DESCRIPTION /by DD-MM-YYYY` + +### **Create new `event` task:** `event` + +**Description:** Adds a new event task. + +- **Use Case:** The user provides an event description and start and end date. +- **Arguments:** Event description followed by `/from` (start date) and `/to` (end date) (e.g., `event Conference /from 20-03-2025 /to 22-03-2025`). +- **Format:** `event TASK_DESCRIPTION /from TASK_START_DATE(dd-MM-yyyy) /to TASK_END_DATE(dd-MM-yyyy)` + +--- + +
+ Note: Tasks are stored within the program 0-indexed, but are displayed and interacted with by users 1-indexed. +
+ +--- + +### **Delete a Task:** `delete` + +**Description:** Deletes a task by index. + +- **Use Case:** The user provides the index of the task to be deleted. +- **Arguments:** Task index (e.g., `delete 2` to delete the second task in the list). +- **Format:** `delete TASK_INDEX` + +### **Mark a task as complete:** `mark` + +**Description:** Marks a task as completed. + +- **Use Case:** The user provides the index of the task to mark as done. +- **Arguments:** Task index (e.g., `mark 3` to mark the third task as done). +- **Format:** `mark TASK_INDEX` + +### **Mark a task as not complete:** `unmark` + +**Description:** Marks a task as not completed. + +- **Use Case:** The user provides the index of the task to mark as not done. +- **Arguments:** Task index (e.g., `unmark 1` to unmark the first task). +- **Format:** `unmark TASK_INDEX` + +### **Find a task:** `find` + +**Description:** Finds tasks with a specific keyword in their description. + +- **Use Case:** The user provides a keyword to search for in the task descriptions. +- **Arguments:** Keyword to search for (e.g., `find meeting`). +- **Format:** `find TASK_DESCRIPTION` + +### **Saving your data** + +BobChungus' `TaskList` data is saved in the hard disk automatically after exiting with the `bye` command. +There is no need to save manually. + +### **Editing Saved data** + +BobChungus' `TaskList` Data is automatically saved as a `.txt` file at `[JAR file location]/data/userTasks.txt`. Advanced users are welcome to update data directly by editing that data file. + +
+ Caution: If your changes to the data file makes its format invalid, BobChungus will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it. + +Furthermore, certain edits can cause BobChungus to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly. + +
+ +--- + +## Command Summary + +| Command | Description | Arguments | Format | +| ---------- | ----------------------------------------- |---------------------------------------------| -------------------------------------------------- | +| `bye` | Exits the program | None | `bye` | +| `list` | Lists all tasks | None | `list` | +| `todo` | Adds a to-do task | TASK_DESCRIPTION | `todo Buy groceries` | +| `deadline` | Adds a deadline task | TASK_DESCRIPTION /by DD-MM-YYYY | `deadline Submit report /by 18-03-2025` | +| `event` | Adds an event task | TASK_DESCRIPTION /from DD-MM-YYYY /to DD-MM-YYYY | `event Conference /from 20-03-2025 /to 22-03-2025` | +| `delete` | Deletes a task by index | TASK_INDEX (1-indexed) | `delete 2` | +| `mark` | Marks a task as completed | TASK_INDEX (1-indexed) | `mark 3` | +| `unmark` | Marks a task as not completed | TASK_INDEX (1-indexed) | `unmark 1` | +| `find` | Finds tasks by keyword in the description | SEARCH_KEYWORD | `find meeting` | + +--- + +## FAQs + +### 1. **What happens if I enter an invalid command?** + +- If you enter a command that is not recognized, the program will throw an exception and display an error message indicating that the command is invalid. You may then follow the instructions in the error code for the correct formatting of inputs. + +### 2. **What if I forget to include the arguments for a command like `todo` or `deadline`?** + +- The program will throw an exception (`MissingTodoArgumentException`, `MissingDeadlineArgumentException`, etc.) and inform you that you are missing required arguments. + +### 3. **What format should the dates be in?** + +- All dates should be in the format `dd-MM-yyyy`, for Format, example `15-03-2025`. + +--- + +## Notes on Editing Data + +- The commands are case-insensitive, meaning you can type commands in any letter case (e.g., `todo`, `TODO`, `ToDo`). +- Ensure that the date format for commands like `deadline` and `event` is strictly followed (`dd-MM-yyyy`). +- You can modify the task list and storage handling by editing the `TaskList` and `Storage` classes. + +--- + +## Known Issues + +- **Date parsing errors**: If a date is not in the correct format (`dd-MM-yyyy`), a `DateTimeParseException` will be thrown. +- **Index errors**: Deleting or marking tasks by index requires the index to be valid; otherwise, an exception may occur if the user enters an invalid index. diff --git a/runtest.bat b/runtest.bat new file mode 100644 index 000000000..1a491cd91 --- /dev/null +++ b/runtest.bat @@ -0,0 +1,21 @@ +@ECHO OFF + +REM create bin directory if it doesn't exist +if not exist ..\bin mkdir ..\bin + +REM delete output from previous run +del ACTUAL.TXT + +REM compile the code into the bin folder +javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\*.java +IF ERRORLEVEL 1 ( + echo ********** BUILD FAILURE ********** + exit /b 1 +) +REM no error here, errorlevel == 0 + +REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT +java -classpath ..\bin Duke < input.txt > ACTUAL.TXT + +REM compare the output to the expected output +FC ACTUAL.TXT EXPECTED.TXT diff --git a/src/main/java/ASCII_Art.java b/src/main/java/ASCII_Art.java new file mode 100644 index 000000000..ea7836739 --- /dev/null +++ b/src/main/java/ASCII_Art.java @@ -0,0 +1,30 @@ +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; + +public class ASCII_Art { + public static String art = """ + ____ _ _____ _ + | _ \\ | | / ____| | + | |_) | ___ | |__ | | | |__ _ _ _ __ __ _ _ _ ___ + | _ < / _ \\| '_ \\ | | | '_ \\| | | | '_ \\ / _` | | | / __| + | |_) | (_) | |_) | | |____| | | | |_| | | | | (_| | |_| \\__ \\ + |____/ \\___/|_.__/ \\_____|_| |_|\\__,_|_| |_|\\__, |\\__,_|___/ + __/ | + |___/"""; + + + + + public static void printArt() { + try { + System.out.println(art); + } catch (Exception e) { + System.err.println("Error printing ASCII Art: " + e.getMessage()); + } + } + + + +} + + diff --git a/src/main/java/BobChat.java b/src/main/java/BobChat.java new file mode 100644 index 000000000..a7bc62e75 --- /dev/null +++ b/src/main/java/BobChat.java @@ -0,0 +1,65 @@ +import java.io.*; +import Errors.*; + +/** + * Represents the Bob chatbot application, handling user input and task management. + */ +class Bob { + + /** + * Constructs a Bob chatbot instance and initializes necessary components. + * + * @throws FileNotFoundException If the task storage file is not found. + */ + Bob() throws FileNotFoundException { + } + + /** User interface for interacting with the user. */ + private final UI ui = new UI(); + + /** Storage handler for saving and loading tasks from a file. */ + private final Storage storage = new Storage("data/userTasks.txt"); + + /** Task list containing all user tasks. */ + private final TaskList taskList = new TaskList(storage.loadTasks()); + + /** + * Starts the chatbot, displaying a welcome message and handling user input continuously. + */ + public void start() { + ui.showWelcome(); + + while (true) { + String userInput = Parser.getUserInput(); + try { + Parser.parseCommand(userInput, taskList, storage, ui); + } catch (InputExceptions e) { + System.out.println("Error: " + e.getMessage()); // Prints error but continues running + } catch (NumberFormatException e) { + System.out.println("Error: Please enter a valid number."); + } + } + + } +} + + +/** + * Entry point for running the Bob chatbot application. + */ +public class BobChat { + /** + * Main method that initializes and starts the chatbot. + * + * @param args Command-line arguments. + * @throws InputExceptions If an input-related exception occurs. + * @throws IOException If an error occurs during file operations. + */ + public static void main(String[] args) throws InputExceptions, IOException { + Bob chatbot = new Bob(); + chatbot.start(); + } +} + + + diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 000000000..a8d22491d --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,44 @@ +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * The Deadline class represents a task with a specific due date. + */ +class Deadline extends Task { + private final LocalDate deadlineBy; + private final DateTimeFormatter dateTimeOutputFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + + /** + * Constructs a Deadline task with a description, completion status, and due date. + * + * @param isDone The completion status of the deadline task. + * @param description The description of the task. + * @param deadlineBy The due date of the task. + */ + public Deadline(boolean isDone, String description, LocalDate deadlineBy) { + super(description); + this.isDone = isDone; + this.deadlineBy = deadlineBy; + System.out.println("I have added this deadline: "); + System.out.println(this); + } + + /** + * Gets the formatted due date of the deadline task. + * + * @return The formatted due date. + */ + public String getDeadlineBy() { + return deadlineBy.format(dateTimeOutputFormat); + } + + /** + * Returns a string representation of the Deadline task. + * + * @return The formatted string representation of the deadline task. + */ + @Override + public String toString() { + return "[D]" + getStatusIcon() + getDescription() + " (by: " + deadlineBy + ")"; + } +} diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334c..000000000 --- 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/Errors/InputExceptions.java b/src/main/java/Errors/InputExceptions.java new file mode 100644 index 000000000..c4dba1fea --- /dev/null +++ b/src/main/java/Errors/InputExceptions.java @@ -0,0 +1,54 @@ +package Errors; + +public class InputExceptions extends Exception { + public InputExceptions(String message) { + super(message); + } + + public static class MissingTodoArgumentException extends InputExceptions { + public MissingTodoArgumentException(String command) { + super(command + " must have arguments." + "\n" + + "Please use: \"todo\" \"TASK_DESCRIPTION\" instead"); + } + } + + public static class MissingDeadlineArgumentException extends InputExceptions { + public MissingDeadlineArgumentException(String command) { + super(command + " must have arguments." + "\n" + + "Please use: \"deadline\" \"TASK_DESCRIPTION\" /by \"TASK_DEADLINE\" instead"); + } + } + + public static class MissingEventArgumentException extends InputExceptions { + public MissingEventArgumentException(String command) { + super(command + " must have arguments." + "\n" + + "Please use: \"event\" \"TASK_DESCRIPTION\" /from \"TASK_START_DATE\" /to \"TASK_END_DATE\" instead"); + } + } + + public static class MissingFindArgumentException extends InputExceptions { + public MissingFindArgumentException(String command) { + super(command + " must contain 1 argument." + "\n" + + "Please use: \"find\" \"description\" instead"); + } + } + + public static class InvalidIndexException extends InputExceptions { + public InvalidIndexException() { + super("Invalid task number."); + } + } + + public static class InvalidCommandException extends InputExceptions { + public InvalidCommandException() { + super("Invalid command. Please try again."); + } + } + + public static class InvalidDateFormatException extends InputExceptions { + public InvalidDateFormatException() { + super("Invalid date format. Please use dd-MM-yyyy."); + } + } + +} \ No newline at end of file diff --git a/src/main/java/Event.java b/src/main/java/Event.java new file mode 100644 index 000000000..fb2e7884b --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,57 @@ +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * The Event class represents a task with a start and end date. + */ +class Event extends Task { + private final LocalDate eventFrom; + private final LocalDate eventTo; + private final DateTimeFormatter dateTimeOutputFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + + /** + * Constructs an Event task with a description, completion status, start date, and end date. + * + * @param isDone The completion status of the event. + * @param description The description of the event. + * @param eventFrom The start date of the event. + * @param eventTo The end date of the event. + */ + public Event(boolean isDone, String description, LocalDate eventFrom, LocalDate eventTo) { + super(description); + this.isDone = isDone; + this.eventFrom = eventFrom; + this.eventTo = eventTo; + System.out.println("I have added this event: "); + System.out.println(this); + } + + /** + * Gets the formatted start date of the event. + * + * @return The formatted start date. + */ + public String getEventFrom() { + return eventFrom.format(dateTimeOutputFormat); + } + + /** + * Gets the formatted end date of the event. + * + * @return The formatted end date. + */ + public String getEventTo() { + return eventTo.format(dateTimeOutputFormat); + } + + + /** + * Returns a string representation of the Event task. + * + * @return The formatted string representation of the event. + */ + @Override + public String toString() { + return "[E]" + getStatusIcon() + getDescription() + " (from: " + eventFrom + " to: " + eventTo + ")"; + } +} diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..a630d3fc2 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: BobChat + diff --git a/src/main/java/Parser.java b/src/main/java/Parser.java new file mode 100644 index 000000000..1a5f452b9 --- /dev/null +++ b/src/main/java/Parser.java @@ -0,0 +1,145 @@ +import Errors.InputExceptions; +import java.util.Scanner; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + + +/** + * Parses user commands and executes corresponding actions. + */ +class Parser { + + /** Scanner for reading user input. */ + private static final Scanner scanner = new Scanner(System.in); + private static final DateTimeFormatter dateTimeInputFormat = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + + /** + * Reads and returns user input from the console, trimming whitespace. + * + * @return The trimmed user input as a string. + */ + public static String getUserInput() { + return scanner.nextLine().trim(); + } + + /** + * Parses and executes the user command. + * + * @param input The user input string. + * @param taskList The list of tasks. + * @param storage The storage handler for saving tasks. + * @param ui The user interface handler for displaying messages. + * @throws InputExceptions If an invalid command or missing argument is encountered. + */ + public static void parseCommand(String input, TaskList taskList, Storage storage, UI ui) throws InputExceptions { + String[] inputParts = input.split(" ", 2); + String command = inputParts[0].toLowerCase(); + String arguments = (inputParts.length > 1) ? inputParts[1] : ""; + + switch (command) { + case "bye": + ui.showGoodbye(); + storage.saveTasks(taskList.getListOfTasks()); + System.exit(0); + break; + + case "list": + ui.showList(); + if (taskList.size() == 0) { + System.out.println("No tasks added yet."); + } else { + for (int i = 0; i < taskList.size(); i++) { + System.out.println((i + 1) + ". " + taskList.getTask(i)); + } + } + ui.printLine(); + break; + + case "todo": + if (arguments.isEmpty()) { + throw new InputExceptions.MissingTodoArgumentException("todo"); + } + taskList.addTask(new ToDo(false, arguments)); + ui.printLine(); + break; + + case "deadline": + String[] parts = arguments.split(" /by ", 2); + if (parts.length < 2) { + throw new InputExceptions.MissingDeadlineArgumentException("deadline"); + } + try { + LocalDate deadlineBy = LocalDate.parse(parts[1], dateTimeInputFormat); + taskList.addTask(new Deadline(false, parts[0], deadlineBy)); + } catch (DateTimeParseException e) { + throw new InputExceptions.InvalidDateFormatException(); + } + ui.printLine(); + break; + + case "event": + String[] eventParts = arguments.split(" /from ", 2); + if (eventParts.length < 2) { + throw new InputExceptions.MissingEventArgumentException("event"); + } + String[] timeParts = eventParts[1].split(" /to ", 2); + if (timeParts.length < 2) { + throw new InputExceptions.MissingEventArgumentException("event"); + } + try { + LocalDate startDate = LocalDate.parse(timeParts[0], dateTimeInputFormat); + LocalDate endDate = LocalDate.parse(timeParts[1], dateTimeInputFormat); + taskList.addTask(new Event(false, eventParts[0], startDate, endDate)); + } catch (DateTimeParseException e) { + throw new InputExceptions.InvalidDateFormatException(); + } + ui.printLine(); + break; + + case "delete": + int deleteIndex = Integer.parseInt(arguments) - 1; + taskList.removeTask(deleteIndex); + ui.deletedTask(deleteIndex); + break; + + case "mark": + int markIndex = Integer.parseInt(arguments) - 1; + taskList.getTask(markIndex).markAsDone(); + ui.markedTask(markIndex); + break; + + case "unmark": + int unmarkIndex = Integer.parseInt(arguments) - 1; + taskList.getTask(unmarkIndex).markAsNotDone(); + ui.unmarkedTask(unmarkIndex); + break; + + case "find": + if (arguments.isEmpty()) { + throw new InputExceptions.MissingFindArgumentException("find"); + } + ui.printFindingTask(); + int taskFound = 0; + for (int i = 0; i < taskList.size(); i++) { + Task task = taskList.getTask(i); + if (task.getDescription().toLowerCase().contains(arguments)) { + taskFound++; + ui.printFoundTask(taskFound, task); + } + } + + if (taskFound == 0) { + ui.printNoTaskFound(); + } + + ui.printLine(); + break; + + default: + throw new InputExceptions.InvalidCommandException(); + } + } + + + } \ No newline at end of file diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java new file mode 100644 index 000000000..1f2615803 --- /dev/null +++ b/src/main/java/Storage.java @@ -0,0 +1,163 @@ +import java.io.*; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Objects; +import java.util.Scanner; + +/** + * The Storage class handles loading and saving tasks to a file. + * It reads from and writes to a specified file path, ensuring tasks are + * persisted across program executions. + */ +class Storage { + private final String filePath; + + /** + * Constructs a Storage object with a specified file path. + * + * @param filePath The path to the file where tasks are stored. + */ + public Storage(String filePath) { + this.filePath = filePath; + } + + /** + * Loads tasks from the file into an ArrayList. + * + * @return An ArrayList of tasks loaded from storage. + * @throws FileNotFoundException If the file does not exist. + */ + public ArrayList loadTasks() throws FileNotFoundException { + ArrayList tasks = new ArrayList<>(); + File file = new File(filePath); + File parentDir = file.getParentFile(); + + if (!file.exists()) { + try { + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); + } + file.createNewFile(); + System.out.println("File not found. A new file 'userData.txt' has been created in 'data' folder."); + } catch (IOException e) { + System.err.println("Error creating new file: " + e.getMessage()); + } + return tasks; + } + Scanner scanner = new Scanner(file); + while (scanner.hasNextLine()) { + tasks.add(formatTaskFromStorage(scanner.nextLine())); + } + + scanner.close(); + System.out.println("Tasks loaded successfully."); + return tasks; + } + + + /** + * Saves tasks to the file in a structured format. + * + * @param tasks The list of tasks to be saved. + */ + public void saveTasks(ArrayList tasks) { + try (FileWriter fileWriter = new FileWriter(filePath); + PrintWriter writer = new PrintWriter(fileWriter)) { + for (Task task : tasks) { + String taskInStorageFormat = formatTaskToStorage(task); + writer.println(taskInStorageFormat); + } + + System.out.println("Tasks saved successfully."); + } catch (IOException e) { + System.err.println("Error saving tasks: " + e.getMessage()); + } + } + + /** + * Converts a Task object into a storage-friendly string format. + * + * @param task The task to be formatted. + * @return A string representation of the task for storage. + */ + public String formatTaskToStorage(Task task) { + String taskInStorageFormat; + + if (task instanceof ToDo) { + taskInStorageFormat = "todo|" + task.getDoneStatus() + "|" + task.getDescription(); + } + else if (task instanceof Deadline deadlineTask) { + taskInStorageFormat = "deadline|" + deadlineTask.getDoneStatus() + "|" + deadlineTask.getDescription() + + "|" + deadlineTask.getDeadlineBy(); + } + else if (task instanceof Event eventTask) { + + taskInStorageFormat = "event|" + eventTask.getDoneStatus() + "|" + eventTask.getDescription() + + "|" + eventTask.getEventFrom() + "|" + eventTask.getEventTo(); + } else { + throw new IllegalArgumentException("Unknown task type: " + task.getClass().getSimpleName()); + } + + assert taskInStorageFormat != null : "taskInStorageFormat should not be null"; + + return taskInStorageFormat; + } + + /** + * Parses a task from its stored string format. + * + * @param savedTask The string representation of the task from storage. + * @return The reconstructed Task object. + * @throws IllegalArgumentException If the format is invalid or unknown. + */ + public Task formatTaskFromStorage(String savedTask) { + String[] parts = savedTask.split("\\|"); // Split by "|" + DateTimeFormatter dateTimeLoadFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + + // Ensure the task format is valid + if (parts.length < 3) { + throw new IllegalArgumentException("Invalid task format: " + savedTask); + } + + String taskType = parts[0]; + String taskDoneFormat = parts[1]; + String description = parts[2]; + + boolean taskDone; + + if (Objects.equals(taskDoneFormat, "done")){ + taskDone = true; + } else if (Objects.equals(taskDoneFormat, "not done")){ + taskDone = false; + } else { + throw new IllegalArgumentException("Data corrupted" + savedTask); + } + + switch (taskType) { + case "todo": + assert parts.length == 3 : "Todo task should have 2 arguments"; + return new ToDo(taskDone, description); + + case "deadline": + assert parts.length == 4 : "Deadline task should have 3 arguments"; + if (parts.length != 4) { + throw new IllegalArgumentException("Invalid deadline format: " + savedTask); + } + return new Deadline(taskDone, description, LocalDate.parse(parts[3], dateTimeLoadFormat)); + + case "event": + assert parts.length == 5 : "Event task should have 4 arguments"; + if (parts.length != 5) { + throw new IllegalArgumentException("Invalid event format: " + savedTask); + } + return new Event(taskDone, description, LocalDate.parse(parts[3], dateTimeLoadFormat), LocalDate.parse(parts[4],dateTimeLoadFormat)); + + default: + throw new IllegalArgumentException("Unknown task type: " + taskType); + } + } + + + +} \ No newline at end of file diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 000000000..2350a8eb8 --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,74 @@ +/** + * Represents a task with a description and completion status. + */ +public class Task { + private final String description; + protected boolean isDone; + + /** + * Constructs a new task with the given description. + * By default, the task is not done. + * + * @param description The description of the task. + */ + public Task(String description) { + this.description = description; + this.isDone = false; + } + + /** + * Constructs a new task with the given completion status and description. + * + * @param isDone The completion status of the task. + * @param description The description of the task. + */ + public Task(boolean isDone, String description) { + this.isDone = isDone; + this.description = description; + } + + + /** + * Marks the task as done. + */ + public void markAsDone() { + isDone = true; + } + + + /** + * Marks the task as not done. + */ + public void markAsNotDone() { + isDone = false; + } + + /** + * Returns the status icon representing whether the task is done. + * + * @return "[X] " if the task is done, "[ ] " otherwise. + */ + public String getStatusIcon() { + return (isDone ? "[X] " : "[ ] "); + } + + /** + * Returns the completion status of the task as a string. + * + * @return "done" if the task is completed, "not done" otherwise. + */ + public String getDoneStatus() { + return (isDone ? "done" : "not done"); + } + + /** + * Returns the description of the task. + * + * @return The task description. + */ + public String getDescription() { + return description; + } + +} + diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java new file mode 100644 index 000000000..632c18094 --- /dev/null +++ b/src/main/java/TaskList.java @@ -0,0 +1,73 @@ +import Errors.InputExceptions; +import java.util.ArrayList; + +/** + * The TaskList class manages a collection of tasks. + * It provides methods to add, retrieve, remove, and get the size of the task list. + */ +class TaskList { + private final ArrayList tasks; + + /** + * Constructs a TaskList with an existing list of tasks. + * + * @param tasks The list of tasks to manage. + */ + public TaskList(ArrayList tasks) { + this.tasks = tasks; + } + + /** + * Adds a new task to the list. + * + * @param task The task to be added. + */ + public void addTask(Task task) { + tasks.add(task); + } + + /** + * Retrieves a task at a specific index. + * + * @param index The index of the task. + * @return The task at the specified index. + * @throws InputExceptions If the index is out of bounds. + */ + public Task getTask(int index) throws InputExceptions { + if (index < 0 || index >= tasks.size()) { + throw new InputExceptions.InvalidIndexException(); + } + return tasks.get(index); + } + + /** + * Returns the list of all tasks. + * + * @return An ArrayList of tasks. + */ + public ArrayList getListOfTasks() { + return tasks; + } + + /** + * Returns the number of tasks in the list. + * + * @return The size of the task list. + */ + public int size() { + return tasks.size(); + } + + /** + * Removes a task at a specified index. + * + * @param index The index of the task to be removed. + * @throws InputExceptions If the index is out of bounds. + */ + public void removeTask(int index) throws InputExceptions { + if (index < 0 || index >= tasks.size()) { + throw new InputExceptions.InvalidIndexException(); + } + tasks.remove(index); + } +} diff --git a/src/main/java/ToDo.java b/src/main/java/ToDo.java new file mode 100644 index 000000000..dd3938f7f --- /dev/null +++ b/src/main/java/ToDo.java @@ -0,0 +1,28 @@ +/** + * The ToDo class represents a simple task without a specific deadline or event period. + */ +public class ToDo extends Task { + /** + * Constructs a ToDo task with a description and completion status. + * + * @param isDone The completion status of the task. + * @param description The description of the task. + */ + public ToDo(boolean isDone, String description) { + super(description); + this.isDone = isDone; + System.out.println("I have added this Todo: "); + System.out.println(this); + } + + /** + * Returns a string representation of the ToDo task. + * + * @return The formatted string representation of the task. + */ + @Override + public String toString() { + return "[T]" + getStatusIcon() + getDescription(); + } + } + diff --git a/src/main/java/Ui.java b/src/main/java/Ui.java new file mode 100644 index 000000000..4f55774d8 --- /dev/null +++ b/src/main/java/Ui.java @@ -0,0 +1,101 @@ +/** + * Handles user interface interactions by displaying messages and formatting output. + */ +class UI { + + /** + * Displays the welcome message along with ASCII art of BobChungus. + */ + public void showWelcome() { + System.out.println("____________________________________________________________"); + ASCII_Art.printArt(); + System.out.println(" Hello! I'm BobChungus "); + System.out.println(" What can I do for you?"); + System.out.println("____________________________________________________________"); + } + + /** + * Displays the goodbye message when exiting the program. + */ + public void showGoodbye() { + System.out.println("____________________________________________________________"); + System.out.println(" Bye. Hope to see you again soon!"); + System.out.println("____________________________________________________________"); + } + + /** + * Displays a header for the task list output. + */ + public void showList() { + System.out.println("____________________________________________________________"); + System.out.println(" Here are the tasks in your list:"); + } + + /** + * Displays a message indicating a task has been deleted. + * + * @param deleteIndex The index of the task that was deleted. + */ + public void deletedTask(int deleteIndex){ + System.out.println("____________________________________________________________"); + System.out.println("Okay, deleted Task " + (deleteIndex + 1)); + System.out.println("____________________________________________________________"); + } + + /** + * Displays a message indicating a task has been marked as completed. + * + * @param markIndex The index of the task that was marked as done. + */ + public void markedTask(int markIndex){ + System.out.println("____________________________________________________________"); + System.out.println("Okay, marked Task " + (markIndex + 1)); + System.out.println("____________________________________________________________"); + } + + /** + * Displays a message indicating a task has been unmarked as completed. + * + * @param unmarkIndex The index of the task that was unmarked. + */ + public void unmarkedTask(int unmarkIndex){ + System.out.println("____________________________________________________________"); + System.out.println("Okay, unmarked Task " + (unmarkIndex + 1)); + System.out.println("____________________________________________________________"); + } + + /** + * Displays a message indicating that a search for tasks has begun. + */ + public void printFindingTask(){ + System.out.println("____________________________________________________________"); + System.out.println("Here are the matching tasks in your list:"); + } + + /** + * Displays a message when no matching tasks are found during a search. + */ + public void printNoTaskFound(){ + printLine(); + System.out.println("No task with matching keyword found"); + } + + /** + * Displays a found task from a search result. + * + * @param position The position of the task in the list. + * @param task The task object that was found. + */ + public void printFoundTask(int position, Task task){ + printLine(); + System.out.println( position + "." + " " + task.toString() ); + } + + /** + * Prints a horizontal line for formatting output. + */ + public void printLine() { + System.out.println("____________________________________________________________"); + } + +}