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