From 1795e0430b6c33dba0cb82273ae40571f2f92f21 Mon Sep 17 00:00:00 2001 From: owenyeo Date: Wed, 23 Aug 2023 22:41:53 +0800 Subject: [PATCH 01/28] Added Skeleton code that outputs the required output as per source website. Added functionality to rename chatbot. --- src/main/java/ChatBot.java | 38 ++++++++++++++++++++++++++++++++++++++ src/main/java/Duke.java | 10 ---------- 2 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 src/main/java/ChatBot.java delete mode 100644 src/main/java/Duke.java diff --git a/src/main/java/ChatBot.java b/src/main/java/ChatBot.java new file mode 100644 index 0000000000..842a76475c --- /dev/null +++ b/src/main/java/ChatBot.java @@ -0,0 +1,38 @@ +public class ChatBot { + private String message = ""; + private String name; + + private class Message { + private String line = "____________________________________________________________\n"; + private String message = ""; + + public Message(String message) { + this.message = message; + } + + public void send() { + System.out.println(message + "\n" + line); + } + } + + public ChatBot(String name) { + this.name = name; + } + public void intro() { + Message intro = new Message("____________________________________________________________\n" + + "Hello! I am " + this.name + ".\n" + + "What can I do for you today?"); + intro.send(); + } + + public void goodbye() { + Message goodbye = new Message("Bye. Hope to see you again!"); + goodbye.send(); + } + + public static void main(String[] args) { + ChatBot chatbot = new ChatBot("Bobby Wasabi"); + chatbot.intro(); + chatbot.goodbye(); + } +} diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} From 413012363b9ff5f4b878e0fe934155b5392d95c8 Mon Sep 17 00:00:00 2001 From: owenyeo Date: Thu, 24 Aug 2023 12:49:49 +0800 Subject: [PATCH 02/28] Added echoing functionality Added functionality to read User input Added ability to exit chat when User Input is "bye" Provided documentation. Removed Message subclass due to redundancy. --- src/main/java/ChatBot.java | 55 ++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/main/java/ChatBot.java b/src/main/java/ChatBot.java index 842a76475c..b88a3fd63b 100644 --- a/src/main/java/ChatBot.java +++ b/src/main/java/ChatBot.java @@ -1,38 +1,53 @@ +import java.util.Scanner; + public class ChatBot { - private String message = ""; private String name; + private static final Scanner sc = new Scanner(System.in); + private boolean hasEnded = false; - private class Message { - private String line = "____________________________________________________________\n"; - private String message = ""; - - public Message(String message) { - this.message = message; - } - - public void send() { - System.out.println(message + "\n" + line); - } - } public ChatBot(String name) { this.name = name; } public void intro() { - Message intro = new Message("____________________________________________________________\n" + + System.out.println("____________________________________________________________\n" + "Hello! I am " + this.name + ".\n" + - "What can I do for you today?"); - intro.send(); + "What can I do for you today?\n" + + "____________________________________________________________\n"); + } + + public void exitChat() { + System.out.println("Bye. Have a bad day you doofus.\n" + + "____________________________________________________________\n"); + hasEnded = true; } - public void goodbye() { - Message goodbye = new Message("Bye. Hope to see you again!"); - goodbye.send(); + public boolean isEnded() { + return this.hasEnded; + } + + public void echo(String message) { + System.out.println(message +"!\n" + + "____________________________________________________________\n"); + } + + public void readInput(String input) { + switch(input) { + case("bye"): + this.exitChat(); + break; + default: + this.echo(input); + } } public static void main(String[] args) { ChatBot chatbot = new ChatBot("Bobby Wasabi"); chatbot.intro(); - chatbot.goodbye(); + + while(!chatbot.isEnded()) { + String input = sc.nextLine(); + chatbot.readInput(input); + } } } From 6390de9ac5672c7423960d145b1ca21265680528 Mon Sep 17 00:00:00 2001 From: owenyeo Date: Thu, 24 Aug 2023 13:19:52 +0800 Subject: [PATCH 03/28] Added the functionality of listing Added functionality to add items into list --- src/main/java/ChatBot.java | 57 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/src/main/java/ChatBot.java b/src/main/java/ChatBot.java index b88a3fd63b..84cd324b29 100644 --- a/src/main/java/ChatBot.java +++ b/src/main/java/ChatBot.java @@ -1,14 +1,30 @@ import java.util.Scanner; +import java.util.ArrayList; public class ChatBot { + + //Name of the user's ChatBot. private String name; + + //Scanner used to see user input private static final Scanner sc = new Scanner(System.in); + + //Check if the chat has ended. private boolean hasEnded = false; + //Array of Strings to store in the list. + private ArrayList list = new ArrayList(); + + //Constructor that allows for the naming of your own bot. public ChatBot(String name) { this.name = name; } + + /* Command to introduce the bot. Outputs an introduction with the bot's name. + * + * @return void + */ public void intro() { System.out.println("____________________________________________________________\n" + "Hello! I am " + this.name + ".\n" + @@ -16,32 +32,67 @@ public void intro() { "____________________________________________________________\n"); } + /* To exit chat and end the session. + * + * @return void + */ public void exitChat() { System.out.println("Bye. Have a bad day you doofus.\n" + "____________________________________________________________\n"); hasEnded = true; } + /* To exit chat and end the session. + * + * @return boolean The hasEnded encapsulated in the chatbot. + */ public boolean isEnded() { return this.hasEnded; } - public void echo(String message) { - System.out.println(message +"!\n" + + /* Echos the user's input + * @param String message User input, parsed in readInput. + * @return void + */ + public void addToList(String item) { + list.add(item); + System.out.println("____________________________________________________________\n"); + System.out.println("Added: " + item + "\n" + "____________________________________________________________\n"); } + public void displayList() { + + System.out.println("____________________________________________________________\n"); + + for (int i = 0; i < list.size(); i++) { + System.out.println((i+1) + ". " + list.get(i)); + } + + System.out.println("____________________________________________________________\n"); + } + + /* Reads the input of the user, and executes the commands accordingly. + * + * @param String message User input. + * @return void + */ public void readInput(String input) { switch(input) { case("bye"): this.exitChat(); break; + case("list"): + this.displayList(); + break; default: - this.echo(input); + this.addToList(input); } } public static void main(String[] args) { + + //Test chatbot named "Bobby Wasabi". ChatBot chatbot = new ChatBot("Bobby Wasabi"); chatbot.intro(); From b9fcb6aabba3f071f5710b8bff430b9ddc29ccaa Mon Sep 17 00:00:00 2001 From: owenyeo Date: Thu, 24 Aug 2023 14:45:35 +0800 Subject: [PATCH 04/28] Added functionality to unmark and mark tasks as done --- src/main/java/ChatBot.java | 113 ++++++++++++++++++++++++++++++------- 1 file changed, 93 insertions(+), 20 deletions(-) diff --git a/src/main/java/ChatBot.java b/src/main/java/ChatBot.java index 84cd324b29..3079e7d367 100644 --- a/src/main/java/ChatBot.java +++ b/src/main/java/ChatBot.java @@ -12,8 +12,10 @@ public class ChatBot { //Check if the chat has ended. private boolean hasEnded = false; - //Array of Strings to store in the list. - private ArrayList list = new ArrayList(); + //Array of Tasks to store in the list. + private ArrayList list = new ArrayList(); + + private static final String BORDER = "____________________________________________________________\n"; //Constructor that allows for the naming of your own bot. @@ -21,28 +23,32 @@ public ChatBot(String name) { this.name = name; } - /* Command to introduce the bot. Outputs an introduction with the bot's name. + /* + * Command to introduce the bot. Outputs an introduction with the bot's name. * * @return void */ public void intro() { - System.out.println("____________________________________________________________\n" + - "Hello! I am " + this.name + ".\n" + - "What can I do for you today?\n" + - "____________________________________________________________\n"); + System.out.println(BORDER); + System.out.println("Hello! I am " + this.name + ".\n"); + System.out.println("What can I do for you today?\n"); + System.out.println(BORDER); } - /* To exit chat and end the session. + /* + * To exit chat and end the session. * * @return void */ public void exitChat() { - System.out.println("Bye. Have a bad day you doofus.\n" + - "____________________________________________________________\n"); + System.out.println(BORDER); + System.out.println("Bye. Have a bad day you doofus.\n"); + System.out.println(BORDER); hasEnded = true; } - /* To exit chat and end the session. + /* + * To exit chat and end the session. * * @return boolean The hasEnded encapsulated in the chatbot. */ @@ -50,26 +56,55 @@ public boolean isEnded() { return this.hasEnded; } - /* Echos the user's input + /* + * Adds the user input into a list. * @param String message User input, parsed in readInput. * @return void */ public void addToList(String item) { - list.add(item); - System.out.println("____________________________________________________________\n"); - System.out.println("Added: " + item + "\n" + - "____________________________________________________________\n"); + list.add(new Task(item)); + System.out.println(BORDER); + System.out.println("Added: " + item + "\n"); + System.out.println(BORDER); } + /* + * Prints the list that has been built so far. + * + * @return void + */ public void displayList() { - System.out.println("____________________________________________________________\n"); - + System.out.println(BORDER); for (int i = 0; i < list.size(); i++) { System.out.println((i+1) + ". " + list.get(i)); } - System.out.println("____________________________________________________________\n"); + System.out.println(BORDER); + } + + public void mark(int listNum) { + int index = listNum - 1; + + Task task = list.get(index); + task.done(); + System.out.println(BORDER); + System.out.println("Took you long enough. I've marked this task as done:"); + System.out.println(task.toString()); + System.out.println(BORDER); + } + + public void unmark(int listNum) { + int index = listNum - 1; + + Task task = list.get(index); + task.undone(); + + System.out.println(BORDER); + System.out.println("You incompetent child. I've unmarked the task. Please get it together."); + System.out.println(task.toString()); + System.out.println(BORDER); + } /* Reads the input of the user, and executes the commands accordingly. @@ -78,18 +113,56 @@ public void displayList() { * @return void */ public void readInput(String input) { - switch(input) { + //Split the input so that we can read integers. + String[] inputStrings = input.split(" "); + + switch(inputStrings[0]) { + case("bye"): this.exitChat(); break; + case("list"): this.displayList(); break; + + case("mark"): + mark(Integer.parseInt(inputStrings[1])); + break; + case("unmark"): + unmark(Integer.parseInt(inputStrings[1])); + break; default: this.addToList(input); } } + private class Task { + private String label; + private boolean done; + + Task(String label) { + this.label = label; + this.done = false; + } + + public void done() { + done = true; + } + + public void undone() { + done = false; + } + + @Override + public String toString() { + if (done) { + return "[X] " + label; + } + return "[ ] " + label; + } + } + public static void main(String[] args) { //Test chatbot named "Bobby Wasabi". From 61b2190d55e210f50404f6b850dabc4c1c2a1693 Mon Sep 17 00:00:00 2001 From: owenyeo Date: Thu, 24 Aug 2023 16:01:09 +0800 Subject: [PATCH 05/28] Added functionality to add ToDo, Event, and Deadline Added a Command Enum for cleaner code Added new classes Task, Event, Todo, Deadline --- src/main/java/ChatBot.java | 128 ++++++++++++++++++++++++++---------- src/main/java/Deadline.java | 15 +++++ src/main/java/Event.java | 15 +++++ src/main/java/Task.java | 47 +++++++++++++ src/main/java/ToDo.java | 11 ++++ 5 files changed, 180 insertions(+), 36 deletions(-) create mode 100644 src/main/java/Deadline.java create mode 100644 src/main/java/Event.java create mode 100644 src/main/java/Task.java create mode 100644 src/main/java/ToDo.java diff --git a/src/main/java/ChatBot.java b/src/main/java/ChatBot.java index 3079e7d367..6aa9dbb74c 100644 --- a/src/main/java/ChatBot.java +++ b/src/main/java/ChatBot.java @@ -1,6 +1,12 @@ import java.util.Scanner; import java.util.ArrayList; +/* + * A chat bot that can be renamed, and responds to inputs from users + * + * @author Owen Yeo + * Version Level-3 + */ public class ChatBot { //Name of the user's ChatBot. @@ -13,10 +19,35 @@ public class ChatBot { private boolean hasEnded = false; //Array of Tasks to store in the list. - private ArrayList list = new ArrayList(); + private ArrayList list = new ArrayList<>(); private static final String BORDER = "____________________________________________________________\n"; + private static enum Command { + BYE("bye"), + DISPLAY_LIST("list"), + MARK("mark"), + UNMARK("unmark"), + ADD_TODO("todo"), + ADD_DEADLINE("deadline"), + ADD_EVENT("event"); + + private final String input; + + private Command(String input) { + this.input = input; + } + + public static Command parseInput(String input) { + for(Command command: Command.values()) { + if (command.input.equals(input)) { + return command; + } + } + + return null; + } + } //Constructor that allows for the naming of your own bot. public ChatBot(String name) { @@ -61,10 +92,29 @@ public boolean isEnded() { * @param String message User input, parsed in readInput. * @return void */ - public void addToList(String item) { - list.add(new Task(item)); + public void addToList(String taskString, Command command) { + switch(command) { + case ADD_TODO: + list.add(new ToDo(taskString)); + break; + + case ADD_DEADLINE: + String[] deadlineParts = taskString.split("/by"); + list.add(new Deadline(deadlineParts[0].trim(), deadlineParts[1].trim())); + break; + + case ADD_EVENT: + String[] eventParts = taskString.split("/from"); + String eventLabel = eventParts[0]; + String[] eventParts2 = eventParts[1].split("/to"); + list.add(new Event(eventLabel.trim(), eventParts2[0].trim(), eventParts2[1].trim())); + break; + + } System.out.println(BORDER); - System.out.println("Added: " + item + "\n"); + System.out.println("What? You ain't finishing it. Added: \n"); + System.out.println(list.get(list.size() - 1) + "\n"); + System.out.println("Now you have an overwhelming " + list.size() + " things to do.\n"); System.out.println(BORDER); } @@ -83,6 +133,12 @@ public void displayList() { System.out.println(BORDER); } + /* + * To mark tasks as done. + * + * @param int listNum the item on the list to mark. + * @return void + */ public void mark(int listNum) { int index = listNum - 1; @@ -94,6 +150,12 @@ public void mark(int listNum) { System.out.println(BORDER); } + /* + * To unmark a list item as undone. + * + * @param int listNum item on the list to unmark. + * @return void + */ public void unmark(int listNum) { int index = listNum - 1; @@ -114,52 +176,45 @@ public void unmark(int listNum) { */ public void readInput(String input) { //Split the input so that we can read integers. - String[] inputStrings = input.split(" "); - - switch(inputStrings[0]) { + String[] inputStrings = input.split(" ", 2); + Command command = Command.parseInput(inputStrings[0]); + if (command == null) { + System.out.println(BORDER); + System.out.println("What are you saying?"); + System.out.println(BORDER); + return; + } - case("bye"): + switch(command) { + case BYE: this.exitChat(); break; - case("list"): + case DISPLAY_LIST: this.displayList(); break; - case("mark"): + case MARK: mark(Integer.parseInt(inputStrings[1])); break; - case("unmark"): + case UNMARK: unmark(Integer.parseInt(inputStrings[1])); break; - default: - this.addToList(input); - } - } - - private class Task { - private String label; - private boolean done; - Task(String label) { - this.label = label; - this.done = false; - } - - public void done() { - done = true; - } + case ADD_TODO: + addToList(inputStrings[1], command); + break; - public void undone() { - done = false; - } + case ADD_DEADLINE: + addToList(inputStrings[1], command); + break; + + case ADD_EVENT: + addToList(inputStrings[1], command); + break; - @Override - public String toString() { - if (done) { - return "[X] " + label; - } - return "[ ] " + label; + default: + System.out.println("What in the world are you saying?"); } } @@ -169,6 +224,7 @@ public static void main(String[] args) { ChatBot chatbot = new ChatBot("Bobby Wasabi"); chatbot.intro(); + //While chat has not ended, keep reading input. while(!chatbot.isEnded()) { String input = sc.nextLine(); chatbot.readInput(input); diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 0000000000..8dc6465ddf --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,15 @@ +public class Deadline extends Task{ + + private String deadline; + + Deadline(String label, String deadline) { + super(label); + this.deadline = deadline; + } + + @Override + public String toString() { + return "[D]" + super.toString() + " (by: " + deadline + ")"; + } + +} diff --git a/src/main/java/Event.java b/src/main/java/Event.java new file mode 100644 index 0000000000..018072f144 --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,15 @@ +public class Event extends Task{ + private String from; + private String to; + + Event(String label, String from, String to) { + super(label); + this.from = from; + this.to = to; + } + + @Override + public String toString() { + return "[E]" + super.toString() + "(from: " + from + " to: " + to + ")"; + } +} diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 0000000000..a3fcc149df --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,47 @@ +/* + * Abstract class representing a Task. + */ + public abstract class Task { + private String label; + private boolean isDone; + + /* + * Constructor that takes in a label + * @param String label label for the task. + */ + Task(String label) { + this.label = label; + this.isDone = false; + } + + /* + * Marks task as isDone + * + * @return void + */ + public void done() { + isDone = true; + } + + /* + * Marks task as undone + * + * @return void + */ + public void undone() { + isDone = false; + } + + /* + * String representation of task. Shows whether it is isDone or not. + * + * @return String String representation of task. + */ + @Override + public String toString() { + if (isDone) { + return "[X] " + label; + } + return "[ ] " + label; + } + } diff --git a/src/main/java/ToDo.java b/src/main/java/ToDo.java new file mode 100644 index 0000000000..652c55c81c --- /dev/null +++ b/src/main/java/ToDo.java @@ -0,0 +1,11 @@ +public class ToDo extends Task { + + ToDo(String label) { + super(label); + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } +} From 7dfe76ed136f59e8c4256810c851d76e130799af Mon Sep 17 00:00:00 2001 From: owenyeo Date: Thu, 24 Aug 2023 16:44:37 +0800 Subject: [PATCH 06/28] Added ChatBotException, InvalidCommandException, and InvalidDescrptionException. Added error handling in ChatBot. --- src/main/java/ChatBot.java | 45 ++++++++++++------- src/main/java/ChatBotException.java | 7 +++ src/main/java/Deadline.java | 7 +++ src/main/java/Event.java | 8 ++++ src/main/java/InvalidCommandException.java | 7 +++ .../java/InvalidDescriptionException.java | 7 +++ src/main/java/ToDo.java | 6 +++ 7 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ChatBotException.java create mode 100644 src/main/java/InvalidCommandException.java create mode 100644 src/main/java/InvalidDescriptionException.java diff --git a/src/main/java/ChatBot.java b/src/main/java/ChatBot.java index 6aa9dbb74c..e1d922dabd 100644 --- a/src/main/java/ChatBot.java +++ b/src/main/java/ChatBot.java @@ -21,8 +21,11 @@ public class ChatBot { //Array of Tasks to store in the list. private ArrayList list = new ArrayList<>(); + //String representing a border. private static final String BORDER = "____________________________________________________________\n"; + //Enum Command to make code cleaner and allow for the use of + //switch case statements. private static enum Command { BYE("bye"), DISPLAY_LIST("list"), @@ -88,26 +91,38 @@ public boolean isEnded() { } /* - * Adds the user input into a list. + * Adds the user input into a list, depending on the command. * @param String message User input, parsed in readInput. * @return void */ - public void addToList(String taskString, Command command) { + public void addToList(String taskString, Command command) + throws InvalidDescriptionException { switch(command) { case ADD_TODO: + if (taskString.equals(" ")) { + throw new InvalidDescriptionException("What? Where's your label? Stop this."); + } list.add(new ToDo(taskString)); break; case ADD_DEADLINE: - String[] deadlineParts = taskString.split("/by"); - list.add(new Deadline(deadlineParts[0].trim(), deadlineParts[1].trim())); + try { + String[] deadlineParts = taskString.split("/by"); + list.add(new Deadline(deadlineParts[0].trim(), deadlineParts[1].trim())); + } catch (IndexOutOfBoundsException e) { + throw new InvalidDescriptionException("Are you stupid? Can you follow instructions?"); + } break; case ADD_EVENT: - String[] eventParts = taskString.split("/from"); - String eventLabel = eventParts[0]; - String[] eventParts2 = eventParts[1].split("/to"); - list.add(new Event(eventLabel.trim(), eventParts2[0].trim(), eventParts2[1].trim())); + try { + String[] eventParts = taskString.split("/from"); + String eventLabel = eventParts[0]; + String[] eventParts2 = eventParts[1].split("/to"); + list.add(new Event(eventLabel.trim(), eventParts2[0].trim(), eventParts2[1].trim())); + } catch (IndexOutOfBoundsException e) { + throw new InvalidDescriptionException("Are you stupid? Can you follow instructions?"); + } break; } @@ -169,20 +184,20 @@ public void unmark(int listNum) { } - /* Reads the input of the user, and executes the commands accordingly. + /* + * Reads the input of the user, and executes the commands accordingly. + * If a command is unknown, return an error statement. * * @param String message User input. * @return void */ - public void readInput(String input) { + public void readInput(String input) throws + InvalidDescriptionException, InvalidCommandException { //Split the input so that we can read integers. String[] inputStrings = input.split(" ", 2); Command command = Command.parseInput(inputStrings[0]); if (command == null) { - System.out.println(BORDER); - System.out.println("What are you saying?"); - System.out.println(BORDER); - return; + throw new InvalidCommandException("What are you saying? Try again."); } switch(command) { @@ -214,7 +229,7 @@ public void readInput(String input) { break; default: - System.out.println("What in the world are you saying?"); + throw new InvalidDescriptionException("Don't be stupid, speak english."); } } diff --git a/src/main/java/ChatBotException.java b/src/main/java/ChatBotException.java new file mode 100644 index 0000000000..9f4f4e16d5 --- /dev/null +++ b/src/main/java/ChatBotException.java @@ -0,0 +1,7 @@ +public class ChatBotException extends RuntimeException { + + public ChatBotException(String e) { + super(e); + } + +} diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 8dc6465ddf..d12e3abbfe 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -1,3 +1,10 @@ +/* + * Deadline class that inherits from Task. + * + * @var deadline Representing deadline. + * + * @author Owen Yeo + */ public class Deadline extends Task{ private String deadline; diff --git a/src/main/java/Event.java b/src/main/java/Event.java index 018072f144..5a4dc3f842 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -1,3 +1,11 @@ +/* + * Event class that inherits from Task. + * + * @var from Representing start time + * @var to Representing end time + * + * @author Owen Yeo + */ public class Event extends Task{ private String from; private String to; diff --git a/src/main/java/InvalidCommandException.java b/src/main/java/InvalidCommandException.java new file mode 100644 index 0000000000..04bd56ed0f --- /dev/null +++ b/src/main/java/InvalidCommandException.java @@ -0,0 +1,7 @@ +public class InvalidCommandException extends ChatBotException{ + + public InvalidCommandException(String e) { + super(e); + } + +} diff --git a/src/main/java/InvalidDescriptionException.java b/src/main/java/InvalidDescriptionException.java new file mode 100644 index 0000000000..6d6a376507 --- /dev/null +++ b/src/main/java/InvalidDescriptionException.java @@ -0,0 +1,7 @@ +public class InvalidDescriptionException extends ChatBotException { + + public InvalidDescriptionException(String e) { + super(e); + } + +} \ No newline at end of file diff --git a/src/main/java/ToDo.java b/src/main/java/ToDo.java index 652c55c81c..d4c8bd2181 100644 --- a/src/main/java/ToDo.java +++ b/src/main/java/ToDo.java @@ -1,3 +1,9 @@ +/* + * Todo class that inherits from Task. + * + * + * @author Owen Yeo + */ public class ToDo extends Task { ToDo(String label) { From 85de7a8f6f042e289cf8a4d990108e937cdf4378 Mon Sep 17 00:00:00 2001 From: owenyeo Date: Thu, 24 Aug 2023 17:03:26 +0800 Subject: [PATCH 07/28] Added delete functionality Added InvalidIndexException for number errors in mark, unmark, and delete. --- 1 | 18 +++++++ src/main/java/ChatBot.java | 51 ++++++++++++++++--- src/main/java/ChatBotException.java | 5 ++ src/main/java/InvalidCommandException.java | 5 ++ .../java/InvalidDescriptionException.java | 6 +++ src/main/java/InvalidIndexException.java | 7 +++ 6 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 1 create mode 100644 src/main/java/InvalidIndexException.java diff --git a/1 b/1 new file mode 100644 index 0000000000..77842d2067 --- /dev/null +++ b/1 @@ -0,0 +1,18 @@ + +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +# +# On branch master +# Your branch is up to date with 'origin/master'. +# +# Changes to be committed: +# modified: src/main/java/ChatBot.java +# new file: src/main/java/ChatBotException.java +# modified: src/main/java/Deadline.java +# modified: src/main/java/Event.java +# new file: src/main/java/InvalidCommandException.java +# new file: src/main/java/InvalidDescriptionException.java +# modified: src/main/java/ToDo.java +# +Added ChatBotException, InvalidCommandException, and InvalidDescriptionException. + diff --git a/src/main/java/ChatBot.java b/src/main/java/ChatBot.java index e1d922dabd..457216292f 100644 --- a/src/main/java/ChatBot.java +++ b/src/main/java/ChatBot.java @@ -33,7 +33,8 @@ private static enum Command { UNMARK("unmark"), ADD_TODO("todo"), ADD_DEADLINE("deadline"), - ADD_EVENT("event"); + ADD_EVENT("event"), + DELETE("delete"); private final String input; @@ -92,6 +93,8 @@ public boolean isEnded() { /* * Adds the user input into a list, depending on the command. + * If description is wrong, throws an exception. + * * @param String message User input, parsed in readInput. * @return void */ @@ -184,15 +187,35 @@ public void unmark(int listNum) { } + /* + * Deletes the item off the list and prints it out with a message. + * + * @return void + */ + public void delete(int listNum) { + int index = listNum - 1; + + Task task = list.get(index); + + System.out.println(BORDER); + System.out.println("I knew you couldn't finish it. Or maybe you did. I don't care. Deleted:\n"); + System.out.println(task.toString()); + System.out.println("Now you have an overwhelming " + (list.size() - 1) + " things to do.\n"); + + System.out.println(BORDER); + + list.remove(index); + } + /* * Reads the input of the user, and executes the commands accordingly. - * If a command is unknown, return an error statement. + * If a command is unknown, throws an exception. * * @param String message User input. * @return void */ public void readInput(String input) throws - InvalidDescriptionException, InvalidCommandException { + InvalidDescriptionException, InvalidCommandException, InvalidIndexException { //Split the input so that we can read integers. String[] inputStrings = input.split(" ", 2); Command command = Command.parseInput(inputStrings[0]); @@ -210,10 +233,18 @@ public void readInput(String input) throws break; case MARK: - mark(Integer.parseInt(inputStrings[1])); + try { + mark(Integer.parseInt(inputStrings[1])); + } catch (NumberFormatException e) { + throw new InvalidIndexException("Are you stupid? That's not a number."); + } break; case UNMARK: - unmark(Integer.parseInt(inputStrings[1])); + try { + unmark(Integer.parseInt(inputStrings[1])); + } catch (NumberFormatException e) { + throw new InvalidIndexException("Are you stupid? That's not a number."); + } break; case ADD_TODO: @@ -228,8 +259,16 @@ public void readInput(String input) throws addToList(inputStrings[1], command); break; + case DELETE: + try { + delete(Integer.parseInt(inputStrings[1])); + } catch (NumberFormatException e) { + throw new InvalidIndexException("Are you stupid? That's not a number."); + } + break; + default: - throw new InvalidDescriptionException("Don't be stupid, speak english."); + throw new InvalidCommandException("Don't be stupid, speak english."); } } diff --git a/src/main/java/ChatBotException.java b/src/main/java/ChatBotException.java index 9f4f4e16d5..01ef8c90ee 100644 --- a/src/main/java/ChatBotException.java +++ b/src/main/java/ChatBotException.java @@ -1,3 +1,8 @@ +/* + * Class representing exceptions thrown by the chatbot. + * + * @author Owen Yeo + */ public class ChatBotException extends RuntimeException { public ChatBotException(String e) { diff --git a/src/main/java/InvalidCommandException.java b/src/main/java/InvalidCommandException.java index 04bd56ed0f..7412d2e038 100644 --- a/src/main/java/InvalidCommandException.java +++ b/src/main/java/InvalidCommandException.java @@ -1,3 +1,8 @@ +/* + * Exception that is thrown when an invalid command is keyed into the chatbot. + * + * @author Owen Yeo + */ public class InvalidCommandException extends ChatBotException{ public InvalidCommandException(String e) { diff --git a/src/main/java/InvalidDescriptionException.java b/src/main/java/InvalidDescriptionException.java index 6d6a376507..7fa2acce01 100644 --- a/src/main/java/InvalidDescriptionException.java +++ b/src/main/java/InvalidDescriptionException.java @@ -1,3 +1,9 @@ +/* + * Exception that is thrown when a command is correct but the description + * is wrong. + * + * @author Owen Yeo + */ public class InvalidDescriptionException extends ChatBotException { public InvalidDescriptionException(String e) { diff --git a/src/main/java/InvalidIndexException.java b/src/main/java/InvalidIndexException.java new file mode 100644 index 0000000000..a244b5fceb --- /dev/null +++ b/src/main/java/InvalidIndexException.java @@ -0,0 +1,7 @@ +public class InvalidIndexException extends ChatBotException { + + public InvalidIndexException(String e) { + super(e); + } + +} From 4fe58216de9bbcc76259411f3863519d9b2329ec Mon Sep 17 00:00:00 2001 From: owenyeo Date: Thu, 31 Aug 2023 13:06:28 +0800 Subject: [PATCH 08/28] Added InvalidIndexException Added saveTasks to save data on a text file. Added loadTasks for potential future use. --- src/main/java/ChatBot.java | 92 +++++++++++++++++++----- src/main/java/InvalidIndexException.java | 6 +- src/main/java/data/chatBot.txt | 1 + 3 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 src/main/java/data/chatBot.txt diff --git a/src/main/java/ChatBot.java b/src/main/java/ChatBot.java index 457216292f..7f15212142 100644 --- a/src/main/java/ChatBot.java +++ b/src/main/java/ChatBot.java @@ -1,4 +1,10 @@ import java.util.Scanner; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; import java.util.ArrayList; /* @@ -24,6 +30,9 @@ public class ChatBot { //String representing a border. private static final String BORDER = "____________________________________________________________\n"; + //String representing the file directory. + private static final String DATA_SAVE_PATH = "src/main/java/data/chatBot.txt"; + //Enum Command to make code cleaner and allow for the use of //switch case statements. private static enum Command { @@ -106,12 +115,14 @@ public void addToList(String taskString, Command command) throw new InvalidDescriptionException("What? Where's your label? Stop this."); } list.add(new ToDo(taskString)); + saveTasks(); break; case ADD_DEADLINE: try { String[] deadlineParts = taskString.split("/by"); list.add(new Deadline(deadlineParts[0].trim(), deadlineParts[1].trim())); + saveTasks(); } catch (IndexOutOfBoundsException e) { throw new InvalidDescriptionException("Are you stupid? Can you follow instructions?"); } @@ -123,6 +134,7 @@ public void addToList(String taskString, Command command) String eventLabel = eventParts[0]; String[] eventParts2 = eventParts[1].split("/to"); list.add(new Event(eventLabel.trim(), eventParts2[0].trim(), eventParts2[1].trim())); + saveTasks(); } catch (IndexOutOfBoundsException e) { throw new InvalidDescriptionException("Are you stupid? Can you follow instructions?"); } @@ -166,14 +178,15 @@ public void mark(int listNum) { System.out.println("Took you long enough. I've marked this task as done:"); System.out.println(task.toString()); System.out.println(BORDER); + + saveTasks(); } - /* - * To unmark a list item as undone. - * - * @param int listNum item on the list to unmark. - * @return void - */ + /** + * To unmark a list item as undone. + * + * @param listNum Index of the item on the list to unmark. + */ public void unmark(int listNum) { int index = listNum - 1; @@ -184,13 +197,15 @@ public void unmark(int listNum) { System.out.println("You incompetent child. I've unmarked the task. Please get it together."); System.out.println(task.toString()); System.out.println(BORDER); + + saveTasks(); } - /* - * Deletes the item off the list and prints it out with a message. + /** + * Deletes the item off the list and prints it out with a message. * - * @return void + * @param listNum Index of the item of the list to delete. */ public void delete(int listNum) { int index = listNum - 1; @@ -201,19 +216,54 @@ public void delete(int listNum) { System.out.println("I knew you couldn't finish it. Or maybe you did. I don't care. Deleted:\n"); System.out.println(task.toString()); System.out.println("Now you have an overwhelming " + (list.size() - 1) + " things to do.\n"); - System.out.println(BORDER); list.remove(index); + saveTasks(); } - /* - * Reads the input of the user, and executes the commands accordingly. - * If a command is unknown, throws an exception. - * - * @param String message User input. - * @return void - */ + private void loadTasks() { + try { + File file = new File(DATA_SAVE_PATH); + + if (file.exists()) { + BufferedReader br = new BufferedReader(new FileReader(file)); + String line; + + while ((line = br.readLine()) != null) { + + } + } + } catch (IOException e) { + System.out.println("Error loading tasks: " + e.getMessage()); + } + } + + private void saveTasks() { + try { + BufferedWriter bw = new BufferedWriter(new FileWriter + (DATA_SAVE_PATH, false)); + + for (int i = 0; i < list.size(); i++) { + bw.write(list.get(i).toString()); + bw.newLine(); + } + bw.close(); + + } catch (IOException e) { + System.out.println("Error writing file:" + e.getMessage()); + } + } + + /** + * Reads the input of the user, and executes the commands accordingly. + * If a command is unknown, throws an exception. + * + * @param input + * @throws InvalidDescriptionException + * @throws InvalidCommandException + * @throws InvalidIndexException + */ public void readInput(String input) throws InvalidDescriptionException, InvalidCommandException, InvalidIndexException { //Split the input so that we can read integers. @@ -237,6 +287,8 @@ public void readInput(String input) throws mark(Integer.parseInt(inputStrings[1])); } catch (NumberFormatException e) { throw new InvalidIndexException("Are you stupid? That's not a number."); + } catch (IndexOutOfBoundsException e) { + throw new InvalidIndexException("That's not even a number on the list, idiot."); } break; case UNMARK: @@ -244,6 +296,8 @@ public void readInput(String input) throws unmark(Integer.parseInt(inputStrings[1])); } catch (NumberFormatException e) { throw new InvalidIndexException("Are you stupid? That's not a number."); + } catch (IndexOutOfBoundsException e) { + throw new InvalidIndexException("That's not even a number on the list, idiot."); } break; @@ -264,7 +318,9 @@ public void readInput(String input) throws delete(Integer.parseInt(inputStrings[1])); } catch (NumberFormatException e) { throw new InvalidIndexException("Are you stupid? That's not a number."); - } + } catch (IndexOutOfBoundsException e) { + throw new InvalidIndexException("That's not even a number on the list, idiot."); + } break; default: diff --git a/src/main/java/InvalidIndexException.java b/src/main/java/InvalidIndexException.java index a244b5fceb..d339c9c3ca 100644 --- a/src/main/java/InvalidIndexException.java +++ b/src/main/java/InvalidIndexException.java @@ -1,7 +1,11 @@ +/* + * Exception thrown when the index given for mark, unmark, or delete is wrong. + * + * @author Owen Yeo + */ public class InvalidIndexException extends ChatBotException { public InvalidIndexException(String e) { super(e); } - } diff --git a/src/main/java/data/chatBot.txt b/src/main/java/data/chatBot.txt new file mode 100644 index 0000000000..b687c61684 --- /dev/null +++ b/src/main/java/data/chatBot.txt @@ -0,0 +1 @@ +[E][ ] yourmom(from: today to: tomorrow) From 3201c1952093818854059a2e688edec9443204b3 Mon Sep 17 00:00:00 2001 From: owenyeo Date: Thu, 31 Aug 2023 14:11:25 +0800 Subject: [PATCH 09/28] Improved on documentation Improved formatting of toSaveString in ChatBot.java Fixed bug where type of task is not correctly shown. Removed loadFile due to redundancy. --- src/main/java/ChatBot.java | 58 +++++++++------------- src/main/java/Deadline.java | 19 +++++++- src/main/java/Event.java | 22 ++++++++- src/main/java/Task.java | 89 +++++++++++++++++++--------------- src/main/java/ToDo.java | 18 ++++++- src/main/java/data/chatBot.txt | 3 +- 6 files changed, 130 insertions(+), 79 deletions(-) diff --git a/src/main/java/ChatBot.java b/src/main/java/ChatBot.java index 7f15212142..04da037211 100644 --- a/src/main/java/ChatBot.java +++ b/src/main/java/ChatBot.java @@ -1,17 +1,14 @@ import java.util.Scanner; -import java.io.BufferedReader; import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; -/* +/** * A chat bot that can be renamed, and responds to inputs from users * * @author Owen Yeo - * Version Level-3 + * Version Level-7 */ public class ChatBot { @@ -51,6 +48,14 @@ private Command(String input) { this.input = input; } + /** + * Parses the input and returns the appropriate command if the input is + * valid. + * + * @param input User's input + * @return Command that tells what the chatbot should do. + * @return null if the input in invalid + */ public static Command parseInput(String input) { for(Command command: Command.values()) { if (command.input.equals(input)) { @@ -67,19 +72,19 @@ public ChatBot(String name) { this.name = name; } - /* + /** * Command to introduce the bot. Outputs an introduction with the bot's name. * - * @return void */ public void intro() { System.out.println(BORDER); System.out.println("Hello! I am " + this.name + ".\n"); System.out.println("What can I do for you today?\n"); System.out.println(BORDER); + saveTasks(); } - /* + /** * To exit chat and end the session. * * @return void @@ -91,7 +96,7 @@ public void exitChat() { hasEnded = true; } - /* + /** * To exit chat and end the session. * * @return boolean The hasEnded encapsulated in the chatbot. @@ -100,12 +105,13 @@ public boolean isEnded() { return this.hasEnded; } - /* + /** * Adds the user input into a list, depending on the command. * If description is wrong, throws an exception. * - * @param String message User input, parsed in readInput. - * @return void + * @param taskString + * @param command + * @throws InvalidDescriptionException */ public void addToList(String taskString, Command command) throws InvalidDescriptionException { @@ -148,10 +154,9 @@ public void addToList(String taskString, Command command) System.out.println(BORDER); } - /* + /** * Prints the list that has been built so far. * - * @return void */ public void displayList() { @@ -163,11 +168,10 @@ public void displayList() { System.out.println(BORDER); } - /* + /** * To mark tasks as done. * * @param int listNum the item on the list to mark. - * @return void */ public void mark(int listNum) { int index = listNum - 1; @@ -222,30 +226,16 @@ public void delete(int listNum) { saveTasks(); } - private void loadTasks() { - try { - File file = new File(DATA_SAVE_PATH); - - if (file.exists()) { - BufferedReader br = new BufferedReader(new FileReader(file)); - String line; - - while ((line = br.readLine()) != null) { - - } - } - } catch (IOException e) { - System.out.println("Error loading tasks: " + e.getMessage()); - } - } - + /** + * Saves the tasks into a text file. + */ private void saveTasks() { try { BufferedWriter bw = new BufferedWriter(new FileWriter (DATA_SAVE_PATH, false)); for (int i = 0; i < list.size(); i++) { - bw.write(list.get(i).toString()); + bw.write(list.get(i).toSaveString()); bw.newLine(); } bw.close(); diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index d12e3abbfe..9b34f540b5 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -1,4 +1,4 @@ -/* +/** * Deadline class that inherits from Task. * * @var deadline Representing deadline. @@ -9,11 +9,28 @@ public class Deadline extends Task{ private String deadline; + /** + * Constructor for a deadline object. + * + * @param label Descriptor for the task with deadline + * @param deadline Deadline + */ Deadline(String label, String deadline) { super(label); this.deadline = deadline; } + /** + * + */ + @Override + public String toSaveString() { + return "D " + super.toSaveString() + " | " + deadline; + } + + /** + * + */ @Override public String toString() { return "[D]" + super.toString() + " (by: " + deadline + ")"; diff --git a/src/main/java/Event.java b/src/main/java/Event.java index 5a4dc3f842..981f5f3280 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -1,4 +1,4 @@ -/* +/** * Event class that inherits from Task. * * @var from Representing start time @@ -10,14 +10,32 @@ public class Event extends Task{ private String from; private String to; + /** + * Constructor for an event object. + * + * @param label Descriptor for the event + * @param from Start time + * @param to End time + */ Event(String label, String from, String to) { super(label); this.from = from; this.to = to; } + /** + * {@inheritDoc} + */ + @Override + public String toSaveString() { + return "E " + super.toSaveString() + " | " + from + " - " + to; + } + + /** + * {@inheritDoc} + */ @Override public String toString() { - return "[E]" + super.toString() + "(from: " + from + " to: " + to + ")"; + return "[E]" + super.toString() + " (from: " + from + " to: " + to + ")"; } } diff --git a/src/main/java/Task.java b/src/main/java/Task.java index a3fcc149df..a7ff37a9c9 100644 --- a/src/main/java/Task.java +++ b/src/main/java/Task.java @@ -1,47 +1,56 @@ -/* - * Abstract class representing a Task. +/** + * Abstract class representing a task object. + * + * @author Owen Yeo + * @version Level-7 + */ +public abstract class Task { + private String label; + private boolean isDone; + + /** + * Constructor for a task object + * + * @param label */ - public abstract class Task { - private String label; - private boolean isDone; + Task(String label) { + this.label = label; + this.isDone = false; + } - /* - * Constructor that takes in a label - * @param String label label for the task. - */ - Task(String label) { - this.label = label; - this.isDone = false; - } + /** + * sets isDone to true. + */ + public void done() { + isDone = true; + } - /* - * Marks task as isDone - * - * @return void - */ - public void done() { - isDone = true; - } + /** + * sets isDone to false. + */ + public void undone() { + isDone = false; + } - /* - * Marks task as undone - * - * @return void - */ - public void undone() { - isDone = false; - } + /** + * Returns a string to be saved in a file, representing a task. + * + * @return String representing a task. + */ + public String toSaveString() { + return "| " + (isDone ? 1 : 0) + " | " + label; + } - /* - * String representation of task. Shows whether it is isDone or not. - * - * @return String String representation of task. - */ - @Override - public String toString() { - if (isDone) { - return "[X] " + label; - } - return "[ ] " + label; + /** + * {@inheritDoc} + * + * Represents whether a task has been done or not. + */ + @Override + public String toString() { + if (isDone) { + return "[X] " + label; } + return "[ ] " + label; } +} diff --git a/src/main/java/ToDo.java b/src/main/java/ToDo.java index d4c8bd2181..e81acd256f 100644 --- a/src/main/java/ToDo.java +++ b/src/main/java/ToDo.java @@ -1,4 +1,4 @@ -/* +/** * Todo class that inherits from Task. * * @@ -6,10 +6,26 @@ */ public class ToDo extends Task { + /** + * Constructor for a ToDo instance. + * + * @param label descriptor of the tas + */ ToDo(String label) { super(label); } + /** + * {@inheritDoc} + */ + @Override + public String toSaveString() { + return "T " + super.toSaveString(); + } + + /** + * {@inheritDoc} + */ @Override public String toString() { return "[T]" + super.toString(); diff --git a/src/main/java/data/chatBot.txt b/src/main/java/data/chatBot.txt index b687c61684..1c530d2cf5 100644 --- a/src/main/java/data/chatBot.txt +++ b/src/main/java/data/chatBot.txt @@ -1 +1,2 @@ -[E][ ] yourmom(from: today to: tomorrow) +D | 0 | die | today +E | 1 | dog giving birth | 10pm - 2am From 2c3b2455a594dae4e0061a093ae8ebc943652b0a Mon Sep 17 00:00:00 2001 From: owenyeo Date: Thu, 31 Aug 2023 17:41:07 +0800 Subject: [PATCH 10/28] Added functionality to store DateTime objects Added functionality to reformat DateTime when printing. --- src/main/java/Deadline.java | 18 ++++++++++----- src/main/java/Event.java | 22 ++++++++++++++----- src/main/java/InvalidCommandException.java | 2 +- .../java/InvalidDescriptionException.java | 2 +- src/main/java/InvalidIndexException.java | 2 +- src/main/java/ToDo.java | 2 +- src/main/java/data/chatBot.txt | 3 +-- 7 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 9b34f540b5..953e1d18d9 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -1,3 +1,8 @@ +import java.sql.Date; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + /** * Deadline class that inherits from Task. * @@ -7,17 +12,18 @@ */ public class Deadline extends Task{ - private String deadline; + private LocalDateTime deadline; /** * Constructor for a deadline object. * * @param label Descriptor for the task with deadline - * @param deadline Deadline + * @param deadline String representing deadline */ Deadline(String label, String deadline) { super(label); - this.deadline = deadline; + this.deadline = LocalDateTime.parse(deadline, DateTimeFormatter + .ofPattern("yyyy-MM-dd HHmm")); } /** @@ -25,7 +31,8 @@ public class Deadline extends Task{ */ @Override public String toSaveString() { - return "D " + super.toSaveString() + " | " + deadline; + return "D " + super.toSaveString() + " | " + deadline + .format(DateTimeFormatter.ofPattern("MMM dd YYYY ha")); } /** @@ -33,7 +40,8 @@ public String toSaveString() { */ @Override public String toString() { - return "[D]" + super.toString() + " (by: " + deadline + ")"; + return "[D]" + super.toString() + " (by: " + deadline + .format(DateTimeFormatter.ofPattern("MMM dd YYYY ha")) + ")"; } } diff --git a/src/main/java/Event.java b/src/main/java/Event.java index 981f5f3280..d76a57e314 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -1,3 +1,7 @@ +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + /** * Event class that inherits from Task. * @@ -7,8 +11,8 @@ * @author Owen Yeo */ public class Event extends Task{ - private String from; - private String to; + private LocalDateTime from; + private LocalDateTime to; /** * Constructor for an event object. @@ -19,8 +23,10 @@ public class Event extends Task{ */ Event(String label, String from, String to) { super(label); - this.from = from; - this.to = to; + this.from = LocalDateTime.parse(from, DateTimeFormatter + .ofPattern("yyyy-MM-dd HHmm")); + this.to = LocalDateTime.parse(to, DateTimeFormatter + .ofPattern("yyyy-MM-dd HHmm")); } /** @@ -28,7 +34,9 @@ public class Event extends Task{ */ @Override public String toSaveString() { - return "E " + super.toSaveString() + " | " + from + " - " + to; + return "E " + super.toSaveString() + " | " + from + .format(DateTimeFormatter.ofPattern("MMM dd YYYY ha")) + " - " + to + .format(DateTimeFormatter.ofPattern("MMM dd YYYY ha")); } /** @@ -36,6 +44,8 @@ public String toSaveString() { */ @Override public String toString() { - return "[E]" + super.toString() + " (from: " + from + " to: " + to + ")"; + return "[E]" + super.toString() + " (from: " + from + .format(DateTimeFormatter.ofPattern("MMM dd YYYY ha")) + " to: " + to + .format(DateTimeFormatter.ofPattern("MMM dd YYYY ha")) + ")"; } } diff --git a/src/main/java/InvalidCommandException.java b/src/main/java/InvalidCommandException.java index 7412d2e038..3720897567 100644 --- a/src/main/java/InvalidCommandException.java +++ b/src/main/java/InvalidCommandException.java @@ -1,4 +1,4 @@ -/* +/** * Exception that is thrown when an invalid command is keyed into the chatbot. * * @author Owen Yeo diff --git a/src/main/java/InvalidDescriptionException.java b/src/main/java/InvalidDescriptionException.java index 7fa2acce01..d38982ed51 100644 --- a/src/main/java/InvalidDescriptionException.java +++ b/src/main/java/InvalidDescriptionException.java @@ -1,4 +1,4 @@ -/* +/** * Exception that is thrown when a command is correct but the description * is wrong. * diff --git a/src/main/java/InvalidIndexException.java b/src/main/java/InvalidIndexException.java index d339c9c3ca..37786c5531 100644 --- a/src/main/java/InvalidIndexException.java +++ b/src/main/java/InvalidIndexException.java @@ -1,4 +1,4 @@ -/* +/** * Exception thrown when the index given for mark, unmark, or delete is wrong. * * @author Owen Yeo diff --git a/src/main/java/ToDo.java b/src/main/java/ToDo.java index e81acd256f..4538c43477 100644 --- a/src/main/java/ToDo.java +++ b/src/main/java/ToDo.java @@ -1,5 +1,5 @@ /** - * Todo class that inherits from Task. + * Todo class that is a task. * * * @author Owen Yeo diff --git a/src/main/java/data/chatBot.txt b/src/main/java/data/chatBot.txt index 1c530d2cf5..b9e583cebc 100644 --- a/src/main/java/data/chatBot.txt +++ b/src/main/java/data/chatBot.txt @@ -1,2 +1 @@ -D | 0 | die | today -E | 1 | dog giving birth | 10pm - 2am +E | 0 | penis | May 05 1900 9AM - Oct 25 1957 8PM From 88f8909ee2a0685220eab8ef03d4c47c803e47dc Mon Sep 17 00:00:00 2001 From: owenyeo Date: Fri, 1 Sep 2023 19:31:22 +0800 Subject: [PATCH 11/28] Refactored code into different files for more OOP Added functionality for Storage to load previous lists. Added SaveFileNotFound exception --- src/main/java/AddDeadline.java | 19 ++ src/main/java/AddEvent.java | 19 ++ src/main/java/AddToDo.java | 19 ++ src/main/java/Bye.java | 16 ++ src/main/java/ChatBot.java | 332 ++-------------------------- src/main/java/Command.java | 15 ++ src/main/java/CommandType.java | 34 +++ src/main/java/Deadline.java | 9 +- src/main/java/DeleteItem.java | 26 +++ src/main/java/DisplayList.java | 18 ++ src/main/java/Event.java | 13 +- src/main/java/MarkItem.java | 25 +++ src/main/java/Parser.java | 41 ++++ src/main/java/SaveFileNotFound.java | 7 + src/main/java/Storage.java | 78 +++++++ src/main/java/TaskList.java | 89 ++++++++ src/main/java/TaskType.java | 11 + src/main/java/Ui.java | 53 +++++ src/main/java/UnmarkItem.java | 25 +++ src/main/java/data/chatBot.txt | 1 - 20 files changed, 531 insertions(+), 319 deletions(-) create mode 100644 src/main/java/AddDeadline.java create mode 100644 src/main/java/AddEvent.java create mode 100644 src/main/java/AddToDo.java create mode 100644 src/main/java/Bye.java create mode 100644 src/main/java/Command.java create mode 100644 src/main/java/CommandType.java create mode 100644 src/main/java/DeleteItem.java create mode 100644 src/main/java/DisplayList.java create mode 100644 src/main/java/MarkItem.java create mode 100644 src/main/java/Parser.java create mode 100644 src/main/java/SaveFileNotFound.java create mode 100644 src/main/java/Storage.java create mode 100644 src/main/java/TaskList.java create mode 100644 src/main/java/TaskType.java create mode 100644 src/main/java/Ui.java create mode 100644 src/main/java/UnmarkItem.java diff --git a/src/main/java/AddDeadline.java b/src/main/java/AddDeadline.java new file mode 100644 index 0000000000..1636d87d82 --- /dev/null +++ b/src/main/java/AddDeadline.java @@ -0,0 +1,19 @@ +public class AddDeadline extends Command{ + + public AddDeadline(String input) { + super(input); + } + + @Override + public void execute(TaskList tasks, Storage storage, Ui ui) { + tasks.addTask(input, TaskType.DEADLINE); + + ui.print(new String[] { + "What? You ain't finishing it. Added: ", + tasks.getTask(tasks.getLength()).toString(), + "Now you have an overwhelming " + tasks.getLength() + " things to do." + }); + + storage.saveTasks(tasks); + } +} diff --git a/src/main/java/AddEvent.java b/src/main/java/AddEvent.java new file mode 100644 index 0000000000..011e12208b --- /dev/null +++ b/src/main/java/AddEvent.java @@ -0,0 +1,19 @@ +public class AddEvent extends Command { + + public AddEvent(String input) { + super(input); + } + + @Override + public void execute(TaskList tasks, Storage storage, Ui ui) { + tasks.addTask(input, TaskType.EVENT); + + ui.print(new String[] { + "What? You ain't finishing it. Added: ", + tasks.getTask(tasks.getLength()).toString(), + "Now you have an overwhelming " + tasks.getLength() + " things to do." + }); + + storage.saveTasks(tasks); + } +} diff --git a/src/main/java/AddToDo.java b/src/main/java/AddToDo.java new file mode 100644 index 0000000000..4e23455c84 --- /dev/null +++ b/src/main/java/AddToDo.java @@ -0,0 +1,19 @@ +public class AddToDo extends Command{ + + public AddToDo(String input) { + super(input); + } + + @Override + public void execute(TaskList tasks, Storage storage, Ui ui) { + tasks.addTask(input, TaskType.TODO); + + ui.print(new String[] { + "What? You ain't finishing it. Added: ", + tasks.getTask(tasks.getLength()).toString(), + "Now you have an overwhelming " + tasks.getLength() + " things to do." + }); + + storage.saveTasks(tasks); + } +} diff --git a/src/main/java/Bye.java b/src/main/java/Bye.java new file mode 100644 index 0000000000..82a73cc277 --- /dev/null +++ b/src/main/java/Bye.java @@ -0,0 +1,16 @@ +public class Bye extends Command { + + public Bye(String input) { + super(input); + } + + @Override + public void execute(TaskList tasks, Storage storage, Ui ui) { + ui.bye(); + } + + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/ChatBot.java b/src/main/java/ChatBot.java index 04da037211..9b193c4de9 100644 --- a/src/main/java/ChatBot.java +++ b/src/main/java/ChatBot.java @@ -1,8 +1,3 @@ -import java.util.Scanner; -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; /** * A chat bot that can be renamed, and responds to inputs from users @@ -12,322 +7,41 @@ */ public class ChatBot { - //Name of the user's ChatBot. - private String name; + private Ui ui; - //Scanner used to see user input - private static final Scanner sc = new Scanner(System.in); + private Storage storage; - //Check if the chat has ended. - private boolean hasEnded = false; - - //Array of Tasks to store in the list. - private ArrayList list = new ArrayList<>(); - - //String representing a border. - private static final String BORDER = "____________________________________________________________\n"; - - //String representing the file directory. - private static final String DATA_SAVE_PATH = "src/main/java/data/chatBot.txt"; - - //Enum Command to make code cleaner and allow for the use of - //switch case statements. - private static enum Command { - BYE("bye"), - DISPLAY_LIST("list"), - MARK("mark"), - UNMARK("unmark"), - ADD_TODO("todo"), - ADD_DEADLINE("deadline"), - ADD_EVENT("event"), - DELETE("delete"); - - private final String input; - - private Command(String input) { - this.input = input; - } - - /** - * Parses the input and returns the appropriate command if the input is - * valid. - * - * @param input User's input - * @return Command that tells what the chatbot should do. - * @return null if the input in invalid - */ - public static Command parseInput(String input) { - for(Command command: Command.values()) { - if (command.input.equals(input)) { - return command; - } - } - - return null; - } - } + private TaskList tasks; //Constructor that allows for the naming of your own bot. - public ChatBot(String name) { - this.name = name; - } - - /** - * Command to introduce the bot. Outputs an introduction with the bot's name. - * - */ - public void intro() { - System.out.println(BORDER); - System.out.println("Hello! I am " + this.name + ".\n"); - System.out.println("What can I do for you today?\n"); - System.out.println(BORDER); - saveTasks(); - } - - /** - * To exit chat and end the session. - * - * @return void - */ - public void exitChat() { - System.out.println(BORDER); - System.out.println("Bye. Have a bad day you doofus.\n"); - System.out.println(BORDER); - hasEnded = true; - } - - /** - * To exit chat and end the session. - * - * @return boolean The hasEnded encapsulated in the chatbot. - */ - public boolean isEnded() { - return this.hasEnded; - } - - /** - * Adds the user input into a list, depending on the command. - * If description is wrong, throws an exception. - * - * @param taskString - * @param command - * @throws InvalidDescriptionException - */ - public void addToList(String taskString, Command command) - throws InvalidDescriptionException { - switch(command) { - case ADD_TODO: - if (taskString.equals(" ")) { - throw new InvalidDescriptionException("What? Where's your label? Stop this."); - } - list.add(new ToDo(taskString)); - saveTasks(); - break; - - case ADD_DEADLINE: - try { - String[] deadlineParts = taskString.split("/by"); - list.add(new Deadline(deadlineParts[0].trim(), deadlineParts[1].trim())); - saveTasks(); - } catch (IndexOutOfBoundsException e) { - throw new InvalidDescriptionException("Are you stupid? Can you follow instructions?"); - } - break; - - case ADD_EVENT: - try { - String[] eventParts = taskString.split("/from"); - String eventLabel = eventParts[0]; - String[] eventParts2 = eventParts[1].split("/to"); - list.add(new Event(eventLabel.trim(), eventParts2[0].trim(), eventParts2[1].trim())); - saveTasks(); - } catch (IndexOutOfBoundsException e) { - throw new InvalidDescriptionException("Are you stupid? Can you follow instructions?"); - } - break; - - } - System.out.println(BORDER); - System.out.println("What? You ain't finishing it. Added: \n"); - System.out.println(list.get(list.size() - 1) + "\n"); - System.out.println("Now you have an overwhelming " + list.size() + " things to do.\n"); - System.out.println(BORDER); - } - - /** - * Prints the list that has been built so far. - * - */ - public void displayList() { - - System.out.println(BORDER); - for (int i = 0; i < list.size(); i++) { - System.out.println((i+1) + ". " + list.get(i)); + public ChatBot(String path) { + ui = new Ui(); + storage = new Storage(path); + try { + tasks = new TaskList(storage.load()); + } catch (ChatBotException e) { + ui.showError(e); + tasks = new TaskList(); } - - System.out.println(BORDER); - } - - /** - * To mark tasks as done. - * - * @param int listNum the item on the list to mark. - */ - public void mark(int listNum) { - int index = listNum - 1; - - Task task = list.get(index); - task.done(); - System.out.println(BORDER); - System.out.println("Took you long enough. I've marked this task as done:"); - System.out.println(task.toString()); - System.out.println(BORDER); - - saveTasks(); - } - - /** - * To unmark a list item as undone. - * - * @param listNum Index of the item on the list to unmark. - */ - public void unmark(int listNum) { - int index = listNum - 1; - - Task task = list.get(index); - task.undone(); - - System.out.println(BORDER); - System.out.println("You incompetent child. I've unmarked the task. Please get it together."); - System.out.println(task.toString()); - System.out.println(BORDER); - - saveTasks(); - } - /** - * Deletes the item off the list and prints it out with a message. - * - * @param listNum Index of the item of the list to delete. - */ - public void delete(int listNum) { - int index = listNum - 1; - - Task task = list.get(index); - - System.out.println(BORDER); - System.out.println("I knew you couldn't finish it. Or maybe you did. I don't care. Deleted:\n"); - System.out.println(task.toString()); - System.out.println("Now you have an overwhelming " + (list.size() - 1) + " things to do.\n"); - System.out.println(BORDER); - - list.remove(index); - saveTasks(); - } - - /** - * Saves the tasks into a text file. - */ - private void saveTasks() { + public void run() { try { - BufferedWriter bw = new BufferedWriter(new FileWriter - (DATA_SAVE_PATH, false)); - - for (int i = 0; i < list.size(); i++) { - bw.write(list.get(i).toSaveString()); - bw.newLine(); + ui.intro(); + boolean hasEnded = false; + while (!hasEnded) { + String fullCommand = ui.readInput(); + Command c = Parser.parse(fullCommand); + c.execute(tasks, storage, ui); + hasEnded = c.isExit(); } - bw.close(); - - } catch (IOException e) { - System.out.println("Error writing file:" + e.getMessage()); - } - } - - /** - * Reads the input of the user, and executes the commands accordingly. - * If a command is unknown, throws an exception. - * - * @param input - * @throws InvalidDescriptionException - * @throws InvalidCommandException - * @throws InvalidIndexException - */ - public void readInput(String input) throws - InvalidDescriptionException, InvalidCommandException, InvalidIndexException { - //Split the input so that we can read integers. - String[] inputStrings = input.split(" ", 2); - Command command = Command.parseInput(inputStrings[0]); - if (command == null) { - throw new InvalidCommandException("What are you saying? Try again."); - } - - switch(command) { - case BYE: - this.exitChat(); - break; - - case DISPLAY_LIST: - this.displayList(); - break; - - case MARK: - try { - mark(Integer.parseInt(inputStrings[1])); - } catch (NumberFormatException e) { - throw new InvalidIndexException("Are you stupid? That's not a number."); - } catch (IndexOutOfBoundsException e) { - throw new InvalidIndexException("That's not even a number on the list, idiot."); - } - break; - case UNMARK: - try { - unmark(Integer.parseInt(inputStrings[1])); - } catch (NumberFormatException e) { - throw new InvalidIndexException("Are you stupid? That's not a number."); - } catch (IndexOutOfBoundsException e) { - throw new InvalidIndexException("That's not even a number on the list, idiot."); - } - break; - - case ADD_TODO: - addToList(inputStrings[1], command); - break; - - case ADD_DEADLINE: - addToList(inputStrings[1], command); - break; - - case ADD_EVENT: - addToList(inputStrings[1], command); - break; - - case DELETE: - try { - delete(Integer.parseInt(inputStrings[1])); - } catch (NumberFormatException e) { - throw new InvalidIndexException("Are you stupid? That's not a number."); - } catch (IndexOutOfBoundsException e) { - throw new InvalidIndexException("That's not even a number on the list, idiot."); - } - break; - - default: - throw new InvalidCommandException("Don't be stupid, speak english."); + } catch (ChatBotException e) { + ui.showError(e); } } public static void main(String[] args) { - - //Test chatbot named "Bobby Wasabi". - ChatBot chatbot = new ChatBot("Bobby Wasabi"); - chatbot.intro(); - - //While chat has not ended, keep reading input. - while(!chatbot.isEnded()) { - String input = sc.nextLine(); - chatbot.readInput(input); - } + //Test chatbot + new ChatBot("src/main/java/data/chatBot.txt").run(); } } diff --git a/src/main/java/Command.java b/src/main/java/Command.java new file mode 100644 index 0000000000..56751d8358 --- /dev/null +++ b/src/main/java/Command.java @@ -0,0 +1,15 @@ + + +public abstract class Command { + + protected String input; + + public Command(String input) { + this.input = input; + } + + public void execute(TaskList tasks, Storage storage, Ui ui) {} + + public boolean isExit() {return false;} + +} diff --git a/src/main/java/CommandType.java b/src/main/java/CommandType.java new file mode 100644 index 0000000000..2e5216c978 --- /dev/null +++ b/src/main/java/CommandType.java @@ -0,0 +1,34 @@ +public enum CommandType { + BYE("bye"), + DISPLAY_LIST("list"), + MARK("mark"), + UNMARK("unmark"), + ADD_TODO("todo"), + ADD_DEADLINE("deadline"), + ADD_EVENT("event"), + DELETE("delete"); + + private final String input; + + private CommandType(String input) { + this.input = input; + } + + /** + * Parses the input and returns the appropriate command if the input is + * valid. + * + * @param input User's input + * @return Command that tells what the chatbot should do. + * @return null if the input in invalid + */ + public static CommandType parseInput(String input) { + for(CommandType command: CommandType.values()) { + if (command.input.equals(input)) { + return command; + } + } + + return null; + } +} diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 953e1d18d9..a81c99a474 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -1,5 +1,4 @@ -import java.sql.Date; -import java.time.LocalDate; + import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -14,6 +13,8 @@ public class Deadline extends Task{ private LocalDateTime deadline; + private String originalString; + /** * Constructor for a deadline object. * @@ -24,6 +25,7 @@ public class Deadline extends Task{ super(label); this.deadline = LocalDateTime.parse(deadline, DateTimeFormatter .ofPattern("yyyy-MM-dd HHmm")); + this.originalString = deadline; } /** @@ -31,8 +33,7 @@ public class Deadline extends Task{ */ @Override public String toSaveString() { - return "D " + super.toSaveString() + " | " + deadline - .format(DateTimeFormatter.ofPattern("MMM dd YYYY ha")); + return "D " + super.toSaveString() + " | " + originalString; } /** diff --git a/src/main/java/DeleteItem.java b/src/main/java/DeleteItem.java new file mode 100644 index 0000000000..44999238af --- /dev/null +++ b/src/main/java/DeleteItem.java @@ -0,0 +1,26 @@ +public class DeleteItem extends Command{ + + public DeleteItem(String input) { + super(input); + } + + @Override + public void execute(TaskList tasks, Storage storage, Ui ui) + throws InvalidIndexException { + try { + int index = Integer.parseInt(input); + Task deletedTask = tasks.getTask(index); + ui.print(new String[] {"I knew you couldn't finish it. Or maybe you did. I don't care. Deleted:", + deletedTask.toString(), + "Now you have an overwhelming "+ (tasks.getLength() - 1) + + " things to do"}); + tasks.delete(Integer.parseInt(input)); + + storage.saveTasks(tasks); + } catch (NumberFormatException e) { + throw new InvalidIndexException("Are you stupid? That's not a number."); + } catch (IndexOutOfBoundsException e) { + throw new InvalidIndexException("That's not even a number on the list, idiot."); + } + } +} diff --git a/src/main/java/DisplayList.java b/src/main/java/DisplayList.java new file mode 100644 index 0000000000..2c4e9d8690 --- /dev/null +++ b/src/main/java/DisplayList.java @@ -0,0 +1,18 @@ +public class DisplayList extends Command{ + + public DisplayList(String input) { + super(input); + } + + @Override + public void execute(TaskList tasks, Storage storage, Ui ui) { + String[] taskStrings = new String[100] ; + + for (int i = 1; i < tasks.getLength() + 1; i++) { + String listString = (i) + ". " + tasks.getTask(i).toString(); + taskStrings[i - 1] = listString; + } + + ui.print(taskStrings); + } +} diff --git a/src/main/java/Event.java b/src/main/java/Event.java index d76a57e314..139d9d27e3 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -1,4 +1,4 @@ -import java.time.LocalDate; + import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -13,6 +13,8 @@ public class Event extends Task{ private LocalDateTime from; private LocalDateTime to; + private String fromString; + private String toString; /** * Constructor for an event object. @@ -25,8 +27,10 @@ public class Event extends Task{ super(label); this.from = LocalDateTime.parse(from, DateTimeFormatter .ofPattern("yyyy-MM-dd HHmm")); + this.fromString = from; this.to = LocalDateTime.parse(to, DateTimeFormatter .ofPattern("yyyy-MM-dd HHmm")); + this.toString = to; } /** @@ -34,9 +38,8 @@ public class Event extends Task{ */ @Override public String toSaveString() { - return "E " + super.toSaveString() + " | " + from - .format(DateTimeFormatter.ofPattern("MMM dd YYYY ha")) + " - " + to - .format(DateTimeFormatter.ofPattern("MMM dd YYYY ha")); + return "E " + super.toSaveString() + " | " + fromString + " | " + + toString; } /** @@ -45,7 +48,7 @@ public String toSaveString() { @Override public String toString() { return "[E]" + super.toString() + " (from: " + from - .format(DateTimeFormatter.ofPattern("MMM dd YYYY ha")) + " to: " + to + .format(DateTimeFormatter.ofPattern("MMM dd YYYY ha")) + " | to: " + to .format(DateTimeFormatter.ofPattern("MMM dd YYYY ha")) + ")"; } } diff --git a/src/main/java/MarkItem.java b/src/main/java/MarkItem.java new file mode 100644 index 0000000000..4d53b631d1 --- /dev/null +++ b/src/main/java/MarkItem.java @@ -0,0 +1,25 @@ +public class MarkItem extends Command{ + + public MarkItem(String input) { + super(input); + } + + @Override + public void execute(TaskList tasks, Storage storage, Ui ui) + throws InvalidIndexException { + try { + int index = Integer.parseInt(input); + tasks.mark(index); + ui.print(new String[] { + "Impossible! You must have cheated. Horrible.", + tasks.getTask(index).toString() + }); + + storage.saveTasks(tasks); + } catch (NumberFormatException e) { + throw new InvalidIndexException("Are you stupid? That's not a number."); + } catch (IndexOutOfBoundsException e) { + throw new InvalidIndexException("That's not even a number on the list, idiot."); + } + } +} diff --git a/src/main/java/Parser.java b/src/main/java/Parser.java new file mode 100644 index 0000000000..8a31959efb --- /dev/null +++ b/src/main/java/Parser.java @@ -0,0 +1,41 @@ +public class Parser { + + public static Command parse(String input) throws + InvalidDescriptionException, InvalidCommandException, InvalidIndexException { + //Split the input so that we can read integers. + String[] inputStrings = input.split(" ", 2); + CommandType command = CommandType.parseInput(inputStrings[0]); + if (command == null) { + throw new InvalidCommandException("What are you saying? Try again."); + } + + switch(command) { + case BYE: + return new Bye(""); + + case DISPLAY_LIST: + return new DisplayList(""); + + case MARK: + return new MarkItem(inputStrings[1]); + + case UNMARK: + return new UnmarkItem(inputStrings[1]); + + case ADD_TODO: + return new AddToDo(inputStrings[1]); + + case ADD_DEADLINE: + return new AddDeadline(inputStrings[1]); + + case ADD_EVENT: + return new AddEvent(inputStrings[1]); + + case DELETE: + return new DeleteItem(inputStrings[1]); + + default: + throw new InvalidCommandException("Don't be stupid, speak english."); + } + } +} diff --git a/src/main/java/SaveFileNotFound.java b/src/main/java/SaveFileNotFound.java new file mode 100644 index 0000000000..6ef0b6ba31 --- /dev/null +++ b/src/main/java/SaveFileNotFound.java @@ -0,0 +1,7 @@ +public class SaveFileNotFound extends ChatBotException{ + + public SaveFileNotFound(String e) { + super(e); + } + +} diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java new file mode 100644 index 0000000000..7fa6e27dd7 --- /dev/null +++ b/src/main/java/Storage.java @@ -0,0 +1,78 @@ +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; + +public class Storage { + private String path; + private TaskList storedTasks; + + public Storage(String path) { + this.path = path; + } + + public void saveTasks(TaskList tasks) { + try { + BufferedWriter bw = new BufferedWriter(new FileWriter + (path, false)); + + for (int i = 1; i < tasks.getLength() + 1; i++) { + bw.write(tasks.getTask(i).toSaveString()); + bw.newLine(); + } + bw.close(); + + } catch (IOException e) { + System.out.println("Error writing file:" + e.getMessage()); + } + } + + public ArrayList load() { + try { + ArrayList loadedList = new ArrayList<>(); + File file = new File(path); + + if (file.exists()) { + BufferedReader reader = new BufferedReader(new FileReader(file)); + String line; + while ((line = reader.readLine()) != null) { + String[] parts = line.split("\\|"); + System.out.println(parts[1].trim()); + switch (parts[0].trim()) { + case("T"): + loadedList.add(new ToDo(parts[2].trim())); + if (Integer.parseInt(parts[1].trim()) == 1) { + loadedList.get(loadedList.size() - 1).done(); + } + break; + + case("D"): + loadedList.add(new Deadline(parts[2].trim(), parts[3].trim())); + if (Integer.parseInt(parts[1].trim()) == 1) { + Task task = loadedList.get(loadedList.size() - 1); + task.done(); + } + break; + + case("E"): + loadedList.add(new Event(parts[2].trim(), parts[3].trim(), parts[4].trim())); + if (Integer.parseInt(parts[1].trim()) == 1) { + loadedList.get(loadedList.size() - 1).done(); + } + break; + } + } + reader.close(); + + return loadedList; + } + } catch (IOException e) { + throw new SaveFileNotFound("You forgot your file path, idiot."); + } + return null; + } + +} diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java new file mode 100644 index 0000000000..d930125ac6 --- /dev/null +++ b/src/main/java/TaskList.java @@ -0,0 +1,89 @@ +import java.util.ArrayList; + +public class TaskList { + + private ArrayList tasks = new ArrayList<>(); + + public TaskList(ArrayList tasks) { + this.tasks = tasks; + } + + public TaskList() { + this.tasks = new ArrayList<>(); + } + + public void addTask(String taskString, TaskType taskType) + throws InvalidDescriptionException { + switch(taskType) { + case TODO: + if (taskString.equals(" ")) { + throw new InvalidDescriptionException( + "What? Where's your label? Stop this."); + } + tasks.add(new ToDo(taskString)); + break; + + case DEADLINE: + try { + String[] deadlineParts = taskString.split("/by"); + tasks.add(new Deadline(deadlineParts[0].trim(), deadlineParts[1].trim())); + } catch (IndexOutOfBoundsException e) { + throw new InvalidDescriptionException( + "Are you stupid? Can you follow instructions?"); + } + break; + + case EVENT: + try { + String[] eventParts = taskString.split("/from"); + String eventLabel = eventParts[0]; + String[] eventParts2 = eventParts[1].split("/to"); + tasks.add(new Event(eventLabel.trim(), eventParts2[0].trim(), eventParts2[1].trim())); + } catch (IndexOutOfBoundsException e) { + throw new InvalidDescriptionException( + "Are you stupid? Can you follow instructions?"); + } + break; + } + } + + /** + * Deletes the item off the list. + * + * @param listNum Index of the item of the list to delete. + */ + public void delete(int listNum) { + int index = listNum - 1; + tasks.remove(index); + } + + /** + * To mark tasks as done. + * + * @param int listNum the item on the list to mark. + */ + public void mark(int listNum) throws InvalidIndexException { + Task task = tasks.get(listNum - 1); + task.done(); + } + + /** + * To unmark a list item as undone. + * + * @param listNum Index of the item on the list to unmark. + */ + public void unmark(int listNum) throws InvalidIndexException { + Task task = tasks.get(listNum - 1); + task.undone(); + } + + public Task getTask(int listNum) { + int index = listNum - 1; + return tasks.get(index); + } + + public int getLength() { + return tasks.size(); + } + +} diff --git a/src/main/java/TaskType.java b/src/main/java/TaskType.java new file mode 100644 index 0000000000..9d1f64e7e0 --- /dev/null +++ b/src/main/java/TaskType.java @@ -0,0 +1,11 @@ +public enum TaskType { + TODO("T"), + DEADLINE("D"), + EVENT("E"); + + private String input; + + private TaskType(String input) { + this.input = input; + } +} diff --git a/src/main/java/Ui.java b/src/main/java/Ui.java new file mode 100644 index 0000000000..2f260c74cf --- /dev/null +++ b/src/main/java/Ui.java @@ -0,0 +1,53 @@ +import java.util.Scanner; + +/** + * + */ +public class Ui { + + //Scanner used to see user input + private static final Scanner sc = new Scanner(System.in); + + //String representing a border. + private static final String BORDER = "____________________________________________________________\n"; + + /** + * Prints the inputs out for the user. + * + * @param inputs + */ + public void print(String[] inputs) { + System.out.println(BORDER); + for (int i = 0; i < inputs.length; i++) { + if (inputs[i] != null) { + System.out.println(inputs[i]); + } + } + System.out.println(BORDER); + } + + + /** + * Prints an introduction. + */ + public void intro() { + print(new String[] {"Hello! I am Bobby Wasabi", + "What can I do for you today?"}); + } + + /** + * Prints a goodbye message. + */ + public void bye() { + print(new String[] {"Bye. Have a bad day you doofus."}); + } + + public String readInput() { + return sc.nextLine(); + } + + public void showError(Exception e) { + System.out.println("Error! " + e.getMessage()); + } + +} diff --git a/src/main/java/UnmarkItem.java b/src/main/java/UnmarkItem.java new file mode 100644 index 0000000000..82230da88d --- /dev/null +++ b/src/main/java/UnmarkItem.java @@ -0,0 +1,25 @@ +public class UnmarkItem extends Command{ + + public UnmarkItem(String input) { + super(input); + } + + @Override + public void execute(TaskList tasks, Storage storage, Ui ui) + throws InvalidIndexException { + try { + int index = Integer.parseInt(input); + tasks.unmark(index); + ui.print(new String[] { + "You incompetent child. I've unmarked the task. Please get it together.", + tasks.getTask(index).toString() + }); + + storage.saveTasks(tasks); + } catch (NumberFormatException e) { + throw new InvalidIndexException("Are you stupid? That's not a number."); + } catch (IndexOutOfBoundsException e) { + throw new InvalidIndexException("That's not even a number on the list, idiot."); + } + } +} diff --git a/src/main/java/data/chatBot.txt b/src/main/java/data/chatBot.txt index b9e583cebc..e69de29bb2 100644 --- a/src/main/java/data/chatBot.txt +++ b/src/main/java/data/chatBot.txt @@ -1 +0,0 @@ -E | 0 | penis | May 05 1900 9AM - Oct 25 1957 8PM From 2ed0b5e3d95c7f6c017d81a1705cc64cf1057ac6 Mon Sep 17 00:00:00 2001 From: owenyeo Date: Fri, 1 Sep 2023 22:15:59 +0800 Subject: [PATCH 12/28] Reorganised code into packages. Added documentation for all new classes. Fixed bug when marking tasks as done and reloading it in the next session. --- src/main/java/Bye.java | 16 --------- src/main/java/ChatBot.java | 6 ++++ src/main/java/Command.java | 15 -------- .../{ => chatbot/commands}/AddDeadline.java | 14 +++++++- .../java/{ => chatbot/commands}/AddEvent.java | 12 +++++++ .../java/{ => chatbot/commands}/AddToDo.java | 17 +++++++++ src/main/java/chatbot/commands/Bye.java | 35 ++++++++++++++++++ src/main/java/chatbot/commands/Command.java | 36 +++++++++++++++++++ .../{ => chatbot/commands}/CommandType.java | 5 +++ .../{ => chatbot/commands}/DeleteItem.java | 17 ++++++++- .../{ => chatbot/commands}/DisplayList.java | 16 +++++++++ .../java/{ => chatbot/commands}/MarkItem.java | 17 +++++++++ .../{ => chatbot/commands}/UnmarkItem.java | 17 +++++++++ .../exceptions}/ChatBotException.java | 2 ++ .../exceptions}/InvalidCommandException.java | 1 + .../InvalidDescriptionException.java | 1 + .../exceptions}/InvalidIndexException.java | 1 + .../exceptions}/SaveFileNotFound.java | 3 ++ .../java/{ => chatbot/parser}/Parser.java | 34 +++++++++++++++++- .../java/{ => chatbot/storage}/Storage.java | 26 ++++++++++++-- .../java/{ => chatbot/task}/Deadline.java | 3 +- src/main/java/{ => chatbot/task}/Event.java | 3 +- src/main/java/{ => chatbot/task}/Task.java | 2 ++ .../java/{ => chatbot/task}/TaskList.java | 27 ++++++++++++++ .../java/{ => chatbot/task}/TaskType.java | 5 +++ src/main/java/{ => chatbot/task}/ToDo.java | 5 ++- src/main/java/{ => chatbot/ui}/Ui.java | 15 ++++++++ src/main/java/data/chatBot.txt | 2 ++ 28 files changed, 314 insertions(+), 39 deletions(-) delete mode 100644 src/main/java/Bye.java delete mode 100644 src/main/java/Command.java rename src/main/java/{ => chatbot/commands}/AddDeadline.java (65%) rename src/main/java/{ => chatbot/commands}/AddEvent.java (71%) rename src/main/java/{ => chatbot/commands}/AddToDo.java (65%) create mode 100644 src/main/java/chatbot/commands/Bye.java create mode 100644 src/main/java/chatbot/commands/Command.java rename src/main/java/{ => chatbot/commands}/CommandType.java (90%) rename src/main/java/{ => chatbot/commands}/DeleteItem.java (77%) rename src/main/java/{ => chatbot/commands}/DisplayList.java (62%) rename src/main/java/{ => chatbot/commands}/MarkItem.java (73%) rename src/main/java/{ => chatbot/commands}/UnmarkItem.java (72%) rename src/main/java/{ => chatbot/exceptions}/ChatBotException.java (88%) rename src/main/java/{ => chatbot/exceptions}/InvalidCommandException.java (90%) rename src/main/java/{ => chatbot/exceptions}/InvalidDescriptionException.java (90%) rename src/main/java/{ => chatbot/exceptions}/InvalidIndexException.java (89%) rename src/main/java/{ => chatbot/exceptions}/SaveFileNotFound.java (63%) rename src/main/java/{ => chatbot/parser}/Parser.java (56%) rename src/main/java/{ => chatbot/storage}/Storage.java (82%) rename src/main/java/{ => chatbot/task}/Deadline.java (93%) rename src/main/java/{ => chatbot/task}/Event.java (94%) rename src/main/java/{ => chatbot/task}/Task.java (97%) rename src/main/java/{ => chatbot/task}/TaskList.java (79%) rename src/main/java/{ => chatbot/task}/TaskType.java (67%) rename src/main/java/{ => chatbot/task}/ToDo.java (90%) rename src/main/java/{ => chatbot/ui}/Ui.java (77%) diff --git a/src/main/java/Bye.java b/src/main/java/Bye.java deleted file mode 100644 index 82a73cc277..0000000000 --- a/src/main/java/Bye.java +++ /dev/null @@ -1,16 +0,0 @@ -public class Bye extends Command { - - public Bye(String input) { - super(input); - } - - @Override - public void execute(TaskList tasks, Storage storage, Ui ui) { - ui.bye(); - } - - @Override - public boolean isExit() { - return true; - } -} diff --git a/src/main/java/ChatBot.java b/src/main/java/ChatBot.java index 9b193c4de9..53796ab52a 100644 --- a/src/main/java/ChatBot.java +++ b/src/main/java/ChatBot.java @@ -1,3 +1,9 @@ +import chatbot.exceptions.ChatBotException; +import chatbot.parser.Parser; +import chatbot.storage.Storage; +import chatbot.task.TaskList; +import chatbot.ui.Ui; +import chatbot.commands.Command; /** * A chat bot that can be renamed, and responds to inputs from users diff --git a/src/main/java/Command.java b/src/main/java/Command.java deleted file mode 100644 index 56751d8358..0000000000 --- a/src/main/java/Command.java +++ /dev/null @@ -1,15 +0,0 @@ - - -public abstract class Command { - - protected String input; - - public Command(String input) { - this.input = input; - } - - public void execute(TaskList tasks, Storage storage, Ui ui) {} - - public boolean isExit() {return false;} - -} diff --git a/src/main/java/AddDeadline.java b/src/main/java/chatbot/commands/AddDeadline.java similarity index 65% rename from src/main/java/AddDeadline.java rename to src/main/java/chatbot/commands/AddDeadline.java index 1636d87d82..04131480d6 100644 --- a/src/main/java/AddDeadline.java +++ b/src/main/java/chatbot/commands/AddDeadline.java @@ -1,4 +1,16 @@ -public class AddDeadline extends Command{ +package chatbot.commands; + +import chatbot.storage.Storage; +import chatbot.task.TaskList; +import chatbot.task.TaskType; +import chatbot.ui.Ui; + +/** + * Adds a deadline task to the TaskList + * + * @author Owen Yeo + */ +public class AddDeadline extends Command { public AddDeadline(String input) { super(input); diff --git a/src/main/java/AddEvent.java b/src/main/java/chatbot/commands/AddEvent.java similarity index 71% rename from src/main/java/AddEvent.java rename to src/main/java/chatbot/commands/AddEvent.java index 011e12208b..b77bdf30f0 100644 --- a/src/main/java/AddEvent.java +++ b/src/main/java/chatbot/commands/AddEvent.java @@ -1,3 +1,15 @@ +package chatbot.commands; + +import chatbot.storage.Storage; +import chatbot.task.TaskList; +import chatbot.task.TaskType; +import chatbot.ui.Ui; + +/** + * Add an event task to the TaskList + * + * @author Owen Yeo + */ public class AddEvent extends Command { public AddEvent(String input) { diff --git a/src/main/java/AddToDo.java b/src/main/java/chatbot/commands/AddToDo.java similarity index 65% rename from src/main/java/AddToDo.java rename to src/main/java/chatbot/commands/AddToDo.java index 4e23455c84..3cb0c22d46 100644 --- a/src/main/java/AddToDo.java +++ b/src/main/java/chatbot/commands/AddToDo.java @@ -1,9 +1,26 @@ +package chatbot.commands; + +import chatbot.storage.Storage; +import chatbot.task.TaskList; +import chatbot.task.TaskType; +import chatbot.ui.Ui; + +/** + * Adds a To Do task to the TaskList + * + * @author Owen Yeo + */ public class AddToDo extends Command{ public AddToDo(String input) { super(input); } + /** + * {@inheritDoc} + * + * + */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) { tasks.addTask(input, TaskType.TODO); diff --git a/src/main/java/chatbot/commands/Bye.java b/src/main/java/chatbot/commands/Bye.java new file mode 100644 index 0000000000..e7108593af --- /dev/null +++ b/src/main/java/chatbot/commands/Bye.java @@ -0,0 +1,35 @@ +package chatbot.commands; + +import chatbot.storage.Storage; +import chatbot.task.TaskList; +import chatbot.ui.Ui; + +/** + * Command that exits the chatbot and forces it close. + */ +public class Bye extends Command { + + public Bye(String input) { + super(input); + } + + /** + * {@inheritDoc} + * + * Asks UI to print a goodbye message. + */ + @Override + public void execute(TaskList tasks, Storage storage, Ui ui) { + ui.bye(); + } + + /** + * {@inheritDoc} + * + * @return true. + */ + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/chatbot/commands/Command.java b/src/main/java/chatbot/commands/Command.java new file mode 100644 index 0000000000..090065fb56 --- /dev/null +++ b/src/main/java/chatbot/commands/Command.java @@ -0,0 +1,36 @@ +package chatbot.commands; + +import chatbot.storage.Storage; +import chatbot.task.TaskList; +import chatbot.ui.Ui; + +/** + * Abstract class representing a command. + * + * @author Owen Yeo + */ +public abstract class Command { + + protected String input; + + public Command(String input) { + this.input = input; + } + + /** + * Modifies the TaskList, Storage, and UI of the ChatBot. + * + * @param tasks TaskList + * @param storage Storage + * @param ui UI + */ + public void execute(TaskList tasks, Storage storage, Ui ui) {} + + /** + * Checks if the current command will cause an exit. + * + * @return false + */ + public boolean isExit() {return false;} + +} diff --git a/src/main/java/CommandType.java b/src/main/java/chatbot/commands/CommandType.java similarity index 90% rename from src/main/java/CommandType.java rename to src/main/java/chatbot/commands/CommandType.java index 2e5216c978..d4f285830e 100644 --- a/src/main/java/CommandType.java +++ b/src/main/java/chatbot/commands/CommandType.java @@ -1,3 +1,8 @@ +package chatbot.commands; + +/** + * enum that represents the different command types. + */ public enum CommandType { BYE("bye"), DISPLAY_LIST("list"), diff --git a/src/main/java/DeleteItem.java b/src/main/java/chatbot/commands/DeleteItem.java similarity index 77% rename from src/main/java/DeleteItem.java rename to src/main/java/chatbot/commands/DeleteItem.java index 44999238af..f36cd9e7c5 100644 --- a/src/main/java/DeleteItem.java +++ b/src/main/java/chatbot/commands/DeleteItem.java @@ -1,9 +1,24 @@ +package chatbot.commands; + +import chatbot.exceptions.InvalidIndexException; +import chatbot.storage.Storage; +import chatbot.task.Task; +import chatbot.task.TaskList; +import chatbot.ui.Ui; + +/** + * + */ public class DeleteItem extends Command{ public DeleteItem(String input) { super(input); } - + /** + * {@inheritDoc} + * + * Deletes item from the TaskList. + */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) throws InvalidIndexException { diff --git a/src/main/java/DisplayList.java b/src/main/java/chatbot/commands/DisplayList.java similarity index 62% rename from src/main/java/DisplayList.java rename to src/main/java/chatbot/commands/DisplayList.java index 2c4e9d8690..2dee140166 100644 --- a/src/main/java/DisplayList.java +++ b/src/main/java/chatbot/commands/DisplayList.java @@ -1,9 +1,25 @@ +package chatbot.commands; + +import chatbot.storage.Storage; +import chatbot.task.TaskList; +import chatbot.ui.Ui; + +/** + * Displays the existing list when executed + * + * @author Owen Yeo + */ public class DisplayList extends Command{ public DisplayList(String input) { super(input); } + /** + * {@inheritDoc} + * + * Displays the current list when executed. + */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) { String[] taskStrings = new String[100] ; diff --git a/src/main/java/MarkItem.java b/src/main/java/chatbot/commands/MarkItem.java similarity index 73% rename from src/main/java/MarkItem.java rename to src/main/java/chatbot/commands/MarkItem.java index 4d53b631d1..c095c123e4 100644 --- a/src/main/java/MarkItem.java +++ b/src/main/java/chatbot/commands/MarkItem.java @@ -1,9 +1,26 @@ +package chatbot.commands; + +import chatbot.exceptions.InvalidIndexException; +import chatbot.storage.Storage; +import chatbot.task.TaskList; +import chatbot.ui.Ui; + +/** + * Marks item as completed in the list + * + * @author Owen Yeo + */ public class MarkItem extends Command{ public MarkItem(String input) { super(input); } + /** + * {@inheritDoc} + * + * + */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) throws InvalidIndexException { diff --git a/src/main/java/UnmarkItem.java b/src/main/java/chatbot/commands/UnmarkItem.java similarity index 72% rename from src/main/java/UnmarkItem.java rename to src/main/java/chatbot/commands/UnmarkItem.java index 82230da88d..27883a6a8c 100644 --- a/src/main/java/UnmarkItem.java +++ b/src/main/java/chatbot/commands/UnmarkItem.java @@ -1,9 +1,26 @@ +package chatbot.commands; + +import chatbot.exceptions.InvalidIndexException; +import chatbot.storage.Storage; +import chatbot.task.TaskList; +import chatbot.ui.Ui; + +/** + * Unmarks a completed item as undone. + * + * @author Owen Yeo + */ public class UnmarkItem extends Command{ public UnmarkItem(String input) { super(input); } + /** + * {@inheritDoc} + * + * unmarks a task as undone. + */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) throws InvalidIndexException { diff --git a/src/main/java/ChatBotException.java b/src/main/java/chatbot/exceptions/ChatBotException.java similarity index 88% rename from src/main/java/ChatBotException.java rename to src/main/java/chatbot/exceptions/ChatBotException.java index 01ef8c90ee..94d12a2f2d 100644 --- a/src/main/java/ChatBotException.java +++ b/src/main/java/chatbot/exceptions/ChatBotException.java @@ -1,3 +1,5 @@ +package chatbot.exceptions; + /* * Class representing exceptions thrown by the chatbot. * diff --git a/src/main/java/InvalidCommandException.java b/src/main/java/chatbot/exceptions/InvalidCommandException.java similarity index 90% rename from src/main/java/InvalidCommandException.java rename to src/main/java/chatbot/exceptions/InvalidCommandException.java index 3720897567..d27e4c5e84 100644 --- a/src/main/java/InvalidCommandException.java +++ b/src/main/java/chatbot/exceptions/InvalidCommandException.java @@ -1,3 +1,4 @@ +package chatbot.exceptions; /** * Exception that is thrown when an invalid command is keyed into the chatbot. * diff --git a/src/main/java/InvalidDescriptionException.java b/src/main/java/chatbot/exceptions/InvalidDescriptionException.java similarity index 90% rename from src/main/java/InvalidDescriptionException.java rename to src/main/java/chatbot/exceptions/InvalidDescriptionException.java index d38982ed51..aa1407a194 100644 --- a/src/main/java/InvalidDescriptionException.java +++ b/src/main/java/chatbot/exceptions/InvalidDescriptionException.java @@ -1,3 +1,4 @@ +package chatbot.exceptions; /** * Exception that is thrown when a command is correct but the description * is wrong. diff --git a/src/main/java/InvalidIndexException.java b/src/main/java/chatbot/exceptions/InvalidIndexException.java similarity index 89% rename from src/main/java/InvalidIndexException.java rename to src/main/java/chatbot/exceptions/InvalidIndexException.java index 37786c5531..645032796b 100644 --- a/src/main/java/InvalidIndexException.java +++ b/src/main/java/chatbot/exceptions/InvalidIndexException.java @@ -1,3 +1,4 @@ +package chatbot.exceptions; /** * Exception thrown when the index given for mark, unmark, or delete is wrong. * diff --git a/src/main/java/SaveFileNotFound.java b/src/main/java/chatbot/exceptions/SaveFileNotFound.java similarity index 63% rename from src/main/java/SaveFileNotFound.java rename to src/main/java/chatbot/exceptions/SaveFileNotFound.java index 6ef0b6ba31..f5fdb2eb18 100644 --- a/src/main/java/SaveFileNotFound.java +++ b/src/main/java/chatbot/exceptions/SaveFileNotFound.java @@ -1,3 +1,6 @@ +package chatbot.exceptions; +import chatbot.exceptions.ChatBotException; + public class SaveFileNotFound extends ChatBotException{ public SaveFileNotFound(String e) { diff --git a/src/main/java/Parser.java b/src/main/java/chatbot/parser/Parser.java similarity index 56% rename from src/main/java/Parser.java rename to src/main/java/chatbot/parser/Parser.java index 8a31959efb..e438192df8 100644 --- a/src/main/java/Parser.java +++ b/src/main/java/chatbot/parser/Parser.java @@ -1,5 +1,37 @@ +package chatbot.parser; + +import chatbot.commands.Command; +import chatbot.commands.AddDeadline; +import chatbot.commands.AddEvent; +import chatbot.commands.AddToDo; +import chatbot.commands.Bye; +import chatbot.commands.CommandType; +import chatbot.commands.DeleteItem; +import chatbot.commands.DisplayList; +import chatbot.commands.MarkItem; +import chatbot.commands.UnmarkItem; + +import chatbot.exceptions.InvalidCommandException; +import chatbot.exceptions.InvalidDescriptionException; +import chatbot.exceptions.InvalidIndexException; + +/** + * Parser that parses the inputs from the user and inteprets it. + * + * @author Owen Yeo + */ public class Parser { + /** + * Parses the input from the user and outputs a command for + * the chatbot to execute. + * + * @param input String input from user + * @return Command depending on input. + * @throws InvalidDescriptionException + * @throws InvalidCommandException + * @throws InvalidIndexException + */ public static Command parse(String input) throws InvalidDescriptionException, InvalidCommandException, InvalidIndexException { //Split the input so that we can read integers. @@ -18,7 +50,7 @@ public static Command parse(String input) throws case MARK: return new MarkItem(inputStrings[1]); - + case UNMARK: return new UnmarkItem(inputStrings[1]); diff --git a/src/main/java/Storage.java b/src/main/java/chatbot/storage/Storage.java similarity index 82% rename from src/main/java/Storage.java rename to src/main/java/chatbot/storage/Storage.java index 7fa6e27dd7..13af428670 100644 --- a/src/main/java/Storage.java +++ b/src/main/java/chatbot/storage/Storage.java @@ -1,3 +1,5 @@ +package chatbot.storage; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -6,14 +8,30 @@ import java.io.IOException; import java.util.ArrayList; +import chatbot.exceptions.SaveFileNotFound; +import chatbot.task.ToDo; +import chatbot.task.TaskList; +import chatbot.task.Event; +import chatbot.task.Task; +import chatbot.task.Deadline; + +/** + * Storage class that handles storing and loading saved lists. + * + * @author Owen Yeo + */ public class Storage { private String path; - private TaskList storedTasks; public Storage(String path) { this.path = path; } + /** + * Saves tasks into a text file. + * + * @param tasks TaskList + */ public void saveTasks(TaskList tasks) { try { BufferedWriter bw = new BufferedWriter(new FileWriter @@ -30,6 +48,11 @@ public void saveTasks(TaskList tasks) { } } + /** + * Loads the existing list found on the storage file into an ArrayList. + * + * @return ArrayList containing all the tasks parsed from the file. + */ public ArrayList load() { try { ArrayList loadedList = new ArrayList<>(); @@ -40,7 +63,6 @@ public ArrayList load() { String line; while ((line = reader.readLine()) != null) { String[] parts = line.split("\\|"); - System.out.println(parts[1].trim()); switch (parts[0].trim()) { case("T"): loadedList.add(new ToDo(parts[2].trim())); diff --git a/src/main/java/Deadline.java b/src/main/java/chatbot/task/Deadline.java similarity index 93% rename from src/main/java/Deadline.java rename to src/main/java/chatbot/task/Deadline.java index a81c99a474..9949ca8278 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/chatbot/task/Deadline.java @@ -1,3 +1,4 @@ +package chatbot.task; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -21,7 +22,7 @@ public class Deadline extends Task{ * @param label Descriptor for the task with deadline * @param deadline String representing deadline */ - Deadline(String label, String deadline) { + public Deadline(String label, String deadline) { super(label); this.deadline = LocalDateTime.parse(deadline, DateTimeFormatter .ofPattern("yyyy-MM-dd HHmm")); diff --git a/src/main/java/Event.java b/src/main/java/chatbot/task/Event.java similarity index 94% rename from src/main/java/Event.java rename to src/main/java/chatbot/task/Event.java index 139d9d27e3..e44f090d62 100644 --- a/src/main/java/Event.java +++ b/src/main/java/chatbot/task/Event.java @@ -1,3 +1,4 @@ +package chatbot.task; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -23,7 +24,7 @@ public class Event extends Task{ * @param from Start time * @param to End time */ - Event(String label, String from, String to) { + public Event(String label, String from, String to) { super(label); this.from = LocalDateTime.parse(from, DateTimeFormatter .ofPattern("yyyy-MM-dd HHmm")); diff --git a/src/main/java/Task.java b/src/main/java/chatbot/task/Task.java similarity index 97% rename from src/main/java/Task.java rename to src/main/java/chatbot/task/Task.java index a7ff37a9c9..e3f9918a05 100644 --- a/src/main/java/Task.java +++ b/src/main/java/chatbot/task/Task.java @@ -1,3 +1,5 @@ +package chatbot.task; + /** * Abstract class representing a task object. * diff --git a/src/main/java/TaskList.java b/src/main/java/chatbot/task/TaskList.java similarity index 79% rename from src/main/java/TaskList.java rename to src/main/java/chatbot/task/TaskList.java index d930125ac6..5a3c7015af 100644 --- a/src/main/java/TaskList.java +++ b/src/main/java/chatbot/task/TaskList.java @@ -1,17 +1,44 @@ +package chatbot.task; + import java.util.ArrayList; +import chatbot.exceptions.InvalidDescriptionException; +import chatbot.exceptions.InvalidIndexException; + +/** + * Representation of a list that takes in tasks, + * and is able to modify their states + * + * @author Owen Yeo + */ public class TaskList { + //ArrayList to store the tasks private ArrayList tasks = new ArrayList<>(); + /** + * Constructor for an isntance of TaskList + * + * @param tasks ArrayList for tasks + */ public TaskList(ArrayList tasks) { this.tasks = tasks; } + /** + * Empty constructor for a TaskList + */ public TaskList() { this.tasks = new ArrayList<>(); } + /** + * Adds a task to the TaskList + * + * @param taskString representing the descriptor for the task + * @param taskType type of the task getting added + * @throws InvalidDescriptionException + */ public void addTask(String taskString, TaskType taskType) throws InvalidDescriptionException { switch(taskType) { diff --git a/src/main/java/TaskType.java b/src/main/java/chatbot/task/TaskType.java similarity index 67% rename from src/main/java/TaskType.java rename to src/main/java/chatbot/task/TaskType.java index 9d1f64e7e0..1c5198a767 100644 --- a/src/main/java/TaskType.java +++ b/src/main/java/chatbot/task/TaskType.java @@ -1,3 +1,8 @@ +package chatbot.task; + +/** + * Enum representing the different types of tasks. + */ public enum TaskType { TODO("T"), DEADLINE("D"), diff --git a/src/main/java/ToDo.java b/src/main/java/chatbot/task/ToDo.java similarity index 90% rename from src/main/java/ToDo.java rename to src/main/java/chatbot/task/ToDo.java index 4538c43477..31ddac5e9f 100644 --- a/src/main/java/ToDo.java +++ b/src/main/java/chatbot/task/ToDo.java @@ -1,3 +1,6 @@ +package chatbot.task; + + /** * Todo class that is a task. * @@ -11,7 +14,7 @@ public class ToDo extends Task { * * @param label descriptor of the tas */ - ToDo(String label) { + public ToDo(String label) { super(label); } diff --git a/src/main/java/Ui.java b/src/main/java/chatbot/ui/Ui.java similarity index 77% rename from src/main/java/Ui.java rename to src/main/java/chatbot/ui/Ui.java index 2f260c74cf..633caf7323 100644 --- a/src/main/java/Ui.java +++ b/src/main/java/chatbot/ui/Ui.java @@ -1,7 +1,12 @@ +package chatbot.ui; + import java.util.Scanner; /** + * Class representing a UI which reads inputs from users + * and prints outputs depending on the command or error. * + * @author Owen Yeo */ public class Ui { @@ -42,10 +47,20 @@ public void bye() { print(new String[] {"Bye. Have a bad day you doofus."}); } + /** + * Reads the input from the users + * + * @return String representing input + */ public String readInput() { return sc.nextLine(); } + /** + * Prints error messages. + * + * @param e Exception instance. + */ public void showError(Exception e) { System.out.println("Error! " + e.getMessage()); } diff --git a/src/main/java/data/chatBot.txt b/src/main/java/data/chatBot.txt index e69de29bb2..d5d66a9da6 100644 --- a/src/main/java/data/chatBot.txt +++ b/src/main/java/data/chatBot.txt @@ -0,0 +1,2 @@ +E | 1 | cat | 1924-09-11 1900 | 2001-09-11 0900 +D | 0 | chicken | 2400-03-30 0000 From c311b414345deb936c1d10e8fca2571d9ba744e0 Mon Sep 17 00:00:00 2001 From: owenyeo Date: Sat, 2 Sep 2023 18:43:18 +0800 Subject: [PATCH 13/28] Add Gradle support for Chat Bot Previous version of ChatBot did not have support for Gradle. Support for gradle is required as we ramp up automated unit testing. Gradle support is therefore added, with JUnit tests coming in the next commit. Gradle is used for its ease of use and compatibility with JUnit. Fixed bugs regarding error messages not printing out. --- .gitattributes | 9 + .gitignore | 6 + .vscode/settings.json | 3 + 1 | 18 -- build.gradle | 17 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 ++++++++++++++++++ gradlew.bat | 92 +++++++ settings.gradle | 8 + src/main/java/{ => chatbot}/ChatBot.java | 2 + .../java/chatbot/commands/CommandType.java | 1 - .../chatbot/exceptions/SaveFileNotFound.java | 1 - src/main/java/chatbot/parser/Parser.java | 8 +- src/main/java/chatbot/storage/Storage.java | 10 +- src/main/java/chatbot/task/TaskType.java | 17 ++ src/main/java/data/chatBot.txt | 2 + 17 files changed, 422 insertions(+), 28 deletions(-) create mode 100644 .gitattributes create mode 100644 .vscode/settings.json delete mode 100644 1 create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle rename src/main/java/{ => chatbot}/ChatBot.java (98%) diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..097f9f98d9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore index 2873e189e1..a189f150d4 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,9 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..c5f3f6b9c7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/1 b/1 deleted file mode 100644 index 77842d2067..0000000000 --- a/1 +++ /dev/null @@ -1,18 +0,0 @@ - -# Please enter the commit message for your changes. Lines starting -# with '#' will be ignored, and an empty message aborts the commit. -# -# On branch master -# Your branch is up to date with 'origin/master'. -# -# Changes to be committed: -# modified: src/main/java/ChatBot.java -# new file: src/main/java/ChatBotException.java -# modified: src/main/java/Deadline.java -# modified: src/main/java/Event.java -# new file: src/main/java/InvalidCommandException.java -# new file: src/main/java/InvalidDescriptionException.java -# modified: src/main/java/ToDo.java -# -Added ChatBotException, InvalidCommandException, and InvalidDescriptionException. - diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..c28ce58eee --- /dev/null +++ b/build.gradle @@ -0,0 +1,17 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '7.1.2' +} + +application { + mainClass.set("chatbot.ChatBot") +} + +repositories { + mavenCentral() +} + +run { + standardInput = System.in; +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7f93135c49b765f8051ef9d0a6055ff8e46073d8 GIT binary patch literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..d11cdd907d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..0adc8e1a53 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..93e3f59f13 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..1bffb74d31 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,8 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.3/userguide/building_swift_projects.html in the Gradle documentation. + */ + +rootProject.name = "ip" diff --git a/src/main/java/ChatBot.java b/src/main/java/chatbot/ChatBot.java similarity index 98% rename from src/main/java/ChatBot.java rename to src/main/java/chatbot/ChatBot.java index 53796ab52a..f9d1038efc 100644 --- a/src/main/java/ChatBot.java +++ b/src/main/java/chatbot/ChatBot.java @@ -1,3 +1,5 @@ +package chatbot; + import chatbot.exceptions.ChatBotException; import chatbot.parser.Parser; import chatbot.storage.Storage; diff --git a/src/main/java/chatbot/commands/CommandType.java b/src/main/java/chatbot/commands/CommandType.java index d4f285830e..791f971a41 100644 --- a/src/main/java/chatbot/commands/CommandType.java +++ b/src/main/java/chatbot/commands/CommandType.java @@ -33,7 +33,6 @@ public static CommandType parseInput(String input) { return command; } } - return null; } } diff --git a/src/main/java/chatbot/exceptions/SaveFileNotFound.java b/src/main/java/chatbot/exceptions/SaveFileNotFound.java index f5fdb2eb18..e35da0527a 100644 --- a/src/main/java/chatbot/exceptions/SaveFileNotFound.java +++ b/src/main/java/chatbot/exceptions/SaveFileNotFound.java @@ -1,5 +1,4 @@ package chatbot.exceptions; -import chatbot.exceptions.ChatBotException; public class SaveFileNotFound extends ChatBotException{ diff --git a/src/main/java/chatbot/parser/Parser.java b/src/main/java/chatbot/parser/Parser.java index e438192df8..4f9599c102 100644 --- a/src/main/java/chatbot/parser/Parser.java +++ b/src/main/java/chatbot/parser/Parser.java @@ -27,10 +27,10 @@ public class Parser { * the chatbot to execute. * * @param input String input from user - * @return Command depending on input. - * @throws InvalidDescriptionException - * @throws InvalidCommandException - * @throws InvalidIndexException + * @return Command command depending on input. + * @throws InvalidDescriptionException Exception thrown when an invalid command description is passed. + * @throws InvalidCommandException Exception thrown when an invalid command is passed + * @throws InvalidIndexException Exception thrown when an invalid index is passed. */ public static Command parse(String input) throws InvalidDescriptionException, InvalidCommandException, InvalidIndexException { diff --git a/src/main/java/chatbot/storage/Storage.java b/src/main/java/chatbot/storage/Storage.java index 13af428670..b4ee6d3fd5 100644 --- a/src/main/java/chatbot/storage/Storage.java +++ b/src/main/java/chatbot/storage/Storage.java @@ -14,6 +14,7 @@ import chatbot.task.Event; import chatbot.task.Task; import chatbot.task.Deadline; +import chatbot.task.TaskType; /** * Storage class that handles storing and loading saved lists. @@ -63,15 +64,16 @@ public ArrayList load() { String line; while ((line = reader.readLine()) != null) { String[] parts = line.split("\\|"); - switch (parts[0].trim()) { - case("T"): + TaskType taskType = TaskType.parseInput(parts[0].trim()); + switch (taskType) { + case TODO: loadedList.add(new ToDo(parts[2].trim())); if (Integer.parseInt(parts[1].trim()) == 1) { loadedList.get(loadedList.size() - 1).done(); } break; - case("D"): + case DEADLINE: loadedList.add(new Deadline(parts[2].trim(), parts[3].trim())); if (Integer.parseInt(parts[1].trim()) == 1) { Task task = loadedList.get(loadedList.size() - 1); @@ -79,7 +81,7 @@ public ArrayList load() { } break; - case("E"): + case EVENT: loadedList.add(new Event(parts[2].trim(), parts[3].trim(), parts[4].trim())); if (Integer.parseInt(parts[1].trim()) == 1) { loadedList.get(loadedList.size() - 1).done(); diff --git a/src/main/java/chatbot/task/TaskType.java b/src/main/java/chatbot/task/TaskType.java index 1c5198a767..5f4ed57f59 100644 --- a/src/main/java/chatbot/task/TaskType.java +++ b/src/main/java/chatbot/task/TaskType.java @@ -13,4 +13,21 @@ public enum TaskType { private TaskType(String input) { this.input = input; } + + /** + * Parses the input and returns the appropriate command if the input is + * valid. + * + * @param input User's input + * @return Command that tells what the chatbot should do. + * @return null if the input in invalid + */ + public static TaskType parseInput(String input) { + for(TaskType task: TaskType.values()) { + if (task.input.equals(input)) { + return task; + } + } + return null; + } } diff --git a/src/main/java/data/chatBot.txt b/src/main/java/data/chatBot.txt index d5d66a9da6..e4dde000a7 100644 --- a/src/main/java/data/chatBot.txt +++ b/src/main/java/data/chatBot.txt @@ -1,2 +1,4 @@ E | 1 | cat | 1924-09-11 1900 | 2001-09-11 0900 D | 0 | chicken | 2400-03-30 0000 +T | 0 | dog +D | 0 | die | 2050-01-01 0000 From 7c128665cbb29a153071b78e27e0c9a6b3d4bbc2 Mon Sep 17 00:00:00 2001 From: owenyeo Date: Mon, 4 Sep 2023 21:16:40 +0800 Subject: [PATCH 14/28] Add JUnit tests into IP No automated unit testing was implemented in this project. This led to time inefficiency as I had to manually test each component one by one. Therefore, JUnit tests are being implemented to test the various components in this project. Currently, I am implementing only tests for TaskList and Parser. More will be implemented as required. Fixed a bug regarding InvalidDescriptionException not being thrown when an empty description is passed through. --- build.gradle | 19 ++++++++++ .../java/chatbot/commands/AddDeadline.java | 23 +++++++----- src/main/java/chatbot/commands/AddEvent.java | 23 +++++++----- src/main/java/chatbot/commands/AddToDo.java | 23 +++++++----- src/main/java/chatbot/commands/Bye.java | 4 +- src/main/java/chatbot/commands/Command.java | 15 +++++++- .../java/chatbot/commands/CommandType.java | 8 ++-- .../java/chatbot/commands/DeleteItem.java | 4 +- .../java/chatbot/commands/DisplayList.java | 4 +- src/main/java/chatbot/commands/MarkItem.java | 4 +- .../java/chatbot/commands/UnmarkItem.java | 4 +- src/main/java/chatbot/parser/Parser.java | 18 ++++----- src/main/java/chatbot/task/TaskList.java | 8 ++-- src/main/java/data/chatBot.txt | 2 +- src/test/java/chatbot/parser/ParserTest.java | 31 ++++++++++++++++ src/test/java/chatbot/task/TaskListTest.java | 37 +++++++++++++++++++ 16 files changed, 171 insertions(+), 56 deletions(-) create mode 100644 src/test/java/chatbot/parser/ParserTest.java create mode 100644 src/test/java/chatbot/task/TaskListTest.java diff --git a/build.gradle b/build.gradle index c28ce58eee..4fd4a3ea73 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,25 @@ application { mainClass.set("chatbot.ChatBot") } +dependencies { + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + repositories { mavenCentral() } diff --git a/src/main/java/chatbot/commands/AddDeadline.java b/src/main/java/chatbot/commands/AddDeadline.java index 04131480d6..7735ab938c 100644 --- a/src/main/java/chatbot/commands/AddDeadline.java +++ b/src/main/java/chatbot/commands/AddDeadline.java @@ -1,5 +1,6 @@ package chatbot.commands; +import chatbot.exceptions.InvalidDescriptionException; import chatbot.storage.Storage; import chatbot.task.TaskList; import chatbot.task.TaskType; @@ -12,20 +13,24 @@ */ public class AddDeadline extends Command { - public AddDeadline(String input) { - super(input); + public AddDeadline(String input, CommandType commandType) { + super(input, commandType); } @Override public void execute(TaskList tasks, Storage storage, Ui ui) { - tasks.addTask(input, TaskType.DEADLINE); + try { + tasks.addTask(input, TaskType.DEADLINE); - ui.print(new String[] { - "What? You ain't finishing it. Added: ", - tasks.getTask(tasks.getLength()).toString(), - "Now you have an overwhelming " + tasks.getLength() + " things to do." - }); + ui.print(new String[] { + "What? You ain't finishing it. Added: ", + tasks.getTask(tasks.getLength()).toString(), + "Now you have an overwhelming " + tasks.getLength() + " things to do." + }); - storage.saveTasks(tasks); + storage.saveTasks(tasks); + } catch (InvalidDescriptionException e) { + ui.showError(e); + } } } diff --git a/src/main/java/chatbot/commands/AddEvent.java b/src/main/java/chatbot/commands/AddEvent.java index b77bdf30f0..e50bf7f98f 100644 --- a/src/main/java/chatbot/commands/AddEvent.java +++ b/src/main/java/chatbot/commands/AddEvent.java @@ -1,5 +1,6 @@ package chatbot.commands; +import chatbot.exceptions.InvalidDescriptionException; import chatbot.storage.Storage; import chatbot.task.TaskList; import chatbot.task.TaskType; @@ -12,20 +13,24 @@ */ public class AddEvent extends Command { - public AddEvent(String input) { - super(input); + public AddEvent(String input, CommandType commandType) { + super(input, commandType); } @Override public void execute(TaskList tasks, Storage storage, Ui ui) { - tasks.addTask(input, TaskType.EVENT); + try { + tasks.addTask(input, TaskType.EVENT); - ui.print(new String[] { - "What? You ain't finishing it. Added: ", - tasks.getTask(tasks.getLength()).toString(), - "Now you have an overwhelming " + tasks.getLength() + " things to do." - }); + ui.print(new String[] { + "What? You ain't finishing it. Added: ", + tasks.getTask(tasks.getLength()).toString(), + "Now you have an overwhelming " + tasks.getLength() + " things to do." + }); - storage.saveTasks(tasks); + storage.saveTasks(tasks); + } catch (InvalidDescriptionException e) { + ui.showError(e); + } } } diff --git a/src/main/java/chatbot/commands/AddToDo.java b/src/main/java/chatbot/commands/AddToDo.java index 3cb0c22d46..7f38fe7e82 100644 --- a/src/main/java/chatbot/commands/AddToDo.java +++ b/src/main/java/chatbot/commands/AddToDo.java @@ -1,5 +1,6 @@ package chatbot.commands; +import chatbot.exceptions.InvalidDescriptionException; import chatbot.storage.Storage; import chatbot.task.TaskList; import chatbot.task.TaskType; @@ -12,8 +13,8 @@ */ public class AddToDo extends Command{ - public AddToDo(String input) { - super(input); + public AddToDo(String input, CommandType commandType) { + super(input, commandType); } /** @@ -23,14 +24,18 @@ public AddToDo(String input) { */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) { - tasks.addTask(input, TaskType.TODO); + try { + tasks.addTask(input, TaskType.TODO); - ui.print(new String[] { - "What? You ain't finishing it. Added: ", - tasks.getTask(tasks.getLength()).toString(), - "Now you have an overwhelming " + tasks.getLength() + " things to do." - }); + ui.print(new String[] { + "What? You ain't finishing it. Added: ", + tasks.getTask(tasks.getLength()).toString(), + "Now you have an overwhelming " + tasks.getLength() + " things to do." + }); - storage.saveTasks(tasks); + storage.saveTasks(tasks); + } catch (InvalidDescriptionException e) { + ui.showError(e); + } } } diff --git a/src/main/java/chatbot/commands/Bye.java b/src/main/java/chatbot/commands/Bye.java index e7108593af..855643e0fb 100644 --- a/src/main/java/chatbot/commands/Bye.java +++ b/src/main/java/chatbot/commands/Bye.java @@ -9,8 +9,8 @@ */ public class Bye extends Command { - public Bye(String input) { - super(input); + public Bye(String input, CommandType commandType) { + super(input, commandType); } /** diff --git a/src/main/java/chatbot/commands/Command.java b/src/main/java/chatbot/commands/Command.java index 090065fb56..6ab6802423 100644 --- a/src/main/java/chatbot/commands/Command.java +++ b/src/main/java/chatbot/commands/Command.java @@ -11,10 +11,12 @@ */ public abstract class Command { - protected String input; + protected final String input; + protected final CommandType commandType; - public Command(String input) { + public Command(String input, CommandType commandType) { this.input = input; + this.commandType = commandType; } /** @@ -32,5 +34,14 @@ public void execute(TaskList tasks, Storage storage, Ui ui) {} * @return false */ public boolean isExit() {return false;} + + /** + * Getter for commandType. Mainly used for testing. + * + * @return CommandType of the command. + */ + public CommandType getType() { + return this.commandType; + } } diff --git a/src/main/java/chatbot/commands/CommandType.java b/src/main/java/chatbot/commands/CommandType.java index 791f971a41..d1fc57d3f6 100644 --- a/src/main/java/chatbot/commands/CommandType.java +++ b/src/main/java/chatbot/commands/CommandType.java @@ -28,9 +28,11 @@ private CommandType(String input) { * @return null if the input in invalid */ public static CommandType parseInput(String input) { - for(CommandType command: CommandType.values()) { - if (command.input.equals(input)) { - return command; + String[] parts = input.split(" ", 2); + + for(CommandType commandType: CommandType.values()) { + if (commandType.input.equals(parts[0])) { + return commandType; } } return null; diff --git a/src/main/java/chatbot/commands/DeleteItem.java b/src/main/java/chatbot/commands/DeleteItem.java index f36cd9e7c5..fd55bf607e 100644 --- a/src/main/java/chatbot/commands/DeleteItem.java +++ b/src/main/java/chatbot/commands/DeleteItem.java @@ -11,8 +11,8 @@ */ public class DeleteItem extends Command{ - public DeleteItem(String input) { - super(input); + public DeleteItem(String input, CommandType commandType) { + super(input, commandType); } /** * {@inheritDoc} diff --git a/src/main/java/chatbot/commands/DisplayList.java b/src/main/java/chatbot/commands/DisplayList.java index 2dee140166..acf3995c3c 100644 --- a/src/main/java/chatbot/commands/DisplayList.java +++ b/src/main/java/chatbot/commands/DisplayList.java @@ -11,8 +11,8 @@ */ public class DisplayList extends Command{ - public DisplayList(String input) { - super(input); + public DisplayList(String input, CommandType commandType) { + super(input, commandType); } /** diff --git a/src/main/java/chatbot/commands/MarkItem.java b/src/main/java/chatbot/commands/MarkItem.java index c095c123e4..429dc3871c 100644 --- a/src/main/java/chatbot/commands/MarkItem.java +++ b/src/main/java/chatbot/commands/MarkItem.java @@ -12,8 +12,8 @@ */ public class MarkItem extends Command{ - public MarkItem(String input) { - super(input); + public MarkItem(String input, CommandType commandType) { + super(input, commandType); } /** diff --git a/src/main/java/chatbot/commands/UnmarkItem.java b/src/main/java/chatbot/commands/UnmarkItem.java index 27883a6a8c..ff7bcea0dd 100644 --- a/src/main/java/chatbot/commands/UnmarkItem.java +++ b/src/main/java/chatbot/commands/UnmarkItem.java @@ -12,8 +12,8 @@ */ public class UnmarkItem extends Command{ - public UnmarkItem(String input) { - super(input); + public UnmarkItem(String input, CommandType commandType) { + super(input, commandType); } /** diff --git a/src/main/java/chatbot/parser/Parser.java b/src/main/java/chatbot/parser/Parser.java index 4f9599c102..4b522c66cf 100644 --- a/src/main/java/chatbot/parser/Parser.java +++ b/src/main/java/chatbot/parser/Parser.java @@ -34,7 +34,7 @@ public class Parser { */ public static Command parse(String input) throws InvalidDescriptionException, InvalidCommandException, InvalidIndexException { - //Split the input so that we can read integers. + //Split the input so that we can read command and their description (if any). String[] inputStrings = input.split(" ", 2); CommandType command = CommandType.parseInput(inputStrings[0]); if (command == null) { @@ -43,28 +43,28 @@ public static Command parse(String input) throws switch(command) { case BYE: - return new Bye(""); + return new Bye("", CommandType.BYE); case DISPLAY_LIST: - return new DisplayList(""); + return new DisplayList("", CommandType.DISPLAY_LIST); case MARK: - return new MarkItem(inputStrings[1]); + return new MarkItem(inputStrings[1], CommandType.MARK); case UNMARK: - return new UnmarkItem(inputStrings[1]); + return new UnmarkItem(inputStrings[1], CommandType.UNMARK); case ADD_TODO: - return new AddToDo(inputStrings[1]); + return new AddToDo(inputStrings[1], CommandType.ADD_TODO); case ADD_DEADLINE: - return new AddDeadline(inputStrings[1]); + return new AddDeadline(inputStrings[1], CommandType.ADD_DEADLINE); case ADD_EVENT: - return new AddEvent(inputStrings[1]); + return new AddEvent(inputStrings[1], CommandType.ADD_EVENT); case DELETE: - return new DeleteItem(inputStrings[1]); + return new DeleteItem(inputStrings[1], CommandType.DELETE); default: throw new InvalidCommandException("Don't be stupid, speak english."); diff --git a/src/main/java/chatbot/task/TaskList.java b/src/main/java/chatbot/task/TaskList.java index 5a3c7015af..5fd6847a04 100644 --- a/src/main/java/chatbot/task/TaskList.java +++ b/src/main/java/chatbot/task/TaskList.java @@ -41,12 +41,12 @@ public TaskList() { */ public void addTask(String taskString, TaskType taskType) throws InvalidDescriptionException { + if (taskString == "") { + throw new InvalidDescriptionException( + "What? Where's your label? Stop this."); + } switch(taskType) { case TODO: - if (taskString.equals(" ")) { - throw new InvalidDescriptionException( - "What? Where's your label? Stop this."); - } tasks.add(new ToDo(taskString)); break; diff --git a/src/main/java/data/chatBot.txt b/src/main/java/data/chatBot.txt index e4dde000a7..001a84239c 100644 --- a/src/main/java/data/chatBot.txt +++ b/src/main/java/data/chatBot.txt @@ -1,4 +1,4 @@ E | 1 | cat | 1924-09-11 1900 | 2001-09-11 0900 -D | 0 | chicken | 2400-03-30 0000 +D | 1 | chicken | 2400-03-30 0000 T | 0 | dog D | 0 | die | 2050-01-01 0000 diff --git a/src/test/java/chatbot/parser/ParserTest.java b/src/test/java/chatbot/parser/ParserTest.java new file mode 100644 index 0000000000..267a766f44 --- /dev/null +++ b/src/test/java/chatbot/parser/ParserTest.java @@ -0,0 +1,31 @@ +package chatbot.parser; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import chatbot.commands.*; +import chatbot.exceptions.InvalidCommandException; + + +public class ParserTest { + + @Test + public void ParserInputSuccessTest() { + String[] validCommands = { "bye", "list", "todo yes", "deadline yes", "event yes", "mark 1", "unmark 1", "delete 1" }; + + for (String commandString : validCommands) { + assertEquals(CommandType.parseInput(commandString), Parser.parse(commandString).getType()); + } + } + + @Test + public void ParserInputErrorTest() { + String[] invalidCommands = {"lol", "gg"}; + + for (String commandString: invalidCommands) { + assertThrows(InvalidCommandException.class, () -> Parser.parse(commandString)); + } + } +} diff --git a/src/test/java/chatbot/task/TaskListTest.java b/src/test/java/chatbot/task/TaskListTest.java new file mode 100644 index 0000000000..65649d871a --- /dev/null +++ b/src/test/java/chatbot/task/TaskListTest.java @@ -0,0 +1,37 @@ +package chatbot.task; + +import org.junit.jupiter.api.Test; + +import chatbot.exceptions.InvalidDescriptionException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + + +public class TaskListTest { + + @Test + public void NoDescriptionTest() { + TaskList tasks = new TaskList(); + assertThrows(InvalidDescriptionException.class, () -> tasks.addTask("", TaskType.TODO)); + } + + @Test + public void WrongDescriptionTest() { + TaskList tasks = new TaskList(); + assertThrows(InvalidDescriptionException.class, () -> tasks.addTask("test /by", TaskType.DEADLINE)); + assertThrows(InvalidDescriptionException.class, () -> tasks.addTask("test /from /to", TaskType.EVENT)); + } + + @Test + public void AddSuccessTest() { + TaskList tasks = new TaskList(); + tasks.addTask("chicken", TaskType.TODO); + tasks.addTask("chicken /by 1900-01-01 1200", TaskType.DEADLINE); + tasks.addTask("chicken /from 1900-01-01 1200 /to 1950-01-01 0000", TaskType.EVENT); + + assertEquals(tasks.getTask(1).toString(), "[T][ ] chicken"); + assertEquals(tasks.getTask(2).toString(), "[D][ ] chicken (by: Jan 01 1900 12PM)"); + assertEquals(tasks.getTask(3).toString(), "[E][ ] chicken (from: Jan 01 1900 12PM | to: Jan 01 1950 12AM)"); + } +} From e11eebb40ed120b45ff5fba54bbc2dd365925806 Mon Sep 17 00:00:00 2001 From: owenyeo Date: Mon, 4 Sep 2023 22:03:40 +0800 Subject: [PATCH 15/28] Follow coding convention No coding convention was adhered to. This causes code to be less readable by others. Thus, from now on the project will adhere to Java Coding Convention. Checkstyle will be implemented and consistent checks from developer side will continue to ensure this. --- build.gradle | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/build.gradle b/build.gradle index 4fd4a3ea73..0451132255 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java' id 'application' + id 'checkstyle' id 'com.github.johnrengelman.shadow' version '7.1.2' } @@ -13,6 +14,10 @@ dependencies { testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' } +checkstyle { + configFile = file("$buildDir/config/checkstyle/checkstyle.xml") +} + test { useJUnitPlatform() @@ -27,6 +32,10 @@ test { } } +shadowJar { + archiveFileName = 'ChatBot.jar' +} + repositories { mavenCentral() } From d367c95115089ca39fa3c99429c9202c343cd981 Mon Sep 17 00:00:00 2001 From: owenyeo Date: Mon, 4 Sep 2023 22:23:09 +0800 Subject: [PATCH 16/28] Add Find command There is no find command currently in the chatbot. This makes it hard to look for tasks that the user may specifically is looking for. A find command is thus added and is currently working as per developer's testing. --- build.gradle | 5 --- .../java/chatbot/commands/AddDeadline.java | 11 +++++++ src/main/java/chatbot/commands/AddEvent.java | 6 ++++ src/main/java/chatbot/commands/AddToDo.java | 3 +- src/main/java/chatbot/commands/Command.java | 6 ++++ .../java/chatbot/commands/CommandType.java | 5 ++- .../java/chatbot/commands/DeleteItem.java | 8 +++-- .../java/chatbot/commands/DisplayList.java | 2 +- src/main/java/chatbot/commands/FindTask.java | 32 +++++++++++++++++++ src/main/java/chatbot/commands/MarkItem.java | 3 ++ .../java/chatbot/commands/UnmarkItem.java | 5 ++- src/main/java/chatbot/parser/Parser.java | 4 +++ src/main/java/data/chatBot.txt | 2 ++ src/test/java/chatbot/parser/ParserTest.java | 13 +++++++- 14 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 src/main/java/chatbot/commands/FindTask.java diff --git a/build.gradle b/build.gradle index 0451132255..f03d332e8b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,6 @@ plugins { id 'java' id 'application' - id 'checkstyle' id 'com.github.johnrengelman.shadow' version '7.1.2' } @@ -14,10 +13,6 @@ dependencies { testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' } -checkstyle { - configFile = file("$buildDir/config/checkstyle/checkstyle.xml") -} - test { useJUnitPlatform() diff --git a/src/main/java/chatbot/commands/AddDeadline.java b/src/main/java/chatbot/commands/AddDeadline.java index 7735ab938c..49f2bd8c71 100644 --- a/src/main/java/chatbot/commands/AddDeadline.java +++ b/src/main/java/chatbot/commands/AddDeadline.java @@ -13,10 +13,21 @@ */ public class AddDeadline extends Command { + /** + * {@inheritDoc} + * @param input + * @param commandType + */ public AddDeadline(String input, CommandType commandType) { super(input, commandType); } + /** + * {@inheritDoc} + * + * Adds a Deadline to the TaskList of the chatbot and saves it. + * Prints a message on the UI. + */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) { try { diff --git a/src/main/java/chatbot/commands/AddEvent.java b/src/main/java/chatbot/commands/AddEvent.java index e50bf7f98f..cf7575816e 100644 --- a/src/main/java/chatbot/commands/AddEvent.java +++ b/src/main/java/chatbot/commands/AddEvent.java @@ -17,6 +17,12 @@ public AddEvent(String input, CommandType commandType) { super(input, commandType); } + /** + * {@inheritDoc} + * + * Adds an Event to the TaskList of the chatbot and saves it. + * Prints a message on the UI. + */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) { try { diff --git a/src/main/java/chatbot/commands/AddToDo.java b/src/main/java/chatbot/commands/AddToDo.java index 7f38fe7e82..f955f6bd27 100644 --- a/src/main/java/chatbot/commands/AddToDo.java +++ b/src/main/java/chatbot/commands/AddToDo.java @@ -20,7 +20,8 @@ public AddToDo(String input, CommandType commandType) { /** * {@inheritDoc} * - * + * Adds a todo to the TaskList of the chatbot and saves it. + * Prints a message on the UI. */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) { diff --git a/src/main/java/chatbot/commands/Command.java b/src/main/java/chatbot/commands/Command.java index 6ab6802423..43104cba57 100644 --- a/src/main/java/chatbot/commands/Command.java +++ b/src/main/java/chatbot/commands/Command.java @@ -14,6 +14,12 @@ public abstract class Command { protected final String input; protected final CommandType commandType; + /** + * Constructs a Command instance with a given type and input. + * + * @param input String containing task label and other info. + * @param commandType type of command instance + */ public Command(String input, CommandType commandType) { this.input = input; this.commandType = commandType; diff --git a/src/main/java/chatbot/commands/CommandType.java b/src/main/java/chatbot/commands/CommandType.java index d1fc57d3f6..cb7d2f9f0f 100644 --- a/src/main/java/chatbot/commands/CommandType.java +++ b/src/main/java/chatbot/commands/CommandType.java @@ -2,6 +2,8 @@ /** * enum that represents the different command types. + * + * @author Owen Yeo */ public enum CommandType { BYE("bye"), @@ -11,7 +13,8 @@ public enum CommandType { ADD_TODO("todo"), ADD_DEADLINE("deadline"), ADD_EVENT("event"), - DELETE("delete"); + DELETE("delete"), + FIND("find"); private final String input; diff --git a/src/main/java/chatbot/commands/DeleteItem.java b/src/main/java/chatbot/commands/DeleteItem.java index fd55bf607e..a7867caca4 100644 --- a/src/main/java/chatbot/commands/DeleteItem.java +++ b/src/main/java/chatbot/commands/DeleteItem.java @@ -7,17 +7,21 @@ import chatbot.ui.Ui; /** - * + * Command that deletes an item from the TaskList, and prints a message. */ public class DeleteItem extends Command{ public DeleteItem(String input, CommandType commandType) { super(input, commandType); } + /** * {@inheritDoc} * - * Deletes item from the TaskList. + * Deletes item from the TaskList and saves it. + * Prints a message on the UI. + * + * @throws InvalidIndexEception Thrown when no tasks matches the index. */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) diff --git a/src/main/java/chatbot/commands/DisplayList.java b/src/main/java/chatbot/commands/DisplayList.java index acf3995c3c..5528bb2622 100644 --- a/src/main/java/chatbot/commands/DisplayList.java +++ b/src/main/java/chatbot/commands/DisplayList.java @@ -18,7 +18,7 @@ public DisplayList(String input, CommandType commandType) { /** * {@inheritDoc} * - * Displays the current list when executed. + * Displays the current list on the UI when executed. */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) { diff --git a/src/main/java/chatbot/commands/FindTask.java b/src/main/java/chatbot/commands/FindTask.java new file mode 100644 index 0000000000..ee2edb0810 --- /dev/null +++ b/src/main/java/chatbot/commands/FindTask.java @@ -0,0 +1,32 @@ +package chatbot.commands; + + +import chatbot.task.TaskList; +import chatbot.storage.Storage; +import chatbot.ui.Ui; + +public class FindTask extends Command{ + + public FindTask(String input, CommandType commandType) { + super(input, commandType); + } + + @Override + public void execute(TaskList tasks, Storage storage, Ui ui) { + String[] validStrings = new String[100]; + validStrings[0] = "Here are items that match your search:"; + + int index = 1; + for (int i = 1; i < tasks.getLength() + 1; i++) { + String taskString = tasks.getTask(i).toString(); + if (taskString.indexOf(input) != -1) { + validStrings[index] = index + ". " + taskString; + index++; + } + } + + ui.print(validStrings); + } + + +} diff --git a/src/main/java/chatbot/commands/MarkItem.java b/src/main/java/chatbot/commands/MarkItem.java index 429dc3871c..9b5107b6cd 100644 --- a/src/main/java/chatbot/commands/MarkItem.java +++ b/src/main/java/chatbot/commands/MarkItem.java @@ -19,7 +19,10 @@ public MarkItem(String input, CommandType commandType) { /** * {@inheritDoc} * + * Marks an item on the tasklist as done, and prints a message on the UI. + * Saves the change on the text file. * + * @throws InvalidIndexEception Thrown when no tasks matches the index. */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) diff --git a/src/main/java/chatbot/commands/UnmarkItem.java b/src/main/java/chatbot/commands/UnmarkItem.java index ff7bcea0dd..25fdfb881c 100644 --- a/src/main/java/chatbot/commands/UnmarkItem.java +++ b/src/main/java/chatbot/commands/UnmarkItem.java @@ -19,7 +19,10 @@ public UnmarkItem(String input, CommandType commandType) { /** * {@inheritDoc} * - * unmarks a task as undone. + * Unmarks a task on the TaskList as undone, and prints a message. + * Saves the change on a data file. + * + * @throws InvalidIndexEception Thrown when no tasks matches the index. */ @Override public void execute(TaskList tasks, Storage storage, Ui ui) diff --git a/src/main/java/chatbot/parser/Parser.java b/src/main/java/chatbot/parser/Parser.java index 4b522c66cf..d442bbf676 100644 --- a/src/main/java/chatbot/parser/Parser.java +++ b/src/main/java/chatbot/parser/Parser.java @@ -10,6 +10,7 @@ import chatbot.commands.DisplayList; import chatbot.commands.MarkItem; import chatbot.commands.UnmarkItem; +import chatbot.commands.FindTask; import chatbot.exceptions.InvalidCommandException; import chatbot.exceptions.InvalidDescriptionException; @@ -66,6 +67,9 @@ public static Command parse(String input) throws case DELETE: return new DeleteItem(inputStrings[1], CommandType.DELETE); + case FIND: + return new FindTask(inputStrings[1], CommandType.FIND); + default: throw new InvalidCommandException("Don't be stupid, speak english."); } diff --git a/src/main/java/data/chatBot.txt b/src/main/java/data/chatBot.txt index 001a84239c..3784d9abba 100644 --- a/src/main/java/data/chatBot.txt +++ b/src/main/java/data/chatBot.txt @@ -2,3 +2,5 @@ E | 1 | cat | 1924-09-11 1900 | 2001-09-11 0900 D | 1 | chicken | 2400-03-30 0000 T | 0 | dog D | 0 | die | 2050-01-01 0000 +T | 0 | dog +T | 0 | dog chicken diff --git a/src/test/java/chatbot/parser/ParserTest.java b/src/test/java/chatbot/parser/ParserTest.java index 267a766f44..53ff54744e 100644 --- a/src/test/java/chatbot/parser/ParserTest.java +++ b/src/test/java/chatbot/parser/ParserTest.java @@ -8,9 +8,17 @@ import chatbot.commands.*; import chatbot.exceptions.InvalidCommandException; - +/** + * Test class for Parser. Important as the parser is in charge of + * giving commands. + * + * @author Owen Yeo + */ public class ParserTest { + /** + * Tests if the parser can successfully take in valid commands. + */ @Test public void ParserInputSuccessTest() { String[] validCommands = { "bye", "list", "todo yes", "deadline yes", "event yes", "mark 1", "unmark 1", "delete 1" }; @@ -20,6 +28,9 @@ public void ParserInputSuccessTest() { } } + /** + * Tests if the parser correctly throws an error when a wrong input is given. + */ @Test public void ParserInputErrorTest() { String[] invalidCommands = {"lol", "gg"}; From 15915cc7d8e57eddb35e18b4802d56c5ca235606 Mon Sep 17 00:00:00 2001 From: owenyeo Date: Wed, 6 Sep 2023 01:07:44 +0800 Subject: [PATCH 17/28] Add Checkstyle Support Project has no checkstyle support, which makes it difficult to check for errors pertaining to coding convetion. Thus, checkstyle support has been added to automate checking and ensure better code quality. --- build.gradle | 5 + config/checkstyle/checkstyle.xml | 434 +++++++++++++++++++++++++++++ config/checkstyle/suppressions.xml | 10 + 3 files changed, 449 insertions(+) create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 config/checkstyle/suppressions.xml diff --git a/build.gradle b/build.gradle index f03d332e8b..af95ca3723 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java' id 'application' + id 'checkstyle' id 'com.github.johnrengelman.shadow' version '7.1.2' } @@ -8,6 +9,10 @@ application { mainClass.set("chatbot.ChatBot") } +checkstyle { + toolVersion = '10.2' +} + dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..d1399810b5 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..dcaa1af3c3 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file From 105a851b1539e4c416d11f5e10f8a77fbd006a2a Mon Sep 17 00:00:00 2001 From: owenyeo Date: Thu, 7 Sep 2023 20:14:10 +0800 Subject: [PATCH 18/28] Add GUI support for ChatBot There is no GUI for ChatBot, which is not ideal for User Experience. A UI was therefore added using JavaFX, and is currently working well as per developer testing and JUnit tests. --- .vscode/settings.json | 2 +- build.gradle | 8 +- src/main/java/chatbot/ChatBot.java | 153 ++++++++++++++---- .../java/chatbot/commands/AddDeadline.java | 11 +- src/main/java/chatbot/commands/AddEvent.java | 11 +- src/main/java/chatbot/commands/AddToDo.java | 13 +- src/main/java/chatbot/commands/Bye.java | 6 +- src/main/java/chatbot/commands/Command.java | 8 +- .../java/chatbot/commands/CommandType.java | 5 +- .../java/chatbot/commands/DeleteItem.java | 16 +- .../java/chatbot/commands/DisplayList.java | 6 +- src/main/java/chatbot/commands/FindTask.java | 14 +- src/main/java/chatbot/commands/MarkItem.java | 14 +- src/main/java/chatbot/commands/ShowError.java | 23 +++ .../java/chatbot/commands/UnmarkItem.java | 15 +- src/main/java/chatbot/parser/Parser.java | 10 +- src/main/java/chatbot/ui/DialogBox.java | 48 ++++++ src/main/java/chatbot/ui/Launcher.java | 12 ++ src/main/java/chatbot/ui/MainWindow.java | 55 +++++++ .../java/chatbot/ui/{Ui.java => Printer.java} | 27 ++-- src/main/java/data/chatBot.txt | 4 +- src/main/resources/images/Bobby Wasabi.jpeg | Bin 0 -> 38538 bytes src/main/resources/images/User.jpeg | Bin 0 -> 11954 bytes src/main/resources/scene.fxml | 11 ++ src/main/resources/styles.css | 3 + src/main/resources/view/DialogBox.fxml | 0 src/main/resources/view/MainWindow.fxml | 19 +++ 27 files changed, 388 insertions(+), 106 deletions(-) create mode 100644 src/main/java/chatbot/commands/ShowError.java create mode 100644 src/main/java/chatbot/ui/DialogBox.java create mode 100644 src/main/java/chatbot/ui/Launcher.java create mode 100644 src/main/java/chatbot/ui/MainWindow.java rename src/main/java/chatbot/ui/{Ui.java => Printer.java} (67%) create mode 100644 src/main/resources/images/Bobby Wasabi.jpeg create mode 100644 src/main/resources/images/User.jpeg create mode 100644 src/main/resources/scene.fxml create mode 100644 src/main/resources/styles.css create mode 100644 src/main/resources/view/DialogBox.fxml create mode 100644 src/main/resources/view/MainWindow.fxml diff --git a/.vscode/settings.json b/.vscode/settings.json index c5f3f6b9c7..e0f15db2eb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "java.configuration.updateBuildConfiguration": "interactive" + "java.configuration.updateBuildConfiguration": "automatic" } \ No newline at end of file diff --git a/build.gradle b/build.gradle index af95ca3723..49f2190a0b 100644 --- a/build.gradle +++ b/build.gradle @@ -2,11 +2,12 @@ plugins { id 'java' id 'application' id 'checkstyle' + id 'org.openjfx.javafxplugin' version '0.0.10' id 'com.github.johnrengelman.shadow' version '7.1.2' } application { - mainClass.set("chatbot.ChatBot") + mainClass.set("chatbot.ui.Launcher") } checkstyle { @@ -18,6 +19,11 @@ dependencies { testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' } +javafx { + version = "17.0.7" + modules = [ 'javafx.controls', 'javafx.fxml' ] +} + test { useJUnitPlatform() diff --git a/src/main/java/chatbot/ChatBot.java b/src/main/java/chatbot/ChatBot.java index f9d1038efc..adab0ba92d 100644 --- a/src/main/java/chatbot/ChatBot.java +++ b/src/main/java/chatbot/ChatBot.java @@ -1,30 +1,51 @@ package chatbot; -import chatbot.exceptions.ChatBotException; +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +import chatbot.commands.Command; import chatbot.parser.Parser; +import chatbot.exceptions.ChatBotException; import chatbot.storage.Storage; import chatbot.task.TaskList; -import chatbot.ui.Ui; -import chatbot.commands.Command; - +import chatbot.ui.DialogBox; +import chatbot.ui.Printer; /** * A chat bot that can be renamed, and responds to inputs from users * * @author Owen Yeo * Version Level-7 */ -public class ChatBot { - - private Ui ui; +public class ChatBot extends Application{ private Storage storage; - private TaskList tasks; + private ScrollPane scrollPane; + private VBox dialogContainer; + private TextField userInput; + private Button sendButton; + private Scene scene; + private Printer ui; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images/User.jpeg")); + private Image chatBotImage = new Image(this.getClass().getResourceAsStream("/images/Bobby Wasabi.jpeg")); + + private static final String FILE_PATH = "src/main/java/data/chatBot.txt"; + //Constructor that allows for the naming of your own bot. - public ChatBot(String path) { - ui = new Ui(); - storage = new Storage(path); + public ChatBot() { + ui = new Printer(); + storage = new Storage(FILE_PATH); try { tasks = new TaskList(storage.load()); } catch (ChatBotException e) { @@ -33,23 +54,101 @@ public ChatBot(String path) { } } - public void run() { - try { - ui.intro(); - boolean hasEnded = false; - while (!hasEnded) { - String fullCommand = ui.readInput(); - Command c = Parser.parse(fullCommand); - c.execute(tasks, storage, ui); - hasEnded = c.isExit(); - } - } catch (ChatBotException e) { - ui.showError(e); - } + @Override + public void start(Stage stage) { + + //Step 1. Setting up required components + + //The container for the content of the chat to scroll. + scrollPane = new ScrollPane(); + dialogContainer = new VBox(); + scrollPane.setContent(dialogContainer); + + userInput = new TextField(); + sendButton = new Button("Send"); + + AnchorPane mainLayout = new AnchorPane(); + mainLayout.getChildren().addAll(scrollPane, userInput, sendButton); + + scene = new Scene(mainLayout); + + stage.setScene(scene); + stage.show(); + + //Step 2. Formatting the window to look as expected + stage.setTitle("BOBBY WASABI"); + stage.setResizable(false); + stage.setMinHeight(600.0); + stage.setMinWidth(400.0); + + mainLayout.setPrefSize(400.0, 600.0); + + scrollPane.setPrefSize(385, 535); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); + + scrollPane.setVvalue(1.0); + scrollPane.setFitToWidth(true); + + // You will need to import `javafx.scene.layout.Region` for this. + dialogContainer.setPrefHeight(javafx.scene.layout.Region.USE_COMPUTED_SIZE); + + userInput.setPrefWidth(325.0); + + sendButton.setPrefWidth(55.0); + + AnchorPane.setTopAnchor(scrollPane, 1.0); + + AnchorPane.setBottomAnchor(sendButton, 1.0); + AnchorPane.setRightAnchor(sendButton, 1.0); + + AnchorPane.setLeftAnchor(userInput , 1.0); + AnchorPane.setBottomAnchor(userInput, 1.0); + + //Step 3. Add functionality to handle user input. + sendButton.setOnMouseClicked((event) -> { + handleUserInput(); + }); + + userInput.setOnAction((event) -> { + handleUserInput(); + }); + + dialogContainer.heightProperty().addListener((observable) -> scrollPane.setVvalue(1.0)); + + startChat(); + + } + + public void startChat() { + Label botText = new Label("Hello! I am Bobby Wasabi\n" + + "What can I do for you today?"); + dialogContainer.getChildren().addAll( + DialogBox.getDukeDialog(botText, new ImageView(chatBotImage)) + ); + } + + /** + * Iteration 2: + * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to + * the dialog container. Clears the user input after processing. + */ + public void handleUserInput() { + Label userText = new Label(userInput.getText()); + Label botText = new Label(getResponse(userInput.getText())); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(userText, new ImageView(userImage)), + DialogBox.getDukeDialog(botText, new ImageView(chatBotImage)) + ); + userInput.clear(); } - public static void main(String[] args) { - //Test chatbot - new ChatBot("src/main/java/data/chatBot.txt").run(); + /** + * You should have your own function to generate a response to user input. + * Replace this stub with your completed method. + */ + public String getResponse(String input) { + Command c = Parser.parse(input); + return c.execute(tasks, storage, ui); } } diff --git a/src/main/java/chatbot/commands/AddDeadline.java b/src/main/java/chatbot/commands/AddDeadline.java index 49f2bd8c71..1bc932cc2b 100644 --- a/src/main/java/chatbot/commands/AddDeadline.java +++ b/src/main/java/chatbot/commands/AddDeadline.java @@ -4,7 +4,7 @@ import chatbot.storage.Storage; import chatbot.task.TaskList; import chatbot.task.TaskType; -import chatbot.ui.Ui; +import chatbot.ui.Printer; /** * Adds a deadline task to the TaskList @@ -29,19 +29,18 @@ public AddDeadline(String input, CommandType commandType) { * Prints a message on the UI. */ @Override - public void execute(TaskList tasks, Storage storage, Ui ui) { + public String execute(TaskList tasks, Storage storage, Printer ui) { try { tasks.addTask(input, TaskType.DEADLINE); + storage.saveTasks(tasks); - ui.print(new String[] { + return ui.print(new String[] { "What? You ain't finishing it. Added: ", tasks.getTask(tasks.getLength()).toString(), "Now you have an overwhelming " + tasks.getLength() + " things to do." }); - - storage.saveTasks(tasks); } catch (InvalidDescriptionException e) { - ui.showError(e); + return ui.showError(e); } } } diff --git a/src/main/java/chatbot/commands/AddEvent.java b/src/main/java/chatbot/commands/AddEvent.java index cf7575816e..c40d644ddb 100644 --- a/src/main/java/chatbot/commands/AddEvent.java +++ b/src/main/java/chatbot/commands/AddEvent.java @@ -4,7 +4,7 @@ import chatbot.storage.Storage; import chatbot.task.TaskList; import chatbot.task.TaskType; -import chatbot.ui.Ui; +import chatbot.ui.Printer; /** * Add an event task to the TaskList @@ -24,19 +24,18 @@ public AddEvent(String input, CommandType commandType) { * Prints a message on the UI. */ @Override - public void execute(TaskList tasks, Storage storage, Ui ui) { + public String execute(TaskList tasks, Storage storage, Printer ui) { try { tasks.addTask(input, TaskType.EVENT); + storage.saveTasks(tasks); - ui.print(new String[] { + return ui.print(new String[] { "What? You ain't finishing it. Added: ", tasks.getTask(tasks.getLength()).toString(), "Now you have an overwhelming " + tasks.getLength() + " things to do." }); - - storage.saveTasks(tasks); } catch (InvalidDescriptionException e) { - ui.showError(e); + return ui.showError(e); } } } diff --git a/src/main/java/chatbot/commands/AddToDo.java b/src/main/java/chatbot/commands/AddToDo.java index f955f6bd27..d0a4a3ee2d 100644 --- a/src/main/java/chatbot/commands/AddToDo.java +++ b/src/main/java/chatbot/commands/AddToDo.java @@ -4,7 +4,7 @@ import chatbot.storage.Storage; import chatbot.task.TaskList; import chatbot.task.TaskType; -import chatbot.ui.Ui; +import chatbot.ui.Printer; /** * Adds a To Do task to the TaskList @@ -24,19 +24,18 @@ public AddToDo(String input, CommandType commandType) { * Prints a message on the UI. */ @Override - public void execute(TaskList tasks, Storage storage, Ui ui) { + public String execute(TaskList tasks, Storage storage, Printer ui) { try { tasks.addTask(input, TaskType.TODO); - - ui.print(new String[] { + storage.saveTasks(tasks); + + return ui.print(new String[] { "What? You ain't finishing it. Added: ", tasks.getTask(tasks.getLength()).toString(), "Now you have an overwhelming " + tasks.getLength() + " things to do." }); - - storage.saveTasks(tasks); } catch (InvalidDescriptionException e) { - ui.showError(e); + return ui.showError(e); } } } diff --git a/src/main/java/chatbot/commands/Bye.java b/src/main/java/chatbot/commands/Bye.java index 855643e0fb..3c91a5b2ca 100644 --- a/src/main/java/chatbot/commands/Bye.java +++ b/src/main/java/chatbot/commands/Bye.java @@ -2,7 +2,7 @@ import chatbot.storage.Storage; import chatbot.task.TaskList; -import chatbot.ui.Ui; +import chatbot.ui.Printer; /** * Command that exits the chatbot and forces it close. @@ -19,8 +19,8 @@ public Bye(String input, CommandType commandType) { * Asks UI to print a goodbye message. */ @Override - public void execute(TaskList tasks, Storage storage, Ui ui) { - ui.bye(); + public String execute(TaskList tasks, Storage storage, Printer ui) { + return ui.bye(); } /** diff --git a/src/main/java/chatbot/commands/Command.java b/src/main/java/chatbot/commands/Command.java index 43104cba57..ff3994e8e6 100644 --- a/src/main/java/chatbot/commands/Command.java +++ b/src/main/java/chatbot/commands/Command.java @@ -2,7 +2,7 @@ import chatbot.storage.Storage; import chatbot.task.TaskList; -import chatbot.ui.Ui; +import chatbot.ui.Printer; /** * Abstract class representing a command. @@ -30,9 +30,11 @@ public Command(String input, CommandType commandType) { * * @param tasks TaskList * @param storage Storage - * @param ui UI + * @param printer Printer */ - public void execute(TaskList tasks, Storage storage, Ui ui) {} + public String execute(TaskList tasks, Storage storage, Printer printer) { + return printer.intro(); + } /** * Checks if the current command will cause an exit. diff --git a/src/main/java/chatbot/commands/CommandType.java b/src/main/java/chatbot/commands/CommandType.java index cb7d2f9f0f..2ce1f392c5 100644 --- a/src/main/java/chatbot/commands/CommandType.java +++ b/src/main/java/chatbot/commands/CommandType.java @@ -14,7 +14,8 @@ public enum CommandType { ADD_DEADLINE("deadline"), ADD_EVENT("event"), DELETE("delete"), - FIND("find"); + FIND("find"), + SHOWERROR(""); private final String input; @@ -38,6 +39,6 @@ public static CommandType parseInput(String input) { return commandType; } } - return null; + return SHOWERROR; } } diff --git a/src/main/java/chatbot/commands/DeleteItem.java b/src/main/java/chatbot/commands/DeleteItem.java index a7867caca4..708f3cca31 100644 --- a/src/main/java/chatbot/commands/DeleteItem.java +++ b/src/main/java/chatbot/commands/DeleteItem.java @@ -4,7 +4,7 @@ import chatbot.storage.Storage; import chatbot.task.Task; import chatbot.task.TaskList; -import chatbot.ui.Ui; +import chatbot.ui.Printer; /** * Command that deletes an item from the TaskList, and prints a message. @@ -24,22 +24,22 @@ public DeleteItem(String input, CommandType commandType) { * @throws InvalidIndexEception Thrown when no tasks matches the index. */ @Override - public void execute(TaskList tasks, Storage storage, Ui ui) + public String execute(TaskList tasks, Storage storage, Printer ui) throws InvalidIndexException { try { int index = Integer.parseInt(input); Task deletedTask = tasks.getTask(index); - ui.print(new String[] {"I knew you couldn't finish it. Or maybe you did. I don't care. Deleted:", + tasks.delete(Integer.parseInt(input)); + storage.saveTasks(tasks); + + return ui.print(new String[] {"I knew you couldn't finish it. Or maybe you did. I don't care. Deleted:", deletedTask.toString(), "Now you have an overwhelming "+ (tasks.getLength() - 1) + " things to do"}); - tasks.delete(Integer.parseInt(input)); - - storage.saveTasks(tasks); } catch (NumberFormatException e) { - throw new InvalidIndexException("Are you stupid? That's not a number."); + return ui.showError(new InvalidIndexException("Are you stupid? That's not a number.")); } catch (IndexOutOfBoundsException e) { - throw new InvalidIndexException("That's not even a number on the list, idiot."); + return ui.showError(new InvalidIndexException("Are you stupid? That's not a number on the list.")); } } } diff --git a/src/main/java/chatbot/commands/DisplayList.java b/src/main/java/chatbot/commands/DisplayList.java index 5528bb2622..0564128bf6 100644 --- a/src/main/java/chatbot/commands/DisplayList.java +++ b/src/main/java/chatbot/commands/DisplayList.java @@ -2,7 +2,7 @@ import chatbot.storage.Storage; import chatbot.task.TaskList; -import chatbot.ui.Ui; +import chatbot.ui.Printer; /** * Displays the existing list when executed @@ -21,7 +21,7 @@ public DisplayList(String input, CommandType commandType) { * Displays the current list on the UI when executed. */ @Override - public void execute(TaskList tasks, Storage storage, Ui ui) { + public String execute(TaskList tasks, Storage storage, Printer ui) { String[] taskStrings = new String[100] ; for (int i = 1; i < tasks.getLength() + 1; i++) { @@ -29,6 +29,6 @@ public void execute(TaskList tasks, Storage storage, Ui ui) { taskStrings[i - 1] = listString; } - ui.print(taskStrings); + return ui.print(taskStrings); } } diff --git a/src/main/java/chatbot/commands/FindTask.java b/src/main/java/chatbot/commands/FindTask.java index ee2edb0810..3c66fa7488 100644 --- a/src/main/java/chatbot/commands/FindTask.java +++ b/src/main/java/chatbot/commands/FindTask.java @@ -2,17 +2,25 @@ import chatbot.task.TaskList; +import chatbot.ui.Printer; import chatbot.storage.Storage; -import chatbot.ui.Ui; +/** + * Finds the items in the list that match the query. + */ public class FindTask extends Command{ public FindTask(String input, CommandType commandType) { super(input, commandType); } + /** + * {@inheritDoc} + * + * Finds the tasks that contain the words found in the input. + */ @Override - public void execute(TaskList tasks, Storage storage, Ui ui) { + public String execute(TaskList tasks, Storage storage, Printer ui) { String[] validStrings = new String[100]; validStrings[0] = "Here are items that match your search:"; @@ -25,7 +33,7 @@ public void execute(TaskList tasks, Storage storage, Ui ui) { } } - ui.print(validStrings); + return ui.print(validStrings); } diff --git a/src/main/java/chatbot/commands/MarkItem.java b/src/main/java/chatbot/commands/MarkItem.java index 9b5107b6cd..83505433d4 100644 --- a/src/main/java/chatbot/commands/MarkItem.java +++ b/src/main/java/chatbot/commands/MarkItem.java @@ -3,7 +3,7 @@ import chatbot.exceptions.InvalidIndexException; import chatbot.storage.Storage; import chatbot.task.TaskList; -import chatbot.ui.Ui; +import chatbot.ui.Printer; /** * Marks item as completed in the list @@ -25,21 +25,21 @@ public MarkItem(String input, CommandType commandType) { * @throws InvalidIndexEception Thrown when no tasks matches the index. */ @Override - public void execute(TaskList tasks, Storage storage, Ui ui) + public String execute(TaskList tasks, Storage storage, Printer ui) throws InvalidIndexException { try { int index = Integer.parseInt(input); tasks.mark(index); - ui.print(new String[] { + storage.saveTasks(tasks); + + return ui.print(new String[] { "Impossible! You must have cheated. Horrible.", tasks.getTask(index).toString() }); - - storage.saveTasks(tasks); } catch (NumberFormatException e) { - throw new InvalidIndexException("Are you stupid? That's not a number."); + return ui.showError(new InvalidIndexException("Are you stupid? That's not a number.")); } catch (IndexOutOfBoundsException e) { - throw new InvalidIndexException("That's not even a number on the list, idiot."); + return ui.showError(new InvalidIndexException("Are you stupid? That's not a number on the list.")); } } } diff --git a/src/main/java/chatbot/commands/ShowError.java b/src/main/java/chatbot/commands/ShowError.java new file mode 100644 index 0000000000..0ac845533c --- /dev/null +++ b/src/main/java/chatbot/commands/ShowError.java @@ -0,0 +1,23 @@ +package chatbot.commands; + +import chatbot.exceptions.ChatBotException; +import chatbot.exceptions.InvalidIndexException; +import chatbot.storage.Storage; +import chatbot.task.TaskList; +import chatbot.ui.Printer; + +public class ShowError extends Command{ + + private ChatBotException chatBotException; + + public ShowError(String input, CommandType commandType, ChatBotException exception) { + super(input, commandType); + this.chatBotException = exception; + } + + @Override + public String execute(TaskList tasks, Storage storage, Printer ui) + throws InvalidIndexException { + return ui.showError(this.chatBotException); + } +} diff --git a/src/main/java/chatbot/commands/UnmarkItem.java b/src/main/java/chatbot/commands/UnmarkItem.java index 25fdfb881c..d035c82ad6 100644 --- a/src/main/java/chatbot/commands/UnmarkItem.java +++ b/src/main/java/chatbot/commands/UnmarkItem.java @@ -3,7 +3,7 @@ import chatbot.exceptions.InvalidIndexException; import chatbot.storage.Storage; import chatbot.task.TaskList; -import chatbot.ui.Ui; +import chatbot.ui.Printer; /** * Unmarks a completed item as undone. @@ -25,21 +25,22 @@ public UnmarkItem(String input, CommandType commandType) { * @throws InvalidIndexEception Thrown when no tasks matches the index. */ @Override - public void execute(TaskList tasks, Storage storage, Ui ui) + public String execute(TaskList tasks, Storage storage, Printer ui) throws InvalidIndexException { try { int index = Integer.parseInt(input); tasks.unmark(index); - ui.print(new String[] { + + storage.saveTasks(tasks); + + return ui.print(new String[] { "You incompetent child. I've unmarked the task. Please get it together.", tasks.getTask(index).toString() }); - - storage.saveTasks(tasks); } catch (NumberFormatException e) { - throw new InvalidIndexException("Are you stupid? That's not a number."); + return ui.showError(new InvalidIndexException("Are you stupid? That's not a number.")); } catch (IndexOutOfBoundsException e) { - throw new InvalidIndexException("That's not even a number on the list, idiot."); + return ui.showError(new InvalidIndexException("Are you stupid? That's not a number on the list.")); } } } diff --git a/src/main/java/chatbot/parser/Parser.java b/src/main/java/chatbot/parser/Parser.java index d442bbf676..f9ec567d81 100644 --- a/src/main/java/chatbot/parser/Parser.java +++ b/src/main/java/chatbot/parser/Parser.java @@ -1,16 +1,17 @@ package chatbot.parser; -import chatbot.commands.Command; import chatbot.commands.AddDeadline; import chatbot.commands.AddEvent; import chatbot.commands.AddToDo; import chatbot.commands.Bye; +import chatbot.commands.Command; import chatbot.commands.CommandType; import chatbot.commands.DeleteItem; import chatbot.commands.DisplayList; +import chatbot.commands.FindTask; import chatbot.commands.MarkItem; +import chatbot.commands.ShowError; import chatbot.commands.UnmarkItem; -import chatbot.commands.FindTask; import chatbot.exceptions.InvalidCommandException; import chatbot.exceptions.InvalidDescriptionException; @@ -38,9 +39,6 @@ public static Command parse(String input) throws //Split the input so that we can read command and their description (if any). String[] inputStrings = input.split(" ", 2); CommandType command = CommandType.parseInput(inputStrings[0]); - if (command == null) { - throw new InvalidCommandException("What are you saying? Try again."); - } switch(command) { case BYE: @@ -71,7 +69,7 @@ public static Command parse(String input) throws return new FindTask(inputStrings[1], CommandType.FIND); default: - throw new InvalidCommandException("Don't be stupid, speak english."); + return new ShowError("", CommandType.SHOWERROR, new InvalidCommandException("Don't be stupid, speak english.")); } } } diff --git a/src/main/java/chatbot/ui/DialogBox.java b/src/main/java/chatbot/ui/DialogBox.java new file mode 100644 index 0000000000..691c3beffc --- /dev/null +++ b/src/main/java/chatbot/ui/DialogBox.java @@ -0,0 +1,48 @@ +package chatbot.ui; + + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; + +public class DialogBox extends HBox { + + private Label text; + private ImageView displayPicture; + + public DialogBox(Label l, ImageView iv) { + text = l; + displayPicture = iv; + + text.setWrapText(true); + displayPicture.setFitWidth(100.0); + displayPicture.setFitHeight(100.0); + + this.setAlignment(Pos.TOP_RIGHT); + this.getChildren().addAll(text, displayPicture); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + this.setAlignment(Pos.TOP_LEFT); + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + FXCollections.reverse(tmp); + this.getChildren().setAll(tmp); + } + + public static DialogBox getUserDialog(Label l, ImageView iv) { + return new DialogBox(l, iv); + } + + public static DialogBox getDukeDialog(Label l, ImageView iv) { + var db = new DialogBox(l, iv); + db.flip(); + return db; + } +} \ No newline at end of file diff --git a/src/main/java/chatbot/ui/Launcher.java b/src/main/java/chatbot/ui/Launcher.java new file mode 100644 index 0000000000..1206f6f8d9 --- /dev/null +++ b/src/main/java/chatbot/ui/Launcher.java @@ -0,0 +1,12 @@ +package chatbot.ui; +import chatbot.ChatBot; +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class Launcher { + public static void main(String[] args) { + Application.launch(ChatBot.class, args); + } +} diff --git a/src/main/java/chatbot/ui/MainWindow.java b/src/main/java/chatbot/ui/MainWindow.java new file mode 100644 index 0000000000..0023f69855 --- /dev/null +++ b/src/main/java/chatbot/ui/MainWindow.java @@ -0,0 +1,55 @@ +package chatbot.ui; + +import chatbot.ChatBot; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +/** + * Controller for MainWindow. Provides the layout for the other controls. + */ +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private ChatBot chatBot; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images/User.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/Bobby Wasabi.png")); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + + public void setChatBot(ChatBot c) { + chatBot = c; + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to + * the dialog container. Clears the user input after processing. + */ + @FXML + private void handleUserInput() { + Label input = new Label(userInput.getText()); + Label response = new Label(chatBot.getResponse(userInput.getText())); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, new ImageView(userImage)), + DialogBox.getDukeDialog(response, new ImageView(dukeImage)) + ); + userInput.clear(); + } +} + diff --git a/src/main/java/chatbot/ui/Ui.java b/src/main/java/chatbot/ui/Printer.java similarity index 67% rename from src/main/java/chatbot/ui/Ui.java rename to src/main/java/chatbot/ui/Printer.java index 633caf7323..da581a0114 100644 --- a/src/main/java/chatbot/ui/Ui.java +++ b/src/main/java/chatbot/ui/Printer.java @@ -5,10 +5,10 @@ /** * Class representing a UI which reads inputs from users * and prints outputs depending on the command or error. - * + * * @author Owen Yeo */ -public class Ui { +public class Printer { //Scanner used to see user input private static final Scanner sc = new Scanner(System.in); @@ -18,33 +18,32 @@ public class Ui { /** * Prints the inputs out for the user. - * + * * @param inputs */ - public void print(String[] inputs) { - System.out.println(BORDER); + public String print(String[] inputs) { + String output = ""; for (int i = 0; i < inputs.length; i++) { if (inputs[i] != null) { - System.out.println(inputs[i]); + output += inputs[i] +"\n"; } } - System.out.println(BORDER); + return output; } /** * Prints an introduction. */ - public void intro() { - print(new String[] {"Hello! I am Bobby Wasabi", - "What can I do for you today?"}); + public String intro() { + return "Hello! I am Bobby Wasabi\nWhat can I do for you today?"; } /** * Prints a goodbye message. */ - public void bye() { - print(new String[] {"Bye. Have a bad day you doofus."}); + public String bye() { + return "Bye. Have a bad day you doofus."; } /** @@ -61,8 +60,8 @@ public String readInput() { * * @param e Exception instance. */ - public void showError(Exception e) { - System.out.println("Error! " + e.getMessage()); + public String showError(Exception e) { + return "Error! " + e.getMessage(); } } diff --git a/src/main/java/data/chatBot.txt b/src/main/java/data/chatBot.txt index 3784d9abba..b83c4bd826 100644 --- a/src/main/java/data/chatBot.txt +++ b/src/main/java/data/chatBot.txt @@ -1,6 +1,6 @@ E | 1 | cat | 1924-09-11 1900 | 2001-09-11 0900 D | 1 | chicken | 2400-03-30 0000 -T | 0 | dog +T | 1 | dog D | 0 | die | 2050-01-01 0000 T | 0 | dog -T | 0 | dog chicken +T | 1 | sleep diff --git a/src/main/resources/images/Bobby Wasabi.jpeg b/src/main/resources/images/Bobby Wasabi.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..cc745fc183305be3df22aeda672c7d1a868d694f GIT binary patch literal 38538 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/images/User.jpeg b/src/main/resources/images/User.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..bfbaba944b307701c87f919f3ac766e2ece45a23 GIT binary patch literal 11954 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/scene.fxml b/src/main/resources/scene.fxml new file mode 100644 index 0000000000..4a8c1896ca --- /dev/null +++ b/src/main/resources/scene.fxml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/styles.css b/src/main/resources/styles.css new file mode 100644 index 0000000000..ec1d16e3f6 --- /dev/null +++ b/src/main/resources/styles.css @@ -0,0 +1,3 @@ +.label { + -fx-text-fill: blue; +} \ No newline at end of file diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..451d8fd41b --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + +