diff --git a/.gitignore b/.gitignore index 2873e189e..2ae219250 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT +data/savedList.txt diff --git a/docs/README.md b/docs/README.md index 8077118eb..717657da7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,158 @@ # User Guide -## Features +## `Introduction` -### Feature-ABC +This is Duke, a chatbot. He is here to assist you in compiling a task list that you can reference to. -Description of the feature. +## `Download` -### Feature-XYZ +1) Make sure that your computer supports Java 11 or above. +2) Download this [jar file](https://github.com/geraldkoh4/ip/releases/download/A-Release/Duke.jar). +3) Copy the file path. +4) Go into your console and enter `"java -jar {file path}"`. -Description of the feature. +## `Features` -## Usage +1) Add a Task +2) List all Tasks +3) Mark/Unmark a Task +4) Find a Task +5) Delete a Task +6) Exit -### `Keyword` - Describe action +### 1.1) Add a Task (To Do) -Describe the action and its outcome. +Add a "To Do" Task to the list. -Example of usage: +**`Format`**: todo {description} -`keyword (optional arguments)` +*Sample Input Command*: **`todo Fly a Kite`** -Expected outcome: +*Output*: +``` +Got it. I've added this task: +[T][ ] Fly a Kite +Now you have 1 tasks in the list. +``` + +### 1.2) Add a Task (Deadline) + +Add a "Deadline" Task to the list. + +**`Format`**: deadline {description} /by {time/date} + +*Sample Input Command*: **`deadline Finish My Homework /by Today`** + +*Output*: +``` +Got it. I've added this task: +[D][ ] Finish My Homework (by: Today) +Now you have 2 tasks in the list. +``` + +### 1.3) Add a Task (Event) + +Add a "Event" Task to the list. + +**`Format`**: event {description} /from {time/date} /to {time/date} + +*Sample Input Command*: **`event Play Video Games /from Today 12pm /to Today Night`** + +*Output*: +``` +Got it. I've added this task: +[E][ ] Play Video Games (from: Today 12pm to: Today Night) +Now you have 3 tasks in the list. +``` + +### 2) List all Tasks + +Lists every Task you currently have in your list. + +**`Format`**: list + +*Output*: +``` +1) [T][ ] Fly a Kite +2) [D][ ] Finish My Homework (by: Today) +3) [E][ ] Play Video Games (from: Today 12pm to: Today Night) +``` + +### 3) Mark/Unmark a Task + +Mark or unmark a Task to note if you have completed the Task through the tasknumber. + +**`Format`**: mark {tasknumber} + +*Sample Input Command*: **`mark 1`** + +*Output*: +``` +Done! +[T][X] Fly a Kite +``` + +### 4) Find a Task + +Find a task through a search criteria. + +**`Format`**: find {search criteria} + +*Sample Input Command*: **`find Play`** + +*Output*: +``` +3) [E][ ] Play Video Games (from: Today 12pm to: Today Night) +Done! +``` + +### 5) Delete a Task + +Delete a task that you no longer need through the tasknumber. + +**`Format`**: delete {tasknumber} + +*Sample Input Command*: **`delete 1`** + +*Output*: +``` +Got it. I've removed this task +[T][X] Fly a Kite +Now you have 2 tasks in the list. +``` + +### 6) Exit + +Exit from the program. + +**`Format`**: bye + +*Output*: +``` +That's all from me! Goodbye! +``` -Description of the outcome. +# Note +## Test - Echo + +Echo is a command that you can use to test if the chatbot is responding! + +**`Format`**: echo {description} + +*Sample Input Command*: **`echo 1`** + +*Output*: ``` -expected output +1 ``` + +## Flags + +Do not use the character '/'. It is solely reserved for flags. +Example of the flags include: +Deadline by flag: "/by " +Event from flag: "/from " +Event to flag: "/to " + +Thank you! diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5d313334c..be3e1f6d5 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,22 @@ +import parser.DukeSession; +import utility.Ui; + +/** + * Represents the Main of the entire project, a chatbot named DUKE. + * It is made simple to understand the flow of the whole project. + */ public class Duke { + + /** + * Calls the session to start and initialises the session. + * + * @param args Contains any information the user inputs when running the process in the terminal. + */ public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + DukeSession dukeSession = new DukeSession(); + Ui.printGreetings(); + dukeSession.setUpArrayList(); + dukeSession.execute(); } } + diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..9f37e4e0a --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Duke + diff --git a/src/main/java/file/storage/Storage.java b/src/main/java/file/storage/Storage.java new file mode 100644 index 000000000..f1f481e5f --- /dev/null +++ b/src/main/java/file/storage/Storage.java @@ -0,0 +1,125 @@ +package file.storage; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; + +import tasks.Task; +import tasks.Todo; +import tasks.Event; +import tasks.Deadline; +import utility.Ui; + +/** + * Represents the Storage class, where it is used to deal with file and storage matters. + * It sets up the locally stored file first. + * It will check for the file in the default save path. If it does not exist, it will create the file and directory. + * If the local copy exists, it will load it and be incorporated into this session (DukeSession). + * After the user exits, it will also save the file to the default save path. + */ +public class Storage { + private static final String DEFAULT_SAVE_PATH = "data/savedList.txt"; + private static final String DEFAULT_DIRECTORY_NAME = "data"; + private static final String FILE_CORRUPTED_MESSAGE = "File has been corrupted"; + private static final String ERROR_MESSAGE = "Something went wrong: "; + private static final String DEFAULT_TODO_SYMBOL = "t"; + private static final String DEFAULT_DEADLINE_SYMBOL = "d"; + private static final String DEFAULT_EVENT_SYMBOL = "e"; + private static final String DEFAULT_MARKED_TASK_SYMBOL = "X"; + + private static File directoryAndFileChecker() throws IOException { + File f = new File(DEFAULT_SAVE_PATH); + File directory = new File(DEFAULT_DIRECTORY_NAME); + try { + if (!directory.exists()) { + directory.mkdir(); + } + if (!f.exists()) { + f.createNewFile(); + } + } catch (IOException e) { + System.out.println(ERROR_MESSAGE + e.getMessage()); + } + return f; + } + + /** + * It will call the method directoryAndFileChecker to ensure that all file and directory conflicts are resolved. + * After that, it reads the file and parses the data it loaded. + * Other setup methods are called to load different tasks and ensure that this session is same as the saved + * local text file. + * + * @param actions This contains the arraylist of Tasks. + */ + public static void setUpFile(ArrayList actions) { + try { + File f = directoryAndFileChecker(); + String line; + String[] decisions; + Scanner in = new Scanner(f); + while (in.hasNext()) { + line = in.nextLine(); + decisions = line.split(Ui.DEFAULT_FLAG_SEPARATOR); + switch (decisions[0]) { + case DEFAULT_TODO_SYMBOL: + setUpToDo(decisions, actions); + break; + case DEFAULT_DEADLINE_SYMBOL: + setUpDeadline(decisions, actions); + break; + case DEFAULT_EVENT_SYMBOL: + setUpEvent(decisions, actions); + break; + default: + Ui.print(FILE_CORRUPTED_MESSAGE); + } + } + } catch (IOException e) { + System.out.println(ERROR_MESSAGE + e.getMessage()); + } + } + + /** + * Saves any recorded information of this current DukeSession. + * The arraylist in DukeSession is saved into a default save path in a text file. + * + * @param actions This contains the arraylist of Tasks. + */ + public static void saveFile(ArrayList actions) { + try { + FileWriter fw = new FileWriter(DEFAULT_SAVE_PATH); + for (Task i : actions) { + fw.write(i.getSaveCommand() + System.lineSeparator()); + } + fw.close(); + } catch (IOException e) { + System.out.println(ERROR_MESSAGE + e.getMessage()); + } + } + + private static void setUpToDo(String[] decisions, ArrayList actions) { + Task toBeAdded = new Todo(decisions[2]); + if (decisions[1].equals(DEFAULT_MARKED_TASK_SYMBOL)) { + toBeAdded.mark(); + } + actions.add(toBeAdded); + } + + private static void setUpDeadline(String[] decisions, ArrayList actions) { + Task toBeAdded = new Deadline(decisions[2], decisions[3]); + if (decisions[1].equals(DEFAULT_MARKED_TASK_SYMBOL)) { + toBeAdded.mark(); + } + actions.add(toBeAdded); + } + + private static void setUpEvent(String[] decisions, ArrayList actions) { + Task toBeAdded = new Event(decisions[2], decisions[3], decisions[4]); + if (decisions[1].equals(DEFAULT_MARKED_TASK_SYMBOL)) { + toBeAdded.mark(); + } + actions.add(toBeAdded); + } +} diff --git a/src/main/java/parser/DukeSession.java b/src/main/java/parser/DukeSession.java new file mode 100644 index 000000000..cb85b0cd3 --- /dev/null +++ b/src/main/java/parser/DukeSession.java @@ -0,0 +1,165 @@ +package parser; + +import file.storage.Storage; + +import utility.Ui; +import utility.commandChecker; + +import tasks.Deadline; +import tasks.Event; +import tasks.Task; +import tasks.Todo; + +import java.util.Scanner; +import java.util.ArrayList; + +/** + * Represents the whole session of Duke's communication. + * It creates an arraylist that stores all tasks that the user has given in this session. + * It also parses the input that the user has entered. After that, it sends the broken down inputs to + * the commandChecker class, where the inputs are validated. + * If there are no errors in the input, it is executed. + */ +public class DukeSession { + private static final String INPUT_ERROR_DETECTED = "Invalidate"; + + private ArrayList actions = new ArrayList<>(); + + /** + * Calls the function setUpFile in the Storage class. It is used here to ensure arraylist is already setup. + */ + public void setUpArrayList() { + Storage.setUpFile(actions); + } + + /** + * This method parses the inputs into decisions and dates. + * It then calls the method of commandChecker, hasErrors. + * hasErrors will then validate the inputs and then notify DukeSession of any errors caught. + * If there are no errors detected, it will call the method handleInputs where they are processed. + * If errors are detected, user input is discarded. + */ + public void execute() { + String line; + String[] decisions; + String[] dates; + Scanner in = new Scanner(System.in); + do { + line = in.nextLine(); + decisions = line.split(Ui.DEFAULT_LINE_SEPARATOR); + dates = line.split(Ui.DEFAULT_FLAG_SEPARATOR); + commandChecker currentLoop = new commandChecker(decisions, dates, actions.size()); + if (currentLoop.hasErrors()) { + decisions[0] = INPUT_ERROR_DETECTED; + } + handleInputs(line, decisions, dates); + } while (!decisions[0].equals(Ui.DEFAULT_EXIT)); + Storage.saveFile(actions); + Ui.printExitMessage(); + } + + private void handleInputs(String line, String[] decisions, String[] dates) { + switch (decisions[0]) { + case Ui.DEFAULT_ECHO: + System.out.println(findTaskDetails(line)); + break; + case Ui.DEFAULT_TODO: + handleToDo(line); + break; + case Ui.DEFAULT_EVENT: + handleEvent(dates); + break; + case Ui.DEFAULT_DEADLINE: + handleDeadline(dates); + break; + case Ui.DEFAULT_MARK_TASK: + handleMarkTask(decisions); + break; + case Ui.DEFAULT_UNMARK_TASK: + handleUnmarkTask(decisions); + break; + case Ui.DEFAULT_LIST_ALL_TASKS: + handleList(); + break; + case Ui.DEFAULT_DELETE: + handleDeleteTask(decisions); + break; + case Ui.DEFAULT_FIND: + handleFindTask(line); + break; + case Ui.DEFAULT_EXIT: + break; + default: + Ui.printCurrentSupportedActions(); + } + } + + private void handleToDo(String line) { + Task toBeAdded = new Todo(findTaskDetails(line)); + actions.add(toBeAdded); + Ui.printAcknowledgement(toBeAdded, actions.size()); + } + + private void handleEvent(String[] dates) { + Task toBeAdded = new Event(findTaskDetails(dates[0]), findTaskDetails(dates[1]), findTaskDetails(dates[2])); + actions.add(toBeAdded); + Ui.printAcknowledgement(toBeAdded, actions.size()); + } + + private void handleDeadline(String[] dates) { + Task toBeAdded = new Deadline(findTaskDetails(dates[0]), findTaskDetails(dates[1])); + actions.add(toBeAdded); + Ui.printAcknowledgement(toBeAdded, actions.size()); + } + + private void handleMarkTask(String[] decisions) { + int taskNumber = Integer.parseInt(decisions[1]) - 1; + actions.get(taskNumber).mark(); + Ui.printDoneMarkingTasks(actions.get(taskNumber)); + } + + private void handleUnmarkTask(String[] decisions) { + int taskNumber = Integer.parseInt(decisions[1]) - 1; + actions.get(taskNumber).unmark(); + Ui.printDoneMarkingTasks(actions.get(taskNumber)); + } + + private void handleList() { + for (int i = 0; i < actions.size(); i++) { + Ui.printListElement(i, actions.get(i)); + } + } + + private void handleDeleteTask(String[] decisions) { + int taskNumber = Integer.parseInt(decisions[1]) - 1; + Ui.printDeleteAcknowledgement(actions.get(taskNumber), actions.size() - 1); + actions.remove(taskNumber); + } + + private void handleFindTask(String line) { + String termToFind = findTaskDetails(line); + int iterator = 0; + int numberOfPrintedTasks = 0; + for (Task searchTerm : actions) { + boolean containsMatchingTerm = searchTerm.getDescription().toLowerCase().contains(termToFind.toLowerCase()); + if (containsMatchingTerm) { + Ui.printListElement(iterator, actions.get(iterator)); + numberOfPrintedTasks++; + } + iterator++; + } + if (numberOfPrintedTasks > 0) { + Ui.printFindAcknowledgement(); + } else if (numberOfPrintedTasks == 0) { + Ui.printCannotFindAcknowledgement(); + } else { + Ui.printDefaultErrorMessage(); + } + } + + private static String findTaskDetails(String line) { + int correctLineIndex = line.indexOf(Ui.DEFAULT_LINE_SEPARATOR) + 1; + return line.substring(correctLineIndex); + } + +} diff --git a/src/main/java/tasks/Deadline.java b/src/main/java/tasks/Deadline.java new file mode 100644 index 000000000..077b08367 --- /dev/null +++ b/src/main/java/tasks/Deadline.java @@ -0,0 +1,52 @@ +package tasks; + +import utility.Ui; + +/** + * Represents the class Deadline, which inherits from the parent class Task. + * When created, it is stored in the arraylist in DukeSession. + * It is a task, which contains a description and 1 date field, /by. + * E.g. deadline Submit this Assignment /by Mar 3rd 2359 + */ +public class Deadline extends Task { + private static final String DEFAULT_DEADLINE_SYMBOL = "[D]"; + private static final String DEFAULT_BY_FORMATTER = "(by: "; + private static final String END_BRACKET = ")"; + private static final String DEFAULT_DEADLINE_SAVE_SYMBOL = "d/"; + + private String by; + + /** + * Initialises an object of the Class Deadline. + * + * @param description Contains the description of the deadline that the user wants to do. + * @param by Contains the time or date of the due date of the deadline task. + */ + public Deadline(String description, String by) { + super(description); + this.by = by; + } + + /** + * Returns a string that contains the information of the description of the deadline task. + * It also contains information about whether it is marked, and the due date. + * It is properly formatted so that the user will be able to understand it. + * + * @return Returns a formatted string of the deadline task that you wish to display to the user. + */ + @Override + public String toString() { + return DEFAULT_DEADLINE_SYMBOL + super.toString() + DEFAULT_BY_FORMATTER + by + END_BRACKET; + } + + /** + * Returns a string that is formatted specifically to save it into a local text file. + * + * @return Returns a formatted string of the deadline task that you wish to save. + */ + public String getSaveCommand() { + return DEFAULT_DEADLINE_SAVE_SYMBOL + this.getStatusIcon() + + Ui.DEFAULT_FLAG_SEPARATOR + this.getDescription() + + Ui.DEFAULT_FLAG_SEPARATOR + this.by; + } +} \ No newline at end of file diff --git a/src/main/java/tasks/Event.java b/src/main/java/tasks/Event.java new file mode 100644 index 000000000..d0a1fe736 --- /dev/null +++ b/src/main/java/tasks/Event.java @@ -0,0 +1,59 @@ +package tasks; + +import utility.Ui; + +/** + * Represents the class Event, which inherits from the parent class Task. + * When created, it is stored in the arraylist in DukeSession. + * It is a task, which contains a description and 2 date fields, /from and /to. + * E.g. event attend CS2113T tutorial /from thursday 9am /to 10am. + */ +public class Event extends Task { + private static final String DEFAULT_EVENT_SYMBOL = "[E]"; + private static final String DEFAULT_FROM_FORMATTER = "from: "; + private static final String DEFAULT_TO_FORMATTER = "to: "; + private static final String DEFAULT_EVENT_SAVE_SYMBOL = "e/"; + private static final String OPEN_BRACKET = "("; + private static final String CLOSE_BRACKET = ")"; + + private String from; + + private String to; + + /** + * Initialises an object of the Class Event. + * + * @param description Contains the description of the event that the user wants to do. + * @param from Contains the starting date or time of the event. + * @param to Contains the ending date or time of the event. + */ + public Event(String description, String from, String to) { + super(description); + this.from = from; + this.to = to; + } + + /** + * Returns a string that contains the information of the description of the event + * It also contains information about whether it is marked and the start and end of the event. + * It is properly formatted so that the user will be able to understand it. + * + * @return Returns a formatted string of the event that you wish to display to the user. + */ + @Override + public String toString() { + return DEFAULT_EVENT_SYMBOL + super.toString() + OPEN_BRACKET + DEFAULT_FROM_FORMATTER + from + + DEFAULT_TO_FORMATTER + to + CLOSE_BRACKET; + } + + /** + * Returns a string that is formatted specifically to save the event into a local text file. + * + * @return Returns a formatted string of the event that you wish to save. + */ + public String getSaveCommand() { + return DEFAULT_EVENT_SAVE_SYMBOL + this.getStatusIcon() + Ui.DEFAULT_FLAG_SEPARATOR + this.getDescription() + + Ui.DEFAULT_FLAG_SEPARATOR + this.from + + Ui.DEFAULT_FLAG_SEPARATOR + this.to; + } +} diff --git a/src/main/java/tasks/Task.java b/src/main/java/tasks/Task.java new file mode 100644 index 000000000..4411ef4b3 --- /dev/null +++ b/src/main/java/tasks/Task.java @@ -0,0 +1,86 @@ +package tasks; + +import utility.Ui; + +/** + * Represents the parent class Task, which is a parent to Todo, Event, Deadline. + * It contains a description, and another isDone variable that indicates whether the task is done (or marked). + * There are also methods to produce a string that is properly formatted to the user or to save in storage. + */ +public class Task { + private static final String DEFAULT_MARKED_TASK_SYMBOL = "X"; + private static final String START_BRACKET = "["; + private static final String END_BRACKET = "] "; + + private String description; + + private boolean isMarked; + + private String saveCommand; + + /** + * Initialises an object of the class Task. + * + * @param description Contains the details of the description of the task. + */ + public Task(String description) { + this.description = description; + this.isMarked = false; + } + + /** + * Returns a string that shows if the task is marked by the user. + * + * @return The string that shows if it is marked or not. + */ + public String getStatusIcon() { + return (isMarked ? DEFAULT_MARKED_TASK_SYMBOL : Ui.DEFAULT_LINE_SEPARATOR); + } + + /** + * Allows the user to mark the tasks. + * The mark and unmark method is used specifically so that users can mark or unmark tasks more simply. + * By using setMark and getMark, it will allow the user to unmark a marked task accidentally. + *

+ * Can be performed even if the task is already marked. + */ + public void mark() { + this.isMarked = true; + } + + /** + * Allows the user to unmark the tasks. + * Can be performed even if the task is already unmarked. + */ + public void unmark() { + this.isMarked = false; + } + + /** + * Returns the description of the task. + * + * @return Returns the string that contains the description. + */ + public String getDescription() { + return (this.description); + } + + /** + * Returns a string that contains the information of the description of the task and also whether it is marked. + * It is properly formatted so that the user will be able to understand it. + * + * @return Returns a formatted string of the task that you wish to display to the user. + */ + public String toString() { + return (START_BRACKET + this.getStatusIcon() + END_BRACKET + this.getDescription()); + } + + /** + * Returns a string that is formatted specifically to save it into a local text file. + * + * @return Returns a formatted string of the task that you wish to save. + */ + public String getSaveCommand() { + return this.saveCommand; + } +} \ No newline at end of file diff --git a/src/main/java/tasks/Todo.java b/src/main/java/tasks/Todo.java new file mode 100644 index 000000000..b847232a8 --- /dev/null +++ b/src/main/java/tasks/Todo.java @@ -0,0 +1,44 @@ +package tasks; + +import utility.Ui; + +/** + * Represents the class ToDo, which inherits from the parent class Task. + * When created, it is stored in the arraylist in DukeSession. + * It is a task, which only contains a description. + * E.g. todo Say Goodnight To Duke + */ +public class Todo extends Task { + private static final String DEFAULT_TODO_SYMBOL = "[T]"; + private static final String DEFAULT_TODO_SAVE_SYMBOL = "t/"; + + /** + * Initialises an object of the Class Todo. + * + * @param description Contains the description of the task that the user wants to do. + */ + public Todo(String description) { + super(description); + } + + /** + * Returns the formatted string that you wish to display to the user. + * + * @return Formatted string of a Todo task. + */ + @Override + public String toString() { + return DEFAULT_TODO_SYMBOL + super.toString(); + } + + /** + * Returns the formatted string that you wish to save to the local text file. + * + * @return The formatted string of a Todo task that you wish to save. + */ + public String getSaveCommand() { + return DEFAULT_TODO_SAVE_SYMBOL + this.getStatusIcon() + Ui.DEFAULT_FLAG_SEPARATOR + this.getDescription(); + } +} + + diff --git a/src/main/java/utility/DukeException.java b/src/main/java/utility/DukeException.java new file mode 100644 index 000000000..8715a29f6 --- /dev/null +++ b/src/main/java/utility/DukeException.java @@ -0,0 +1,47 @@ +package utility; + +/** + * Represents a custom Exception class for Duke. It contains a description of the errors thrown by Duke. + * The description will describe the error found, and it will be displayed to the user. + */ +public class DukeException extends Exception { + private static final String DEFAULT_EMPTY_DESCRIPTION = "No Description"; + + public String description; + + /** + * Creates an object of the class DukeException. + * This has no inputs and a default empty description is applied. + */ + public DukeException() { + this.description = DEFAULT_EMPTY_DESCRIPTION; + } + + /** + * Creates an object of the class DukeException. + * This has inputs, and it will be stored into the description. + * + * @param description This will contain the description of the error found in commandChecker. + */ + public DukeException(String description) { + this.description = description; + } + + /** + * Sets the description of the error to be stored in DukeException. + * + * @param description Contains the description of the error given in commandChecker. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Sets the description of the error to be stored in DukeException. + * + * @return description of the error stored in DukeException. + */ + public String getDescription() { + return this.description; + } +} diff --git a/src/main/java/utility/Ui.java b/src/main/java/utility/Ui.java new file mode 100644 index 000000000..ae53ccd74 --- /dev/null +++ b/src/main/java/utility/Ui.java @@ -0,0 +1,142 @@ +package utility; + +import tasks.Task; + +/** + * A class in charge of interacting with the user. + * It also contains common messages or String literals that are commonly used throughout Duke. + * All methods are public and static. + */ +public class Ui { + private static final String DEFAULT_LOGO = " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n" + + "__________________________\n"; + private static final String GREETING_MESSAGE = "Hello! Do you need " + + "anything from me?\n" + + "Once my owner is more proficient in what he does, he will give me more functions!\n"; + private static final String LIST_OF_SUPPORTED_FUNCTIONS = "I am currently only able to do: \n " + + "1)echo \n 2)todo\n 3)mark\n 4)unmark\n 5)deadline\n 6)event\n 7)delete\n 8)find\n" + + "When you wish to exit, do tell me by typing : bye"; + private static final String DEFAULT_LIST_FORMATTING = ") "; + private static final String DEFAULT_ACKNOWLEDGEMENT = "Done!"; + private static final String DEFAULT_TASK_ADDED_ACKNOWLEDGEMENT = "Got it. I've added this task:\n"; + private static final String DEFAULT_TASK_DELETED_ACKNOWLEDGEMENT = "Got it. I've removed this task\n"; + private static final String START_OF_USER_REMINDER = "Now you have "; + private static final String END_OF_USER_REMINDER = " tasks in the list."; + private static final String DEFAULT_FAILED_TO_FIND_MESSAGE = "I am sorry, but I did not find any matches."; + private static final String DEFAULT_ERROR_MESSAGE = "Oh dear, Something has went wrong!"; + private static final String DEFAULT_EXIT_MESSAGE = "That's all from me! Goodbye!"; + public static final String DEFAULT_ECHO = "echo"; + public static final String DEFAULT_TODO = "todo"; + public static final String DEFAULT_EVENT = "event"; + public static final String DEFAULT_DEADLINE = "deadline"; + public static final String DEFAULT_MARK_TASK = "mark"; + public static final String DEFAULT_UNMARK_TASK = "unmark"; + public static final String DEFAULT_LIST_ALL_TASKS = "list"; + public static final String DEFAULT_DELETE = "delete"; + public static final String DEFAULT_FIND = "find"; + public static final String DEFAULT_EXIT = "bye"; + public static final String DEFAULT_LINE_SEPARATOR = " "; + public static final String DEFAULT_FLAG_SEPARATOR = "/"; + + /** + * Prints the standard greeting for users to see. + */ + public static void printGreetings() { + Ui.print(DEFAULT_LOGO + GREETING_MESSAGE + LIST_OF_SUPPORTED_FUNCTIONS); + } + + /** + * A shorter printing method call that utilises System.out.print + * + * @param input String that contains what is going to be printed for the user. + */ + public static void print(String input) { + System.out.println(input); + } + + /** + * Prints an element of a list of Tasks with proper formatting. + * + * @param iterator The iterator or index or the list you are accessing. + * @param action The list of Tasks that the user wishes to do. + */ + public static void printListElement(int iterator, Task action) { + int correctListElementNumber = iterator + 1; + Ui.print(correctListElementNumber + DEFAULT_LIST_FORMATTING + action.toString()); + } + + /** + * Prints the acknowledgement message when a task is marked or unmarked. + * + * @param action The list of Tasks that the user wishes to do. + */ + public static void printDoneMarkingTasks(Task action) { + Ui.print(DEFAULT_ACKNOWLEDGEMENT + System.lineSeparator() + action.toString()); + } + + /** + * It prints the acknowledgement after a task has been added. + * It then reminds the user how many tasks are currently in the list. + * + * @param action The list of Tasks that the user wishes to do. + * @param actionCounter The current size of the list action. + */ + public static void printAcknowledgement(Task action, int actionCounter) { + Ui.print(DEFAULT_TASK_ADDED_ACKNOWLEDGEMENT + action.toString() + System.lineSeparator() + + START_OF_USER_REMINDER + actionCounter + END_OF_USER_REMINDER); + } + + /** + * Prints the message containing the currently supported actions. + */ + public static void printCurrentSupportedActions() { + Ui.print(LIST_OF_SUPPORTED_FUNCTIONS); + } + + /** + * It prints the acknowledgement after a task has been deleted. + * It then reminds the user how many tasks are currently in the list. + * + * @param action The list of Tasks that the user wishes to do. + * @param actionCounter The current size of the list action. + */ + public static void printDeleteAcknowledgement(Task action, int actionCounter) { + Ui.print(DEFAULT_TASK_DELETED_ACKNOWLEDGEMENT + action.toString() + System.lineSeparator() + + START_OF_USER_REMINDER + actionCounter + END_OF_USER_REMINDER); + } + + /** + * It prints the acknowledgement after a relevant find/search is done successfully. + */ + public static void printFindAcknowledgement() { + Ui.print(DEFAULT_ACKNOWLEDGEMENT); + } + + /** + * It prints the acknowledgement after a relevant find/search is done but was unsuccessful. + */ + public static void printCannotFindAcknowledgement() { + Ui.print(DEFAULT_FAILED_TO_FIND_MESSAGE); + } + + /** + * It prints the acknowledgement after the exit command is called. + * It also says goodbye to the user. + */ + public static void printExitMessage() { + Ui.print(DEFAULT_EXIT_MESSAGE); + } + + /** + * It prints the default error message when something has gone wrong. + * It is used when the commandChecker fails to figure out what went wrong. + */ + public static void printDefaultErrorMessage() { + Ui.print(DEFAULT_ERROR_MESSAGE); + } + +} diff --git a/src/main/java/utility/commandChecker.java b/src/main/java/utility/commandChecker.java new file mode 100644 index 000000000..1e3e8c4b2 --- /dev/null +++ b/src/main/java/utility/commandChecker.java @@ -0,0 +1,254 @@ +package utility; + + +/** + * Represents a class used to check for proper usage of commands. + * It serves to accept the inputs after it has been broken down, and check for relevant fields to be filled. + * When an error is detected, it will throw a custom exception: DukeException, which contains the relevant information. + */ +public class commandChecker { + private static final String DEFAULT_EMPTY_DESCRIPTION = "No Description"; + private static final String DEFAULT_CAUGHT_ERROR_MESSAGE = "Invalid input, please try again! Error Description: "; + private static final String DEFAULT_INPUT_ERROR_MESSAGE = "Invalid action word! e.g. echo, todo, list, etc."; + private static final String DEFAULT_MISSING_DESCRIPTION_MESSAGE = " description cannot be empty!"; + private static final String DEFAULT_EVENT_ERROR_MESSAGE = "event needs a description and /from date /to date"; + private static final String DEFAULT_DEADLINE_ERROR_MESSAGE = "deadline needs to have a description and /by date"; + private static final String DEFAULT_MARKING_ERROR = "you may only mark or unmark one task at a time"; + private static final String DEFAULT_LIST_EMPTY_ERROR = "list is currently empty!"; + private static final String DEFAULT_DELETE_ERROR = "you may only delete one task at a time"; + private static final String DEFAULT_OUT_OF_BOUND_ERROR = "out of bounds!"; + + private static final String DEFAULT_DEADLINE_FLAG_ERROR = "Please use / only for flags! " + + "e.g. deadline {description} /by {date/time}"; + + private static final String DEFAULT_EVENT_FLAG_ERROR = "Please use / only for flags! " + + "e.g event {description} /from {date/time} /to {date/time} "; + private static final String DEFAULT_DEADLINE_FLAG = "by "; + + private static final String DEFAULT_EVENT_FROM_FLAG = "from "; + + private static final String DEFAULT_EVENT_TO_FLAG = "to "; + + private final String[] decisions; + + private final String[] dates; + + private final int actionCounter; + + private boolean hasErrorFlags = false; + + /** + * Initialise the commandChecker class. It takes in filtered and broken down inputs that are parsed through + * by DukeSession. + * + * @param decisions The parsed input that contains important information about the type of action. + * @param dates The parsed input that contains important information about the dates of the task. + * @param actionCounter The current size of the list action. + */ + public commandChecker(String[] decisions, String[] dates, int actionCounter) { + this.decisions = decisions; + this.dates = dates; + this.actionCounter = actionCounter; + } + + /** + * Returns a flag of whether errors have been detected in the user's input. + * It calls the method validate command, which will be throwing the exception DukeException. + * This method is called for every user input in DukeSession. + * + * @return True or False of whether there are any errors detected. + */ + public boolean hasErrors() { + try { + validateCommand(); + } catch (DukeException e) { + this.hasErrorFlags = true; + boolean isErrorMessageSameAsDefault = e.getDescription().equals(DEFAULT_EMPTY_DESCRIPTION); + if (!isErrorMessageSameAsDefault) { + Ui.print(DEFAULT_CAUGHT_ERROR_MESSAGE + e.getDescription() + System.lineSeparator()); + } + } + return this.hasErrorFlags; + } + + /** + * Validates the user input according to the correct action word. + * It will throw an exception when the custom exception class DukeException is captured. + * + * @throws DukeException if input has an incorrect action word or missing description or dates. + */ + private void validateCommand() throws DukeException { + DukeException currentException = new DukeException(); + switch (decisions[0]) { + case Ui.DEFAULT_ECHO: + validateEcho(currentException); + break; + case Ui.DEFAULT_TODO: + validateToDo(currentException); + break; + case Ui.DEFAULT_EVENT: + validateEvent(currentException); + break; + case Ui.DEFAULT_DEADLINE: + validateDeadline(currentException); + break; + case Ui.DEFAULT_MARK_TASK: + validateMarkTask(currentException); + break; + case Ui.DEFAULT_UNMARK_TASK: + validateUnmarkTask(currentException); + break; + case Ui.DEFAULT_LIST_ALL_TASKS: + validateList(currentException); + break; + case Ui.DEFAULT_DELETE: + validateDeleteTask(currentException); + break; + case Ui.DEFAULT_FIND: + validateFindTask(currentException); + break; + case Ui.DEFAULT_EXIT: + break; + default: + currentException.setDescription(DEFAULT_INPUT_ERROR_MESSAGE); + throw currentException; + } + } + + private void validateEcho(DukeException currentException) throws DukeException { + if (decisions.length < 2) { + currentException.setDescription(Ui.DEFAULT_ECHO + DEFAULT_MISSING_DESCRIPTION_MESSAGE); + throw currentException; + } + } + + private void validateToDo(DukeException currentException) throws DukeException { + if (decisions.length < 2) { + currentException.setDescription(Ui.DEFAULT_TODO + DEFAULT_MISSING_DESCRIPTION_MESSAGE); + throw currentException; + } + } + + private void validateEvent(DukeException currentException) throws DukeException { + if (dates.length < 3) { + currentException.setDescription(DEFAULT_EVENT_ERROR_MESSAGE); + throw currentException; + } + if (!isCorrectEventFlags()) { + currentException.setDescription(DEFAULT_EVENT_FLAG_ERROR); + throw currentException; + } + } + + private void validateDeadline(DukeException currentException) throws DukeException { + if (dates.length < 2) { + currentException.setDescription(DEFAULT_DEADLINE_ERROR_MESSAGE); + throw currentException; + } + if (!isCorrectDeadlineFlag()) { + currentException.setDescription(DEFAULT_DEADLINE_FLAG_ERROR); + throw currentException; + } + } + + private void validateMarkTask(DukeException currentException) throws DukeException { + if (decisions.length < 2) { + currentException.setDescription(Ui.DEFAULT_MARK_TASK + DEFAULT_MISSING_DESCRIPTION_MESSAGE); + throw currentException; + } else if (decisions.length > 2) { + currentException.setDescription(DEFAULT_MARKING_ERROR); + throw currentException; + } + if (actionCounter == 0) { + currentException.setDescription(DEFAULT_LIST_EMPTY_ERROR); + throw currentException; + } + if (Integer.parseInt(decisions[1]) > actionCounter || Integer.parseInt(decisions[1]) < 1) { + currentException.setDescription(DEFAULT_OUT_OF_BOUND_ERROR); + throw currentException; + } + } + + private void validateUnmarkTask(DukeException currentException) throws DukeException { + if (decisions.length < 2) { + currentException.setDescription(Ui.DEFAULT_UNMARK_TASK + DEFAULT_MISSING_DESCRIPTION_MESSAGE); + throw currentException; + } else if (decisions.length > 2) { + currentException.setDescription(DEFAULT_MARKING_ERROR); + throw currentException; + } + if (actionCounter == 0) { + currentException.setDescription(DEFAULT_LIST_EMPTY_ERROR); + throw currentException; + } + if (Integer.parseInt(decisions[1]) > actionCounter || Integer.parseInt(decisions[1]) < 1) { + currentException.setDescription(DEFAULT_OUT_OF_BOUND_ERROR); + throw currentException; + } + } + + private void validateList(DukeException currentException) throws DukeException { + if (actionCounter == 0) { + currentException.setDescription(DEFAULT_LIST_EMPTY_ERROR); + throw currentException; + } + } + + private void validateDeleteTask(DukeException currentException) throws DukeException { + if (decisions.length < 2) { + currentException.setDescription(Ui.DEFAULT_DELETE + DEFAULT_MISSING_DESCRIPTION_MESSAGE); + throw currentException; + } else if (decisions.length > 2) { + currentException.setDescription(DEFAULT_DELETE_ERROR); + throw currentException; + } + if (actionCounter == 0) { + currentException.setDescription(DEFAULT_LIST_EMPTY_ERROR); + throw currentException; + } + if (Integer.parseInt(decisions[1]) > actionCounter || Integer.parseInt(decisions[1]) < 1) { + currentException.setDescription(DEFAULT_OUT_OF_BOUND_ERROR); + throw currentException; + } + } + + private void validateFindTask(DukeException currentException) throws DukeException { + if (decisions.length < 2) { + currentException.setDescription(Ui.DEFAULT_FIND + DEFAULT_MISSING_DESCRIPTION_MESSAGE); + throw currentException; + } + if (actionCounter == 0) { + currentException.setDescription(DEFAULT_LIST_EMPTY_ERROR); + throw currentException; + } + } + + private boolean isCorrectDeadlineFlag() { + boolean hasCorrectFlag = false; + try { + String flagChecker = dates[1].substring(0, 3); + if (flagChecker.equals(DEFAULT_DEADLINE_FLAG)) { + hasCorrectFlag = true; + } + } catch (StringIndexOutOfBoundsException e) { + return false; + } + return hasCorrectFlag; + } + + private boolean isCorrectEventFlags() { + boolean hasCorrectFlags = false; + try { + String fromFlagChecker = dates[1].substring(0, 5); + String toFlagChecker = dates[2].substring(0, 3); + if (fromFlagChecker.equals(DEFAULT_EVENT_FROM_FLAG) && toFlagChecker.equals(DEFAULT_EVENT_TO_FLAG)) { + hasCorrectFlags = true; + } + } catch (StringIndexOutOfBoundsException e) { + return false; + } + return hasCorrectFlags; + } + +} +