diff --git a/.gitignore b/.gitignore index 2873e189e..ccb4f7cc5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT +data/tasks.txt diff --git a/README.md b/README.md index 8715d4d91..6cf65d922 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# Alan project template This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. @@ -13,7 +13,7 @@ Prerequisites: JDK 11, update Intellij to the most recent version. 1. If there are any further prompts, accept the defaults. 1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
In the same dialog, set the **Project language level** field to the `SDK default` option. -3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: +3. After that, locate the `src/main/java/Alan.java` file, right-click it, and choose `Run Alan.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/docs/README.md b/docs/README.md index 8077118eb..f7e4b68b3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,155 @@ # User Guide +Alan is a Personal Assistant chatbot application that can help with tracking tasks such as todos, deadlines and events. -## Features +* Quick Start +* Features + * Adding a todo : `todo` + * Adding a deadline : `deadline` + * Adding a event : `event` + * Listing all tasks: `list` + * Mark task as done : `mark` + * Mark task as not done : `unmark` + * Deleting a task : `delete` + * Finding tasks : `find` + * Exit program : `bye` -### Feature-ABC +## Quick start +1. Ensure you have Java `11` or above installed on your computer. +2. Download the latest `ip.jar` from [here](https://github.com/DextheChik3n/ip/releases) +3. Copy the file to the folder you want to use as the home folder for your Personal Assistant Chatbot. +4. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar ip.jar` command to run the application. +5. If the setup is correct, you should see something like the below as the output: +``` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Sup dude! I'm + ______ __ ______ __ __ +/\ __ \ /\ \ /\ __ \ /\ "-.\ \ +\ \ __ \ \ \ \____ \ \ __ \ \ \ \-. \ + \ \_\ \_\ \ \_____\ \ \_\ \_\ \ \_\\"\_\ + \/_/\/_/ \/_____/ \/_/\/_/ \/_/ \/_/ + @/ +/| +/ \ +What can I do for you, my man? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Input: + + ``` + +## Features +> **Notes about command format:** +> - Words in `UPPER_CASE` are the arguments to be supplied by user.
+ e.g. `deadline DESCRIPTION /by DATETIME`, `DESCRIPTION` and `DATETIME` are arguments to be specified by user. +> - Parameters need to be in the exact format as specified.
+ e.g. `event DESCRIPTION /from DATETIME /to DATETIME`, `/from` must come before `/to`. + +### Adding a todo : `todo` + +Adds a todo task in the task list. + +Format: `todo DESCRIPTION` + +Example:
`todo read book` + +### Adding a deadline : `deadline` + +Adds a deadline task in the task list. -Description of the feature. +Format: `deadline DESCRIPTION /by DATETIME` -### Feature-XYZ +- `DATETIME` can be input as `yyyy-mm-dd` to be stored as `dd MMM yyyy` format + - e.g. the input `2020-02-14` will be stored as `14 Feb 2020` in the task list -Description of the feature. +Example:
`deadline return book /by Feb 14th` -## Usage +### Adding a event : `event` -### `Keyword` - Describe action +Adds a event task in the task list. -Describe the action and its outcome. +Format: `event DESCRIPTION /from DATETIME /to DATETIME` +- `DATETIME` can be input as `yyyy-mm-dd` to be stored as `dd MMM yyyy` format + - e.g. the input `2020-02-14` will be stored as `14 Feb 2020` in the task list -Example of usage: +Example:
`event project meeting /from Aug 6th 2pm /to Aug 6th 4pm` -`keyword (optional arguments)` +### Listing all tasks : `list` + +Lists all the task in the task list. + +Format: `list` Expected outcome: -Description of the outcome. +`list` Prints all the task currently in the task list. + +``` +Input: list +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Dude, check out these tasks on your list: +1. [T][X] read book +2. [D][X] return book (by: June 6th) +3. [E][ ] project meeting (from: Aug 6th 2pm | to: 4pm) +4. [T][X] join sports club +5. [T][ ] borrow book +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` + +### Mark task as done : `mark` + +Mark the task status as done. + +Format: `mark TASK_INDEX` + +- Marks task as done at specified `TASK_INDEX` +- The `TASK_INDEX` refers to the index number shown in the displayed task list. +The index must be a positive integer 1, 2, 3, …​ + +Example:
`mark 1` Marks the first task in the list as done. + +### Mark task as not done : `unmark` + +Unmark the task status as not done. + +Format: `unmark TASK_INDEX` + +- Marks task as not done at specified `TASK_INDEX` +- The `TASK_INDEX` refers to the index number shown in the displayed task list. + The index must be a positive integer 1, 2, 3, …​ + +Example:
`unmark 1` Marks the first task in the list as not done. + +### Deleting a task : `delete` + +Deletes the task in the task list. + +Format: `delete TASK_INDEX` + +- Deletes task at specified `TASK_INDEX` +- The `TASK_INDEX` refers to the index number shown in the displayed task list. + The index must be a positive integer 1, 2, 3, …​ + +Example:
`list` followed by `delete 2` deletes the 2nd task in the task list. + +### Finding tasks : `find` + +Finds tasks whose description matches the given keyword. + +Format: `find KEYWORD` + +Example:
`find book` will find tasks with the keyword `book` in the list. ``` -expected output +Input: find book +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Dude check it out, here are the matching tasks in your list: +1. [T][X] read book +2. [D][X] return book (by: June 6th) +3. [T][ ] borrow book +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` + +### Exit program : `bye` + +Exits the chatbot program. + +Format: `bye` 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/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..07c1f045a --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: alan.Alan + diff --git a/src/main/java/alan/Alan.java b/src/main/java/alan/Alan.java new file mode 100644 index 000000000..8ee9bb44e --- /dev/null +++ b/src/main/java/alan/Alan.java @@ -0,0 +1,72 @@ +package alan; + +import alan.data.TaskList; +import alan.data.exception.AlanException; +import alan.parser.Parser; +import alan.storage.Storage; +import alan.ui.Ui; + +import java.io.FileNotFoundException; + +/** + * Represents the main class and entry point of the Alan chatbot program. + */ +public class Alan { + private static Ui ui; + private static TaskList tasks; + private static Storage storage; + + /** + * Represents the Alan constructor. + * Initializes the Ui, Storage and TaskList. + * + * @param filePath filePath of the text file used to store task data. + */ + public Alan(String filePath) { + ui = new Ui(); + storage = new Storage(filePath); + try { + tasks = new TaskList(storage.load()); + } catch (FileNotFoundException e) { + ui.showLoadingError(); + tasks = new TaskList(); + } + } + + /** + * Runs the Alan chatbot program. + */ + public static void runAlan() { + Parser parser = new Parser(tasks, ui); + + ui.showWelcomeMessage(); + + String userInput = null; + String command = ""; + + do { + try { + userInput = ui.getUserCommand(); + command = parser.processCommandHandler(userInput); + } catch (AlanException e) { + ui.showToUser(e.getMessage()); + } finally { + ui.printHorizontalLine(); + } + } while (!command.equals("bye")); + + try { + storage.save(); + } catch (Exception e) { + ui.showSavingError(); + } + } + + /** + * Main method of the Alan program. + * @param args + */ + public static void main(String[] args) { + new Alan("data/tasks.txt").runAlan(); + } +} diff --git a/src/main/java/alan/common/Messages.java b/src/main/java/alan/common/Messages.java new file mode 100644 index 000000000..07238915e --- /dev/null +++ b/src/main/java/alan/common/Messages.java @@ -0,0 +1,35 @@ +package alan.common; +/** + * Represents a class that stores all the messages sent to the user. + */ +public class Messages { + public static final String manDrawing = " @/\n" + + "/| \n" + + "/ \\"; + public static final String alanText = " ______ __ ______ __ __ \n" + + "/\\ __ \\ /\\ \\ /\\ __ \\ /\\ \"-.\\ \\ \n" + + "\\ \\ __ \\ \\ \\ \\____ \\ \\ __ \\ \\ \\ \\-. \\ \n" + + " \\ \\_\\ \\_\\ \\ \\_____\\ \\ \\_\\ \\_\\ \\ \\_\\\\\"\\_\\ \n" + + " \\/_/\\/_/ \\/_____/ \\/_/\\/_/ \\/_/ \\/_/ "; + + public static final String horizontalDivider = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"; + public static final String MESSAGE_GREET = "Sup dude! I'm \n" + alanText + "\n" + manDrawing + "\n" + "What can I do for you, my man?"; + public static final String MESSAGE_GOODBYE = "Later, dude! Can't wait to catch up again real soon!"; + public static final String MESSAGE_LIST_COMMAND = "Dude, check out these tasks on your list:"; + public static final String MESSAGE_MARK_TASK = "Alright bro! This task is officially checked off:"; + public static final String MESSAGE_UNMARK_TASK = "Ok dude, I've marked this task as ain't done yet amigo:"; + public static final String MESSAGE_DELETE_TASK = "Got it, dude. This task is outta here:"; + public static final String MESSAGE_INVALID_INPUT_COMMAND = "Oof, I have no idea what are you saying duuude"; + public static final String MESSAGE_EMPTY_DESCRIPTION = "Oof Dude, you can't leave the description empty"; + public static final String MESSAGE_INVALID_DEADLINE_FORMAT = "Oof the deadline command isn't quite right you gotta fix the format, bro...\n[Remember it's: /by ]"; + public static final String MESSAGE_INVALID_EVENT_FROM_FORMAT = "Oof duude, your /from formatting is whack\n[Remember it's: /from /to ]"; + public static final String MESSAGE_INVALID_EVENT_TO_FORMAT = "Oof my man, you need to work on that /to formatting\n[Remember it's: /from /to ]"; + public static final String MESSAGE_INVALID_TASK_NUMBER = "Oof maaaan there's no such task"; + public static final String MESSAGE_INVALID_TASK_TYPE_FOUND = "Yo I found a task type that doesn't make sense"; + public static final String MESSAGE_FIND_TASK = "Dude check it out, here are the matching tasks in your list:"; + public static final String MESSAGE_DATA_FOLDER_NOT_FOUND = "Data Folder was not found!\nIt's ok... new data folder has been created in "; + public static final String MESSAGE_DATA_FILE_NOT_FOUND = "tasks.txt was not found!\nIt's ok... new tasks.txt has been created in "; + public static final String MESSAGE_LOAD_FILE_ERROR = "Yo dude something ain't loading right :/"; + public static final String MESSAGE_SAVE_FILE_ERROR = "Sorry man I can't seem to save to the text file D:"; + public static final String MESSAGE_EMPTY_LIST = "...errr I don't see any tasks here ¯\\_(ツ)_/¯"; +} diff --git a/src/main/java/alan/data/TaskList.java b/src/main/java/alan/data/TaskList.java new file mode 100644 index 000000000..f0c823371 --- /dev/null +++ b/src/main/java/alan/data/TaskList.java @@ -0,0 +1,60 @@ +package alan.data; + +import alan.data.task.Deadline; +import alan.data.task.Event; +import alan.data.task.Task; +import alan.data.task.Todo; + +import java.util.ArrayList; +/** + * Represents a list of the Task objects. + */ +public class TaskList { + private final ArrayList tasks; + + public TaskList() { + tasks = new ArrayList<>(); + } + + public TaskList(ArrayList tasks) { + this.tasks = tasks; + } + + public ArrayList getTaskList() { + return tasks; + } + + public Task getSelectedTask(int selectedTaskIndex) { + return tasks.get(selectedTaskIndex); + } + + public int getLastTaskIndex() { + return tasks.size() - 1; + } + + public int getTaskListSize() { + return tasks.size(); + } + + public void addToDo(String description) { + tasks.add(new Todo(description)); + } + + public void addDeadline(String description, String by) { + tasks.add(new Deadline(description, by)); + } + + public void addEvent(String description, String from, String to) { + tasks.add(new Event(description, from, to)); + } + + public void markTask(int selectedTaskIndex, boolean isDone) { + tasks.get(selectedTaskIndex).setDone(isDone); + } + + public void removeTask(int selectedTaskIndex) { + tasks.remove(selectedTaskIndex); + } + + +} diff --git a/src/main/java/alan/data/exception/AlanException.java b/src/main/java/alan/data/exception/AlanException.java new file mode 100644 index 000000000..4ca815731 --- /dev/null +++ b/src/main/java/alan/data/exception/AlanException.java @@ -0,0 +1,148 @@ +package alan.data.exception; + +import alan.data.task.Task; + +import java.util.ArrayList; + +import static alan.common.Messages.MESSAGE_EMPTY_DESCRIPTION; +import static alan.common.Messages.MESSAGE_INVALID_DEADLINE_FORMAT; +import static alan.common.Messages.MESSAGE_INVALID_EVENT_FROM_FORMAT; +import static alan.common.Messages.MESSAGE_INVALID_EVENT_TO_FORMAT; +import static alan.common.Messages.MESSAGE_INVALID_INPUT_COMMAND; +import static alan.common.Messages.MESSAGE_INVALID_TASK_NUMBER; + +/** + * Represents the error handling class. + */ +public class AlanException extends Exception { + + public AlanException(String errorMessage) { + super(errorMessage); + } + + /** + * Checks lateral location of the specified position. + * + * @param selectedIndex index of the selected task. + * @param taskList contains the list of tasks. + * @throws AlanException If selected index is out of range of taskList. + */ + public static void checkOutOfTaskListIndex(int selectedIndex, ArrayList taskList) throws AlanException { + int lastTaskIndex = taskList.size() - 1; + + if (selectedIndex > lastTaskIndex) { + throw new AlanException(MESSAGE_INVALID_TASK_NUMBER); + } + } + + /** + * Outputs message when user enters an invalid/unknown command. + * + * @throws AlanException If selected index is out of range of taskList. + */ + public static void invalidInputCommand() throws AlanException { + throw new AlanException(MESSAGE_INVALID_INPUT_COMMAND); + } + + /** + * Checks for any description text found after the command. + * + * @throws AlanException If there is only 1 String element found in array. + */ + public static void checkEmptyDescription(String userInput) throws AlanException { + String[] userInputWords = userInput.split(" "); + + if (userInputWords.length == 1) { + throw new AlanException(MESSAGE_EMPTY_DESCRIPTION); + } + } + + /** + * Checks for any text found after /by parameter. + * + * @param words contains an array of the information text split by parameter /by. + * @throws AlanException If there is only 1 String element found in array. + */ + public static void checkDeadlineInputFormat(String[] words) throws AlanException { + if (words.length == 1) { + throw new AlanException(MESSAGE_INVALID_DEADLINE_FORMAT); + } + } + /** + * Checks for any text found after /from parameter. + * + * @param splitDescriptionAndDate contains an array of the information text split by parameter /from. + * @throws AlanException If there is only 1 String element found in array. + */ + public static void checkEventInputFromFormat(String[] splitDescriptionAndDate) throws AlanException { + if (splitDescriptionAndDate.length == 1) { + throw new AlanException(MESSAGE_INVALID_EVENT_FROM_FORMAT); + } + } + /** + * Checks for any text found after /to parameter. + * + * @param splitFromAndTo contains an array of the information text split by parameter /to. + * @throws AlanException If there is only 1 String element found in array. + */ + public static void checkEventInputToFormat(String[] splitFromAndTo) throws AlanException { + if (splitFromAndTo.length == 1) { + throw new AlanException(MESSAGE_INVALID_EVENT_TO_FORMAT); + } + } + + public static void checkEmptyInput(String inputString) throws AlanException { + if (inputString.isBlank()) { + throw new AlanException(MESSAGE_INVALID_INPUT_COMMAND); + } + } + + public static void checkIndexInput(String[] inputString) throws AlanException { + if (inputString.length == 1) { //checks for task index after the command + throw new AlanException(MESSAGE_INVALID_INPUT_COMMAND); + } + + if (inputString[1].isBlank()) { //checks for whitespace characters after command + throw new AlanException(MESSAGE_INVALID_INPUT_COMMAND); + } + + if (!isInteger(inputString[1])) { //checks if the text after the command is an integer + throw new AlanException(MESSAGE_INVALID_INPUT_COMMAND); + } + } + + /** + * Checks if the string is integer.
+ * Code taken from Jonas K from: here + * + * @param str text string + */ + public static boolean isInteger(String str) { + if (str == null) { + return false; + } + int length = str.length(); + if (length == 0) { + return false; + } + int i = 0; + if (str.charAt(0) == '-') { + if (length == 1) { + return false; + } + i = 1; + } + for (; i < length; i++) { + char c = str.charAt(i); + if (c < '0' || c > '9') { + return false; + } + } + return true; + } + + @Override + public String toString() { + return super.getMessage(); + } +} diff --git a/src/main/java/alan/data/task/Deadline.java b/src/main/java/alan/data/task/Deadline.java new file mode 100644 index 000000000..22bd5c5ab --- /dev/null +++ b/src/main/java/alan/data/task/Deadline.java @@ -0,0 +1,26 @@ +package alan.data.task; +/** + * Represents a deadline task. A Deadline object corresponds to + * a task consisting of description, by time period and D task type + */ +public class Deadline extends Task { + protected String by; + + public Deadline(String description, String by) { + super(description, TaskType.D); + this.by = by; + } + + public String getBy() { + return by; + } + + public void setBy(String by) { + this.by = by; + } + + @Override + public String toString() { + return "[" + super.getTaskType() + "]" + "[" + super.getStatusIcon() + "] " + super.toString() + " (by: " + by + ")"; + } +} diff --git a/src/main/java/alan/data/task/Event.java b/src/main/java/alan/data/task/Event.java new file mode 100644 index 000000000..dd3804de9 --- /dev/null +++ b/src/main/java/alan/data/task/Event.java @@ -0,0 +1,36 @@ +package alan.data.task; +/** + * Represents a event task. A Event object corresponds to + * a task consisting of description, from time period, to time period and E task type + */ +public class Event extends Task { + protected String from; + protected String to; + + public Event(String description, String from, String to) { + super(description, TaskType.E); + this.from = from; + this.to = to; + } + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public String getTo() { + return to; + } + + public void setTo(String to) { + this.to = to; + } + + @Override + public String toString() { + return "[" + super.getTaskType() + "]" + "[" + super.getStatusIcon() + "] " + super.toString() + " (from: " + from + " | to: "+ to + ")"; + } +} diff --git a/src/main/java/alan/data/task/Task.java b/src/main/java/alan/data/task/Task.java new file mode 100644 index 000000000..a0d71c877 --- /dev/null +++ b/src/main/java/alan/data/task/Task.java @@ -0,0 +1,54 @@ +package alan.data.task; +/** + * Represents a task. + * The Task class is always instantiated as a Todo, Deadline or Event task + */ +public class Task { + private String description; + private TaskType type; + private boolean isDone; + + public Task(String description, TaskType type) { + this.description = description; + this.type = type; + this.isDone = false; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean getDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + public TaskType getTaskType() { + return type; + } + + public void setTaskType(TaskType type) { + this.type = type; + } + + public String getStatusIcon() { + return (isDone ? "X" : " "); + } + + public int getStatusValue() { + return (isDone ? 1 : 0); + } + + @Override + public String toString() { + return getDescription(); + } +} + diff --git a/src/main/java/alan/data/task/TaskType.java b/src/main/java/alan/data/task/TaskType.java new file mode 100644 index 000000000..bbffa329d --- /dev/null +++ b/src/main/java/alan/data/task/TaskType.java @@ -0,0 +1,5 @@ +package alan.data.task; + +public enum TaskType { + T, D, E +} diff --git a/src/main/java/alan/data/task/Todo.java b/src/main/java/alan/data/task/Todo.java new file mode 100644 index 000000000..8d7817df2 --- /dev/null +++ b/src/main/java/alan/data/task/Todo.java @@ -0,0 +1,18 @@ +package alan.data.task; +/** + * Represents a todo task. A Todo object corresponds to + * a task consisting of description and 'T' task type + */ +public class Todo extends Task { + private boolean isDone; + + public Todo(String description) { + super(description, TaskType.T); + isDone = false; + } + + @Override + public String toString() { + return "[" + super.getTaskType() + "]" + "[" + super.getStatusIcon() + "] " + super.toString(); + } +} diff --git a/src/main/java/alan/parser/Parser.java b/src/main/java/alan/parser/Parser.java new file mode 100644 index 000000000..e1cf432e4 --- /dev/null +++ b/src/main/java/alan/parser/Parser.java @@ -0,0 +1,240 @@ +package alan.parser; + +import alan.data.TaskList; +import alan.data.exception.AlanException; +import alan.data.task.Task; +import alan.ui.Ui; + +import static alan.common.Messages.MESSAGE_FIND_TASK; +import static alan.common.Messages.MESSAGE_LIST_COMMAND; + +import static alan.data.exception.AlanException.checkDeadlineInputFormat; +import static alan.data.exception.AlanException.checkEmptyDescription; +import static alan.data.exception.AlanException.checkEmptyInput; +import static alan.data.exception.AlanException.checkEventInputFromFormat; +import static alan.data.exception.AlanException.checkEventInputToFormat; +import static alan.data.exception.AlanException.checkIndexInput; +import static alan.data.exception.AlanException.checkOutOfTaskListIndex; +import static alan.data.exception.AlanException.invalidInputCommand; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * Represents a parser that parses the user input. + */ +public class Parser { + public static final String DATE_PARSE_PATTERN = "dd MMM yyyy"; + private TaskList tasks; + private Ui ui; + + public Parser(TaskList tasks, Ui ui) { + this.tasks = tasks; + this.ui = ui; + } + /** + * Handles extracting the command from the user input and executes the respective command. + * + * @param userInput text of the user input. + * @throws AlanException If an error is detected from any of the command handlers. + */ + public String processCommandHandler(String userInput) throws AlanException { + String command = extractCommand(userInput); + + switch (command) { + case "bye": + ui.showExitMessage(); + break; + case "list": + listCommandHandler(); + break; + case "mark": + markingCommandHandler(userInput, true); + break; + case "unmark": + markingCommandHandler(userInput, false); + break; + case "todo": + checkEmptyDescription(userInput); + todoCommandHandler(userInput); + break; + case "deadline": + checkEmptyDescription(userInput); + deadlineCommandHandler(userInput); + break; + case "event": + checkEmptyDescription(userInput); + eventCommandHandler(userInput); + break; + case "delete": + deleteCommandHandler(userInput); + break; + case "find": + findCommandHandler(userInput); + break; + default: + invalidInputCommand(); + } + + return command; + } + + public String extractCommand(String userInput) throws AlanException { + checkEmptyInput(userInput); + String[] userInputWords = userInput.split(" "); + return userInputWords[0]; + } + + /** + * Handles displaying all the tasks within the TaskList. + */ + public void listCommandHandler() { + ui.showToUser(MESSAGE_LIST_COMMAND); + ui.printTasks(tasks.getTaskList()); + } + + /** + * Checks for any text found after /from parameter. + * + * @param userInput text of the user input. + * @param isMark mark or unmark the selected task. + * @throws AlanException If there is only 1 String element found in array. + */ + public void markingCommandHandler(String userInput, boolean isMark) throws AlanException { + String[] words = userInput.split(" "); + checkIndexInput(words); + int selectedTaskIndex = Integer.parseInt(words[1]) - 1; + + checkOutOfTaskListIndex(selectedTaskIndex, tasks.getTaskList()); + Task targetTask = tasks.getSelectedTask(selectedTaskIndex); + + if (isMark) { + tasks.markTask(selectedTaskIndex, true); + ui.showMarkTaskMessage(targetTask); + } else { + tasks.markTask(selectedTaskIndex, false); + ui.showUnmarkTaskMessage(targetTask); + } + } + + /** + * Handles extracting description text. + * Adds Todo object to the TaskList. + * + * @param userInput text of the user input. + */ + public void todoCommandHandler(String userInput) { + String description = userInput.replace("todo ", ""); + tasks.addToDo(description); + + ui.showTaskAddedMessage(tasks.getTaskList()); + } + + /** + * Handles extracting description and 'by' time period text. + * Adds Deadline object to the TaskList. + * + * @param userInput text of the user input. + * @throws AlanException If input format of /by is not correct. + */ + public void deadlineCommandHandler(String userInput) throws AlanException { + String filteredUserInput = userInput.replace("deadline ", ""); + String[] data = filteredUserInput.split(" /by "); + + checkDeadlineInputFormat(data); + + String description = data[0]; + String by = data[1]; + + String parsedBy = parseDate(by); + + tasks.addDeadline(description, parsedBy); + ui.showTaskAddedMessage(tasks.getTaskList()); + } + + /** + * Handles extracting description, 'from' time period and 'to' time period text. + * Adds Event object to the TaskList. + * + * @param userInput text of the user input. + * @throws AlanException If input format of /from or /to is not correct. + */ + public void eventCommandHandler(String userInput) throws AlanException { + String filteredUserInput = userInput.replace("event ", ""); + String[] splitDescriptionAndDate = filteredUserInput.split(" /from "); + + checkEventInputFromFormat(splitDescriptionAndDate); + + String[] splitFromAndTo = splitDescriptionAndDate[1].split(" /to "); + + checkEventInputToFormat(splitFromAndTo); + + String description = splitDescriptionAndDate[0]; + String from = splitFromAndTo[0]; + String to = splitFromAndTo[1]; + + String parsedFrom = parseDate(from); + String parsedTo = parseDate(to); + + tasks.addEvent(description, parsedFrom, parsedTo); + + ui.showTaskAddedMessage(tasks.getTaskList()); + } + + /** + * Handles extracting the user's selected task index. + * Deletes selected task from TaskList. + * + * @param userInput text of the user input. + * @throws AlanException If the task index is not found in TaskList. + */ + public void deleteCommandHandler(String userInput) throws AlanException { + String[] words = userInput.split(" "); + checkIndexInput(words); + int selectedTaskIndex = Integer.parseInt(words[1]) - 1; + checkOutOfTaskListIndex(selectedTaskIndex, tasks.getTaskList()); + + Task targetTask = tasks.getSelectedTask(selectedTaskIndex); + ui.showDeleteTaskMessage(targetTask); + tasks.removeTask(selectedTaskIndex); + + int numberOfTasks = tasks.getTaskListSize(); + ui.showNumberOfTasksMessage(numberOfTasks); + } + + public void findCommandHandler(String userInput) { + String findText = userInput.replace("find ", ""); + TaskList findResultTasks = new TaskList(); + + for (Task task : tasks.getTaskList()) { + String taskDescription = task.getDescription(); + + if (taskDescription.matches("(.*)" + findText + "(.*)")) { + findResultTasks.getTaskList().add(task); + } + } + + ui.showToUser(MESSAGE_FIND_TASK); + ui.printTasks(findResultTasks.getTaskList()); + } + private String parseDate(String inputDate) { + if (isValidDate(inputDate)) { + LocalDate parsedDate = LocalDate.parse(inputDate); + inputDate = parsedDate.format(DateTimeFormatter.ofPattern(DATE_PARSE_PATTERN)); + } + return inputDate; + } + + public boolean isValidDate(String inDate) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd"); + dateFormat.setLenient(false); + try { + dateFormat.parse(inDate.trim()); + } catch (ParseException pe) { + return false; + } + return true; + } +} diff --git a/src/main/java/alan/storage/Storage.java b/src/main/java/alan/storage/Storage.java new file mode 100644 index 000000000..3816a48b1 --- /dev/null +++ b/src/main/java/alan/storage/Storage.java @@ -0,0 +1,218 @@ +package alan.storage; + +import alan.data.exception.AlanException; +import alan.data.task.Deadline; +import alan.data.task.Event; +import alan.data.task.Task; +import alan.data.task.TaskType; +import alan.data.task.Todo; +import alan.ui.Ui; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Scanner; + +import static alan.common.Messages.MESSAGE_INVALID_TASK_TYPE_FOUND; + +/** + * Handles reading from and storing data to the text file. + */ +public class Storage { + private String filePath; + private ArrayList taskArrayList; + private Ui ui; + + public Storage(String filePath) { + this.filePath = filePath; + this.taskArrayList = new ArrayList<>(); + this.ui = new Ui(); + } + + public ArrayList load() throws FileNotFoundException { + readFileHandler(); + return taskArrayList; + } + + public void save() throws Exception { + saveFileHandler(); + } + + /** + * Reads the text file from the specified file path and stores the tasks in an ArrayList. + * + * @throws FileNotFoundException If file is not found at the specified file path. + */ + private void readFileHandler() throws FileNotFoundException { + String userWorkingDirectory = System.getProperty("user.dir"); + java.nio.file.Path tasksFilePath = java.nio.file.Paths.get(userWorkingDirectory, filePath); + File textFile = new File(String.valueOf(tasksFilePath)); + if (textFile.length() != 0) { + Scanner s = new Scanner(textFile); + ArrayList list = new ArrayList<>(); + + while (s.hasNext()){ + list.add(s.nextLine()); + } + s.close(); + + taskArrayList = extractAndStoreDataInTaskList(list); + } + } + + /** + * Handles the extraction of each parameter. + * Creates a task using the extracted parameters and adds in TaskList. + * + * @return ArrayList containing tasks extracted from the list of strings. + */ + private ArrayList extractAndStoreDataInTaskList(ArrayList list) { + //todo: after this line should check if the data is in correct format + for (String task : list) { + String[] splitTaskString = task.split(" \\| "); + String taskType = splitTaskString[0]; + String isDoneString = splitTaskString[1]; + String description = splitTaskString[2]; + boolean isDone = isDoneStringToBoolean(isDoneString); + try { + addTaskToTaskList(taskType, description, splitTaskString); + } catch (AlanException e) { + ui.showToUser(e.getMessage()); + } + + int lastTaskIndex = taskArrayList.size() - 1; + taskArrayList.get(lastTaskIndex).setDone(isDone); + } + + return taskArrayList; + } + + /** + * Adds task to ArrayList according to the respective taskType. + * + * @throws AlanException If taskType read is invalid. + */ + public void addTaskToTaskList(String taskType, String description, String[] splitTaskString) throws AlanException { + switch (taskType) { + case "T": + taskArrayList.add(new Todo(description)); + break; + case "D": + String by = splitTaskString[3]; + taskArrayList.add(new Deadline(description, by)); + break; + case "E": + String date = splitTaskString[3]; + String[] splitDate = date.split("-"); + String from = splitDate[0]; + String to = splitDate[1]; + + taskArrayList.add(new Event(description, from, to)); + break; + default: + new AlanException(MESSAGE_INVALID_TASK_TYPE_FOUND); + break; + } + } + + private boolean isDoneStringToBoolean (String string) { + if (string.equals("1")) { + return true; + } + return false; + } + + /** + * Saves the tasks in a text file at th specified path. + * + * @throws Exception If file or folder does not exist in the specified path. + */ + private void saveFileHandler() throws Exception { + String userWorkingDirectory = System.getProperty("user.dir"); + java.nio.file.Path tasksFilePath = java.nio.file.Paths.get(userWorkingDirectory, filePath); + java.nio.file.Path dataFolderPath = tasksFilePath.getParent(); + File textFile = new File(String.valueOf(tasksFilePath)); + File folder = new File(String.valueOf(dataFolderPath)); + + if (!Files.exists(dataFolderPath)) { + folder.mkdir(); + ui.showFolderNotFoundMessage(userWorkingDirectory); + } + + if (!Files.exists(tasksFilePath)) { + textFile.createNewFile(); + ui.showFileNotFoundMessage(dataFolderPath); + } + + + if (taskArrayList.isEmpty()) { + writeToFile(tasksFilePath.toString(), ""); //overwrite text file to store empty text + } + + //input arraylist data into text file + for (int i = 0; i < taskArrayList.size(); i++) { + String taskDataRow = getStringOfTaskInformation(i); + + if (i == 0) { + writeToFile(tasksFilePath.toString(), taskDataRow); + } else { + appendToFile(tasksFilePath.toString(), taskDataRow); + } + } + } + + /** + * Formats the task into a string. + * + * @param i index of the task in ArrayList. + * @return a string of the formatted task information to be stored in the text file. + */ + private String getStringOfTaskInformation(int i) { + Task task = taskArrayList.get(i); + String taskDataRow = task.getTaskType() + " | " + task.getStatusValue() + " | " + task.getDescription(); + + if (task.getTaskType() == TaskType.D) { + Deadline deadline = (Deadline) task; + taskDataRow = taskDataRow + " | " + deadline.getBy(); + } + + if (task.getTaskType() == TaskType.E) { + Event event = (Event) task; + taskDataRow = taskDataRow + " | " + event.getFrom() + "-" +event.getTo(); + } + + taskDataRow = taskDataRow + "\n"; + return taskDataRow; + } + + /** + * Writes text to the text file at the specified file path. + * Will overwrite all text in text file. + * + * @param filePath file path of the text file. + * @param textToAdd text to be written to the text file. + * @throws IOException If I/O operations are interrupted. + */ + private void writeToFile(String filePath, String textToAdd) throws IOException { + FileWriter fw = new FileWriter(filePath); + fw.write(textToAdd); + fw.close(); + } + + /** + * Appends text to the text file at the specified file path. + * Will add text to text file. + * + * @param filePath file path of the text file. + * @param textToAdd text to be added to the text file. + * @throws IOException If I/O operations are interrupted. + */ + private void appendToFile(String filePath, String textToAdd) throws IOException { + FileWriter fw = new FileWriter(filePath, true); + fw.write(textToAdd); + fw.close(); + } +} diff --git a/src/main/java/alan/ui/Ui.java b/src/main/java/alan/ui/Ui.java new file mode 100644 index 000000000..b6c1cf5b5 --- /dev/null +++ b/src/main/java/alan/ui/Ui.java @@ -0,0 +1,133 @@ +package alan.ui; + +import alan.data.task.Task; + +import java.io.InputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Scanner; + +import static alan.common.Messages.*; + +/** + * Represents the text UI of the application + */ +public class Ui { + public static final int DISPLAYED_INDEX_OFFSET = 1; + private final Scanner in; + private final PrintStream out; + + public Ui() { + this(System.in, System.out); + } + + public Ui(InputStream in, PrintStream out) { + this.in = new Scanner(in); + this.out = out; + } + + /** + * Reads the user input + * + * @return string of the user's input text + */ + public String getUserCommand() { + System.out.print("Input: "); + + String userInput = in.nextLine(); + + showToUser(horizontalDivider); + + return userInput; + } + + public void printHorizontalLine() { + showToUser(horizontalDivider); + } + + public void showWelcomeMessage() { + showToUser( + horizontalDivider, + MESSAGE_GREET, + horizontalDivider); + } + + public void showExitMessage() { + showToUser(MESSAGE_GOODBYE); + } + + /** + * Prints all the Task objects in a list format + * + * @param taskList tasks in the list + */ + public void printTasks(ArrayList taskList) { + if (taskList.isEmpty()) { + showToUser(MESSAGE_EMPTY_LIST); + return; + } + + for (int i = 0; i < taskList.size(); i++) { + System.out.print((i + DISPLAYED_INDEX_OFFSET) + ". "); + System.out.println(taskList.get(i)); + } + } + + public void showMarkTaskMessage(Task task) { + showToUser(MESSAGE_MARK_TASK); + System.out.println(task); + } + + public void showUnmarkTaskMessage(Task task) { + showToUser(MESSAGE_UNMARK_TASK); + System.out.println(task); + } + + public void showDeleteTaskMessage(Task task) { + showToUser(MESSAGE_DELETE_TASK); + System.out.println(task); + } + + public void showNumberOfTasksMessage(int numberOfTasks) { + if (numberOfTasks == 1) { + System.out.println("Dude! You've got a solid " + numberOfTasks + " task lined up on your list now!"); + } else { + System.out.println("Dude! You've got a solid " + numberOfTasks + " tasks lined up on your list now!"); + } + } + + /** + * Prints the last Task object added to the taskList + * + * @param taskList tasks in the list + */ + public void showTaskAddedMessage(ArrayList taskList) { + int numberOfTasks = taskList.size(); + int lastTaskIndex = taskList.size() - 1; + + System.out.println("added: " + taskList.get(lastTaskIndex)); + showNumberOfTasksMessage(numberOfTasks); + } + + public void showFolderNotFoundMessage(String userWorkingDirectory) { + showToUser(MESSAGE_DATA_FOLDER_NOT_FOUND + userWorkingDirectory); + } + + public void showFileNotFoundMessage(java.nio.file.Path dataFolderPath) { + showToUser(MESSAGE_DATA_FILE_NOT_FOUND + dataFolderPath); + } + + public void showLoadingError() { + System.out.println(MESSAGE_LOAD_FILE_ERROR); + } + + public void showSavingError() { + System.out.println(MESSAGE_SAVE_FILE_ERROR); + } + + public void showToUser(String... message) { + for (String m : message) { + System.out.println(m); + } + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e..9466b7119 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,42 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + @/ +/| +/ \ +Hello! I'm Alan +What can I do for you? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Input: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +added: [T][ ] borrow book +Now you have 1 in the list. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Input: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Here are the tasks in your list: +1. [T][ ] borrow book +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Input: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +added: [D][ ] return book (by: Sunday) +Now you have 2 in the list. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Input: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Mon 2pm +4pm +added: [E][ ] project meeting (from: Mon 2pm | to: 4pm) +Now you have 3 in the list. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Input: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Alright! I've marked this task as done: +[T][X] borrow book +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Input: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Ok, I've marked this task as not done yet: +[T][ ] borrow book +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Input: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Here are the tasks in your list: +1. [T][ ] borrow book +2. [D][ ] return book (by: Sunday) +3. [E][ ] project meeting (from: Mon 2pm | to: 4pm) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Input: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Bye. Hope to see you again soon! +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb..11d693542 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,8 @@ +todo borrow book +list +deadline return book /by Sunday +event project meeting /from Mon 2pm /to 4pm +mark 1 +unmark 1 +list +bye \ No newline at end of file diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 087374464..c0bce8993 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -15,7 +15,7 @@ IF ERRORLEVEL 1 ( REM no error here, errorlevel == 0 REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ..\bin Duke < input.txt > ACTUAL.TXT +java -classpath ..\bin Alan < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT