From 0c3da274e13290c1f9883744862331022e093894 Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 18 Aug 2022 17:27:30 +0800 Subject: [PATCH 01/31] Implement a skeletal version of Duke that starts by greeting the user, simply echos commands entered by the user, and exits when the user types 'bye' --- src/main/java/Duke.java | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5d313334cc..72cc1177cc 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,32 @@ + +import java.util.Scanner; public class Duke { + private static String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; + private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; + + + /** + * Note: You are strongly encouraged to customize the chatbot name, + * command/display formats, and even the personality of the chatbot + * to make your chatbot unique. + */ public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + System.out.println(WELCOME_MESSAGE); + + Scanner scanner = new Scanner(System.in); + + while (true) { + + String input = scanner.nextLine(); + + if (input.equals("bye")) { + System.out.println(GOODBYE_MESSAGE); + break; + } + + System.out.println(input); + } + + scanner.close(); } } From 2ad4f4f7b7439bab2c5008526671f6e835a828e9 Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 18 Aug 2022 17:36:54 +0800 Subject: [PATCH 02/31] Add the ability to store whatever text entered by the user and display them back to the user when requested. --- src/main/java/Duke.java | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 72cc1177cc..6079540e69 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -4,6 +4,18 @@ public class Duke { private static String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; + private String[] inputs = new String[100]; + private int numInputsStored = 0; + + private void store(String input) { + this.inputs[numInputsStored++] = input; + System.out.println(String.format("added: %s", input)); + } + private void getInputs() { + for (int i = 0; i < this.numInputsStored; i++) { + System.out.println(String.format("%d. %s", i+1, this.inputs[i])); + } + } /** * Note: You are strongly encouraged to customize the chatbot name, @@ -14,6 +26,7 @@ public static void main(String[] args) { System.out.println(WELCOME_MESSAGE); Scanner scanner = new Scanner(System.in); + Duke duke = new Duke(); while (true) { @@ -24,7 +37,13 @@ public static void main(String[] args) { break; } - System.out.println(input); + if (input.equals("list")) { + duke.getInputs(); + continue; + } + + duke.store(input); + } scanner.close(); From 924f7a66746f50d1d700008d24dcd2a0575840e6 Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 18 Aug 2022 18:01:34 +0800 Subject: [PATCH 03/31] Add the ability to mark tasks as done. Optionally, add the ability to change the status back to not done. --- src/main/java/Duke.java | 66 +++++++++++++++++++++++++++++++++-------- src/main/java/Task.java | 29 ++++++++++++++++++ 2 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 src/main/java/Task.java diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 6079540e69..21e06ce079 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -4,16 +4,57 @@ public class Duke { private static String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; - private String[] inputs = new String[100]; + private Task[] tasks = new Task[100]; private int numInputsStored = 0; - private void store(String input) { - this.inputs[numInputsStored++] = input; - System.out.println(String.format("added: %s", input)); + private void storeTask(String title) { + this.tasks[numInputsStored++] = new Task(title); + System.out.println(String.format("added: %s", title)); } - private void getInputs() { + private void getTasks() { + System.out.println("Here are the tasks in your list:"); for (int i = 0; i < this.numInputsStored; i++) { - System.out.println(String.format("%d. %s", i+1, this.inputs[i])); + System.out.println(String.format("%d. %s", i+1, this.tasks[i])); + } + } + + private Task getTaskAtIndex(int index) { + return this.tasks[index]; + } + private void markTaskAtIndexAsComplete(int index) { + Task task = this.getTaskAtIndex(index-1); + if (task == null) { + System.out.println("No task found. Are you sure you entered the correct index?"); + } else { + task.markAsCompleted(); + } + } + private void markTaskAtIndexAsIncomplete(int index) { + Task task = this.getTaskAtIndex(index-1); + if (task == null) { + System.out.println("No task found. Are you sure you entered the correct index?"); + } else { + task.markAsIncomplete(); + } + } + + private boolean parseAndHandleCommand(String command) { + String[] components = command.split(" "); + try { + String action = components[0]; + int taskIndex = Integer.parseInt(components[1]); + if (action.equals("mark")) { + this.markTaskAtIndexAsComplete(taskIndex); + } + + if (action.equals("unmark")) { + this.markTaskAtIndexAsIncomplete(taskIndex); + } + + return true; + + } catch (NumberFormatException e) { + return false; } } @@ -30,20 +71,21 @@ public static void main(String[] args) { while (true) { - String input = scanner.nextLine(); + String command = scanner.nextLine(); - if (input.equals("bye")) { + if (command.equals("bye")) { System.out.println(GOODBYE_MESSAGE); break; } - if (input.equals("list")) { - duke.getInputs(); + if (command.equals("list")) { + duke.getTasks(); continue; } - duke.store(input); - + if (!duke.parseAndHandleCommand(command)) { + duke.storeTask(command); + } } scanner.close(); diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 0000000000..56be22d99c --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,29 @@ +public class Task { + private String title; + private boolean completed; + + Task(String title) { + this.title = title; + this.completed = false; + } + + public void markAsCompleted() { + this.completed = true; + System.out.println(String.format("Nice! I've marked this task as done:\n\t%s", this)); + } + + public void markAsIncomplete() { + this.completed = false; + System.out.println(String.format("OK, I've marked this task as not done yet:\n\t%s", this)); + } + + private String getStatusIcon() { + return (this.completed ? "X" : " "); // mark done task with X + } + + @Override + public String toString() { + return String.format("[%s] %s", this.getStatusIcon(), this.title); + } + +} From 1b0d220685a99793bda390f24fa1a9e5dc04df67 Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 18 Aug 2022 18:53:09 +0800 Subject: [PATCH 04/31] Add support for tracking three types of tasks: todo, deadline and event --- src/main/java/Action.java | 21 ++++++++++ src/main/java/Deadline.java | 16 ++++++++ src/main/java/Duke.java | 78 ++++++++++++++++++++++++++----------- src/main/java/Event.java | 17 ++++++++ src/main/java/Todo.java | 13 +++++++ 5 files changed, 122 insertions(+), 23 deletions(-) create mode 100644 src/main/java/Action.java create mode 100644 src/main/java/Deadline.java create mode 100644 src/main/java/Event.java create mode 100644 src/main/java/Todo.java diff --git a/src/main/java/Action.java b/src/main/java/Action.java new file mode 100644 index 0000000000..5189e20399 --- /dev/null +++ b/src/main/java/Action.java @@ -0,0 +1,21 @@ +public enum Action { + Todo("todo"), + Deadline("deadline"), + Event("event"), + Mark("mark"), + Unmark("unmark"); + + public final String label; + private Action(String label) { + this.label = label; + } + + public static Action parseCommand(String command) { + for (Action action : values()) { + if (command.startsWith(action.label)) { + return action; + } + } + return null; + } +} diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 0000000000..1250470bb8 --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,16 @@ +/** + * Deadlines are tasks that need to be done before a specific date/time e.g., submit report by 11/10/2019 5pm + */ +public class Deadline extends Task { + protected String by; + + public Deadline(String description, String by) { + super(description); + this.by = by; + } + + @Override + public String toString() { + return "[D]" + super.toString() + " (by: " + by + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 21e06ce079..221445e715 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -3,14 +3,14 @@ public class Duke { private static String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; - private Task[] tasks = new Task[100]; private int numInputsStored = 0; - private void storeTask(String title) { - this.tasks[numInputsStored++] = new Task(title); - System.out.println(String.format("added: %s", title)); + private void storeTask(Task task) { + this.tasks[numInputsStored++] = task; + System.out.println(String.format("Got it. I've added this task:\n\t%s\nNow you have %d tasks in the list.", task, numInputsStored)); } + private void getTasks() { System.out.println("Here are the tasks in your list:"); for (int i = 0; i < this.numInputsStored; i++) { @@ -21,39 +21,70 @@ private void getTasks() { private Task getTaskAtIndex(int index) { return this.tasks[index]; } + private void markTaskAtIndexAsComplete(int index) { Task task = this.getTaskAtIndex(index-1); if (task == null) { - System.out.println("No task found. Are you sure you entered the correct index?"); + System.out.printf("No task found. You only have %d tasks but you referenced a task at index %d", + this.numInputsStored, + index); } else { task.markAsCompleted(); } } + private void markTaskAtIndexAsIncomplete(int index) { Task task = this.getTaskAtIndex(index-1); if (task == null) { - System.out.println("No task found. Are you sure you entered the correct index?"); + System.out.printf("No task found. You only have %d tasks but you referenced a task at index %d", + this.numInputsStored, + index); } else { task.markAsIncomplete(); } } - private boolean parseAndHandleCommand(String command) { + private boolean handleActionAndCommand(String command, Action action) { String[] components = command.split(" "); - try { - String action = components[0]; - int taskIndex = Integer.parseInt(components[1]); - if (action.equals("mark")) { - this.markTaskAtIndexAsComplete(taskIndex); - } + String contents = command.substring(action.label.length()).trim(); - if (action.equals("unmark")) { - this.markTaskAtIndexAsIncomplete(taskIndex); + try { + switch (action) { + case Mark: + this.markTaskAtIndexAsComplete(Integer.parseInt(contents)); + break; + case Unmark: + this.markTaskAtIndexAsIncomplete(Integer.parseInt(contents)); + break; + case Todo: + this.storeTask(new Todo(contents)); + break; + case Deadline: + String[] deadlineComponents = contents.split(" /by "); + + this.storeTask( + new Deadline(deadlineComponents[0].trim(), + deadlineComponents[1].trim()) + ); + break; + + case Event: + String[] eventComponents = contents.split(" /at "); + + this.storeTask( + new Event(eventComponents[0].trim(), + eventComponents[1].trim()) + ); + break; } return true; } catch (NumberFormatException e) { + System.out.println("Could not parse the command"); + return false; + } catch (IndexOutOfBoundsException e) { + System.out.println("Could not parse the command"); return false; } } @@ -76,15 +107,16 @@ public static void main(String[] args) { if (command.equals("bye")) { System.out.println(GOODBYE_MESSAGE); break; - } - - if (command.equals("list")) { + } else if (command.equals("list")) { duke.getTasks(); - continue; - } - - if (!duke.parseAndHandleCommand(command)) { - duke.storeTask(command); + } else { + Action action = Action.parseCommand(command); + + if (action != null) { + duke.handleActionAndCommand(command, action); + } else { + System.out.println("Invalid command given.\n"); + } } } diff --git a/src/main/java/Event.java b/src/main/java/Event.java new file mode 100644 index 0000000000..93023cca45 --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,17 @@ +/** + * Events are tasks that start at a specific time and ends at a specific time e.g., team project meeting on 2/10/2019 2-4pm + */ +public class Event extends Task { + + protected String at; + + public Event(String description, String at) { + super(description); + this.at = at; + } + + @Override + public String toString() { + return "[E]" + super.toString() + " (at: " + at + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/Todo.java b/src/main/java/Todo.java new file mode 100644 index 0000000000..b6769d2912 --- /dev/null +++ b/src/main/java/Todo.java @@ -0,0 +1,13 @@ +/** + * Todos are tasks without any date/time attached to it e.g., visit new theme park + */ +public class Todo extends Task { + public Todo(String title) { + super(title); + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } +} \ No newline at end of file From 5c79a7a9ecbdbc07a296abf5f99eb7d5863c5b7b Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 18 Aug 2022 19:40:10 +0800 Subject: [PATCH 05/31] Handle various errors such as incorrect inputs entered by the user. --- src/main/java/Action.java | 8 +- src/main/java/Duke.java | 97 ++++++++++++-------- src/main/java/EmptyTitleException.java | 7 ++ src/main/java/InvalidCommandException.java | 7 ++ src/main/java/InvalidDeadlineException.java | 8 ++ src/main/java/InvalidEventException.java | 7 ++ src/main/java/InvalidTaskIndexException.java | 5 + src/main/java/NoSuchTaskException.java | 15 +++ 8 files changed, 113 insertions(+), 41 deletions(-) create mode 100644 src/main/java/EmptyTitleException.java create mode 100644 src/main/java/InvalidCommandException.java create mode 100644 src/main/java/InvalidDeadlineException.java create mode 100644 src/main/java/InvalidEventException.java create mode 100644 src/main/java/InvalidTaskIndexException.java create mode 100644 src/main/java/NoSuchTaskException.java diff --git a/src/main/java/Action.java b/src/main/java/Action.java index 5189e20399..37fbaa2e1e 100644 --- a/src/main/java/Action.java +++ b/src/main/java/Action.java @@ -1,3 +1,5 @@ +import java.util.Arrays; + public enum Action { Todo("todo"), Deadline("deadline"), @@ -10,12 +12,14 @@ private Action(String label) { this.label = label; } - public static Action parseCommand(String command) { + public static Action parseCommand(String command) throws InvalidCommandException { for (Action action : values()) { if (command.startsWith(action.label)) { return action; } } - return null; + + throw new InvalidCommandException( + "Could not determine the command. Valid commands include: todo, deadline, event, mark, unmark"); } } diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 221445e715..68918fd6cb 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -4,64 +4,85 @@ public class Duke { private static String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; private Task[] tasks = new Task[100]; - private int numInputsStored = 0; + private int numTasks = 0; private void storeTask(Task task) { - this.tasks[numInputsStored++] = task; - System.out.println(String.format("Got it. I've added this task:\n\t%s\nNow you have %d tasks in the list.", task, numInputsStored)); + this.tasks[numTasks++] = task; + System.out.println(String.format("Got it. I've added this task:\n\t%s\nNow you have %d tasks in the list.", task, numTasks)); } private void getTasks() { System.out.println("Here are the tasks in your list:"); - for (int i = 0; i < this.numInputsStored; i++) { + for (int i = 0; i < this.numTasks; i++) { System.out.println(String.format("%d. %s", i+1, this.tasks[i])); } } - private Task getTaskAtIndex(int index) { - return this.tasks[index]; + private Task getTaskAtIndex(String index) throws InvalidTaskIndexException { + try { + int i = Integer.parseInt(index); + return this.tasks[i-1]; + } catch (NumberFormatException e) { + throw new InvalidTaskIndexException(); + } } - private void markTaskAtIndexAsComplete(int index) { - Task task = this.getTaskAtIndex(index-1); - if (task == null) { - System.out.printf("No task found. You only have %d tasks but you referenced a task at index %d", - this.numInputsStored, - index); - } else { - task.markAsCompleted(); + private void markTaskAtIndexAsComplete(String index) throws InvalidCommandException { + try { + Task task = this.getTaskAtIndex(index); + + if (task == null) + throw new NoSuchTaskException(this.numTasks, index); + + else + task.markAsCompleted(); + + } catch (InvalidTaskIndexException e) { + throw e; } } - private void markTaskAtIndexAsIncomplete(int index) { - Task task = this.getTaskAtIndex(index-1); - if (task == null) { - System.out.printf("No task found. You only have %d tasks but you referenced a task at index %d", - this.numInputsStored, - index); - } else { - task.markAsIncomplete(); + private void markTaskAtIndexAsIncomplete(String index) throws InvalidCommandException { + try { + Task task = this.getTaskAtIndex(index); + + if (task == null) + throw new NoSuchTaskException(this.numTasks, index); + + else + task.markAsIncomplete(); + + } catch (InvalidTaskIndexException e) { + throw e; } } - private boolean handleActionAndCommand(String command, Action action) { + private void handleActionAndCommand(String command, Action action) throws InvalidCommandException { String[] components = command.split(" "); String contents = command.substring(action.label.length()).trim(); try { switch (action) { case Mark: - this.markTaskAtIndexAsComplete(Integer.parseInt(contents)); + this.markTaskAtIndexAsComplete(contents); break; case Unmark: - this.markTaskAtIndexAsIncomplete(Integer.parseInt(contents)); + this.markTaskAtIndexAsIncomplete(contents); break; case Todo: - this.storeTask(new Todo(contents)); + if (contents.isBlank()) + throw new EmptyTitleException(); + else + this.storeTask(new Todo(contents)); break; case Deadline: String[] deadlineComponents = contents.split(" /by "); + if (deadlineComponents.length != 2) + throw new InvalidDeadlineException(); + else if (deadlineComponents[0].isBlank()) + throw new EmptyTitleException(); + else this.storeTask( new Deadline(deadlineComponents[0].trim(), deadlineComponents[1].trim()) @@ -70,7 +91,11 @@ private boolean handleActionAndCommand(String command, Action action) { case Event: String[] eventComponents = contents.split(" /at "); - + if (eventComponents.length != 2) + throw new InvalidEventException(); + else if (eventComponents[0].isBlank()) + throw new EmptyTitleException(); + else this.storeTask( new Event(eventComponents[0].trim(), eventComponents[1].trim()) @@ -78,14 +103,9 @@ private boolean handleActionAndCommand(String command, Action action) { break; } - return true; - } catch (NumberFormatException e) { - System.out.println("Could not parse the command"); - return false; - } catch (IndexOutOfBoundsException e) { - System.out.println("Could not parse the command"); - return false; + } catch (InvalidCommandException e) { + throw e; } } @@ -110,12 +130,11 @@ public static void main(String[] args) { } else if (command.equals("list")) { duke.getTasks(); } else { - Action action = Action.parseCommand(command); - - if (action != null) { + try { + Action action = Action.parseCommand(command); duke.handleActionAndCommand(command, action); - } else { - System.out.println("Invalid command given.\n"); + } catch (InvalidCommandException e) { + System.out.println("Invalid command given.\n" + e.getMessage()); } } } diff --git a/src/main/java/EmptyTitleException.java b/src/main/java/EmptyTitleException.java new file mode 100644 index 0000000000..9620cdae01 --- /dev/null +++ b/src/main/java/EmptyTitleException.java @@ -0,0 +1,7 @@ +public class EmptyTitleException extends InvalidCommandException { + public EmptyTitleException() { + super("Cannot create a task with an empty title!"); + } +} + + diff --git a/src/main/java/InvalidCommandException.java b/src/main/java/InvalidCommandException.java new file mode 100644 index 0000000000..01c15e9163 --- /dev/null +++ b/src/main/java/InvalidCommandException.java @@ -0,0 +1,7 @@ +public class InvalidCommandException extends Exception { + public InvalidCommandException(String message) { + super(message); + } +} + + diff --git a/src/main/java/InvalidDeadlineException.java b/src/main/java/InvalidDeadlineException.java new file mode 100644 index 0000000000..4bf085303c --- /dev/null +++ b/src/main/java/InvalidDeadlineException.java @@ -0,0 +1,8 @@ +public class InvalidDeadlineException extends InvalidCommandException { + public InvalidDeadlineException() { + super("Could not parse deadline. To create a deadline, " + + "please use the format in this example: " + + "deadline return book /by Sunday"); + } +} + diff --git a/src/main/java/InvalidEventException.java b/src/main/java/InvalidEventException.java new file mode 100644 index 0000000000..2905d47ee7 --- /dev/null +++ b/src/main/java/InvalidEventException.java @@ -0,0 +1,7 @@ +public class InvalidEventException extends InvalidCommandException { + public InvalidEventException() { + super("Could not parse event. To create an event, " + + "please use the format in this example: " + + "event project meeting /at Mon 2-4pm"); + } +} diff --git a/src/main/java/InvalidTaskIndexException.java b/src/main/java/InvalidTaskIndexException.java new file mode 100644 index 0000000000..3b0295bb1e --- /dev/null +++ b/src/main/java/InvalidTaskIndexException.java @@ -0,0 +1,5 @@ +public class InvalidTaskIndexException extends InvalidCommandException { + public InvalidTaskIndexException() { + super("Command should be followed by a number. For example: mark 2, unmark 3, delete 4."); + } +} diff --git a/src/main/java/NoSuchTaskException.java b/src/main/java/NoSuchTaskException.java new file mode 100644 index 0000000000..be786073b0 --- /dev/null +++ b/src/main/java/NoSuchTaskException.java @@ -0,0 +1,15 @@ +public class NoSuchTaskException extends InvalidCommandException { + public NoSuchTaskException(int numTasks, int index) { + super( + String.format("No task found. You only have %d tasks but you referenced a task at index %d", + numTasks, + index)); + } + + public NoSuchTaskException(int numTasks, String index) { + super( + String.format("No task found. You only have %d tasks but you referenced a task at index %s", + numTasks, + index)); + } +} From 0a380b6799ee845a6cc55737176f744e4288ca44 Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 18 Aug 2022 20:04:09 +0800 Subject: [PATCH 06/31] Add support for deleting tasks from the list. --- src/main/java/Action.java | 5 +- src/main/java/Duke.java | 149 +++++++++++++++++++------------------- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/src/main/java/Action.java b/src/main/java/Action.java index 37fbaa2e1e..5304657409 100644 --- a/src/main/java/Action.java +++ b/src/main/java/Action.java @@ -5,7 +5,8 @@ public enum Action { Deadline("deadline"), Event("event"), Mark("mark"), - Unmark("unmark"); + Unmark("unmark"), + Delete("delete"); public final String label; private Action(String label) { @@ -20,6 +21,6 @@ public static Action parseCommand(String command) throws InvalidCommandException } throw new InvalidCommandException( - "Could not determine the command. Valid commands include: todo, deadline, event, mark, unmark"); + "Could not determine the command. Valid commands include: todo, deadline, event, mark, unmark, delete"); } } diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 68918fd6cb..f850ab5bef 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,111 +1,110 @@ +import java.util.ArrayList; +import java.util.List; import java.util.Scanner; public class Duke { private static String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; - private Task[] tasks = new Task[100]; - private int numTasks = 0; + private ArrayList tasks = new ArrayList<>(); + private int getNumTasks() { + return this.tasks.size(); + } + + private String getNumTasksAsString() { + return String.format("Now you have %d tasks in the list.", this.getNumTasks()); + } private void storeTask(Task task) { - this.tasks[numTasks++] = task; - System.out.println(String.format("Got it. I've added this task:\n\t%s\nNow you have %d tasks in the list.", task, numTasks)); + this.tasks.add(task); + System.out.println( + String.format("Got it. I've added this task:\n\t%s\n", + task, + this.getNumTasksAsString())); } private void getTasks() { System.out.println("Here are the tasks in your list:"); - for (int i = 0; i < this.numTasks; i++) { - System.out.println(String.format("%d. %s", i+1, this.tasks[i])); + for (int i = 0; i < this.getNumTasks(); i++) { + System.out.println(String.format("%d. %s", i+1, this.tasks.get(i))); } } - private Task getTaskAtIndex(String index) throws InvalidTaskIndexException { + private Task getTaskAtIndex(String index) throws InvalidCommandException { try { int i = Integer.parseInt(index); - return this.tasks[i-1]; + return this.tasks.get(i-1); } catch (NumberFormatException e) { throw new InvalidTaskIndexException(); + } catch (IndexOutOfBoundsException e) { + throw new NoSuchTaskException(this.getNumTasks(), index); } } private void markTaskAtIndexAsComplete(String index) throws InvalidCommandException { - try { - Task task = this.getTaskAtIndex(index); - - if (task == null) - throw new NoSuchTaskException(this.numTasks, index); - - else - task.markAsCompleted(); - - } catch (InvalidTaskIndexException e) { - throw e; - } + Task task = this.getTaskAtIndex(index); + task.markAsCompleted(); } private void markTaskAtIndexAsIncomplete(String index) throws InvalidCommandException { - try { - Task task = this.getTaskAtIndex(index); - - if (task == null) - throw new NoSuchTaskException(this.numTasks, index); - - else - task.markAsIncomplete(); + Task task = this.getTaskAtIndex(index); + task.markAsIncomplete(); + } - } catch (InvalidTaskIndexException e) { - throw e; - } + private void deleteTaskAtIndex(String index) throws InvalidCommandException { + Task task = this.getTaskAtIndex(index); + this.tasks.remove(Integer.parseInt(index) - 1); + System.out.println( + String.format("Noted. I've removed this task:\n\t%s\n", + task, + this.getNumTasksAsString())); } private void handleActionAndCommand(String command, Action action) throws InvalidCommandException { String[] components = command.split(" "); String contents = command.substring(action.label.length()).trim(); - try { - switch (action) { - case Mark: - this.markTaskAtIndexAsComplete(contents); - break; - case Unmark: - this.markTaskAtIndexAsIncomplete(contents); - break; - case Todo: - if (contents.isBlank()) - throw new EmptyTitleException(); - else - this.storeTask(new Todo(contents)); - break; - case Deadline: - String[] deadlineComponents = contents.split(" /by "); - - if (deadlineComponents.length != 2) - throw new InvalidDeadlineException(); - else if (deadlineComponents[0].isBlank()) - throw new EmptyTitleException(); - else - this.storeTask( - new Deadline(deadlineComponents[0].trim(), - deadlineComponents[1].trim()) - ); - break; - - case Event: - String[] eventComponents = contents.split(" /at "); - if (eventComponents.length != 2) - throw new InvalidEventException(); - else if (eventComponents[0].isBlank()) - throw new EmptyTitleException(); - else - this.storeTask( - new Event(eventComponents[0].trim(), - eventComponents[1].trim()) - ); - break; - } - + switch (action) { + case Mark: + this.markTaskAtIndexAsComplete(contents); + break; + case Unmark: + this.markTaskAtIndexAsIncomplete(contents); + break; + case Delete: + this.deleteTaskAtIndex(contents); + break; + case Todo: + if (contents.isBlank()) + throw new EmptyTitleException(); + else + this.storeTask(new Todo(contents)); + break; + case Deadline: + String[] deadlineComponents = contents.split(" /by "); + + if (deadlineComponents.length != 2) + throw new InvalidDeadlineException(); + else if (deadlineComponents[0].isBlank()) + throw new EmptyTitleException(); + else + this.storeTask( + new Deadline(deadlineComponents[0].trim(), + deadlineComponents[1].trim()) + ); + break; - } catch (InvalidCommandException e) { - throw e; + case Event: + String[] eventComponents = contents.split(" /at "); + if (eventComponents.length != 2) + throw new InvalidEventException(); + else if (eventComponents[0].isBlank()) + throw new EmptyTitleException(); + else + this.storeTask( + new Event(eventComponents[0].trim(), + eventComponents[1].trim()) + ); + break; } } From 76ba80de1c7ff6aa49d3fd714623d97539b08c69 Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 18 Aug 2022 20:04:28 +0800 Subject: [PATCH 07/31] Use the input/output redirection technique to semi-automate the testing of Duke. --- text-ui-test/EXPECTED.TXT | 79 ++++++++++++++++++++++++++++++++++++--- text-ui-test/input.txt | 26 +++++++++++++ 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..ff014b2307 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,74 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| +Hello! I'm Duke +What can I do for you? +Got it. I've added this task: + [T][ ] read book +Got it. I've added this task: + [T][ ] borrow book + +Got it. I've added this task: + [T][ ] return book + +Invalid command given. +Could not determine the command. Valid commands include: todo, deadline, event, mark, unmark, delete +Here are the tasks in your list: +1. [T][ ] read book +2. [T][ ] borrow book +3. [T][ ] return book +Got it. I've added this task: + [D][ ] return book (by: Sunday) + +Got it. I've added this task: + [E][ ] project meeting (at: Mon 2-4pm) + +Here are the tasks in your list: +1. [T][ ] read book +2. [T][ ] borrow book +3. [T][ ] return book +4. [D][ ] return book (by: Sunday) +5. [E][ ] project meeting (at: Mon 2-4pm) +Invalid command given. +Cannot create a task with an empty title! +Invalid command given. +Could not parse deadline. To create a deadline, please use the format in this example: deadline return book /by Sunday +Invalid command given. +Could not parse event. To create an event, please use the format in this example: event project meeting /at Mon 2-4pm +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +Noted. I've removed this task: + [T][ ] read book + +Here are the tasks in your list: +1. [T][ ] borrow book +2. [T][ ] return book +3. [D][ ] return book (by: Sunday) +4. [E][ ] project meeting (at: Mon 2-4pm) +Invalid command given. +No task found. You only have 4 tasks but you referenced a task at index 100 +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +Nice! I've marked this task as done: + [T][X] return book +Here are the tasks in your list: +1. [T][ ] borrow book +2. [T][X] return book +3. [D][ ] return book (by: Sunday) +4. [E][ ] project meeting (at: Mon 2-4pm) +Invalid command given. +No task found. You only have 4 tasks but you referenced a task at index 100 +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +OK, I've marked this task as not done yet: + [T][ ] return book +Here are the tasks in your list: +1. [T][ ] borrow book +2. [T][ ] return book +3. [D][ ] return book (by: Sunday) +4. [E][ ] project meeting (at: Mon 2-4pm) +Invalid command given. +Could not determine the command. Valid commands include: todo, deadline, event, mark, unmark, delete +Bye. Hope to see you again soon! diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..7a00305ede 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,26 @@ +todo read book +todo borrow book +todo return book +list blah blah +list +deadline return book /by Sunday +event project meeting /at Mon 2-4pm +list +todo +deadline /bys Sunday +event /atMon 2-4pm +delete +delete one +delete 1 +list +mark 100 +mark +mark one +mark 2 +list +unmark 100 +unmark one +unmark 2 +list +byeee +bye From a8dd5e0e8f8b2db40012d2528430f7da4427680f Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 19 Aug 2022 17:54:18 +0800 Subject: [PATCH 08/31] Save the tasks in the hard disk automatically whenever the task list changes. Load the data from the hard disk when Duke starts up. --- data/duke.txt | 3 + src/main/java/Deadline.java | 26 ++++++ src/main/java/Duke.java | 6 +- src/main/java/EmptyTitleException.java | 1 - src/main/java/Event.java | 27 +++++++ src/main/java/ParsingTaskException.java | 5 ++ src/main/java/Task.java | 101 ++++++++++++++++++++++++ src/main/java/Todo.java | 24 ++++++ 8 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 data/duke.txt create mode 100644 src/main/java/ParsingTaskException.java diff --git a/data/duke.txt b/data/duke.txt new file mode 100644 index 0000000000..07c9045d99 --- /dev/null +++ b/data/duke.txt @@ -0,0 +1,3 @@ +T,1,return book +D,1,return book ,Sunday +E,0,project meeting ,Mon 2-4pm diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 1250470bb8..a48f97e028 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -9,8 +9,34 @@ public Deadline(String description, String by) { this.by = by; } + public Deadline(String description, String by, boolean completed) { + super(description, completed); + this.by = by; + } @Override public String toString() { return "[D]" + super.toString() + " (by: " + by + ")"; } + + @Override + public String toSaveString() { + return "D," + super.toSaveString() + String.format(",%s", this.by); + } + + public static Deadline parse(String data) throws ParsingTaskException { + String[] components = data.split(","); + if (components.length != 4) { + throw new ParsingTaskException(String.format("Todos require 4 components, but found %d.", components.length)); + } + try { + boolean completed = Integer.parseInt(components[1]) == 1; + + String description = components[2]; + String by = components[3]; + + return new Deadline(description, by, completed); + } catch (NumberFormatException e) { + throw new ParsingTaskException(String.format("Expected a number at component 1, but found %s", components[1])); + } + } } \ No newline at end of file diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index f850ab5bef..c7160d4713 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,11 +1,11 @@ - +import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class Duke { private static String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; - private ArrayList tasks = new ArrayList<>(); + private List tasks = Task.loadSavedTasks(); private int getNumTasks() { return this.tasks.size(); @@ -106,6 +106,8 @@ else if (eventComponents[0].isBlank()) ); break; } + + Task.saveTaskList(this.tasks); } /** diff --git a/src/main/java/EmptyTitleException.java b/src/main/java/EmptyTitleException.java index 9620cdae01..2da6e77f79 100644 --- a/src/main/java/EmptyTitleException.java +++ b/src/main/java/EmptyTitleException.java @@ -4,4 +4,3 @@ public EmptyTitleException() { } } - diff --git a/src/main/java/Event.java b/src/main/java/Event.java index 93023cca45..39d3ca1616 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -10,8 +10,35 @@ public Event(String description, String at) { this.at = at; } + public Event(String description, String at, boolean completed) { + super(description, completed); + this.at = at; + } + @Override public String toString() { return "[E]" + super.toString() + " (at: " + at + ")"; } + + @Override + public String toSaveString() { + return "E," + super.toSaveString() + String.format(",%s", this.at); + } + + public static Event parse(String data) throws ParsingTaskException { + String[] components = data.split(","); + if (components.length != 4) { + throw new ParsingTaskException(String.format("Events require 4 components, but only found %d.", components.length)); + } + try { + boolean completed = Integer.parseInt(components[1]) == 1; + + String description = components[2]; + String at = components[3]; + + return new Event(description, at, completed); + } catch (NumberFormatException e) { + throw new ParsingTaskException(String.format("Expected a number at component 1, but found %s", components[1])); + } + } } \ No newline at end of file diff --git a/src/main/java/ParsingTaskException.java b/src/main/java/ParsingTaskException.java new file mode 100644 index 0000000000..de079aec32 --- /dev/null +++ b/src/main/java/ParsingTaskException.java @@ -0,0 +1,5 @@ +public class ParsingTaskException extends Exception { + public ParsingTaskException(String addtionalMessage) { + super("An error occurred parsing task data!\n" + addtionalMessage); + } +} diff --git a/src/main/java/Task.java b/src/main/java/Task.java index 56be22d99c..aa038b9a83 100644 --- a/src/main/java/Task.java +++ b/src/main/java/Task.java @@ -1,4 +1,15 @@ +import java.io.FileNotFoundException; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.util.Scanner; + public class Task { + private static final String DATA_PATH = new File("").getAbsolutePath() + "/data/duke.txt"; private String title; private boolean completed; @@ -7,6 +18,11 @@ public class Task { this.completed = false; } + Task(String title, boolean completed) { + this.title = title; + this.completed = completed; + } + public void markAsCompleted() { this.completed = true; System.out.println(String.format("Nice! I've marked this task as done:\n\t%s", this)); @@ -26,4 +42,89 @@ public String toString() { return String.format("[%s] %s", this.getStatusIcon(), this.title); } + public String toSaveString() { + return String.format("%d,%s", this.completed ? 1 : 0, this.title); + } + + private static Task parse(String data) throws ParsingTaskException { + String[] components = data.split(","); + + if (components.length == 0) { + throw new ParsingTaskException("Data was empty or not formatted properly."); + } + String type = components[0]; + + if (type.equals("T")) { + return Todo.parse(data); + } else if (type.equals("D")) { + return Deadline.parse(data); + } else if (type.equals("E")) { + return Event.parse(data); + } else { + throw new ParsingTaskException(String.format("Task was of unknown type: %s", type)); + } + } + + public static List loadSavedTasks() { + List tasks = new ArrayList<>(); + try { + + File file = new File(DATA_PATH); + + file.getParentFile().mkdirs(); + file.createNewFile(); + + Scanner scanner = new Scanner(file); + + while (scanner.hasNextLine()) { + String data = scanner.nextLine(); + try { + tasks.add(Task.parse(data)); + } catch (ParsingTaskException e) { + System.out.println(e); + continue; + } + } + + scanner.close(); + + return tasks; + + } catch (Exception e) { + System.out.println("An error occurred.\n" + e); + } finally { + return tasks; + } + } + + public static void saveTaskList(List tasks) { + try { + // Create new file + String content = ""; + for (Task t : tasks ) { + content += t.toSaveString() + "\n"; + } + + File file = new File(DATA_PATH); + + // If the file doesn't exist, then create it + file.getParentFile().mkdirs(); + file.createNewFile(); + + + FileWriter fw = new FileWriter(file.getAbsoluteFile()); + BufferedWriter bw = new BufferedWriter(fw); + + // Write in file + bw.write(content); + + // Close connection + bw.close(); + + System.out.println("Saved tasks list successfully!"); + } + catch (Exception e){ + System.out.println(e + DATA_PATH); + } + } } diff --git a/src/main/java/Todo.java b/src/main/java/Todo.java index b6769d2912..a9c7930b4b 100644 --- a/src/main/java/Todo.java +++ b/src/main/java/Todo.java @@ -6,8 +6,32 @@ public Todo(String title) { super(title); } + public Todo(String title, boolean completed) { + super(title, completed); + } + @Override public String toString() { return "[T]" + super.toString(); } + + @Override + public String toSaveString() { + return "T," + super.toSaveString(); + } + + public static Todo parse(String data) throws ParsingTaskException { + String[] components = data.split(","); + if (components.length != 3) { + throw new ParsingTaskException(String.format("Todos require 3 components, but found %d.", components.length)); + } + try { + boolean completed = Integer.parseInt(components[1]) == 1; + String title = components[2]; + + return new Todo(title, completed); + } catch (NumberFormatException e) { + throw new ParsingTaskException(String.format("Expected a number at component 1, but found %s", components[1])); + } + } } \ No newline at end of file From 2095cd03f7e913f62aacbcf80cc1e6c873c60dfe Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 19 Aug 2022 18:07:22 +0800 Subject: [PATCH 09/31] Store deadline dates as a java.time.LocalDate in task objects. Accept dates in a format such as yyyy-mm-dd format (e.g., 2019-10-15) and print in a different format such as MMM dd yyyy e.g., (Oct 15 2019). --- src/main/java/Deadline.java | 21 ++++++++++++++++---- src/main/java/Event.java | 22 +++++++++++++++++---- src/main/java/InvalidDeadlineException.java | 2 +- src/main/java/Task.java | 5 +++++ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 1250470bb8..87e7dd47e8 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -1,16 +1,29 @@ +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + /** * Deadlines are tasks that need to be done before a specific date/time e.g., submit report by 11/10/2019 5pm */ public class Deadline extends Task { - protected String by; + protected LocalDate by; - public Deadline(String description, String by) { + public Deadline(String description, String by) throws InvalidDeadlineException { super(description); - this.by = by; + try { + this.by = LocalDate.parse(by); + } catch (DateTimeParseException e) { + throw new InvalidDeadlineException(); + } } + public String getFormattedDate() { + return this.by.format(DateTimeFormatter.ofLocalizedDate(Task.DATE_FORMAT)); + } @Override public String toString() { - return "[D]" + super.toString() + " (by: " + by + ")"; + return "[D]" + super.toString() + + " (by: " + this.getFormattedDate() + + ")"; } } \ No newline at end of file diff --git a/src/main/java/Event.java b/src/main/java/Event.java index 93023cca45..bc94768ad6 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -1,17 +1,31 @@ +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + /** * Events are tasks that start at a specific time and ends at a specific time e.g., team project meeting on 2/10/2019 2-4pm */ public class Event extends Task { - protected String at; + protected LocalDate at; - public Event(String description, String at) { + public Event(String description, String at) throws InvalidEventException { super(description); - this.at = at; + try { + this.at = LocalDate.parse(at); + } catch (DateTimeParseException e) { + throw new InvalidEventException(); + } + } + + public String getFormattedDate() { + return this.at.format(DateTimeFormatter.ofLocalizedDate(Task.DATE_FORMAT)); } @Override public String toString() { - return "[E]" + super.toString() + " (at: " + at + ")"; + return "[E]" + super.toString() + + " (at: " + this.getFormattedDate() + + ")"; } } \ No newline at end of file diff --git a/src/main/java/InvalidDeadlineException.java b/src/main/java/InvalidDeadlineException.java index 4bf085303c..9cc26af50b 100644 --- a/src/main/java/InvalidDeadlineException.java +++ b/src/main/java/InvalidDeadlineException.java @@ -2,7 +2,7 @@ public class InvalidDeadlineException extends InvalidCommandException { public InvalidDeadlineException() { super("Could not parse deadline. To create a deadline, " + "please use the format in this example: " - + "deadline return book /by Sunday"); + + "deadline return book /by yyyy-MM-dd"); } } diff --git a/src/main/java/Task.java b/src/main/java/Task.java index 56be22d99c..f994e47df1 100644 --- a/src/main/java/Task.java +++ b/src/main/java/Task.java @@ -1,7 +1,12 @@ +import java.time.format.FormatStyle; +import java.util.Formatter; + public class Task { private String title; private boolean completed; + public static final FormatStyle DATE_FORMAT = FormatStyle.MEDIUM; + Task(String title) { this.title = title; this.completed = false; From d262c934097a9459a59e805651c6c7db3dd4ada6 Mon Sep 17 00:00:00 2001 From: Brandon Date: Sat, 20 Aug 2022 15:11:39 +0800 Subject: [PATCH 10/31] Fix unable to parse date when it has surrounding whitespace --- data/duke.txt | 4 ++-- src/main/java/Deadline.java | 4 ++-- src/main/java/Event.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/duke.txt b/data/duke.txt index 07c9045d99..349e7d15e7 100644 --- a/data/duke.txt +++ b/data/duke.txt @@ -1,3 +1,3 @@ T,1,return book -D,1,return book ,Sunday -E,0,project meeting ,Mon 2-4pm +D,1,return book ,2022-09-12 +E,0,project meeting ,2022-10-11 diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 63f0b51fbf..b23e19adf8 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -11,7 +11,7 @@ public class Deadline extends Task { public Deadline(String description, String by) throws InvalidDeadlineException { super(description); try { - this.by = LocalDate.parse(by); + this.by = LocalDate.parse(by.trim()); } catch (DateTimeParseException e) { throw new InvalidDeadlineException(); } @@ -20,7 +20,7 @@ public Deadline(String description, String by) throws InvalidDeadlineException { public Deadline(String description, String by, boolean completed) throws InvalidDeadlineException { super(description, completed); try { - this.by = LocalDate.parse(by); + this.by = LocalDate.parse(by.trim()); } catch (DateTimeParseException e) { throw new InvalidDeadlineException(); } diff --git a/src/main/java/Event.java b/src/main/java/Event.java index ea4166ac24..8fdd307fe2 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -12,7 +12,7 @@ public class Event extends Task { public Event(String description, String at) throws InvalidEventException { super(description); try { - this.at = LocalDate.parse(at); + this.at = LocalDate.parse(at.trim()); } catch (DateTimeParseException e) { throw new InvalidEventException(); } @@ -25,7 +25,7 @@ public String getFormattedDate() { public Event(String description, String at, boolean completed) throws InvalidEventException { super(description, completed); try { - this.at = LocalDate.parse(at); + this.at = LocalDate.parse(at.trim()); } catch (DateTimeParseException e) { throw new InvalidEventException(); } From bbd1cef823a3294b712a37b75a80437ef3c25d8c Mon Sep 17 00:00:00 2001 From: Brandon Date: Sat, 20 Aug 2022 15:17:02 +0800 Subject: [PATCH 11/31] Organise the classes into suitable java packages. --- README.md | 4 ++-- data/duke.txt | 1 + src/main/java/{ => duke}/Duke.java | 11 ++++++++--- src/main/java/{ => duke/Enums}/Action.java | 3 ++- .../{ => duke/Exceptions}/EmptyTitleException.java | 2 ++ .../Exceptions}/InvalidCommandException.java | 2 ++ .../Exceptions}/InvalidDeadlineException.java | 2 ++ .../{ => duke/Exceptions}/InvalidEventException.java | 2 ++ .../Exceptions}/InvalidTaskIndexException.java | 2 ++ .../{ => duke/Exceptions}/NoSuchTaskException.java | 2 ++ .../{ => duke/Exceptions}/ParsingTaskException.java | 2 ++ src/main/java/{ => duke/Task}/Deadline.java | 5 ++++- src/main/java/{ => duke/Task}/Event.java | 3 +++ src/main/java/{ => duke/Task}/Task.java | 8 +++----- src/main/java/{ => duke/Task}/Todo.java | 3 +++ text-ui-test/EXPECTED.TXT | 2 +- text-ui-test/runtest.bat | 2 +- 17 files changed, 42 insertions(+), 14 deletions(-) rename src/main/java/{ => duke}/Duke.java (96%) rename src/main/java/{ => duke/Enums}/Action.java (93%) rename src/main/java/{ => duke/Exceptions}/EmptyTitleException.java (86%) rename src/main/java/{ => duke/Exceptions}/InvalidCommandException.java (84%) rename src/main/java/{ => duke/Exceptions}/InvalidDeadlineException.java (92%) rename src/main/java/{ => duke/Exceptions}/InvalidEventException.java (91%) rename src/main/java/{ => duke/Exceptions}/InvalidTaskIndexException.java (89%) rename src/main/java/{ => duke/Exceptions}/NoSuchTaskException.java (95%) rename src/main/java/{ => duke/Exceptions}/ParsingTaskException.java (88%) rename src/main/java/{ => duke/Task}/Deadline.java (97%) rename src/main/java/{ => duke/Task}/Event.java (97%) rename src/main/java/{ => duke/Task}/Task.java (94%) rename src/main/java/{ => duke/Task}/Todo.java (96%) diff --git a/README.md b/README.md index 8715d4d915..0f2208ab64 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# duke.Duke project template This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. @@ -13,7 +13,7 @@ Prerequisites: JDK 11, update Intellij to the most recent version. 1. If there are any further prompts, accept the defaults. 1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
In the same dialog, set the **Project language level** field to the `SDK default` option. -3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: +3. After that, locate the `src/main/java/duke.Duke.java` file, right-click it, and choose `Run duke.Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: ``` Hello from ____ _ diff --git a/data/duke.txt b/data/duke.txt index 349e7d15e7..e087686f28 100644 --- a/data/duke.txt +++ b/data/duke.txt @@ -1,3 +1,4 @@ T,1,return book D,1,return book ,2022-09-12 E,0,project meeting ,2022-10-11 +D,1,borrow book,2022-09-17 diff --git a/src/main/java/Duke.java b/src/main/java/duke/Duke.java similarity index 96% rename from src/main/java/Duke.java rename to src/main/java/duke/Duke.java index c7160d4713..36fe899ae9 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/duke/Duke.java @@ -1,9 +1,14 @@ -import java.io.*; -import java.util.ArrayList; +package duke; + +import duke.Task.Task; +import duke.Enums.Action; +import duke.Exceptions.*; +import duke.Task.*; + import java.util.List; import java.util.Scanner; public class Duke { - private static String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; + private static String WELCOME_MESSAGE = "Hello! I'm duke.Duke\n" + "What can I do for you?"; private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; private List tasks = Task.loadSavedTasks(); diff --git a/src/main/java/Action.java b/src/main/java/duke/Enums/Action.java similarity index 93% rename from src/main/java/Action.java rename to src/main/java/duke/Enums/Action.java index 5304657409..06c73e2713 100644 --- a/src/main/java/Action.java +++ b/src/main/java/duke/Enums/Action.java @@ -1,4 +1,5 @@ -import java.util.Arrays; +package duke.Enums; +import duke.Exceptions.*; public enum Action { Todo("todo"), diff --git a/src/main/java/EmptyTitleException.java b/src/main/java/duke/Exceptions/EmptyTitleException.java similarity index 86% rename from src/main/java/EmptyTitleException.java rename to src/main/java/duke/Exceptions/EmptyTitleException.java index 2da6e77f79..12d1a88b04 100644 --- a/src/main/java/EmptyTitleException.java +++ b/src/main/java/duke/Exceptions/EmptyTitleException.java @@ -1,3 +1,5 @@ +package duke.Exceptions; + public class EmptyTitleException extends InvalidCommandException { public EmptyTitleException() { super("Cannot create a task with an empty title!"); diff --git a/src/main/java/InvalidCommandException.java b/src/main/java/duke/Exceptions/InvalidCommandException.java similarity index 84% rename from src/main/java/InvalidCommandException.java rename to src/main/java/duke/Exceptions/InvalidCommandException.java index 01c15e9163..9bbc1ac63c 100644 --- a/src/main/java/InvalidCommandException.java +++ b/src/main/java/duke/Exceptions/InvalidCommandException.java @@ -1,3 +1,5 @@ +package duke.Exceptions; + public class InvalidCommandException extends Exception { public InvalidCommandException(String message) { super(message); diff --git a/src/main/java/InvalidDeadlineException.java b/src/main/java/duke/Exceptions/InvalidDeadlineException.java similarity index 92% rename from src/main/java/InvalidDeadlineException.java rename to src/main/java/duke/Exceptions/InvalidDeadlineException.java index 9cc26af50b..0390817e38 100644 --- a/src/main/java/InvalidDeadlineException.java +++ b/src/main/java/duke/Exceptions/InvalidDeadlineException.java @@ -1,3 +1,5 @@ +package duke.Exceptions; + public class InvalidDeadlineException extends InvalidCommandException { public InvalidDeadlineException() { super("Could not parse deadline. To create a deadline, " diff --git a/src/main/java/InvalidEventException.java b/src/main/java/duke/Exceptions/InvalidEventException.java similarity index 91% rename from src/main/java/InvalidEventException.java rename to src/main/java/duke/Exceptions/InvalidEventException.java index 2905d47ee7..4df3ab81c7 100644 --- a/src/main/java/InvalidEventException.java +++ b/src/main/java/duke/Exceptions/InvalidEventException.java @@ -1,3 +1,5 @@ +package duke.Exceptions; + public class InvalidEventException extends InvalidCommandException { public InvalidEventException() { super("Could not parse event. To create an event, " diff --git a/src/main/java/InvalidTaskIndexException.java b/src/main/java/duke/Exceptions/InvalidTaskIndexException.java similarity index 89% rename from src/main/java/InvalidTaskIndexException.java rename to src/main/java/duke/Exceptions/InvalidTaskIndexException.java index 3b0295bb1e..cb735f29c7 100644 --- a/src/main/java/InvalidTaskIndexException.java +++ b/src/main/java/duke/Exceptions/InvalidTaskIndexException.java @@ -1,3 +1,5 @@ +package duke.Exceptions; + public class InvalidTaskIndexException extends InvalidCommandException { public InvalidTaskIndexException() { super("Command should be followed by a number. For example: mark 2, unmark 3, delete 4."); diff --git a/src/main/java/NoSuchTaskException.java b/src/main/java/duke/Exceptions/NoSuchTaskException.java similarity index 95% rename from src/main/java/NoSuchTaskException.java rename to src/main/java/duke/Exceptions/NoSuchTaskException.java index be786073b0..6733f2a4ac 100644 --- a/src/main/java/NoSuchTaskException.java +++ b/src/main/java/duke/Exceptions/NoSuchTaskException.java @@ -1,3 +1,5 @@ +package duke.Exceptions; + public class NoSuchTaskException extends InvalidCommandException { public NoSuchTaskException(int numTasks, int index) { super( diff --git a/src/main/java/ParsingTaskException.java b/src/main/java/duke/Exceptions/ParsingTaskException.java similarity index 88% rename from src/main/java/ParsingTaskException.java rename to src/main/java/duke/Exceptions/ParsingTaskException.java index de079aec32..3fab7efbc6 100644 --- a/src/main/java/ParsingTaskException.java +++ b/src/main/java/duke/Exceptions/ParsingTaskException.java @@ -1,3 +1,5 @@ +package duke.Exceptions; + public class ParsingTaskException extends Exception { public ParsingTaskException(String addtionalMessage) { super("An error occurred parsing task data!\n" + addtionalMessage); diff --git a/src/main/java/Deadline.java b/src/main/java/duke/Task/Deadline.java similarity index 97% rename from src/main/java/Deadline.java rename to src/main/java/duke/Task/Deadline.java index b23e19adf8..95c0d6c73e 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/duke/Task/Deadline.java @@ -1,3 +1,6 @@ +package duke.Task; +import duke.Exceptions.*; + import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -26,7 +29,7 @@ public Deadline(String description, String by, boolean completed) throws Invalid } } public String getFormattedDate() { - return this.by.format(DateTimeFormatter.ofLocalizedDate(Task.DATE_FORMAT)); + return this.by.format(DateTimeFormatter.ofLocalizedDate(DATE_FORMAT)); } @Override diff --git a/src/main/java/Event.java b/src/main/java/duke/Task/Event.java similarity index 97% rename from src/main/java/Event.java rename to src/main/java/duke/Task/Event.java index 8fdd307fe2..722462ea0f 100644 --- a/src/main/java/Event.java +++ b/src/main/java/duke/Task/Event.java @@ -1,3 +1,6 @@ +package duke.Task; +import duke.Exceptions.*; + import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; diff --git a/src/main/java/Task.java b/src/main/java/duke/Task/Task.java similarity index 94% rename from src/main/java/Task.java rename to src/main/java/duke/Task/Task.java index 6d71eb5895..0cdf0e62a0 100644 --- a/src/main/java/Task.java +++ b/src/main/java/duke/Task/Task.java @@ -1,8 +1,7 @@ +package duke.Task; +import duke.Exceptions.*; -import java.io.FileNotFoundException; -import java.lang.reflect.Array; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.io.BufferedWriter; import java.io.File; @@ -10,7 +9,6 @@ import java.util.Scanner; import java.time.format.FormatStyle; -import java.util.Formatter; public class Task { @@ -68,7 +66,7 @@ private static Task parse(String data) throws ParsingTaskException { } else if (type.equals("E")) { return Event.parse(data); } else { - throw new ParsingTaskException(String.format("Task was of unknown type: %s", type)); + throw new ParsingTaskException(String.format("duke.Task.Task was of unknown type: %s", type)); } } diff --git a/src/main/java/Todo.java b/src/main/java/duke/Task/Todo.java similarity index 96% rename from src/main/java/Todo.java rename to src/main/java/duke/Task/Todo.java index a9c7930b4b..4512437623 100644 --- a/src/main/java/Todo.java +++ b/src/main/java/duke/Task/Todo.java @@ -1,3 +1,6 @@ +package duke.Task; +import duke.Exceptions.*; + /** * Todos are tasks without any date/time attached to it e.g., visit new theme park */ diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index ff014b2307..6e34fd5dbe 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,4 +1,4 @@ -Hello! I'm Duke +Hello! I'm duke.Duke What can I do for you? Got it. I've added this task: [T][ ] read book diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 0873744649..62752b8814 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -15,7 +15,7 @@ IF ERRORLEVEL 1 ( REM no error here, errorlevel == 0 REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ..\bin Duke < input.txt > ACTUAL.TXT +java -classpath ..\bin duke.Duke < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT From 13323ef6f09d26355ae23225d91167ef68306eae Mon Sep 17 00:00:00 2001 From: Brandon Date: Sat, 20 Aug 2022 16:19:16 +0800 Subject: [PATCH 12/31] Extract the following classes: Ui: deals with interactions with the user Storage: deals with loading tasks from the file and saving tasks in the file Parser: deals with making sense of the user command TaskList: contains the task list e.g., it has operations to add/delete tasks in the list Command classes (i.e., AddCommand, DeleteCommand, ExitCommand etc.) that inherit from an abstract Command class, --- data/duke.txt | 6 +- src/main/java/duke/Command.java | 102 ++++++++++++++++++++ src/main/java/duke/Duke.java | 149 ++++++------------------------ src/main/java/duke/Parser.java | 64 +++++++++++++ src/main/java/duke/Storage.java | 103 +++++++++++++++++++++ src/main/java/duke/Task/Task.java | 82 ---------------- src/main/java/duke/TaskList.java | 44 +++++++++ src/main/java/duke/Ui.java | 18 ++++ 8 files changed, 359 insertions(+), 209 deletions(-) create mode 100644 src/main/java/duke/Command.java create mode 100644 src/main/java/duke/Parser.java create mode 100644 src/main/java/duke/Storage.java create mode 100644 src/main/java/duke/TaskList.java create mode 100644 src/main/java/duke/Ui.java diff --git a/data/duke.txt b/data/duke.txt index e087686f28..b84e916f7b 100644 --- a/data/duke.txt +++ b/data/duke.txt @@ -1,4 +1,2 @@ -T,1,return book -D,1,return book ,2022-09-12 -E,0,project meeting ,2022-10-11 -D,1,borrow book,2022-09-17 +T,0,return book +E,1,project meeting ,2022-10-11 diff --git a/src/main/java/duke/Command.java b/src/main/java/duke/Command.java new file mode 100644 index 0000000000..83af5ad5b8 --- /dev/null +++ b/src/main/java/duke/Command.java @@ -0,0 +1,102 @@ +package duke; + +import duke.Exceptions.InvalidCommandException; +import duke.Exceptions.NoSuchTaskException; +import duke.Task.*; + +abstract public class Command { + abstract void execute(TaskList taskList, Ui ui, Storage storage) throws InvalidCommandException; + + boolean isExit() { + return this instanceof ExitCommand; + } +} + +class ExitCommand extends Command { + @Override + void execute(TaskList taskList, Ui ui, Storage storage) { + ui.showGoodByeMessage(); + } +} + +class ListTasksCommand extends Command { + @Override + void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { + ui.showMessage("Here are the tasks in your list:"); + for (int i = 0; i < taskList.getNumTasks(); i++) { + ui.showMessage(String.format("%d. %s", i+1, taskList.get(i))); + } + } +} + +class DeleteTaskCommand extends Command { + int index; + DeleteTaskCommand(int index) { + this.index = index; + } + @Override + void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { + Task task = taskList.deleteTaskAtIndex(index); + ui.showMessage( + String.format("Noted. I've removed this task:\n\t%s\n", + task)); + storage.save(taskList); + } +} +class MarkCommand extends Command { + int index; + MarkCommand(int index) { + this.index = index; + } + @Override + void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { + Task task = taskList.get(index); + task.markAsCompleted(); + storage.save(taskList); + } +} + +class UnmarkCommand extends Command { + int index; + UnmarkCommand(int index) { + this.index = index; + } + @Override + void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { + Task task = taskList.get(index); + task.markAsIncomplete(); + storage.save(taskList); + } +} + +class CreateTaskCommand extends Command { + Task task; + CreateTaskCommand(Task task) { + this.task = task; + } + @Override + void execute(TaskList taskList, Ui ui, Storage storage) { + taskList.store(task); + ui.showMessage( + String.format("Got it. I've added this task:\n\t%s\n", + task)); + storage.save(taskList); + } +} + +class CreateTodoCommand extends CreateTaskCommand { + CreateTodoCommand(Todo todo) { + super(todo); + } +} + +class CreateDeadlineCommand extends CreateTaskCommand { + CreateDeadlineCommand(Deadline deadline) { + super(deadline); + } +} +class CreateEventCommand extends CreateTaskCommand { + CreateEventCommand(Event event) { + super(event); + } +} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java index 36fe899ae9..afb3db0c9b 100644 --- a/src/main/java/duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -1,150 +1,53 @@ package duke; -import duke.Task.Task; -import duke.Enums.Action; import duke.Exceptions.*; -import duke.Task.*; -import java.util.List; +import java.io.File; import java.util.Scanner; public class Duke { - private static String WELCOME_MESSAGE = "Hello! I'm duke.Duke\n" + "What can I do for you?"; - private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; - private List tasks = Task.loadSavedTasks(); + private static final String DATA_PATH = new File("").getAbsolutePath() + "/data/duke.txt"; + private TaskList taskList; + private Storage storage; + private Ui ui; - private int getNumTasks() { - return this.tasks.size(); + Duke() { + Storage storage = new Storage(DATA_PATH); + this.storage = storage; + this.taskList = new TaskList(storage.load()); + this.ui = new Ui(); } - private String getNumTasksAsString() { - return String.format("Now you have %d tasks in the list.", this.getNumTasks()); - } - private void storeTask(Task task) { - this.tasks.add(task); - System.out.println( - String.format("Got it. I've added this task:\n\t%s\n", - task, - this.getNumTasksAsString())); - } + public void run() { + this.ui.showWelcomeMessage(); - private void getTasks() { - System.out.println("Here are the tasks in your list:"); - for (int i = 0; i < this.getNumTasks(); i++) { - System.out.println(String.format("%d. %s", i+1, this.tasks.get(i))); - } - } - - private Task getTaskAtIndex(String index) throws InvalidCommandException { - try { - int i = Integer.parseInt(index); - return this.tasks.get(i-1); - } catch (NumberFormatException e) { - throw new InvalidTaskIndexException(); - } catch (IndexOutOfBoundsException e) { - throw new NoSuchTaskException(this.getNumTasks(), index); - } - } + boolean isExit = false; - private void markTaskAtIndexAsComplete(String index) throws InvalidCommandException { - Task task = this.getTaskAtIndex(index); - task.markAsCompleted(); - } - - private void markTaskAtIndexAsIncomplete(String index) throws InvalidCommandException { - Task task = this.getTaskAtIndex(index); - task.markAsIncomplete(); - } - - private void deleteTaskAtIndex(String index) throws InvalidCommandException { - Task task = this.getTaskAtIndex(index); - this.tasks.remove(Integer.parseInt(index) - 1); - System.out.println( - String.format("Noted. I've removed this task:\n\t%s\n", - task, - this.getNumTasksAsString())); - } + Scanner scanner = new Scanner(System.in); + Duke duke = new Duke(); - private void handleActionAndCommand(String command, Action action) throws InvalidCommandException { - String[] components = command.split(" "); - String contents = command.substring(action.label.length()).trim(); + while (!isExit && scanner.hasNextLine()) { + try { + String fullCommand = scanner.nextLine(); - switch (action) { - case Mark: - this.markTaskAtIndexAsComplete(contents); - break; - case Unmark: - this.markTaskAtIndexAsIncomplete(contents); - break; - case Delete: - this.deleteTaskAtIndex(contents); - break; - case Todo: - if (contents.isBlank()) - throw new EmptyTitleException(); - else - this.storeTask(new Todo(contents)); - break; - case Deadline: - String[] deadlineComponents = contents.split(" /by "); + Command c = Parser.parse(fullCommand); + c.execute(this.taskList, this.ui, this.storage); - if (deadlineComponents.length != 2) - throw new InvalidDeadlineException(); - else if (deadlineComponents[0].isBlank()) - throw new EmptyTitleException(); - else - this.storeTask( - new Deadline(deadlineComponents[0].trim(), - deadlineComponents[1].trim()) - ); - break; + isExit = c.isExit(); - case Event: - String[] eventComponents = contents.split(" /at "); - if (eventComponents.length != 2) - throw new InvalidEventException(); - else if (eventComponents[0].isBlank()) - throw new EmptyTitleException(); - else - this.storeTask( - new Event(eventComponents[0].trim(), - eventComponents[1].trim()) - ); - break; + } catch (InvalidCommandException e) { + ui.showMessage(e.getMessage()); + } } - Task.saveTaskList(this.tasks); + scanner.close(); } - /** * Note: You are strongly encouraged to customize the chatbot name, * command/display formats, and even the personality of the chatbot * to make your chatbot unique. */ public static void main(String[] args) { - System.out.println(WELCOME_MESSAGE); - - Scanner scanner = new Scanner(System.in); Duke duke = new Duke(); - - while (true) { - - String command = scanner.nextLine(); - - if (command.equals("bye")) { - System.out.println(GOODBYE_MESSAGE); - break; - } else if (command.equals("list")) { - duke.getTasks(); - } else { - try { - Action action = Action.parseCommand(command); - duke.handleActionAndCommand(command, action); - } catch (InvalidCommandException e) { - System.out.println("Invalid command given.\n" + e.getMessage()); - } - } - } - - scanner.close(); + duke.run(); } } diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java new file mode 100644 index 0000000000..851caa3a20 --- /dev/null +++ b/src/main/java/duke/Parser.java @@ -0,0 +1,64 @@ +package duke; + +import duke.Enums.Action; +import duke.Exceptions.*; +import duke.Task.Deadline; +import duke.Task.Event; +import duke.Task.Todo; + +public class Parser { + public static Command parse(String fullCommand) throws InvalidCommandException { + if (fullCommand.trim().equals("bye")) { + return new ExitCommand(); + } else if (fullCommand.trim().equals("list")) { + return new ListTasksCommand(); + } else { + Action action = Action.parseCommand(fullCommand); + + String[] components = fullCommand.split(" "); + String contents = fullCommand.substring(action.label.length()).trim(); + try { + switch (action) { + case Mark: + return new MarkCommand(Integer.parseInt(contents)-1); + case Unmark: + return new UnmarkCommand(Integer.parseInt(contents)-1); + case Delete: + return new DeleteTaskCommand(Integer.parseInt(contents)-1); + case Todo: + if (contents.isBlank()) + throw new EmptyTitleException(); + else + return new CreateTodoCommand(new Todo(contents)); + case Deadline: + String[] deadlineComponents = contents.split(" /by "); + + if (deadlineComponents.length != 2) + throw new InvalidDeadlineException(); + else if (deadlineComponents[0].isBlank()) + throw new EmptyTitleException(); + else + return new CreateDeadlineCommand( + new Deadline(deadlineComponents[0].trim(), + deadlineComponents[1].trim()) + ); + + case Event: + String[] eventComponents = contents.split(" /at "); + if (eventComponents.length != 2) + throw new InvalidEventException(); + else if (eventComponents[0].isBlank()) + throw new EmptyTitleException(); + else + return new CreateEventCommand( + new Event(eventComponents[0].trim(), + eventComponents[1].trim()) + ); + } + } catch (NumberFormatException e) { + throw new InvalidTaskIndexException(); + } + } + throw new InvalidCommandException("Unknown command found"); + } +} diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java new file mode 100644 index 0000000000..cd6665add9 --- /dev/null +++ b/src/main/java/duke/Storage.java @@ -0,0 +1,103 @@ +package duke; + +import duke.Exceptions.ParsingTaskException; +import duke.Task.Deadline; +import duke.Task.Event; +import duke.Task.Task; +import duke.Task.Todo; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class Storage { + private String dataPath; + + public Storage(String dataPath) { + this.dataPath = dataPath; + } + private static Task parse(String data) throws ParsingTaskException { + String[] components = data.split(","); + + if (components.length == 0) { + throw new ParsingTaskException("Data was empty or not formatted properly."); + } + String type = components[0]; + + if (type.equals("T")) { + return Todo.parse(data); + } else if (type.equals("D")) { + return Deadline.parse(data); + } else if (type.equals("E")) { + return Event.parse(data); + } else { + throw new ParsingTaskException(String.format("duke.Task.Task was of unknown type: %s", type)); + } + } + + public List load() { + List tasks = new ArrayList<>(); + try { + + File file = new File(this.dataPath); + + file.getParentFile().mkdirs(); + file.createNewFile(); + + Scanner scanner = new Scanner(file); + + while (scanner.hasNextLine()) { + String data = scanner.nextLine(); + try { + tasks.add(Storage.parse(data)); + } catch (ParsingTaskException e) { + System.out.println(e); + continue; + } + } + + scanner.close(); + + return tasks; + + } catch (Exception e) { + System.out.println("An error occurred.\n" + e); + } finally { + return tasks; + } + } + + public void save(TaskList tasks) { + try { + // Create new file + String content = ""; + for (Task t : tasks.getAll()) { + content += t.toSaveString() + "\n"; + } + + File file = new File(this.dataPath); + + // If the file doesn't exist, then create it + file.getParentFile().mkdirs(); + file.createNewFile(); + + + FileWriter fw = new FileWriter(file.getAbsoluteFile()); + BufferedWriter bw = new BufferedWriter(fw); + + // Write in file + bw.write(content); + + // Close connection + bw.close(); + + System.out.println("Saved tasks list successfully!"); + } + catch (Exception e){ + System.out.println(e + this.dataPath); + } + } +} diff --git a/src/main/java/duke/Task/Task.java b/src/main/java/duke/Task/Task.java index 0cdf0e62a0..bf0278e69d 100644 --- a/src/main/java/duke/Task/Task.java +++ b/src/main/java/duke/Task/Task.java @@ -50,86 +50,4 @@ public String toString() { public String toSaveString() { return String.format("%d,%s", this.completed ? 1 : 0, this.title); } - - private static Task parse(String data) throws ParsingTaskException { - String[] components = data.split(","); - - if (components.length == 0) { - throw new ParsingTaskException("Data was empty or not formatted properly."); - } - String type = components[0]; - - if (type.equals("T")) { - return Todo.parse(data); - } else if (type.equals("D")) { - return Deadline.parse(data); - } else if (type.equals("E")) { - return Event.parse(data); - } else { - throw new ParsingTaskException(String.format("duke.Task.Task was of unknown type: %s", type)); - } - } - - public static List loadSavedTasks() { - List tasks = new ArrayList<>(); - try { - - File file = new File(DATA_PATH); - - file.getParentFile().mkdirs(); - file.createNewFile(); - - Scanner scanner = new Scanner(file); - - while (scanner.hasNextLine()) { - String data = scanner.nextLine(); - try { - tasks.add(Task.parse(data)); - } catch (ParsingTaskException e) { - System.out.println(e); - continue; - } - } - - scanner.close(); - - return tasks; - - } catch (Exception e) { - System.out.println("An error occurred.\n" + e); - } finally { - return tasks; - } - } - - public static void saveTaskList(List tasks) { - try { - // Create new file - String content = ""; - for (Task t : tasks ) { - content += t.toSaveString() + "\n"; - } - - File file = new File(DATA_PATH); - - // If the file doesn't exist, then create it - file.getParentFile().mkdirs(); - file.createNewFile(); - - - FileWriter fw = new FileWriter(file.getAbsoluteFile()); - BufferedWriter bw = new BufferedWriter(fw); - - // Write in file - bw.write(content); - - // Close connection - bw.close(); - - System.out.println("Saved tasks list successfully!"); - } - catch (Exception e){ - System.out.println(e + DATA_PATH); - } - } } diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java new file mode 100644 index 0000000000..85f21c234d --- /dev/null +++ b/src/main/java/duke/TaskList.java @@ -0,0 +1,44 @@ +package duke; + +import duke.Exceptions.NoSuchTaskException; +import duke.Task.Task; + +import java.util.List; + +public class TaskList { + private List tasks; + + TaskList(List tasks) { + this.tasks = tasks; + } + + public List getAll() { + return this.tasks; + } + + public int getNumTasks() { + return this.tasks.size(); + } + + private String getNumTasksAsString() { + return String.format("Now you have %d tasks in the list.", this.getNumTasks()); + } + public void store(Task task) { + this.tasks.add(task); + } + + public Task get(int index) throws NoSuchTaskException { + try { + return this.tasks.get(index); + } catch (IndexOutOfBoundsException e) { + throw new NoSuchTaskException(this.getNumTasks(), index+1); + } + } + + + public Task deleteTaskAtIndex(int index) throws NoSuchTaskException { + Task task = this.get(index); + this.tasks.remove(index); + return task; + } +} diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java new file mode 100644 index 0000000000..42bde15333 --- /dev/null +++ b/src/main/java/duke/Ui.java @@ -0,0 +1,18 @@ +package duke; + +public class Ui { + private static String WELCOME_MESSAGE = "Hello! I'm duke.Duke\n" + "What can I do for you?"; + private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; + + public void showWelcomeMessage() { + System.out.println(WELCOME_MESSAGE); + } + + public void showGoodByeMessage() { + System.out.println(GOODBYE_MESSAGE); + } + + public void showMessage(String message) { + System.out.println(message); + } +} From f9b64ff500183817083fd2c749ccacdbd081c3a0 Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 23 Aug 2022 12:13:12 +0800 Subject: [PATCH 13/31] Rename delete function --- src/main/java/duke/Command.java | 2 +- src/main/java/duke/TaskList.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/duke/Command.java b/src/main/java/duke/Command.java index 83af5ad5b8..2388373868 100644 --- a/src/main/java/duke/Command.java +++ b/src/main/java/duke/Command.java @@ -36,7 +36,7 @@ class DeleteTaskCommand extends Command { } @Override void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { - Task task = taskList.deleteTaskAtIndex(index); + Task task = taskList.delete(index); ui.showMessage( String.format("Noted. I've removed this task:\n\t%s\n", task)); diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java index 85f21c234d..764fbd5b17 100644 --- a/src/main/java/duke/TaskList.java +++ b/src/main/java/duke/TaskList.java @@ -35,8 +35,7 @@ public Task get(int index) throws NoSuchTaskException { } } - - public Task deleteTaskAtIndex(int index) throws NoSuchTaskException { + public Task delete(int index) throws NoSuchTaskException { Task task = this.get(index); this.tasks.remove(index); return task; From 780f4eb2bd1bcd27260735913ea0317d17f1ec49 Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 23 Aug 2022 12:17:49 +0800 Subject: [PATCH 14/31] Use Gradle to automate some of the build tasks of the project. --- build.gradle | 41 +++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58695 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 183 +++++++++++++++++++++++ gradlew.bat | 103 +++++++++++++ 5 files changed, 332 insertions(+) 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 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..885198fcfa --- /dev/null +++ b/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "seedu.duke.Duke" +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + +run{ + standardInput = System.in +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f3d88b1c2faf2fc91d853cd5d4242b5547257070 GIT binary patch literal 58695 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..b7c8c5dbf5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..2fe81a7d95 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or 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 UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# 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"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" + which java >/dev/null 2>&1 || 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 + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..62bd9b9cce --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@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=. +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%" == "0" goto init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From ce5ee1235be2b5a8377c00f7c0bd3943a4ee7c5b Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 23 Aug 2022 12:32:00 +0800 Subject: [PATCH 15/31] Package the app as an executable JAR file so that it can be distributed easily. --- build.gradle | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/build.gradle b/build.gradle index 885198fcfa..cbf675804c 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,21 @@ repositories { dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' + + String javaFxVersion = '11' + + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' } test { From 6deaeb9933f471cf544a0288ce4e1afe171f9936 Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 23 Aug 2022 12:32:15 +0800 Subject: [PATCH 16/31] Setup dummy JUnit tests --- src/test/java/duke/DukeTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/test/java/duke/DukeTest.java diff --git a/src/test/java/duke/DukeTest.java b/src/test/java/duke/DukeTest.java new file mode 100644 index 0000000000..eb8bfa747e --- /dev/null +++ b/src/test/java/duke/DukeTest.java @@ -0,0 +1,15 @@ +package duke; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DukeTest { + @Test + public void dummyTest(){ + assertEquals(2, 2); + } + + @Test + public void anotherDummyTest(){ + assertEquals(4, 4); + } +} From 569cd0b17508b457682d4b468ee6f338bdd4ae53 Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 23 Aug 2022 19:46:38 +0800 Subject: [PATCH 17/31] Organise commands, parser, storage, ui into their own packages --- src/main/java/duke/Command.java | 102 ------------------ src/main/java/duke/Duke.java | 20 ++-- src/main/java/duke/Parser.java | 64 ----------- src/main/java/duke/commands/Command.java | 32 ++++++ .../duke/commands/CreateDeadlineCommand.java | 9 ++ .../duke/commands/CreateEventCommand.java | 9 ++ .../java/duke/commands/CreateTaskCommand.java | 21 ++++ .../java/duke/commands/CreateTodoCommand.java | 9 ++ .../java/duke/commands/DeleteTaskCommand.java | 22 ++++ src/main/java/duke/commands/ExitCommand.java | 12 +++ .../java/duke/commands/ListTasksCommand.java | 16 +++ src/main/java/duke/commands/MarkCommand.java | 22 ++++ .../java/duke/commands/UnmarkCommand.java | 22 ++++ .../java/duke/{Enums => enums}/Action.java | 4 +- .../EmptyTitleException.java | 2 +- .../InvalidCommandException.java | 2 +- .../InvalidDeadlineException.java | 2 +- .../InvalidEventException.java | 2 +- .../InvalidTaskIndexException.java | 2 +- .../NoSuchTaskException.java | 2 +- .../ParsingTaskException.java | 2 +- src/main/java/duke/parser/Parser.java | 66 ++++++++++++ src/main/java/duke/{ => storage}/Storage.java | 18 ++-- .../java/duke/{Task => task}/Deadline.java | 4 +- src/main/java/duke/{Task => task}/Event.java | 4 +- src/main/java/duke/{Task => task}/Task.java | 8 +- src/main/java/duke/{ => task}/TaskList.java | 11 +- src/main/java/duke/{Task => task}/Todo.java | 4 +- src/main/java/duke/{ => ui}/Ui.java | 6 +- 29 files changed, 286 insertions(+), 213 deletions(-) delete mode 100644 src/main/java/duke/Command.java delete mode 100644 src/main/java/duke/Parser.java create mode 100644 src/main/java/duke/commands/Command.java create mode 100644 src/main/java/duke/commands/CreateDeadlineCommand.java create mode 100644 src/main/java/duke/commands/CreateEventCommand.java create mode 100644 src/main/java/duke/commands/CreateTaskCommand.java create mode 100644 src/main/java/duke/commands/CreateTodoCommand.java create mode 100644 src/main/java/duke/commands/DeleteTaskCommand.java create mode 100644 src/main/java/duke/commands/ExitCommand.java create mode 100644 src/main/java/duke/commands/ListTasksCommand.java create mode 100644 src/main/java/duke/commands/MarkCommand.java create mode 100644 src/main/java/duke/commands/UnmarkCommand.java rename src/main/java/duke/{Enums => enums}/Action.java (93%) rename src/main/java/duke/{Exceptions => exceptions}/EmptyTitleException.java (87%) rename src/main/java/duke/{Exceptions => exceptions}/InvalidCommandException.java (85%) rename src/main/java/duke/{Exceptions => exceptions}/InvalidDeadlineException.java (92%) rename src/main/java/duke/{Exceptions => exceptions}/InvalidEventException.java (92%) rename src/main/java/duke/{Exceptions => exceptions}/InvalidTaskIndexException.java (89%) rename src/main/java/duke/{Exceptions => exceptions}/NoSuchTaskException.java (95%) rename src/main/java/duke/{Exceptions => exceptions}/ParsingTaskException.java (88%) create mode 100644 src/main/java/duke/parser/Parser.java rename src/main/java/duke/{ => storage}/Storage.java (92%) rename src/main/java/duke/{Task => task}/Deadline.java (97%) rename src/main/java/duke/{Task => task}/Event.java (97%) rename src/main/java/duke/{Task => task}/Task.java (87%) rename src/main/java/duke/{ => task}/TaskList.java (86%) rename src/main/java/duke/{Task => task}/Todo.java (96%) rename src/main/java/duke/{ => ui}/Ui.java (60%) diff --git a/src/main/java/duke/Command.java b/src/main/java/duke/Command.java deleted file mode 100644 index 2388373868..0000000000 --- a/src/main/java/duke/Command.java +++ /dev/null @@ -1,102 +0,0 @@ -package duke; - -import duke.Exceptions.InvalidCommandException; -import duke.Exceptions.NoSuchTaskException; -import duke.Task.*; - -abstract public class Command { - abstract void execute(TaskList taskList, Ui ui, Storage storage) throws InvalidCommandException; - - boolean isExit() { - return this instanceof ExitCommand; - } -} - -class ExitCommand extends Command { - @Override - void execute(TaskList taskList, Ui ui, Storage storage) { - ui.showGoodByeMessage(); - } -} - -class ListTasksCommand extends Command { - @Override - void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { - ui.showMessage("Here are the tasks in your list:"); - for (int i = 0; i < taskList.getNumTasks(); i++) { - ui.showMessage(String.format("%d. %s", i+1, taskList.get(i))); - } - } -} - -class DeleteTaskCommand extends Command { - int index; - DeleteTaskCommand(int index) { - this.index = index; - } - @Override - void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { - Task task = taskList.delete(index); - ui.showMessage( - String.format("Noted. I've removed this task:\n\t%s\n", - task)); - storage.save(taskList); - } -} -class MarkCommand extends Command { - int index; - MarkCommand(int index) { - this.index = index; - } - @Override - void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { - Task task = taskList.get(index); - task.markAsCompleted(); - storage.save(taskList); - } -} - -class UnmarkCommand extends Command { - int index; - UnmarkCommand(int index) { - this.index = index; - } - @Override - void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { - Task task = taskList.get(index); - task.markAsIncomplete(); - storage.save(taskList); - } -} - -class CreateTaskCommand extends Command { - Task task; - CreateTaskCommand(Task task) { - this.task = task; - } - @Override - void execute(TaskList taskList, Ui ui, Storage storage) { - taskList.store(task); - ui.showMessage( - String.format("Got it. I've added this task:\n\t%s\n", - task)); - storage.save(taskList); - } -} - -class CreateTodoCommand extends CreateTaskCommand { - CreateTodoCommand(Todo todo) { - super(todo); - } -} - -class CreateDeadlineCommand extends CreateTaskCommand { - CreateDeadlineCommand(Deadline deadline) { - super(deadline); - } -} -class CreateEventCommand extends CreateTaskCommand { - CreateEventCommand(Event event) { - super(event); - } -} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java index afb3db0c9b..b67832c3c5 100644 --- a/src/main/java/duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -1,10 +1,16 @@ package duke; -import duke.Exceptions.*; +import duke.commands.Command; +import duke.exceptions.*; +import duke.parser.Parser; +import duke.storage.Storage; +import duke.task.TaskList; +import duke.ui.Ui; import java.io.File; import java.util.Scanner; -public class Duke { + +class Duke { private static final String DATA_PATH = new File("").getAbsolutePath() + "/data/duke.txt"; private TaskList taskList; private Storage storage; @@ -33,7 +39,6 @@ public void run() { c.execute(this.taskList, this.ui, this.storage); isExit = c.isExit(); - } catch (InvalidCommandException e) { ui.showMessage(e.getMessage()); } @@ -41,11 +46,12 @@ public void run() { scanner.close(); } + /** - * Note: You are strongly encouraged to customize the chatbot name, - * command/display formats, and even the personality of the chatbot - * to make your chatbot unique. - */ + * Note: You are strongly encouraged to customize the chatbot name, + * command/display formats, and even the personality of the chatbot + * to make your chatbot unique. + */ public static void main(String[] args) { Duke duke = new Duke(); duke.run(); diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java deleted file mode 100644 index 851caa3a20..0000000000 --- a/src/main/java/duke/Parser.java +++ /dev/null @@ -1,64 +0,0 @@ -package duke; - -import duke.Enums.Action; -import duke.Exceptions.*; -import duke.Task.Deadline; -import duke.Task.Event; -import duke.Task.Todo; - -public class Parser { - public static Command parse(String fullCommand) throws InvalidCommandException { - if (fullCommand.trim().equals("bye")) { - return new ExitCommand(); - } else if (fullCommand.trim().equals("list")) { - return new ListTasksCommand(); - } else { - Action action = Action.parseCommand(fullCommand); - - String[] components = fullCommand.split(" "); - String contents = fullCommand.substring(action.label.length()).trim(); - try { - switch (action) { - case Mark: - return new MarkCommand(Integer.parseInt(contents)-1); - case Unmark: - return new UnmarkCommand(Integer.parseInt(contents)-1); - case Delete: - return new DeleteTaskCommand(Integer.parseInt(contents)-1); - case Todo: - if (contents.isBlank()) - throw new EmptyTitleException(); - else - return new CreateTodoCommand(new Todo(contents)); - case Deadline: - String[] deadlineComponents = contents.split(" /by "); - - if (deadlineComponents.length != 2) - throw new InvalidDeadlineException(); - else if (deadlineComponents[0].isBlank()) - throw new EmptyTitleException(); - else - return new CreateDeadlineCommand( - new Deadline(deadlineComponents[0].trim(), - deadlineComponents[1].trim()) - ); - - case Event: - String[] eventComponents = contents.split(" /at "); - if (eventComponents.length != 2) - throw new InvalidEventException(); - else if (eventComponents[0].isBlank()) - throw new EmptyTitleException(); - else - return new CreateEventCommand( - new Event(eventComponents[0].trim(), - eventComponents[1].trim()) - ); - } - } catch (NumberFormatException e) { - throw new InvalidTaskIndexException(); - } - } - throw new InvalidCommandException("Unknown command found"); - } -} diff --git a/src/main/java/duke/commands/Command.java b/src/main/java/duke/commands/Command.java new file mode 100644 index 0000000000..7da4cdc095 --- /dev/null +++ b/src/main/java/duke/commands/Command.java @@ -0,0 +1,32 @@ +package duke.commands; + +import duke.exceptions.InvalidCommandException; +import duke.storage.Storage; +import duke.task.TaskList; +import duke.ui.Ui; + +public abstract class Command { + public abstract void execute(TaskList taskList, Ui ui, Storage storage) throws InvalidCommandException; + + public boolean isExit() { + return this instanceof ExitCommand; + } +} + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/duke/commands/CreateDeadlineCommand.java b/src/main/java/duke/commands/CreateDeadlineCommand.java new file mode 100644 index 0000000000..4772da7025 --- /dev/null +++ b/src/main/java/duke/commands/CreateDeadlineCommand.java @@ -0,0 +1,9 @@ +package duke.commands; + +import duke.task.Deadline; + +public class CreateDeadlineCommand extends CreateTaskCommand { + public CreateDeadlineCommand(Deadline deadline) { + super(deadline); + } +} \ No newline at end of file diff --git a/src/main/java/duke/commands/CreateEventCommand.java b/src/main/java/duke/commands/CreateEventCommand.java new file mode 100644 index 0000000000..138613956e --- /dev/null +++ b/src/main/java/duke/commands/CreateEventCommand.java @@ -0,0 +1,9 @@ +package duke.commands; + +import duke.task.Event; + +public class CreateEventCommand extends CreateTaskCommand { + public CreateEventCommand(Event event) { + super(event); + } +} \ No newline at end of file diff --git a/src/main/java/duke/commands/CreateTaskCommand.java b/src/main/java/duke/commands/CreateTaskCommand.java new file mode 100644 index 0000000000..f289b02027 --- /dev/null +++ b/src/main/java/duke/commands/CreateTaskCommand.java @@ -0,0 +1,21 @@ +package duke.commands; + +import duke.storage.Storage; +import duke.task.Task; +import duke.task.TaskList; +import duke.ui.Ui; + +class CreateTaskCommand extends Command { + Task task; + + CreateTaskCommand(Task task) { + this.task = task; + } + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) { + taskList.store(task); + ui.showMessage(String.format("Got it. I've added this task:\n\t%s\n", task)); + storage.save(taskList); + } +} \ No newline at end of file diff --git a/src/main/java/duke/commands/CreateTodoCommand.java b/src/main/java/duke/commands/CreateTodoCommand.java new file mode 100644 index 0000000000..832a80468e --- /dev/null +++ b/src/main/java/duke/commands/CreateTodoCommand.java @@ -0,0 +1,9 @@ +package duke.commands; + +import duke.task.Todo; + +public class CreateTodoCommand extends CreateTaskCommand { + public CreateTodoCommand(Todo todo) { + super(todo); + } +} \ No newline at end of file diff --git a/src/main/java/duke/commands/DeleteTaskCommand.java b/src/main/java/duke/commands/DeleteTaskCommand.java new file mode 100644 index 0000000000..dad1aece20 --- /dev/null +++ b/src/main/java/duke/commands/DeleteTaskCommand.java @@ -0,0 +1,22 @@ +package duke.commands; + +import duke.exceptions.NoSuchTaskException; +import duke.storage.Storage; +import duke.task.Task; +import duke.task.TaskList; +import duke.ui.Ui; + +public class DeleteTaskCommand extends Command { + int index; + + public DeleteTaskCommand(int index) { + this.index = index; + } + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { + Task task = taskList.delete(index); + ui.showMessage(String.format("Noted. I've removed this task:\n\t%s\n", task)); + storage.save(taskList); + } +} \ No newline at end of file diff --git a/src/main/java/duke/commands/ExitCommand.java b/src/main/java/duke/commands/ExitCommand.java new file mode 100644 index 0000000000..049ca229ac --- /dev/null +++ b/src/main/java/duke/commands/ExitCommand.java @@ -0,0 +1,12 @@ +package duke.commands; + +import duke.storage.Storage; +import duke.task.TaskList; +import duke.ui.Ui; + +public class ExitCommand extends Command { + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) { + ui.showGoodByeMessage(); + } +} diff --git a/src/main/java/duke/commands/ListTasksCommand.java b/src/main/java/duke/commands/ListTasksCommand.java new file mode 100644 index 0000000000..d3350b26a4 --- /dev/null +++ b/src/main/java/duke/commands/ListTasksCommand.java @@ -0,0 +1,16 @@ +package duke.commands; + +import duke.exceptions.NoSuchTaskException; +import duke.storage.Storage; +import duke.task.TaskList; +import duke.ui.Ui; + +public class ListTasksCommand extends Command { + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { + ui.showMessage("Here are the tasks in your list:"); + for (int i = 0; i < taskList.getNumTasks(); i++) { + ui.showMessage(String.format("%d. %s", i + 1, taskList.get(i))); + } + } +} \ No newline at end of file diff --git a/src/main/java/duke/commands/MarkCommand.java b/src/main/java/duke/commands/MarkCommand.java new file mode 100644 index 0000000000..46d7e6151a --- /dev/null +++ b/src/main/java/duke/commands/MarkCommand.java @@ -0,0 +1,22 @@ +package duke.commands; + +import duke.exceptions.NoSuchTaskException; +import duke.storage.Storage; +import duke.task.Task; +import duke.task.TaskList; +import duke.ui.Ui; + +public class MarkCommand extends Command { + int index; + + public MarkCommand(int index) { + this.index = index; + } + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { + Task task = taskList.get(index); + task.markAsCompleted(); + storage.save(taskList); + } +} diff --git a/src/main/java/duke/commands/UnmarkCommand.java b/src/main/java/duke/commands/UnmarkCommand.java new file mode 100644 index 0000000000..1233526c02 --- /dev/null +++ b/src/main/java/duke/commands/UnmarkCommand.java @@ -0,0 +1,22 @@ +package duke.commands; + +import duke.exceptions.NoSuchTaskException; +import duke.storage.Storage; +import duke.task.Task; +import duke.task.TaskList; +import duke.ui.Ui; + +public class UnmarkCommand extends Command { + int index; + + public UnmarkCommand(int index) { + this.index = index; + } + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { + Task task = taskList.get(index); + task.markAsIncomplete(); + storage.save(taskList); + } +} \ No newline at end of file diff --git a/src/main/java/duke/Enums/Action.java b/src/main/java/duke/enums/Action.java similarity index 93% rename from src/main/java/duke/Enums/Action.java rename to src/main/java/duke/enums/Action.java index 06c73e2713..a881e58c8a 100644 --- a/src/main/java/duke/Enums/Action.java +++ b/src/main/java/duke/enums/Action.java @@ -1,5 +1,5 @@ -package duke.Enums; -import duke.Exceptions.*; +package duke.enums; +import duke.exceptions.*; public enum Action { Todo("todo"), diff --git a/src/main/java/duke/Exceptions/EmptyTitleException.java b/src/main/java/duke/exceptions/EmptyTitleException.java similarity index 87% rename from src/main/java/duke/Exceptions/EmptyTitleException.java rename to src/main/java/duke/exceptions/EmptyTitleException.java index 12d1a88b04..b27602e348 100644 --- a/src/main/java/duke/Exceptions/EmptyTitleException.java +++ b/src/main/java/duke/exceptions/EmptyTitleException.java @@ -1,4 +1,4 @@ -package duke.Exceptions; +package duke.exceptions; public class EmptyTitleException extends InvalidCommandException { public EmptyTitleException() { diff --git a/src/main/java/duke/Exceptions/InvalidCommandException.java b/src/main/java/duke/exceptions/InvalidCommandException.java similarity index 85% rename from src/main/java/duke/Exceptions/InvalidCommandException.java rename to src/main/java/duke/exceptions/InvalidCommandException.java index 9bbc1ac63c..e608204bde 100644 --- a/src/main/java/duke/Exceptions/InvalidCommandException.java +++ b/src/main/java/duke/exceptions/InvalidCommandException.java @@ -1,4 +1,4 @@ -package duke.Exceptions; +package duke.exceptions; public class InvalidCommandException extends Exception { public InvalidCommandException(String message) { diff --git a/src/main/java/duke/Exceptions/InvalidDeadlineException.java b/src/main/java/duke/exceptions/InvalidDeadlineException.java similarity index 92% rename from src/main/java/duke/Exceptions/InvalidDeadlineException.java rename to src/main/java/duke/exceptions/InvalidDeadlineException.java index 0390817e38..1c4e145004 100644 --- a/src/main/java/duke/Exceptions/InvalidDeadlineException.java +++ b/src/main/java/duke/exceptions/InvalidDeadlineException.java @@ -1,4 +1,4 @@ -package duke.Exceptions; +package duke.exceptions; public class InvalidDeadlineException extends InvalidCommandException { public InvalidDeadlineException() { diff --git a/src/main/java/duke/Exceptions/InvalidEventException.java b/src/main/java/duke/exceptions/InvalidEventException.java similarity index 92% rename from src/main/java/duke/Exceptions/InvalidEventException.java rename to src/main/java/duke/exceptions/InvalidEventException.java index 4df3ab81c7..9f23988f0f 100644 --- a/src/main/java/duke/Exceptions/InvalidEventException.java +++ b/src/main/java/duke/exceptions/InvalidEventException.java @@ -1,4 +1,4 @@ -package duke.Exceptions; +package duke.exceptions; public class InvalidEventException extends InvalidCommandException { public InvalidEventException() { diff --git a/src/main/java/duke/Exceptions/InvalidTaskIndexException.java b/src/main/java/duke/exceptions/InvalidTaskIndexException.java similarity index 89% rename from src/main/java/duke/Exceptions/InvalidTaskIndexException.java rename to src/main/java/duke/exceptions/InvalidTaskIndexException.java index cb735f29c7..34ddd29662 100644 --- a/src/main/java/duke/Exceptions/InvalidTaskIndexException.java +++ b/src/main/java/duke/exceptions/InvalidTaskIndexException.java @@ -1,4 +1,4 @@ -package duke.Exceptions; +package duke.exceptions; public class InvalidTaskIndexException extends InvalidCommandException { public InvalidTaskIndexException() { diff --git a/src/main/java/duke/Exceptions/NoSuchTaskException.java b/src/main/java/duke/exceptions/NoSuchTaskException.java similarity index 95% rename from src/main/java/duke/Exceptions/NoSuchTaskException.java rename to src/main/java/duke/exceptions/NoSuchTaskException.java index 6733f2a4ac..71520b1d06 100644 --- a/src/main/java/duke/Exceptions/NoSuchTaskException.java +++ b/src/main/java/duke/exceptions/NoSuchTaskException.java @@ -1,4 +1,4 @@ -package duke.Exceptions; +package duke.exceptions; public class NoSuchTaskException extends InvalidCommandException { public NoSuchTaskException(int numTasks, int index) { diff --git a/src/main/java/duke/Exceptions/ParsingTaskException.java b/src/main/java/duke/exceptions/ParsingTaskException.java similarity index 88% rename from src/main/java/duke/Exceptions/ParsingTaskException.java rename to src/main/java/duke/exceptions/ParsingTaskException.java index 3fab7efbc6..c273db97bd 100644 --- a/src/main/java/duke/Exceptions/ParsingTaskException.java +++ b/src/main/java/duke/exceptions/ParsingTaskException.java @@ -1,4 +1,4 @@ -package duke.Exceptions; +package duke.exceptions; public class ParsingTaskException extends Exception { public ParsingTaskException(String addtionalMessage) { diff --git a/src/main/java/duke/parser/Parser.java b/src/main/java/duke/parser/Parser.java new file mode 100644 index 0000000000..1e6331f15a --- /dev/null +++ b/src/main/java/duke/parser/Parser.java @@ -0,0 +1,66 @@ +package duke.parser; + +import duke.commands.*; +import duke.enums.Action; +import duke.exceptions.*; +import duke.task.Deadline; +import duke.task.Event; +import duke.task.Todo; + +public class Parser { + public static Command parse(String fullCommand) throws InvalidCommandException { + if (fullCommand.trim().equals("bye")) { + return new ExitCommand(); + } else if (fullCommand.trim().equals("list")) { + return new ListTasksCommand(); + } else { + Action action = Action.parseCommand(fullCommand); + + String[] components = fullCommand.split(" "); + String contents = fullCommand.substring(action.label.length()).trim(); + + try { + switch (action) { + case Mark: + return new MarkCommand(Integer.parseInt(contents) - 1); + case Unmark: + return new UnmarkCommand(Integer.parseInt(contents) - 1); + case Delete: + return new DeleteTaskCommand(Integer.parseInt(contents) - 1); + case Todo: + if (contents.isBlank()) + throw new EmptyTitleException(); + else + return new CreateTodoCommand(new Todo(contents)); + case Deadline: + String[] deadlineComponents = contents.split(" /by "); + + if (deadlineComponents.length != 2) + throw new InvalidDeadlineException(); + else if (deadlineComponents[0].isBlank()) + throw new EmptyTitleException(); + else + return new CreateDeadlineCommand( + new Deadline(deadlineComponents[0].trim(), + deadlineComponents[1].trim()) + ); + + case Event: + String[] eventComponents = contents.split(" /at "); + if (eventComponents.length != 2) + throw new InvalidEventException(); + else if (eventComponents[0].isBlank()) + throw new EmptyTitleException(); + else + return new CreateEventCommand( + new Event(eventComponents[0].trim(), + eventComponents[1].trim()) + ); + } + } catch (NumberFormatException e) { + throw new InvalidTaskIndexException(); + } + } + throw new InvalidCommandException("Unknown command found"); + } +} diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/storage/Storage.java similarity index 92% rename from src/main/java/duke/Storage.java rename to src/main/java/duke/storage/Storage.java index cd6665add9..6a3115e369 100644 --- a/src/main/java/duke/Storage.java +++ b/src/main/java/duke/storage/Storage.java @@ -1,10 +1,7 @@ -package duke; +package duke.storage; -import duke.Exceptions.ParsingTaskException; -import duke.Task.Deadline; -import duke.Task.Event; -import duke.Task.Task; -import duke.Task.Todo; +import duke.exceptions.ParsingTaskException; +import duke.task.*; import java.io.BufferedWriter; import java.io.File; @@ -19,12 +16,14 @@ public class Storage { public Storage(String dataPath) { this.dataPath = dataPath; } + private static Task parse(String data) throws ParsingTaskException { String[] components = data.split(","); if (components.length == 0) { throw new ParsingTaskException("Data was empty or not formatted properly."); } + String type = components[0]; if (type.equals("T")) { @@ -40,8 +39,8 @@ private static Task parse(String data) throws ParsingTaskException { public List load() { List tasks = new ArrayList<>(); - try { + try { File file = new File(this.dataPath); file.getParentFile().mkdirs(); @@ -62,7 +61,6 @@ public List load() { scanner.close(); return tasks; - } catch (Exception e) { System.out.println("An error occurred.\n" + e); } finally { @@ -74,6 +72,7 @@ public void save(TaskList tasks) { try { // Create new file String content = ""; + for (Task t : tasks.getAll()) { content += t.toSaveString() + "\n"; } @@ -95,8 +94,7 @@ public void save(TaskList tasks) { bw.close(); System.out.println("Saved tasks list successfully!"); - } - catch (Exception e){ + } catch (Exception e) { System.out.println(e + this.dataPath); } } diff --git a/src/main/java/duke/Task/Deadline.java b/src/main/java/duke/task/Deadline.java similarity index 97% rename from src/main/java/duke/Task/Deadline.java rename to src/main/java/duke/task/Deadline.java index 95c0d6c73e..6eb209add0 100644 --- a/src/main/java/duke/Task/Deadline.java +++ b/src/main/java/duke/task/Deadline.java @@ -1,5 +1,5 @@ -package duke.Task; -import duke.Exceptions.*; +package duke.task; +import duke.exceptions.*; import java.time.LocalDate; import java.time.format.DateTimeFormatter; diff --git a/src/main/java/duke/Task/Event.java b/src/main/java/duke/task/Event.java similarity index 97% rename from src/main/java/duke/Task/Event.java rename to src/main/java/duke/task/Event.java index 722462ea0f..b9ebd2c199 100644 --- a/src/main/java/duke/Task/Event.java +++ b/src/main/java/duke/task/Event.java @@ -1,5 +1,5 @@ -package duke.Task; -import duke.Exceptions.*; +package duke.task; +import duke.exceptions.*; import java.time.LocalDate; import java.time.format.DateTimeFormatter; diff --git a/src/main/java/duke/Task/Task.java b/src/main/java/duke/task/Task.java similarity index 87% rename from src/main/java/duke/Task/Task.java rename to src/main/java/duke/task/Task.java index bf0278e69d..12c1223e9b 100644 --- a/src/main/java/duke/Task/Task.java +++ b/src/main/java/duke/task/Task.java @@ -1,12 +1,6 @@ -package duke.Task; -import duke.Exceptions.*; +package duke.task; -import java.util.ArrayList; -import java.util.List; -import java.io.BufferedWriter; import java.io.File; -import java.io.FileWriter; -import java.util.Scanner; import java.time.format.FormatStyle; diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/task/TaskList.java similarity index 86% rename from src/main/java/duke/TaskList.java rename to src/main/java/duke/task/TaskList.java index 764fbd5b17..8a06ac1bfc 100644 --- a/src/main/java/duke/TaskList.java +++ b/src/main/java/duke/task/TaskList.java @@ -1,14 +1,14 @@ -package duke; +package duke.task; -import duke.Exceptions.NoSuchTaskException; -import duke.Task.Task; +import duke.exceptions.NoSuchTaskException; +import duke.task.Task; import java.util.List; public class TaskList { private List tasks; - TaskList(List tasks) { + public TaskList(List tasks) { this.tasks = tasks; } @@ -23,6 +23,7 @@ public int getNumTasks() { private String getNumTasksAsString() { return String.format("Now you have %d tasks in the list.", this.getNumTasks()); } + public void store(Task task) { this.tasks.add(task); } @@ -31,7 +32,7 @@ public Task get(int index) throws NoSuchTaskException { try { return this.tasks.get(index); } catch (IndexOutOfBoundsException e) { - throw new NoSuchTaskException(this.getNumTasks(), index+1); + throw new NoSuchTaskException(this.getNumTasks(), index + 1); } } diff --git a/src/main/java/duke/Task/Todo.java b/src/main/java/duke/task/Todo.java similarity index 96% rename from src/main/java/duke/Task/Todo.java rename to src/main/java/duke/task/Todo.java index 4512437623..f5116ff3af 100644 --- a/src/main/java/duke/Task/Todo.java +++ b/src/main/java/duke/task/Todo.java @@ -1,5 +1,5 @@ -package duke.Task; -import duke.Exceptions.*; +package duke.task; +import duke.exceptions.*; /** * Todos are tasks without any date/time attached to it e.g., visit new theme park diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/ui/Ui.java similarity index 60% rename from src/main/java/duke/Ui.java rename to src/main/java/duke/ui/Ui.java index 42bde15333..1e272be8da 100644 --- a/src/main/java/duke/Ui.java +++ b/src/main/java/duke/ui/Ui.java @@ -1,8 +1,8 @@ -package duke; +package duke.ui; public class Ui { - private static String WELCOME_MESSAGE = "Hello! I'm duke.Duke\n" + "What can I do for you?"; - private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; + private static String WELCOME_MESSAGE = "Hello! I'm duke.Duke\n" + "What can I do for you?"; + private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; public void showWelcomeMessage() { System.out.println(WELCOME_MESSAGE); From aea59458e3aff08b381471f3b13290cf7d7194f8 Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 23 Aug 2022 20:10:02 +0800 Subject: [PATCH 18/31] Add JavaDoc comments to the code. --- src/main/java/duke/Duke.java | 6 ++++++ src/main/java/duke/commands/Command.java | 13 +++++++++++++ .../java/duke/commands/CreateDeadlineCommand.java | 7 +++++++ src/main/java/duke/commands/CreateEventCommand.java | 7 +++++++ src/main/java/duke/commands/CreateTaskCommand.java | 7 +++++++ src/main/java/duke/commands/CreateTodoCommand.java | 7 +++++++ src/main/java/duke/commands/DeleteTaskCommand.java | 7 +++++++ src/main/java/duke/commands/ExitCommand.java | 3 +++ src/main/java/duke/commands/ListTasksCommand.java | 3 +++ src/main/java/duke/commands/MarkCommand.java | 7 +++++++ src/main/java/duke/commands/UnmarkCommand.java | 7 +++++++ src/main/java/duke/enums/Action.java | 4 ++++ src/main/java/duke/parser/Parser.java | 9 +++++++++ src/main/java/duke/storage/Storage.java | 11 +++++++++++ src/main/java/duke/ui/Ui.java | 13 +++++++++++++ 15 files changed, 111 insertions(+) diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java index b67832c3c5..70631ab9dc 100644 --- a/src/main/java/duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -10,6 +10,9 @@ import java.io.File; import java.util.Scanner; +/** + * The entry point to the application. + */ class Duke { private static final String DATA_PATH = new File("").getAbsolutePath() + "/data/duke.txt"; private TaskList taskList; @@ -23,6 +26,9 @@ class Duke { this.ui = new Ui(); } + /** + * Initialises the application and begins interacting with the user. + */ public void run() { this.ui.showWelcomeMessage(); diff --git a/src/main/java/duke/commands/Command.java b/src/main/java/duke/commands/Command.java index 7da4cdc095..70382e03c6 100644 --- a/src/main/java/duke/commands/Command.java +++ b/src/main/java/duke/commands/Command.java @@ -5,9 +5,22 @@ import duke.task.TaskList; import duke.ui.Ui; +/** + * An executable command. + */ public abstract class Command { + /** + * Executes the command. + * @param taskList the user's task list + * @param ui interface to interact with the user + * @param storage storage handler of user data + * @throws InvalidCommandException + */ public abstract void execute(TaskList taskList, Ui ui, Storage storage) throws InvalidCommandException; + /** + * {@return true if and only if this command is the exit command} + */ public boolean isExit() { return this instanceof ExitCommand; } diff --git a/src/main/java/duke/commands/CreateDeadlineCommand.java b/src/main/java/duke/commands/CreateDeadlineCommand.java index 4772da7025..12f9547523 100644 --- a/src/main/java/duke/commands/CreateDeadlineCommand.java +++ b/src/main/java/duke/commands/CreateDeadlineCommand.java @@ -2,7 +2,14 @@ import duke.task.Deadline; +/** + * Creates and stores a deadline. + */ public class CreateDeadlineCommand extends CreateTaskCommand { + /** + * Constructor for CreateDeadlineCommand. + * @param deadline the deadline to be stored + */ public CreateDeadlineCommand(Deadline deadline) { super(deadline); } diff --git a/src/main/java/duke/commands/CreateEventCommand.java b/src/main/java/duke/commands/CreateEventCommand.java index 138613956e..169f8e7a61 100644 --- a/src/main/java/duke/commands/CreateEventCommand.java +++ b/src/main/java/duke/commands/CreateEventCommand.java @@ -2,7 +2,14 @@ import duke.task.Event; +/** + * Creates and stores an event. + */ public class CreateEventCommand extends CreateTaskCommand { + /** + * Constructor for CreateEventCommand. + * @param event the event to be stored + */ public CreateEventCommand(Event event) { super(event); } diff --git a/src/main/java/duke/commands/CreateTaskCommand.java b/src/main/java/duke/commands/CreateTaskCommand.java index f289b02027..1547375aec 100644 --- a/src/main/java/duke/commands/CreateTaskCommand.java +++ b/src/main/java/duke/commands/CreateTaskCommand.java @@ -5,9 +5,16 @@ import duke.task.TaskList; import duke.ui.Ui; +/** + * Creates and stores a task. + */ class CreateTaskCommand extends Command { Task task; + /** + * Constructor for CreateTaskCommand. + * @param task the task to be stored + */ CreateTaskCommand(Task task) { this.task = task; } diff --git a/src/main/java/duke/commands/CreateTodoCommand.java b/src/main/java/duke/commands/CreateTodoCommand.java index 832a80468e..12500d530f 100644 --- a/src/main/java/duke/commands/CreateTodoCommand.java +++ b/src/main/java/duke/commands/CreateTodoCommand.java @@ -2,7 +2,14 @@ import duke.task.Todo; +/** + * Creates and stores a todo. + */ public class CreateTodoCommand extends CreateTaskCommand { + /** + * Constructor for CreateTodoCommand. + * @param todo the todo to be stored + */ public CreateTodoCommand(Todo todo) { super(todo); } diff --git a/src/main/java/duke/commands/DeleteTaskCommand.java b/src/main/java/duke/commands/DeleteTaskCommand.java index dad1aece20..4010c89e75 100644 --- a/src/main/java/duke/commands/DeleteTaskCommand.java +++ b/src/main/java/duke/commands/DeleteTaskCommand.java @@ -6,9 +6,16 @@ import duke.task.TaskList; import duke.ui.Ui; +/** + * Deletes a task from the task list. + */ public class DeleteTaskCommand extends Command { int index; + /** + * Constructor for DeleteTaskCommand. + * @param index the index specifying the task to be deleted. + */ public DeleteTaskCommand(int index) { this.index = index; } diff --git a/src/main/java/duke/commands/ExitCommand.java b/src/main/java/duke/commands/ExitCommand.java index 049ca229ac..51f14b1b24 100644 --- a/src/main/java/duke/commands/ExitCommand.java +++ b/src/main/java/duke/commands/ExitCommand.java @@ -4,6 +4,9 @@ import duke.task.TaskList; import duke.ui.Ui; +/** + * A special command that exits the application. + */ public class ExitCommand extends Command { @Override public void execute(TaskList taskList, Ui ui, Storage storage) { diff --git a/src/main/java/duke/commands/ListTasksCommand.java b/src/main/java/duke/commands/ListTasksCommand.java index d3350b26a4..330aff1170 100644 --- a/src/main/java/duke/commands/ListTasksCommand.java +++ b/src/main/java/duke/commands/ListTasksCommand.java @@ -5,6 +5,9 @@ import duke.task.TaskList; import duke.ui.Ui; +/** + * Displays the user's current tasks in a numbered list format. + */ public class ListTasksCommand extends Command { @Override public void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { diff --git a/src/main/java/duke/commands/MarkCommand.java b/src/main/java/duke/commands/MarkCommand.java index 46d7e6151a..06679876b7 100644 --- a/src/main/java/duke/commands/MarkCommand.java +++ b/src/main/java/duke/commands/MarkCommand.java @@ -6,9 +6,16 @@ import duke.task.TaskList; import duke.ui.Ui; +/** + * Marks a task as completed. + */ public class MarkCommand extends Command { int index; + /** + * Constructor for MarkCommand. + * @param index the index specifying the task to be deleted. + */ public MarkCommand(int index) { this.index = index; } diff --git a/src/main/java/duke/commands/UnmarkCommand.java b/src/main/java/duke/commands/UnmarkCommand.java index 1233526c02..cfe077f55c 100644 --- a/src/main/java/duke/commands/UnmarkCommand.java +++ b/src/main/java/duke/commands/UnmarkCommand.java @@ -6,9 +6,16 @@ import duke.task.TaskList; import duke.ui.Ui; +/** + * Marks a task as incomplete. + */ public class UnmarkCommand extends Command { int index; + /** + * Constructor for UnmarkCommand. + * @param index the index specifying the task to be deleted. + */ public UnmarkCommand(int index) { this.index = index; } diff --git a/src/main/java/duke/enums/Action.java b/src/main/java/duke/enums/Action.java index a881e58c8a..f36d1dd53c 100644 --- a/src/main/java/duke/enums/Action.java +++ b/src/main/java/duke/enums/Action.java @@ -1,6 +1,10 @@ package duke.enums; import duke.exceptions.*; +/** + * A helper enumeration that specifies various keywords and their corresponding actions that are + * available to the user. + */ public enum Action { Todo("todo"), Deadline("deadline"), diff --git a/src/main/java/duke/parser/Parser.java b/src/main/java/duke/parser/Parser.java index 1e6331f15a..bf0c355ad6 100644 --- a/src/main/java/duke/parser/Parser.java +++ b/src/main/java/duke/parser/Parser.java @@ -7,7 +7,16 @@ import duke.task.Event; import duke.task.Todo; +/** + * The interface responsible for parsing user input. + */ public class Parser { + /** + * Parses a raw string into a command, if possible. + * @param fullCommand the string to parse + * @return the appropriate Command corresponding to the input + * @throws InvalidCommandException if the input string is not a valid command. + */ public static Command parse(String fullCommand) throws InvalidCommandException { if (fullCommand.trim().equals("bye")) { return new ExitCommand(); diff --git a/src/main/java/duke/storage/Storage.java b/src/main/java/duke/storage/Storage.java index 6a3115e369..8d2f610e9f 100644 --- a/src/main/java/duke/storage/Storage.java +++ b/src/main/java/duke/storage/Storage.java @@ -10,9 +10,16 @@ import java.util.List; import java.util.Scanner; +/** + * The interface responsible for handling the storing and loading of user data from disk. + */ public class Storage { private String dataPath; + /** + * Constructor for Storage. + * @param dataPath the file path to a .txt file to store and load data from. + */ public Storage(String dataPath) { this.dataPath = dataPath; } @@ -37,6 +44,10 @@ private static Task parse(String data) throws ParsingTaskException { } } + /** + * Loads the user's task list from disk. + * @return the user's task list. If the file does not exist, an empty one is created. + */ public List load() { List tasks = new ArrayList<>(); diff --git a/src/main/java/duke/ui/Ui.java b/src/main/java/duke/ui/Ui.java index 1e272be8da..d785320913 100644 --- a/src/main/java/duke/ui/Ui.java +++ b/src/main/java/duke/ui/Ui.java @@ -1,17 +1,30 @@ package duke.ui; +/** + * The interface responsible for handling the app's interaction with the user. + */ public class Ui { private static String WELCOME_MESSAGE = "Hello! I'm duke.Duke\n" + "What can I do for you?"; private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; + /** + * Welcomes the user. + */ public void showWelcomeMessage() { System.out.println(WELCOME_MESSAGE); } + /** + * Bids the user goodbye. This should only be shown on the ExitCommand. + */ public void showGoodByeMessage() { System.out.println(GOODBYE_MESSAGE); } + /** + * Displays a message to the user. + * @param message the message to be displayed + */ public void showMessage(String message) { System.out.println(message); } From c4284febf04966e603b91a6bfec119e05d4a04af Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 23 Aug 2022 20:13:56 +0800 Subject: [PATCH 19/31] Tweak the code to comply with a coding standard --- src/main/java/duke/enums/Action.java | 4 +++- .../java/duke/exceptions/NoSuchTaskException.java | 14 ++++++-------- src/main/java/duke/parser/Parser.java | 1 - 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/duke/enums/Action.java b/src/main/java/duke/enums/Action.java index a881e58c8a..28c173f9bc 100644 --- a/src/main/java/duke/enums/Action.java +++ b/src/main/java/duke/enums/Action.java @@ -1,4 +1,5 @@ package duke.enums; + import duke.exceptions.*; public enum Action { @@ -10,6 +11,7 @@ public enum Action { Delete("delete"); public final String label; + private Action(String label) { this.label = label; } @@ -22,6 +24,6 @@ public static Action parseCommand(String command) throws InvalidCommandException } throw new InvalidCommandException( - "Could not determine the command. Valid commands include: todo, deadline, event, mark, unmark, delete"); + "Could not determine the command. Valid commands include: todo, deadline, event, mark, unmark, delete"); } } diff --git a/src/main/java/duke/exceptions/NoSuchTaskException.java b/src/main/java/duke/exceptions/NoSuchTaskException.java index 71520b1d06..b707529ed9 100644 --- a/src/main/java/duke/exceptions/NoSuchTaskException.java +++ b/src/main/java/duke/exceptions/NoSuchTaskException.java @@ -2,16 +2,14 @@ public class NoSuchTaskException extends InvalidCommandException { public NoSuchTaskException(int numTasks, int index) { - super( - String.format("No task found. You only have %d tasks but you referenced a task at index %d", - numTasks, - index)); + super(String.format("No task found. You only have %d tasks but you referenced a task at index %d", + numTasks, + index)); } public NoSuchTaskException(int numTasks, String index) { - super( - String.format("No task found. You only have %d tasks but you referenced a task at index %s", - numTasks, - index)); + super(String.format("No task found. You only have %d tasks but you referenced a task at index %s", + numTasks, + index)); } } diff --git a/src/main/java/duke/parser/Parser.java b/src/main/java/duke/parser/Parser.java index 1e6331f15a..34b2f7af6c 100644 --- a/src/main/java/duke/parser/Parser.java +++ b/src/main/java/duke/parser/Parser.java @@ -44,7 +44,6 @@ else if (deadlineComponents[0].isBlank()) new Deadline(deadlineComponents[0].trim(), deadlineComponents[1].trim()) ); - case Event: String[] eventComponents = contents.split(" /at "); if (eventComponents.length != 2) From a326853cc266162c3aa56558a710cbc9af569b49 Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 23 Aug 2022 20:25:51 +0800 Subject: [PATCH 20/31] Give users a way to find a task by searching for a keyword. --- src/main/java/duke/commands/FindCommand.java | 31 +++++++++++++++++++ .../java/duke/commands/ListTasksCommand.java | 8 +++-- src/main/java/duke/enums/Action.java | 3 +- src/main/java/duke/parser/Parser.java | 2 ++ src/main/java/duke/task/Task.java | 4 +++ src/main/java/duke/ui/Ui.java | 2 +- 6 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 src/main/java/duke/commands/FindCommand.java diff --git a/src/main/java/duke/commands/FindCommand.java b/src/main/java/duke/commands/FindCommand.java new file mode 100644 index 0000000000..1b8a3c72bc --- /dev/null +++ b/src/main/java/duke/commands/FindCommand.java @@ -0,0 +1,31 @@ +package duke.commands; + +import duke.storage.Storage; +import duke.task.Task; +import duke.task.TaskList; +import duke.ui.Ui; + +import java.util.List; +import java.util.stream.Collectors; + +public class FindCommand extends Command { + private String keyword; + + public FindCommand(String keyword) { + this.keyword = keyword; + } + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) { + List tasks = taskList.getAll().stream() + .filter(task -> task.getTitle().contains(this.keyword)) + .collect(Collectors.toList()); + + if (tasks.isEmpty()) { + ui.showMessage(String.format("No tasks found for the keyword: [%s]", this.keyword)); + } else { + TaskList filteredTaskLists = new TaskList(tasks); + new ListTasksCommand().execute(filteredTaskLists, ui, storage); + } + } +} diff --git a/src/main/java/duke/commands/ListTasksCommand.java b/src/main/java/duke/commands/ListTasksCommand.java index d3350b26a4..897a3493b5 100644 --- a/src/main/java/duke/commands/ListTasksCommand.java +++ b/src/main/java/duke/commands/ListTasksCommand.java @@ -2,15 +2,19 @@ import duke.exceptions.NoSuchTaskException; import duke.storage.Storage; +import duke.task.Task; import duke.task.TaskList; import duke.ui.Ui; +import java.util.List; + public class ListTasksCommand extends Command { @Override - public void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { + public void execute(TaskList taskList, Ui ui, Storage storage) { + List tasks = taskList.getAll(); ui.showMessage("Here are the tasks in your list:"); for (int i = 0; i < taskList.getNumTasks(); i++) { - ui.showMessage(String.format("%d. %s", i + 1, taskList.get(i))); + ui.showMessage(String.format("%d. %s", i + 1, tasks.get(i))); } } } \ No newline at end of file diff --git a/src/main/java/duke/enums/Action.java b/src/main/java/duke/enums/Action.java index a881e58c8a..995dfec567 100644 --- a/src/main/java/duke/enums/Action.java +++ b/src/main/java/duke/enums/Action.java @@ -7,7 +7,8 @@ public enum Action { Event("event"), Mark("mark"), Unmark("unmark"), - Delete("delete"); + Delete("delete"), + Find("find"); public final String label; private Action(String label) { diff --git a/src/main/java/duke/parser/Parser.java b/src/main/java/duke/parser/Parser.java index 1e6331f15a..d5dc680892 100644 --- a/src/main/java/duke/parser/Parser.java +++ b/src/main/java/duke/parser/Parser.java @@ -27,6 +27,8 @@ public static Command parse(String fullCommand) throws InvalidCommandException { return new UnmarkCommand(Integer.parseInt(contents) - 1); case Delete: return new DeleteTaskCommand(Integer.parseInt(contents) - 1); + case Find: + return new FindCommand(contents); case Todo: if (contents.isBlank()) throw new EmptyTitleException(); diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java index 12c1223e9b..905218f06b 100644 --- a/src/main/java/duke/task/Task.java +++ b/src/main/java/duke/task/Task.java @@ -22,6 +22,10 @@ public class Task { this.completed = completed; } + public String getTitle() { + return this.title; + } + public void markAsCompleted() { this.completed = true; System.out.println(String.format("Nice! I've marked this task as done:\n\t%s", this)); diff --git a/src/main/java/duke/ui/Ui.java b/src/main/java/duke/ui/Ui.java index 1e272be8da..940df1ebcd 100644 --- a/src/main/java/duke/ui/Ui.java +++ b/src/main/java/duke/ui/Ui.java @@ -1,7 +1,7 @@ package duke.ui; public class Ui { - private static String WELCOME_MESSAGE = "Hello! I'm duke.Duke\n" + "What can I do for you?"; + private static String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; public void showWelcomeMessage() { From ba264ca7021028cdc946b2f5e3910c15b1e2efb4 Mon Sep 17 00:00:00 2001 From: Brandon Date: Wed, 24 Aug 2022 15:11:24 +0800 Subject: [PATCH 21/31] Add JUnit tests to test the behavior of the code. --- src/main/java/duke/task/Deadline.java | 10 ++- src/main/java/duke/task/Event.java | 4 + src/main/java/duke/task/Task.java | 4 + src/main/java/duke/task/TaskList.java | 1 - .../commands/CreateDeadlineCommandTest.java | 38 ++++++++++ .../duke/commands/CreateEventCommandTest.java | 38 ++++++++++ .../duke/commands/CreateTodoCommandTest.java | 38 ++++++++++ .../duke/commands/DeleteTaskCommandTest.java | 47 ++++++++++++ .../java/duke/commands/ExitCommandTest.java | 14 ++++ .../java/duke/commands/MarkCommandTest.java | 49 ++++++++++++ .../java/duke/commands/UnmarkCommandTest.java | 49 ++++++++++++ src/test/java/duke/task/DeadlineTest.java | 41 ++++++++++ src/test/java/duke/task/EventTest.java | 41 ++++++++++ src/test/java/duke/task/TaskListTest.java | 75 +++++++++++++++++++ src/test/java/duke/task/TaskTest.java | 35 +++++++++ 15 files changed, 482 insertions(+), 2 deletions(-) create mode 100644 src/test/java/duke/commands/CreateDeadlineCommandTest.java create mode 100644 src/test/java/duke/commands/CreateEventCommandTest.java create mode 100644 src/test/java/duke/commands/CreateTodoCommandTest.java create mode 100644 src/test/java/duke/commands/DeleteTaskCommandTest.java create mode 100644 src/test/java/duke/commands/ExitCommandTest.java create mode 100644 src/test/java/duke/commands/MarkCommandTest.java create mode 100644 src/test/java/duke/commands/UnmarkCommandTest.java create mode 100644 src/test/java/duke/task/DeadlineTest.java create mode 100644 src/test/java/duke/task/EventTest.java create mode 100644 src/test/java/duke/task/TaskListTest.java create mode 100644 src/test/java/duke/task/TaskTest.java diff --git a/src/main/java/duke/task/Deadline.java b/src/main/java/duke/task/Deadline.java index 6eb209add0..778ab5b648 100644 --- a/src/main/java/duke/task/Deadline.java +++ b/src/main/java/duke/task/Deadline.java @@ -1,4 +1,5 @@ package duke.task; + import duke.exceptions.*; import java.time.LocalDate; @@ -11,6 +12,11 @@ public class Deadline extends Task { protected LocalDate by; + public Deadline(String description, LocalDate by) { + super(description); + this.by = by; + } + public Deadline(String description, String by) throws InvalidDeadlineException { super(description); try { @@ -28,14 +34,16 @@ public Deadline(String description, String by, boolean completed) throws Invalid throw new InvalidDeadlineException(); } } + public String getFormattedDate() { return this.by.format(DateTimeFormatter.ofLocalizedDate(DATE_FORMAT)); } + @Override public String toString() { return "[D]" + super.toString() - + " (by: " + this.getFormattedDate() + + " (by: " + this.getFormattedDate() + ")"; } diff --git a/src/main/java/duke/task/Event.java b/src/main/java/duke/task/Event.java index b9ebd2c199..1a3440bd38 100644 --- a/src/main/java/duke/task/Event.java +++ b/src/main/java/duke/task/Event.java @@ -12,6 +12,10 @@ public class Event extends Task { protected LocalDate at; + public Event(String description, LocalDate at) { + super(description); + this.at = at; + } public Event(String description, String at) throws InvalidEventException { super(description); try { diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java index 905218f06b..9fca016501 100644 --- a/src/main/java/duke/task/Task.java +++ b/src/main/java/duke/task/Task.java @@ -26,6 +26,10 @@ public String getTitle() { return this.title; } + public boolean isCompleted() { + return this.completed; + } + public void markAsCompleted() { this.completed = true; System.out.println(String.format("Nice! I've marked this task as done:\n\t%s", this)); diff --git a/src/main/java/duke/task/TaskList.java b/src/main/java/duke/task/TaskList.java index 8a06ac1bfc..5c71f4a823 100644 --- a/src/main/java/duke/task/TaskList.java +++ b/src/main/java/duke/task/TaskList.java @@ -1,7 +1,6 @@ package duke.task; import duke.exceptions.NoSuchTaskException; -import duke.task.Task; import java.util.List; diff --git a/src/test/java/duke/commands/CreateDeadlineCommandTest.java b/src/test/java/duke/commands/CreateDeadlineCommandTest.java new file mode 100644 index 0000000000..ab64804e79 --- /dev/null +++ b/src/test/java/duke/commands/CreateDeadlineCommandTest.java @@ -0,0 +1,38 @@ +package duke.commands; + +import duke.storage.Storage; +import duke.task.Deadline; +import duke.task.Task; +import duke.task.TaskList; +import duke.ui.Ui; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +public class CreateDeadlineCommandTest { + + @Test + public void execution_addsSingleItem() { + TaskList taskList = new TaskList(new ArrayList()); + Deadline deadline = new Deadline("Test", LocalDate.now()); + CreateDeadlineCommand command = new CreateDeadlineCommand(deadline); + + command.execute(taskList, new Ui(), new Storage("")); + + assertEquals(taskList.getNumTasks(), 1); + } + + @Test + public void execution_addsCreatedDeadline() { + TaskList taskList = new TaskList(new ArrayList()); + Deadline deadline = new Deadline("Test", LocalDate.now()); + CreateDeadlineCommand command = new CreateDeadlineCommand(deadline); + + command.execute(taskList, new Ui(), new Storage("")); + + assertDoesNotThrow(() -> assertEquals(taskList.get(0), deadline)); + } +} diff --git a/src/test/java/duke/commands/CreateEventCommandTest.java b/src/test/java/duke/commands/CreateEventCommandTest.java new file mode 100644 index 0000000000..47ad873f0f --- /dev/null +++ b/src/test/java/duke/commands/CreateEventCommandTest.java @@ -0,0 +1,38 @@ +package duke.commands; + +import duke.storage.Storage; +import duke.task.Event; +import duke.task.Task; +import duke.task.TaskList; +import duke.ui.Ui; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +public class CreateEventCommandTest { + + @Test + public void execution_addsSingleItem() { + TaskList taskList = new TaskList(new ArrayList()); + Event event = new Event("Test", LocalDate.now()); + CreateEventCommand command = new CreateEventCommand(event); + + command.execute(taskList, new Ui(), new Storage("")); + + assertEquals(taskList.getNumTasks(), 1); + } + + @Test + public void execution_addsCreatedEvent() { + TaskList taskList = new TaskList(new ArrayList()); + Event event = new Event("Test", LocalDate.now()); + CreateEventCommand command = new CreateEventCommand(event); + + command.execute(taskList, new Ui(), new Storage("")); + + assertDoesNotThrow(() -> assertEquals(taskList.get(0), event)); + } +} diff --git a/src/test/java/duke/commands/CreateTodoCommandTest.java b/src/test/java/duke/commands/CreateTodoCommandTest.java new file mode 100644 index 0000000000..085dfa4925 --- /dev/null +++ b/src/test/java/duke/commands/CreateTodoCommandTest.java @@ -0,0 +1,38 @@ +package duke.commands; + +import duke.storage.Storage; +import duke.task.Event; +import duke.task.Task; +import duke.task.TaskList; +import duke.task.Todo; +import duke.ui.Ui; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +public class CreateTodoCommandTest { + + @Test + public void execution_addsSingleItem() { + TaskList taskList = new TaskList(new ArrayList()); + Todo todo = new Todo("Test"); + CreateTodoCommand command = new CreateTodoCommand(todo); + + command.execute(taskList, new Ui(), new Storage("")); + + assertEquals(taskList.getNumTasks(), 1); + } + + @Test + public void execution_addsCreatedEvent() { + TaskList taskList = new TaskList(new ArrayList()); + Todo todo = new Todo("Test"); + CreateTodoCommand command = new CreateTodoCommand(todo); + + command.execute(taskList, new Ui(), new Storage("")); + + assertDoesNotThrow(() -> assertEquals(taskList.get(0), todo)); + } +} diff --git a/src/test/java/duke/commands/DeleteTaskCommandTest.java b/src/test/java/duke/commands/DeleteTaskCommandTest.java new file mode 100644 index 0000000000..c79bf32dcb --- /dev/null +++ b/src/test/java/duke/commands/DeleteTaskCommandTest.java @@ -0,0 +1,47 @@ +package duke.commands; + +import duke.exceptions.NoSuchTaskException; +import duke.storage.Storage; +import duke.task.TaskList; +import duke.task.Todo; +import duke.ui.Ui; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +public class DeleteTaskCommandTest { + private static TaskList getTaskList() { + return new TaskList(new ArrayList<>() {{ + add(new Todo("Test")); + }}); + } + + @Test + public void execution_invalidIndex_exceptionThrown() { + + DeleteTaskCommand command = new DeleteTaskCommand(1); + + assertThrows(NoSuchTaskException.class, + () -> command.execute(getTaskList(), new Ui(), new Storage(""))); + } + + @Test + public void execution_validIndex_exceptionNotThrown() { + DeleteTaskCommand command = new DeleteTaskCommand(0); + + assertDoesNotThrow(() -> command.execute(getTaskList(), new Ui(), new Storage(""))); + } + + @Test + public void execution_validIndex_singleTaskRemoved() throws NoSuchTaskException { + + DeleteTaskCommand command = new DeleteTaskCommand(0); + TaskList taskList = getTaskList(); + + command.execute(taskList, new Ui(), new Storage("")); + + assertEquals(taskList.getNumTasks(), 0); + } +} diff --git a/src/test/java/duke/commands/ExitCommandTest.java b/src/test/java/duke/commands/ExitCommandTest.java new file mode 100644 index 0000000000..f23c1b2a68 --- /dev/null +++ b/src/test/java/duke/commands/ExitCommandTest.java @@ -0,0 +1,14 @@ +package duke.commands; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ExitCommandTest { + + @Test + public void isExit_returnsTrue() { + ExitCommand command = new ExitCommand(); + assertEquals(command.isExit(), true); + } +} diff --git a/src/test/java/duke/commands/MarkCommandTest.java b/src/test/java/duke/commands/MarkCommandTest.java new file mode 100644 index 0000000000..f17b761c95 --- /dev/null +++ b/src/test/java/duke/commands/MarkCommandTest.java @@ -0,0 +1,49 @@ +package duke.commands; + +import duke.exceptions.NoSuchTaskException; +import duke.storage.Storage; +import duke.task.TaskList; +import duke.task.Todo; +import duke.ui.Ui; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +public class MarkCommandTest { + private static TaskList getTaskList() { + return new TaskList(new ArrayList<>() {{ + add(new Todo("Test", false)); + }}); + } + + @Test + public void execution_invalidIndex_exceptionThrown() { + + MarkCommand command = new MarkCommand(1); + + assertThrows(NoSuchTaskException.class, + () -> command.execute(getTaskList(), new Ui(), new Storage(""))); + } + + @Test + public void execution_validIndex_exceptionNotThrown() { + MarkCommand command = new MarkCommand(0); + + assertDoesNotThrow(() -> command.execute(getTaskList(), new Ui(), new Storage(""))); + } + + @Test + public void execution_validIndex_singleTaskRemoved() throws NoSuchTaskException { + + MarkCommand command = new MarkCommand(0); + TaskList taskList = getTaskList(); + + assertEquals(taskList.get(0).isCompleted(), false); + + command.execute(taskList, new Ui(), new Storage("")); + + assertEquals(taskList.get(0).isCompleted(), true); + } +} diff --git a/src/test/java/duke/commands/UnmarkCommandTest.java b/src/test/java/duke/commands/UnmarkCommandTest.java new file mode 100644 index 0000000000..9852bdae0a --- /dev/null +++ b/src/test/java/duke/commands/UnmarkCommandTest.java @@ -0,0 +1,49 @@ +package duke.commands; + +import duke.exceptions.NoSuchTaskException; +import duke.storage.Storage; +import duke.task.TaskList; +import duke.task.Todo; +import duke.ui.Ui; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +public class UnmarkCommandTest { + private static TaskList getTaskList() { + return new TaskList(new ArrayList<>() {{ + add(new Todo("Test", true)); + }}); + } + + @Test + public void execution_invalidIndex_exceptionThrown() { + + UnmarkCommand command = new UnmarkCommand(1); + + assertThrows(NoSuchTaskException.class, + () -> command.execute(getTaskList(), new Ui(), new Storage(""))); + } + + @Test + public void execution_validIndex_exceptionNotThrown() { + UnmarkCommand command = new UnmarkCommand(0); + + assertDoesNotThrow(() -> command.execute(getTaskList(), new Ui(), new Storage(""))); + } + + @Test + public void execution_validIndex_singleTaskRemoved() throws NoSuchTaskException { + + UnmarkCommand command = new UnmarkCommand(0); + TaskList taskList = getTaskList(); + + assertEquals(taskList.get(0).isCompleted(), true); + + command.execute(taskList, new Ui(), new Storage("")); + + assertEquals(taskList.get(0).isCompleted(), false); + } +} diff --git a/src/test/java/duke/task/DeadlineTest.java b/src/test/java/duke/task/DeadlineTest.java new file mode 100644 index 0000000000..635b0183cb --- /dev/null +++ b/src/test/java/duke/task/DeadlineTest.java @@ -0,0 +1,41 @@ +package duke.task; +import duke.exceptions.InvalidDeadlineException; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; + +public class DeadlineTest { + + @Test + public void constructor_invalidDate_exceptionThrown() { + assertThrows(InvalidDeadlineException.class, + () -> new Deadline("Test task", "09-17-2022", false)); + + assertThrows(InvalidDeadlineException.class, + () -> new Deadline("Test task", "blah blah", false)); + } + + @Test + public void constructor_validDate_exceptionNotThrown(){ + assertDoesNotThrow(() -> new Deadline("Test task", "2022-09-17", false)); + assertDoesNotThrow(() -> new Deadline("Test task", " 2022-09-17 ", false)); + } + + @Test + public void constructor_validDate_deadlineIsCorrect(){ + assertDoesNotThrow(() -> { + Deadline deadline = new Deadline("Test task", "2022-09-17", false); + assertEquals(deadline.by, LocalDate.of(2022, 9, 17)); + }); + } + + @Test + public void constructor_validDateWithWhitespaces_deadlineIsCorrect(){ + assertDoesNotThrow(() -> { + Deadline deadline = new Deadline("Test task", " 2022-09-17 \n ", false); + assertEquals(deadline.by, LocalDate.of(2022, 9, 17)); + }); + } +} diff --git a/src/test/java/duke/task/EventTest.java b/src/test/java/duke/task/EventTest.java new file mode 100644 index 0000000000..9c4fbe36a9 --- /dev/null +++ b/src/test/java/duke/task/EventTest.java @@ -0,0 +1,41 @@ +package duke.task; +import duke.exceptions.InvalidEventException; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; + +public class EventTest { + + @Test + public void constructor_invalidDate_exceptionThrown() { + assertThrows(InvalidEventException.class, + () -> new Event("Test task", "09-17-2022", false)); + + assertThrows(InvalidEventException.class, + () -> new Event("Test task", "blah blah", false)); + } + + @Test + public void constructor_validDate_exceptionNotThrown(){ + assertDoesNotThrow(() -> new Event("Test task", "2022-09-17", false)); + assertDoesNotThrow(() -> new Event("Test task", " 2022-09-17 ", false)); + } + + @Test + public void constructor_validDate_deadlineIsCorrect(){ + assertDoesNotThrow(() -> { + Event event = new Event("Test task", "2022-09-17", false); + assertEquals(event.at, LocalDate.of(2022, 9, 17)); + }); + } + + @Test + public void constructor_validDateWithWhitespaces_deadlineIsCorrect(){ + assertDoesNotThrow(() -> { + Event event = new Event("Test task", " 2022-09-17 \n ", false); + assertEquals(event.at, LocalDate.of(2022, 9, 17)); + }); + } +} diff --git a/src/test/java/duke/task/TaskListTest.java b/src/test/java/duke/task/TaskListTest.java new file mode 100644 index 0000000000..d2690e5ef7 --- /dev/null +++ b/src/test/java/duke/task/TaskListTest.java @@ -0,0 +1,75 @@ +package duke.task; + +import duke.exceptions.NoSuchTaskException; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +public class TaskListTest { + private static TaskList getTaskListWithSingleItem() { + return new TaskList(new ArrayList<>() {{ + add(new Todo("Test", false)); + }}); + } + + @Test + public void store_insertsSingleItem() { + TaskList taskList = getTaskListWithSingleItem(); + int prevNumTasks = taskList.getNumTasks(); + + Task task = new Task("Test task"); + taskList.store(task); + + assertEquals(taskList.getNumTasks(), prevNumTasks + 1); + } + + @Test + public void store_insertsCorrectItem() throws NoSuchTaskException { + TaskList taskList = getTaskListWithSingleItem(); + int prevNumTasks = taskList.getNumTasks(); + + Task task = new Task("Test task"); + taskList.store(task); + + assertEquals(taskList.get(prevNumTasks), task); + } + + @Test + public void delete_validIndex_exceptionNotThrown() { + TaskList taskList = getTaskListWithSingleItem(); + + assertDoesNotThrow(() -> taskList.delete(0)); + } + + @Test + public void delete_invalidIndex_exceptionThrown() { + TaskList taskList = getTaskListWithSingleItem(); + + assertThrows(NoSuchTaskException.class, () -> taskList.delete(1)); + } + + @Test + public void delete_validIndex_removesSingleItem() throws NoSuchTaskException { + TaskList taskList = getTaskListWithSingleItem(); + int prevNumTasks = taskList.getNumTasks(); + + taskList.delete(0); + + assertEquals(taskList.getNumTasks(), prevNumTasks - 1); + } + + @Test + public void delete_removesCorrectItem() throws NoSuchTaskException { + TaskList taskList = getTaskListWithSingleItem(); + int prevNumTasks = taskList.getNumTasks(); + + Task task = new Task("Another task"); + taskList.store(task); + + taskList.delete(1); + + assertEquals(taskList.getAll().stream().filter(e -> e == task).toArray().length, 0); + } +} diff --git a/src/test/java/duke/task/TaskTest.java b/src/test/java/duke/task/TaskTest.java new file mode 100644 index 0000000000..e36a87005e --- /dev/null +++ b/src/test/java/duke/task/TaskTest.java @@ -0,0 +1,35 @@ +package duke.task; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TaskTest { + + @Test + public void markAsCompleted_incompleteTask_isCompletedReturnsTrue(){ + Task task = new Task("Test task", false); + task.markAsCompleted(); + assertEquals(task.isCompleted(), true); + } + + @Test + public void markAsCompleted_completeTask_isCompletedReturnsTrue(){ + Task task = new Task("Test task", true); + task.markAsCompleted(); + assertEquals(task.isCompleted(), true); + } + + @Test + public void markAsIncomplete_incompleteTask_isCompletedReturnsFalse(){ + Task task = new Task("Test task", false); + task.markAsIncomplete(); + assertEquals(task.isCompleted(), false); + } + + @Test + public void markAsInomplete_completeTask_isCompletedReturnsFalse(){ + Task task = new Task("Test task", true); + task.markAsIncomplete(); + assertEquals(task.isCompleted(), false); + } +} From 46c36e28aeb5a5ad9c3ebb7a33cbade5ab03b633 Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 26 Aug 2022 12:50:54 +0800 Subject: [PATCH 22/31] Use checkStyle to detect coding style violations. --- build.gradle | 5 + config/checkstyle/checkstyle.xml | 434 ++++++++++++++++++ config/checkstyle/suppressions.xml | 10 + src/main/java/duke/Duke.java | 8 +- .../duke/commands/CreateDeadlineCommand.java | 2 +- .../duke/commands/CreateEventCommand.java | 2 +- .../java/duke/commands/CreateTaskCommand.java | 4 +- .../java/duke/commands/CreateTodoCommand.java | 2 +- .../java/duke/commands/DeleteTaskCommand.java | 4 +- src/main/java/duke/commands/FindCommand.java | 6 +- .../java/duke/commands/ListTasksCommand.java | 6 +- src/main/java/duke/commands/MarkCommand.java | 2 +- .../java/duke/commands/UnmarkCommand.java | 4 +- src/main/java/duke/enums/Action.java | 4 +- src/main/java/duke/parser/Parser.java | 21 +- src/main/java/duke/storage/Storage.java | 6 +- src/main/java/duke/task/Deadline.java | 13 +- src/main/java/duke/task/Event.java | 53 ++- src/main/java/duke/task/Task.java | 8 +- src/main/java/duke/task/TaskList.java | 4 +- src/main/java/duke/task/Todo.java | 10 +- src/main/java/duke/ui/Ui.java | 4 +- 22 files changed, 552 insertions(+), 60 deletions(-) create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 config/checkstyle/suppressions.xml diff --git a/build.gradle b/build.gradle index cbf675804c..b252d78408 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'application' id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'checkstyle' } repositories { @@ -42,6 +43,10 @@ test { } } +checkstyle { + toolVersion = '10.2' +} + application { mainClassName = "seedu.duke.Duke" } diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..d618671b83 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..39efb6e4ac --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java index 70631ab9dc..296c199232 100644 --- a/src/main/java/duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -1,15 +1,15 @@ package duke; +import java.io.File; +import java.util.Scanner; + import duke.commands.Command; -import duke.exceptions.*; +import duke.exceptions.InvalidCommandException; import duke.parser.Parser; import duke.storage.Storage; import duke.task.TaskList; import duke.ui.Ui; -import java.io.File; -import java.util.Scanner; - /** * The entry point to the application. */ diff --git a/src/main/java/duke/commands/CreateDeadlineCommand.java b/src/main/java/duke/commands/CreateDeadlineCommand.java index 12f9547523..6a8e3d2dcd 100644 --- a/src/main/java/duke/commands/CreateDeadlineCommand.java +++ b/src/main/java/duke/commands/CreateDeadlineCommand.java @@ -13,4 +13,4 @@ public class CreateDeadlineCommand extends CreateTaskCommand { public CreateDeadlineCommand(Deadline deadline) { super(deadline); } -} \ No newline at end of file +} diff --git a/src/main/java/duke/commands/CreateEventCommand.java b/src/main/java/duke/commands/CreateEventCommand.java index 169f8e7a61..c4df7c7d65 100644 --- a/src/main/java/duke/commands/CreateEventCommand.java +++ b/src/main/java/duke/commands/CreateEventCommand.java @@ -13,4 +13,4 @@ public class CreateEventCommand extends CreateTaskCommand { public CreateEventCommand(Event event) { super(event); } -} \ No newline at end of file +} diff --git a/src/main/java/duke/commands/CreateTaskCommand.java b/src/main/java/duke/commands/CreateTaskCommand.java index 1547375aec..ebc66cf70a 100644 --- a/src/main/java/duke/commands/CreateTaskCommand.java +++ b/src/main/java/duke/commands/CreateTaskCommand.java @@ -9,7 +9,7 @@ * Creates and stores a task. */ class CreateTaskCommand extends Command { - Task task; + private final Task task; /** * Constructor for CreateTaskCommand. @@ -25,4 +25,4 @@ public void execute(TaskList taskList, Ui ui, Storage storage) { ui.showMessage(String.format("Got it. I've added this task:\n\t%s\n", task)); storage.save(taskList); } -} \ No newline at end of file +} diff --git a/src/main/java/duke/commands/CreateTodoCommand.java b/src/main/java/duke/commands/CreateTodoCommand.java index 12500d530f..7f22d260cd 100644 --- a/src/main/java/duke/commands/CreateTodoCommand.java +++ b/src/main/java/duke/commands/CreateTodoCommand.java @@ -13,4 +13,4 @@ public class CreateTodoCommand extends CreateTaskCommand { public CreateTodoCommand(Todo todo) { super(todo); } -} \ No newline at end of file +} diff --git a/src/main/java/duke/commands/DeleteTaskCommand.java b/src/main/java/duke/commands/DeleteTaskCommand.java index 4010c89e75..f3accb15c5 100644 --- a/src/main/java/duke/commands/DeleteTaskCommand.java +++ b/src/main/java/duke/commands/DeleteTaskCommand.java @@ -10,7 +10,7 @@ * Deletes a task from the task list. */ public class DeleteTaskCommand extends Command { - int index; + private final int index; /** * Constructor for DeleteTaskCommand. @@ -26,4 +26,4 @@ public void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTask ui.showMessage(String.format("Noted. I've removed this task:\n\t%s\n", task)); storage.save(taskList); } -} \ No newline at end of file +} diff --git a/src/main/java/duke/commands/FindCommand.java b/src/main/java/duke/commands/FindCommand.java index 1b8a3c72bc..a31448fab3 100644 --- a/src/main/java/duke/commands/FindCommand.java +++ b/src/main/java/duke/commands/FindCommand.java @@ -1,13 +1,13 @@ package duke.commands; +import java.util.List; +import java.util.stream.Collectors; + import duke.storage.Storage; import duke.task.Task; import duke.task.TaskList; import duke.ui.Ui; -import java.util.List; -import java.util.stream.Collectors; - public class FindCommand extends Command { private String keyword; diff --git a/src/main/java/duke/commands/ListTasksCommand.java b/src/main/java/duke/commands/ListTasksCommand.java index e4b3d1f792..3c569ef4e2 100644 --- a/src/main/java/duke/commands/ListTasksCommand.java +++ b/src/main/java/duke/commands/ListTasksCommand.java @@ -1,12 +1,12 @@ package duke.commands; +import java.util.List; + import duke.storage.Storage; import duke.task.Task; import duke.task.TaskList; import duke.ui.Ui; -import java.util.List; - /** * Displays the user's current tasks in a numbered list format. */ @@ -19,4 +19,4 @@ public void execute(TaskList taskList, Ui ui, Storage storage) { ui.showMessage(String.format("%d. %s", i + 1, tasks.get(i))); } } -} \ No newline at end of file +} diff --git a/src/main/java/duke/commands/MarkCommand.java b/src/main/java/duke/commands/MarkCommand.java index 06679876b7..4283effdcf 100644 --- a/src/main/java/duke/commands/MarkCommand.java +++ b/src/main/java/duke/commands/MarkCommand.java @@ -10,7 +10,7 @@ * Marks a task as completed. */ public class MarkCommand extends Command { - int index; + private final int index; /** * Constructor for MarkCommand. diff --git a/src/main/java/duke/commands/UnmarkCommand.java b/src/main/java/duke/commands/UnmarkCommand.java index cfe077f55c..e245630929 100644 --- a/src/main/java/duke/commands/UnmarkCommand.java +++ b/src/main/java/duke/commands/UnmarkCommand.java @@ -10,7 +10,7 @@ * Marks a task as incomplete. */ public class UnmarkCommand extends Command { - int index; + private final int index; /** * Constructor for UnmarkCommand. @@ -26,4 +26,4 @@ public void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTask task.markAsIncomplete(); storage.save(taskList); } -} \ No newline at end of file +} diff --git a/src/main/java/duke/enums/Action.java b/src/main/java/duke/enums/Action.java index 6cebb444fc..47888ff3c0 100644 --- a/src/main/java/duke/enums/Action.java +++ b/src/main/java/duke/enums/Action.java @@ -1,6 +1,6 @@ package duke.enums; -import duke.exceptions.*; +import duke.exceptions.InvalidCommandException; /** * A helper enumeration that specifies various keywords and their corresponding actions that are @@ -17,7 +17,7 @@ public enum Action { public final String label; - private Action(String label) { + Action(String label) { this.label = label; } diff --git a/src/main/java/duke/parser/Parser.java b/src/main/java/duke/parser/Parser.java index ef660dc242..f39e828bdc 100644 --- a/src/main/java/duke/parser/Parser.java +++ b/src/main/java/duke/parser/Parser.java @@ -39,33 +39,38 @@ public static Command parse(String fullCommand) throws InvalidCommandException { case Find: return new FindCommand(contents); case Todo: - if (contents.isBlank()) + if (contents.isBlank()) { throw new EmptyTitleException(); - else + } else { return new CreateTodoCommand(new Todo(contents)); + } case Deadline: String[] deadlineComponents = contents.split(" /by "); - if (deadlineComponents.length != 2) + if (deadlineComponents.length != 2) { throw new InvalidDeadlineException(); - else if (deadlineComponents[0].isBlank()) + } else if (deadlineComponents[0].isBlank()) { throw new EmptyTitleException(); - else + } else { return new CreateDeadlineCommand( new Deadline(deadlineComponents[0].trim(), deadlineComponents[1].trim()) ); + } case Event: String[] eventComponents = contents.split(" /at "); - if (eventComponents.length != 2) + if (eventComponents.length != 2) { throw new InvalidEventException(); - else if (eventComponents[0].isBlank()) + } else if (eventComponents[0].isBlank()) { throw new EmptyTitleException(); - else + } else { return new CreateEventCommand( new Event(eventComponents[0].trim(), eventComponents[1].trim()) ); + } + default: + break; } } catch (NumberFormatException e) { throw new InvalidTaskIndexException(); diff --git a/src/main/java/duke/storage/Storage.java b/src/main/java/duke/storage/Storage.java index 8d2f610e9f..ea38e215a7 100644 --- a/src/main/java/duke/storage/Storage.java +++ b/src/main/java/duke/storage/Storage.java @@ -1,8 +1,5 @@ package duke.storage; -import duke.exceptions.ParsingTaskException; -import duke.task.*; - import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; @@ -10,6 +7,9 @@ import java.util.List; import java.util.Scanner; +import duke.exceptions.ParsingTaskException; +import duke.task.*; + /** * The interface responsible for handling the storing and loading of user data from disk. */ diff --git a/src/main/java/duke/task/Deadline.java b/src/main/java/duke/task/Deadline.java index 778ab5b648..8d2b6f6710 100644 --- a/src/main/java/duke/task/Deadline.java +++ b/src/main/java/duke/task/Deadline.java @@ -1,11 +1,12 @@ package duke.task; -import duke.exceptions.*; - import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import duke.exceptions.InvalidDeadlineException; +import duke.exceptions.ParsingTaskException; + /** * Deadlines are tasks that need to be done before a specific date/time e.g., submit report by 11/10/2019 5pm */ @@ -55,7 +56,8 @@ public String toSaveString() { public static Deadline parse(String data) throws ParsingTaskException { String[] components = data.split(","); if (components.length != 4) { - throw new ParsingTaskException(String.format("Todos require 4 components, but found %d.", components.length)); + throw new ParsingTaskException(String.format("Todos require 4 components, but found %d.", + components.length)); } try { boolean completed = Integer.parseInt(components[1]) == 1; @@ -65,9 +67,10 @@ public static Deadline parse(String data) throws ParsingTaskException { return new Deadline(description, by, completed); } catch (NumberFormatException e) { - throw new ParsingTaskException(String.format("Expected a number at component 1, but found %s", components[1])); + throw new ParsingTaskException(String.format("Expected a number at component 1, but found %s", + components[1])); } catch (InvalidDeadlineException e) { throw new ParsingTaskException(e.getMessage()); } } -} \ No newline at end of file +} diff --git a/src/main/java/duke/task/Event.java b/src/main/java/duke/task/Event.java index 1a3440bd38..5dbab99099 100644 --- a/src/main/java/duke/task/Event.java +++ b/src/main/java/duke/task/Event.java @@ -1,21 +1,40 @@ package duke.task; -import duke.exceptions.*; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import duke.exceptions.InvalidEventException; +import duke.exceptions.ParsingTaskException; + + /** - * Events are tasks that start at a specific time and ends at a specific time e.g., team project meeting on 2/10/2019 2-4pm + * Events are tasks that start at a specific time and ends at a specific time. + * For example, team project meeting on 2/10/2019 2-4pm */ public class Event extends Task { + /** + * The time of the event. + */ protected LocalDate at; + /** + * Constructor for an Event. + * @param description the event's description + * @param at the date of the event + */ public Event(String description, LocalDate at) { super(description); this.at = at; } + + /** + * Constructor for an Event. + * @param description the event's description + * @param at a String representing the event's date + * @throws InvalidEventException if the time string cannot be parsed correctly + */ public Event(String description, String at) throws InvalidEventException { super(description); try { @@ -25,11 +44,15 @@ public Event(String description, String at) throws InvalidEventException { } } - public String getFormattedDate() { - return this.at.format(DateTimeFormatter.ofLocalizedDate(Task.DATE_FORMAT)); - } - - public Event(String description, String at, boolean completed) throws InvalidEventException { + /** + * Constructor for an Event. + * @param description the event's description + * @param at a String representing the event's date + * @param completed the completion state of the event + * @throws InvalidEventException if the time string cannot be parsed correctly + */ + public Event(String description, String at, boolean completed) + throws InvalidEventException { super(description, completed); try { this.at = LocalDate.parse(at.trim()); @@ -38,6 +61,14 @@ public Event(String description, String at, boolean completed) throws InvalidEve } } + /** + * Gets the event's date, in a reader-friendly format + * @return the formatted date string + */ + public String getFormattedDate() { + return this.at.format(DateTimeFormatter.ofLocalizedDate(Task.DATE_FORMAT)); + } + @Override public String toString() { return "[E]" + super.toString() @@ -53,7 +84,8 @@ public String toSaveString() { public static Event parse(String data) throws ParsingTaskException { String[] components = data.split(","); if (components.length != 4) { - throw new ParsingTaskException(String.format("Events require 4 components, but only found %d.", components.length)); + throw new ParsingTaskException(String.format("Events require 4 components, but only found %d.", + components.length)); } try { boolean completed = Integer.parseInt(components[1]) == 1; @@ -63,9 +95,10 @@ public static Event parse(String data) throws ParsingTaskException { return new Event(description, at, completed); } catch (NumberFormatException e) { - throw new ParsingTaskException(String.format("Expected a number at component 1, but found %s", components[1])); + throw new ParsingTaskException(String.format("Expected a number at component 1, but found %s", + components[1])); } catch (InvalidEventException e) { throw new ParsingTaskException(e.getMessage()); } } -} \ No newline at end of file +} diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java index 9fca016501..d171b2931c 100644 --- a/src/main/java/duke/task/Task.java +++ b/src/main/java/duke/task/Task.java @@ -1,17 +1,17 @@ package duke.task; import java.io.File; - import java.time.format.FormatStyle; - +/** + * An object representing a task in a checklist. This is the application's primary class. + */ public class Task { + public static final FormatStyle DATE_FORMAT = FormatStyle.MEDIUM; private static final String DATA_PATH = new File("").getAbsolutePath() + "/data/duke.txt"; private String title; private boolean completed; - public static final FormatStyle DATE_FORMAT = FormatStyle.MEDIUM; - Task(String title) { this.title = title; this.completed = false; diff --git a/src/main/java/duke/task/TaskList.java b/src/main/java/duke/task/TaskList.java index 5c71f4a823..487d0559da 100644 --- a/src/main/java/duke/task/TaskList.java +++ b/src/main/java/duke/task/TaskList.java @@ -1,9 +1,9 @@ package duke.task; -import duke.exceptions.NoSuchTaskException; - import java.util.List; +import duke.exceptions.NoSuchTaskException; + public class TaskList { private List tasks; diff --git a/src/main/java/duke/task/Todo.java b/src/main/java/duke/task/Todo.java index f5116ff3af..445b499d51 100644 --- a/src/main/java/duke/task/Todo.java +++ b/src/main/java/duke/task/Todo.java @@ -1,5 +1,5 @@ package duke.task; -import duke.exceptions.*; +import duke.exceptions.ParsingTaskException; /** * Todos are tasks without any date/time attached to it e.g., visit new theme park @@ -26,7 +26,8 @@ public String toSaveString() { public static Todo parse(String data) throws ParsingTaskException { String[] components = data.split(","); if (components.length != 3) { - throw new ParsingTaskException(String.format("Todos require 3 components, but found %d.", components.length)); + throw new ParsingTaskException(String.format("Todos require 3 components, but found %d.", + components.length)); } try { boolean completed = Integer.parseInt(components[1]) == 1; @@ -34,7 +35,8 @@ public static Todo parse(String data) throws ParsingTaskException { return new Todo(title, completed); } catch (NumberFormatException e) { - throw new ParsingTaskException(String.format("Expected a number at component 1, but found %s", components[1])); + throw new ParsingTaskException(String.format("Expected a number at component 1, but found %s", + components[1])); } } -} \ No newline at end of file +} diff --git a/src/main/java/duke/ui/Ui.java b/src/main/java/duke/ui/Ui.java index a95b236547..00088cd6f9 100644 --- a/src/main/java/duke/ui/Ui.java +++ b/src/main/java/duke/ui/Ui.java @@ -4,8 +4,8 @@ * The interface responsible for handling the app's interaction with the user. */ public class Ui { - private static String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; - private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; + private static final String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; + private static final String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; /** * Welcomes the user. From c5236d6d0b80fec87da995710fa3667ec4706d5b Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 26 Aug 2022 13:12:00 +0800 Subject: [PATCH 23/31] Got javaFX minimal example working --- src/main/java/duke/App.java | 18 ++++++++++++++++++ src/main/java/duke/Duke.java | 3 ++- src/main/java/duke/Launcher.java | 12 ++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/main/java/duke/App.java create mode 100644 src/main/java/duke/Launcher.java diff --git a/src/main/java/duke/App.java b/src/main/java/duke/App.java new file mode 100644 index 0000000000..b7895bc4d2 --- /dev/null +++ b/src/main/java/duke/App.java @@ -0,0 +1,18 @@ +package duke; + +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.stage.Stage; + +public class App extends Application { + + @Override + public void start(Stage stage) { + Label helloWorld = new Label("Hello World!"); // Creating a new Label control + Scene scene = new Scene(helloWorld); // Setting the scene to be our Label + + stage.setScene(scene); // Setting the stage to show our screen + stage.show(); // Render the stage. + } +} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java index 70631ab9dc..430d7add90 100644 --- a/src/main/java/duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -19,13 +19,14 @@ class Duke { private Storage storage; private Ui ui; - Duke() { + public Duke() { Storage storage = new Storage(DATA_PATH); this.storage = storage; this.taskList = new TaskList(storage.load()); this.ui = new Ui(); } + /** * Initialises the application and begins interacting with the user. */ diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java new file mode 100644 index 0000000000..3664518c1a --- /dev/null +++ b/src/main/java/duke/Launcher.java @@ -0,0 +1,12 @@ +package duke; + +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class Launcher { + public static void main(String[] args) { + Application.launch(App.class, args); + } +} From b610b492b26378a26765197697be32a694646861 Mon Sep 17 00:00:00 2001 From: beetee17 Date: Thu, 1 Sep 2022 08:08:59 +0800 Subject: [PATCH 24/31] Add minimal GUI using JavaFX --- .gitignore | 4 +- data/duke.txt | 3 +- src/main/java/duke/App.java | 18 --- src/main/java/duke/Duke.java | 20 +-- src/main/java/duke/DukeApp.java | 145 ++++++++++++++++++ src/main/java/duke/Launcher.java | 2 +- src/main/java/duke/Response.java | 13 ++ src/main/java/duke/commands/Command.java | 8 +- .../java/duke/commands/CreateTaskCommand.java | 10 +- .../java/duke/commands/DeleteTaskCommand.java | 7 +- src/main/java/duke/commands/ExitCommand.java | 7 +- src/main/java/duke/commands/FindCommand.java | 9 +- .../java/duke/commands/ListTasksCommand.java | 9 +- src/main/java/duke/commands/MarkCommand.java | 6 +- .../java/duke/commands/UnmarkCommand.java | 6 +- src/main/java/duke/task/Task.java | 2 - src/main/java/duke/ui/DialogBox.java | 46 ++++++ src/main/java/duke/ui/TextUi.java | 49 ++++++ src/main/java/duke/ui/Ui.java | 31 ---- .../commands/CreateDeadlineCommandTest.java | 5 +- .../duke/commands/CreateEventCommandTest.java | 5 +- .../duke/commands/CreateTodoCommandTest.java | 6 +- .../duke/commands/DeleteTaskCommandTest.java | 7 +- .../java/duke/commands/MarkCommandTest.java | 7 +- .../java/duke/commands/UnmarkCommandTest.java | 7 +- text-ui-test/ACTUAL.TXT | 74 +++++++++ text-ui-test/EXPECTED-UNIX.TXT | 74 +++++++++ 27 files changed, 468 insertions(+), 112 deletions(-) delete mode 100644 src/main/java/duke/App.java create mode 100644 src/main/java/duke/DukeApp.java create mode 100644 src/main/java/duke/Response.java create mode 100644 src/main/java/duke/ui/DialogBox.java create mode 100644 src/main/java/duke/ui/TextUi.java delete mode 100644 src/main/java/duke/ui/Ui.java create mode 100644 text-ui-test/ACTUAL.TXT create mode 100644 text-ui-test/EXPECTED-UNIX.TXT diff --git a/.gitignore b/.gitignore index f69985ef1f..e70b899aff 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,5 @@ src/main/resources/docs/ *.iml bin/ -/text-ui-test/ACTUAL.txt -text-ui-test/EXPECTED-UNIX.TXT +/text-textUi-test/ACTUAL.txt +text-textUi-test/EXPECTED-UNIX.TXT diff --git a/data/duke.txt b/data/duke.txt index b84e916f7b..1f75acba01 100644 --- a/data/duke.txt +++ b/data/duke.txt @@ -1,2 +1,3 @@ -T,0,return book +T,1,return book E,1,project meeting ,2022-10-11 +D,0,finish project,2022-09-15 diff --git a/src/main/java/duke/App.java b/src/main/java/duke/App.java deleted file mode 100644 index b7895bc4d2..0000000000 --- a/src/main/java/duke/App.java +++ /dev/null @@ -1,18 +0,0 @@ -package duke; - -import javafx.application.Application; -import javafx.scene.Scene; -import javafx.scene.control.Label; -import javafx.stage.Stage; - -public class App extends Application { - - @Override - public void start(Stage stage) { - Label helloWorld = new Label("Hello World!"); // Creating a new Label control - Scene scene = new Scene(helloWorld); // Setting the scene to be our Label - - stage.setScene(scene); // Setting the stage to show our screen - stage.show(); // Render the stage. - } -} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java index 430d7add90..86ee8c95d0 100644 --- a/src/main/java/duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -5,25 +5,25 @@ import duke.parser.Parser; import duke.storage.Storage; import duke.task.TaskList; -import duke.ui.Ui; +import duke.ui.TextUi; import java.io.File; import java.util.Scanner; /** - * The entry point to the application. + * Runs the CLI Application. */ class Duke { private static final String DATA_PATH = new File("").getAbsolutePath() + "/data/duke.txt"; - private TaskList taskList; - private Storage storage; - private Ui ui; + private final TaskList taskList; + private final Storage storage; + private final TextUi textUi; public Duke() { Storage storage = new Storage(DATA_PATH); this.storage = storage; this.taskList = new TaskList(storage.load()); - this.ui = new Ui(); + this.textUi = new TextUi(); } @@ -31,7 +31,7 @@ public Duke() { * Initialises the application and begins interacting with the user. */ public void run() { - this.ui.showWelcomeMessage(); + textUi.showWelcomeMessage(); boolean isExit = false; @@ -43,11 +43,11 @@ public void run() { String fullCommand = scanner.nextLine(); Command c = Parser.parse(fullCommand); - c.execute(this.taskList, this.ui, this.storage); - + Response response = c.execute(taskList, storage); + System.out.println(response.getMessage()); isExit = c.isExit(); } catch (InvalidCommandException e) { - ui.showMessage(e.getMessage()); + System.out.println(e.getMessage()); } } diff --git a/src/main/java/duke/DukeApp.java b/src/main/java/duke/DukeApp.java new file mode 100644 index 0000000000..4af22edc16 --- /dev/null +++ b/src/main/java/duke/DukeApp.java @@ -0,0 +1,145 @@ +package duke; + +import java.io.File; + +import duke.commands.Command; +import duke.exceptions.InvalidCommandException; +import duke.parser.Parser; +import duke.storage.Storage; +import duke.task.TaskList; +import duke.ui.DialogBox; +import duke.ui.TextUi; +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +/** + * Runs the GUI application. + */ +public class DukeApp extends Application { + private static final String DATA_PATH = new File("").getAbsolutePath() + "/data/duke.txt"; + private ScrollPane scrollPane; + private VBox dialogContainer; + private TextField userInput; + private Storage storage; + private TaskList taskList; + private TextUi textUi; + + @Override + public void start(Stage stage) { + + textUi = new TextUi(); + storage = new Storage(DATA_PATH); + taskList = new TaskList(storage.load()); + + scrollPane = new ScrollPane(); + dialogContainer = new VBox(); + scrollPane.setContent(dialogContainer); + + userInput = new TextField(); + Button submitButton = new Button("Done"); + + AnchorPane mainLayout = new AnchorPane(); + mainLayout.getChildren().addAll(scrollPane, userInput, submitButton); + + Scene scene = new Scene(mainLayout); + + stage.setTitle("Duke"); + 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); + + dialogContainer.setPrefHeight(Region.USE_COMPUTED_SIZE); + + userInput.setPrefWidth(325.0); + + submitButton.setPrefWidth(55.0); + + AnchorPane.setTopAnchor(scrollPane, 1.0); + + AnchorPane.setBottomAnchor(submitButton, 1.0); + AnchorPane.setRightAnchor(submitButton, 1.0); + + AnchorPane.setLeftAnchor(userInput , 1.0); + AnchorPane.setBottomAnchor(userInput, 1.0); + + submitButton.setOnMouseClicked((event) -> { + handleUserInput(); + }); + + userInput.setOnAction((event) -> { + handleUserInput(); + }); + + //Scroll down to the end every time dialogContainer's height changes. + dialogContainer.heightProperty().addListener((observable) -> scrollPane.setVvalue(1.0)); + + stage.setScene(scene); + stage.show(); + + welcomeUser(); + } + + /** + * 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. + */ + private void handleUserInput() { + boolean isExit = false; + + Label userText = new Label(userInput.getText()); + + final String userCommand = userInput.getText(); + + Label dukeResult; + + try { + + final Command c = Parser.parse(userCommand); + isExit = c.isExit(); + + Response response = c.execute(taskList, storage); + + dukeResult = new Label(response.getMessage()); + + } catch (InvalidCommandException e) { + textUi.showMessage(e.getMessage()); + dukeResult = new Label(e.getMessage()); + } + + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(userText), + DialogBox.getDukeDialog(dukeResult) + ); + userInput.clear(); + + if (isExit) { + System.exit(0); + } + } + + /** + * Displays the welcome message to the user. + */ + private void welcomeUser() { + dialogContainer.getChildren().addAll( + DialogBox.getDukeDialog(new Label(TextUi.WELCOME_MESSAGE)) + ); + } +} diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java index 3664518c1a..220d23814b 100644 --- a/src/main/java/duke/Launcher.java +++ b/src/main/java/duke/Launcher.java @@ -7,6 +7,6 @@ */ public class Launcher { public static void main(String[] args) { - Application.launch(App.class, args); + Application.launch(DukeApp.class, args); } } diff --git a/src/main/java/duke/Response.java b/src/main/java/duke/Response.java new file mode 100644 index 0000000000..0c1070c5fc --- /dev/null +++ b/src/main/java/duke/Response.java @@ -0,0 +1,13 @@ +package duke; + +public class Response { + private final String message; + + public Response(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/duke/commands/Command.java b/src/main/java/duke/commands/Command.java index 70382e03c6..02b4d2c47c 100644 --- a/src/main/java/duke/commands/Command.java +++ b/src/main/java/duke/commands/Command.java @@ -1,9 +1,9 @@ package duke.commands; +import duke.Response; import duke.exceptions.InvalidCommandException; import duke.storage.Storage; import duke.task.TaskList; -import duke.ui.Ui; /** * An executable command. @@ -11,12 +11,12 @@ public abstract class Command { /** * Executes the command. + * * @param taskList the user's task list - * @param ui interface to interact with the user - * @param storage storage handler of user data + * @param storage storage handler of user data * @throws InvalidCommandException */ - public abstract void execute(TaskList taskList, Ui ui, Storage storage) throws InvalidCommandException; + public abstract Response execute(TaskList taskList, Storage storage) throws InvalidCommandException; /** * {@return true if and only if this command is the exit command} diff --git a/src/main/java/duke/commands/CreateTaskCommand.java b/src/main/java/duke/commands/CreateTaskCommand.java index 1547375aec..d699fa6c98 100644 --- a/src/main/java/duke/commands/CreateTaskCommand.java +++ b/src/main/java/duke/commands/CreateTaskCommand.java @@ -1,9 +1,9 @@ package duke.commands; +import duke.Response; import duke.storage.Storage; import duke.task.Task; import duke.task.TaskList; -import duke.ui.Ui; /** * Creates and stores a task. @@ -20,9 +20,13 @@ class CreateTaskCommand extends Command { } @Override - public void execute(TaskList taskList, Ui ui, Storage storage) { + public Response execute(TaskList taskList, Storage storage) { taskList.store(task); - ui.showMessage(String.format("Got it. I've added this task:\n\t%s\n", task)); + + String message = String.format("Got it. I've added this task:\n\t%s\n", task); + storage.save(taskList); + + return new Response(message); } } \ No newline at end of file diff --git a/src/main/java/duke/commands/DeleteTaskCommand.java b/src/main/java/duke/commands/DeleteTaskCommand.java index 4010c89e75..015995c975 100644 --- a/src/main/java/duke/commands/DeleteTaskCommand.java +++ b/src/main/java/duke/commands/DeleteTaskCommand.java @@ -1,10 +1,10 @@ package duke.commands; +import duke.Response; import duke.exceptions.NoSuchTaskException; import duke.storage.Storage; import duke.task.Task; import duke.task.TaskList; -import duke.ui.Ui; /** * Deletes a task from the task list. @@ -21,9 +21,10 @@ public DeleteTaskCommand(int index) { } @Override - public void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { + public Response execute(TaskList taskList, Storage storage) throws NoSuchTaskException { Task task = taskList.delete(index); - ui.showMessage(String.format("Noted. I've removed this task:\n\t%s\n", task)); + String message = String.format("Noted. I've removed this task:\n\t%s\n", task); storage.save(taskList); + return new Response(message); } } \ No newline at end of file diff --git a/src/main/java/duke/commands/ExitCommand.java b/src/main/java/duke/commands/ExitCommand.java index 51f14b1b24..38a4135c63 100644 --- a/src/main/java/duke/commands/ExitCommand.java +++ b/src/main/java/duke/commands/ExitCommand.java @@ -1,15 +1,16 @@ package duke.commands; +import duke.Response; import duke.storage.Storage; import duke.task.TaskList; -import duke.ui.Ui; +import duke.ui.TextUi; /** * A special command that exits the application. */ public class ExitCommand extends Command { @Override - public void execute(TaskList taskList, Ui ui, Storage storage) { - ui.showGoodByeMessage(); + public Response execute(TaskList taskList, Storage storage) { + return new Response(TextUi.GOODBYE_MESSAGE); } } diff --git a/src/main/java/duke/commands/FindCommand.java b/src/main/java/duke/commands/FindCommand.java index 1b8a3c72bc..612e2e7422 100644 --- a/src/main/java/duke/commands/FindCommand.java +++ b/src/main/java/duke/commands/FindCommand.java @@ -1,9 +1,9 @@ package duke.commands; +import duke.Response; import duke.storage.Storage; import duke.task.Task; import duke.task.TaskList; -import duke.ui.Ui; import java.util.List; import java.util.stream.Collectors; @@ -16,16 +16,17 @@ public FindCommand(String keyword) { } @Override - public void execute(TaskList taskList, Ui ui, Storage storage) { + public Response execute(TaskList taskList, Storage storage) { List tasks = taskList.getAll().stream() .filter(task -> task.getTitle().contains(this.keyword)) .collect(Collectors.toList()); if (tasks.isEmpty()) { - ui.showMessage(String.format("No tasks found for the keyword: [%s]", this.keyword)); + String message = String.format("No tasks found for the keyword: [%s]", this.keyword); + return new Response(message); } else { TaskList filteredTaskLists = new TaskList(tasks); - new ListTasksCommand().execute(filteredTaskLists, ui, storage); + return new ListTasksCommand().execute(filteredTaskLists, storage); } } } diff --git a/src/main/java/duke/commands/ListTasksCommand.java b/src/main/java/duke/commands/ListTasksCommand.java index e4b3d1f792..e79c131b81 100644 --- a/src/main/java/duke/commands/ListTasksCommand.java +++ b/src/main/java/duke/commands/ListTasksCommand.java @@ -1,9 +1,9 @@ package duke.commands; +import duke.Response; import duke.storage.Storage; import duke.task.Task; import duke.task.TaskList; -import duke.ui.Ui; import java.util.List; @@ -12,11 +12,12 @@ */ public class ListTasksCommand extends Command { @Override - public void execute(TaskList taskList, Ui ui, Storage storage) { + public Response execute(TaskList taskList, Storage storage) { List tasks = taskList.getAll(); - ui.showMessage("Here are the tasks in your list:"); + StringBuilder message = new StringBuilder("Here are the tasks in your list:\n"); for (int i = 0; i < taskList.getNumTasks(); i++) { - ui.showMessage(String.format("%d. %s", i + 1, tasks.get(i))); + message.append(String.format("%d. %s\n", i + 1, tasks.get(i))); } + return new Response(message.toString()); } } \ No newline at end of file diff --git a/src/main/java/duke/commands/MarkCommand.java b/src/main/java/duke/commands/MarkCommand.java index 06679876b7..e8d62dac3a 100644 --- a/src/main/java/duke/commands/MarkCommand.java +++ b/src/main/java/duke/commands/MarkCommand.java @@ -1,10 +1,10 @@ package duke.commands; +import duke.Response; import duke.exceptions.NoSuchTaskException; import duke.storage.Storage; import duke.task.Task; import duke.task.TaskList; -import duke.ui.Ui; /** * Marks a task as completed. @@ -21,9 +21,11 @@ public MarkCommand(int index) { } @Override - public void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { + public Response execute(TaskList taskList, Storage storage) throws NoSuchTaskException { Task task = taskList.get(index); + String message = String.format("OK, I've marked this task as completed:\n\t%s", task); task.markAsCompleted(); storage.save(taskList); + return new Response(message); } } diff --git a/src/main/java/duke/commands/UnmarkCommand.java b/src/main/java/duke/commands/UnmarkCommand.java index cfe077f55c..77a63b2a9f 100644 --- a/src/main/java/duke/commands/UnmarkCommand.java +++ b/src/main/java/duke/commands/UnmarkCommand.java @@ -1,10 +1,10 @@ package duke.commands; +import duke.Response; import duke.exceptions.NoSuchTaskException; import duke.storage.Storage; import duke.task.Task; import duke.task.TaskList; -import duke.ui.Ui; /** * Marks a task as incomplete. @@ -21,9 +21,11 @@ public UnmarkCommand(int index) { } @Override - public void execute(TaskList taskList, Ui ui, Storage storage) throws NoSuchTaskException { + public Response execute(TaskList taskList, Storage storage) throws NoSuchTaskException { Task task = taskList.get(index); + String message = String.format("OK, I've marked this task as not done yet:\n\t%s", task); task.markAsIncomplete(); storage.save(taskList); + return new Response(message); } } \ No newline at end of file diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java index 9fca016501..519440c224 100644 --- a/src/main/java/duke/task/Task.java +++ b/src/main/java/duke/task/Task.java @@ -32,12 +32,10 @@ public boolean isCompleted() { public void markAsCompleted() { this.completed = true; - System.out.println(String.format("Nice! I've marked this task as done:\n\t%s", this)); } public void markAsIncomplete() { this.completed = false; - System.out.println(String.format("OK, I've marked this task as not done yet:\n\t%s", this)); } private String getStatusIcon() { diff --git a/src/main/java/duke/ui/DialogBox.java b/src/main/java/duke/ui/DialogBox.java new file mode 100644 index 0000000000..d9b0761d80 --- /dev/null +++ b/src/main/java/duke/ui/DialogBox.java @@ -0,0 +1,46 @@ +package duke.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.layout.HBox; + +/** + * The UI component that is responsible for displaying outputs to the user. + */ +public class DialogBox extends HBox { + + /** + * Constructs a component to display output, containing an image and text. + * + * @param label label containing text + */ + public DialogBox(Label label) { + label.setWrapText(true); + + this.setAlignment(Pos.TOP_RIGHT); + this.getChildren().addAll(label); + } + + /** + * 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 label) { + return new DialogBox(label); + } + + public static DialogBox getDukeDialog(Label label) { + var db = new DialogBox(label); + db.flip(); + return db; + } +} diff --git a/src/main/java/duke/ui/TextUi.java b/src/main/java/duke/ui/TextUi.java new file mode 100644 index 0000000000..dfbf7e4065 --- /dev/null +++ b/src/main/java/duke/ui/TextUi.java @@ -0,0 +1,49 @@ +package duke.ui; + +import java.io.PrintStream; +import java.util.Scanner; + +/** + * The interface responsible for handling the app's interaction with the user. + */ +public class TextUi { + public static final String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; + public static final String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; + private static final String PREFIX = "duke >> "; + + private static final PrintStream out = System.out; + private final Scanner in = new Scanner(System.in); + + /** + * Gets the command inputted by the user and returns it. + */ + public String getUserCommand() { + out.print("Enter command: "); + String inputLine = in.nextLine(); + out.println(PREFIX + inputLine); + + return inputLine; + } + + /** + * Welcomes the user. + */ + public void showWelcomeMessage() { + System.out.println(WELCOME_MESSAGE); + } + + /** + * Bids the user goodbye. This should only be shown on the ExitCommand. + */ + public void showGoodByeMessage() { + System.out.println(GOODBYE_MESSAGE); + } + + /** + * Displays a message to the user. + * @param message the message to be displayed + */ + public void showMessage(String message) { + System.out.println(message); + } +} diff --git a/src/main/java/duke/ui/Ui.java b/src/main/java/duke/ui/Ui.java deleted file mode 100644 index a95b236547..0000000000 --- a/src/main/java/duke/ui/Ui.java +++ /dev/null @@ -1,31 +0,0 @@ -package duke.ui; - -/** - * The interface responsible for handling the app's interaction with the user. - */ -public class Ui { - private static String WELCOME_MESSAGE = "Hello! I'm Duke\n" + "What can I do for you?"; - private static String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!"; - - /** - * Welcomes the user. - */ - public void showWelcomeMessage() { - System.out.println(WELCOME_MESSAGE); - } - - /** - * Bids the user goodbye. This should only be shown on the ExitCommand. - */ - public void showGoodByeMessage() { - System.out.println(GOODBYE_MESSAGE); - } - - /** - * Displays a message to the user. - * @param message the message to be displayed - */ - public void showMessage(String message) { - System.out.println(message); - } -} diff --git a/src/test/java/duke/commands/CreateDeadlineCommandTest.java b/src/test/java/duke/commands/CreateDeadlineCommandTest.java index ab64804e79..cf725b8bb0 100644 --- a/src/test/java/duke/commands/CreateDeadlineCommandTest.java +++ b/src/test/java/duke/commands/CreateDeadlineCommandTest.java @@ -4,7 +4,6 @@ import duke.task.Deadline; import duke.task.Task; import duke.task.TaskList; -import duke.ui.Ui; import org.junit.jupiter.api.Test; import java.time.LocalDate; @@ -20,7 +19,7 @@ public void execution_addsSingleItem() { Deadline deadline = new Deadline("Test", LocalDate.now()); CreateDeadlineCommand command = new CreateDeadlineCommand(deadline); - command.execute(taskList, new Ui(), new Storage("")); + command.execute(taskList, new Storage("")); assertEquals(taskList.getNumTasks(), 1); } @@ -31,7 +30,7 @@ public void execution_addsCreatedDeadline() { Deadline deadline = new Deadline("Test", LocalDate.now()); CreateDeadlineCommand command = new CreateDeadlineCommand(deadline); - command.execute(taskList, new Ui(), new Storage("")); + command.execute(taskList, new Storage("")); assertDoesNotThrow(() -> assertEquals(taskList.get(0), deadline)); } diff --git a/src/test/java/duke/commands/CreateEventCommandTest.java b/src/test/java/duke/commands/CreateEventCommandTest.java index 47ad873f0f..07395f1cd0 100644 --- a/src/test/java/duke/commands/CreateEventCommandTest.java +++ b/src/test/java/duke/commands/CreateEventCommandTest.java @@ -4,7 +4,6 @@ import duke.task.Event; import duke.task.Task; import duke.task.TaskList; -import duke.ui.Ui; import org.junit.jupiter.api.Test; import java.time.LocalDate; @@ -20,7 +19,7 @@ public void execution_addsSingleItem() { Event event = new Event("Test", LocalDate.now()); CreateEventCommand command = new CreateEventCommand(event); - command.execute(taskList, new Ui(), new Storage("")); + command.execute(taskList, new Storage("")); assertEquals(taskList.getNumTasks(), 1); } @@ -31,7 +30,7 @@ public void execution_addsCreatedEvent() { Event event = new Event("Test", LocalDate.now()); CreateEventCommand command = new CreateEventCommand(event); - command.execute(taskList, new Ui(), new Storage("")); + command.execute(taskList, new Storage("")); assertDoesNotThrow(() -> assertEquals(taskList.get(0), event)); } diff --git a/src/test/java/duke/commands/CreateTodoCommandTest.java b/src/test/java/duke/commands/CreateTodoCommandTest.java index 085dfa4925..894da1fb62 100644 --- a/src/test/java/duke/commands/CreateTodoCommandTest.java +++ b/src/test/java/duke/commands/CreateTodoCommandTest.java @@ -1,11 +1,9 @@ package duke.commands; import duke.storage.Storage; -import duke.task.Event; import duke.task.Task; import duke.task.TaskList; import duke.task.Todo; -import duke.ui.Ui; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -20,7 +18,7 @@ public void execution_addsSingleItem() { Todo todo = new Todo("Test"); CreateTodoCommand command = new CreateTodoCommand(todo); - command.execute(taskList, new Ui(), new Storage("")); + command.execute(taskList, new Storage("")); assertEquals(taskList.getNumTasks(), 1); } @@ -31,7 +29,7 @@ public void execution_addsCreatedEvent() { Todo todo = new Todo("Test"); CreateTodoCommand command = new CreateTodoCommand(todo); - command.execute(taskList, new Ui(), new Storage("")); + command.execute(taskList, new Storage("")); assertDoesNotThrow(() -> assertEquals(taskList.get(0), todo)); } diff --git a/src/test/java/duke/commands/DeleteTaskCommandTest.java b/src/test/java/duke/commands/DeleteTaskCommandTest.java index c79bf32dcb..f19e7a7435 100644 --- a/src/test/java/duke/commands/DeleteTaskCommandTest.java +++ b/src/test/java/duke/commands/DeleteTaskCommandTest.java @@ -4,7 +4,6 @@ import duke.storage.Storage; import duke.task.TaskList; import duke.task.Todo; -import duke.ui.Ui; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -24,14 +23,14 @@ public void execution_invalidIndex_exceptionThrown() { DeleteTaskCommand command = new DeleteTaskCommand(1); assertThrows(NoSuchTaskException.class, - () -> command.execute(getTaskList(), new Ui(), new Storage(""))); + () -> command.execute(getTaskList(), new Storage(""))); } @Test public void execution_validIndex_exceptionNotThrown() { DeleteTaskCommand command = new DeleteTaskCommand(0); - assertDoesNotThrow(() -> command.execute(getTaskList(), new Ui(), new Storage(""))); + assertDoesNotThrow(() -> command.execute(getTaskList(), new Storage(""))); } @Test @@ -40,7 +39,7 @@ public void execution_validIndex_singleTaskRemoved() throws NoSuchTaskException DeleteTaskCommand command = new DeleteTaskCommand(0); TaskList taskList = getTaskList(); - command.execute(taskList, new Ui(), new Storage("")); + command.execute(taskList, new Storage("")); assertEquals(taskList.getNumTasks(), 0); } diff --git a/src/test/java/duke/commands/MarkCommandTest.java b/src/test/java/duke/commands/MarkCommandTest.java index f17b761c95..75ad0e606f 100644 --- a/src/test/java/duke/commands/MarkCommandTest.java +++ b/src/test/java/duke/commands/MarkCommandTest.java @@ -4,7 +4,6 @@ import duke.storage.Storage; import duke.task.TaskList; import duke.task.Todo; -import duke.ui.Ui; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -24,14 +23,14 @@ public void execution_invalidIndex_exceptionThrown() { MarkCommand command = new MarkCommand(1); assertThrows(NoSuchTaskException.class, - () -> command.execute(getTaskList(), new Ui(), new Storage(""))); + () -> command.execute(getTaskList(), new Storage(""))); } @Test public void execution_validIndex_exceptionNotThrown() { MarkCommand command = new MarkCommand(0); - assertDoesNotThrow(() -> command.execute(getTaskList(), new Ui(), new Storage(""))); + assertDoesNotThrow(() -> command.execute(getTaskList(), new Storage(""))); } @Test @@ -42,7 +41,7 @@ public void execution_validIndex_singleTaskRemoved() throws NoSuchTaskException assertEquals(taskList.get(0).isCompleted(), false); - command.execute(taskList, new Ui(), new Storage("")); + command.execute(taskList, new Storage("")); assertEquals(taskList.get(0).isCompleted(), true); } diff --git a/src/test/java/duke/commands/UnmarkCommandTest.java b/src/test/java/duke/commands/UnmarkCommandTest.java index 9852bdae0a..cf044375b4 100644 --- a/src/test/java/duke/commands/UnmarkCommandTest.java +++ b/src/test/java/duke/commands/UnmarkCommandTest.java @@ -4,7 +4,6 @@ import duke.storage.Storage; import duke.task.TaskList; import duke.task.Todo; -import duke.ui.Ui; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -24,14 +23,14 @@ public void execution_invalidIndex_exceptionThrown() { UnmarkCommand command = new UnmarkCommand(1); assertThrows(NoSuchTaskException.class, - () -> command.execute(getTaskList(), new Ui(), new Storage(""))); + () -> command.execute(getTaskList(), new Storage(""))); } @Test public void execution_validIndex_exceptionNotThrown() { UnmarkCommand command = new UnmarkCommand(0); - assertDoesNotThrow(() -> command.execute(getTaskList(), new Ui(), new Storage(""))); + assertDoesNotThrow(() -> command.execute(getTaskList(), new Storage(""))); } @Test @@ -42,7 +41,7 @@ public void execution_validIndex_singleTaskRemoved() throws NoSuchTaskException assertEquals(taskList.get(0).isCompleted(), true); - command.execute(taskList, new Ui(), new Storage("")); + command.execute(taskList, new Storage("")); assertEquals(taskList.get(0).isCompleted(), false); } diff --git a/text-ui-test/ACTUAL.TXT b/text-ui-test/ACTUAL.TXT new file mode 100644 index 0000000000..6e34fd5dbe --- /dev/null +++ b/text-ui-test/ACTUAL.TXT @@ -0,0 +1,74 @@ +Hello! I'm duke.Duke +What can I do for you? +Got it. I've added this task: + [T][ ] read book + +Got it. I've added this task: + [T][ ] borrow book + +Got it. I've added this task: + [T][ ] return book + +Invalid command given. +Could not determine the command. Valid commands include: todo, deadline, event, mark, unmark, delete +Here are the tasks in your list: +1. [T][ ] read book +2. [T][ ] borrow book +3. [T][ ] return book +Got it. I've added this task: + [D][ ] return book (by: Sunday) + +Got it. I've added this task: + [E][ ] project meeting (at: Mon 2-4pm) + +Here are the tasks in your list: +1. [T][ ] read book +2. [T][ ] borrow book +3. [T][ ] return book +4. [D][ ] return book (by: Sunday) +5. [E][ ] project meeting (at: Mon 2-4pm) +Invalid command given. +Cannot create a task with an empty title! +Invalid command given. +Could not parse deadline. To create a deadline, please use the format in this example: deadline return book /by Sunday +Invalid command given. +Could not parse event. To create an event, please use the format in this example: event project meeting /at Mon 2-4pm +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +Noted. I've removed this task: + [T][ ] read book + +Here are the tasks in your list: +1. [T][ ] borrow book +2. [T][ ] return book +3. [D][ ] return book (by: Sunday) +4. [E][ ] project meeting (at: Mon 2-4pm) +Invalid command given. +No task found. You only have 4 tasks but you referenced a task at index 100 +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +Nice! I've marked this task as done: + [T][X] return book +Here are the tasks in your list: +1. [T][ ] borrow book +2. [T][X] return book +3. [D][ ] return book (by: Sunday) +4. [E][ ] project meeting (at: Mon 2-4pm) +Invalid command given. +No task found. You only have 4 tasks but you referenced a task at index 100 +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +OK, I've marked this task as not done yet: + [T][ ] return book +Here are the tasks in your list: +1. [T][ ] borrow book +2. [T][ ] return book +3. [D][ ] return book (by: Sunday) +4. [E][ ] project meeting (at: Mon 2-4pm) +Invalid command given. +Could not determine the command. Valid commands include: todo, deadline, event, mark, unmark, delete +Bye. Hope to see you again soon! diff --git a/text-ui-test/EXPECTED-UNIX.TXT b/text-ui-test/EXPECTED-UNIX.TXT new file mode 100644 index 0000000000..6e34fd5dbe --- /dev/null +++ b/text-ui-test/EXPECTED-UNIX.TXT @@ -0,0 +1,74 @@ +Hello! I'm duke.Duke +What can I do for you? +Got it. I've added this task: + [T][ ] read book + +Got it. I've added this task: + [T][ ] borrow book + +Got it. I've added this task: + [T][ ] return book + +Invalid command given. +Could not determine the command. Valid commands include: todo, deadline, event, mark, unmark, delete +Here are the tasks in your list: +1. [T][ ] read book +2. [T][ ] borrow book +3. [T][ ] return book +Got it. I've added this task: + [D][ ] return book (by: Sunday) + +Got it. I've added this task: + [E][ ] project meeting (at: Mon 2-4pm) + +Here are the tasks in your list: +1. [T][ ] read book +2. [T][ ] borrow book +3. [T][ ] return book +4. [D][ ] return book (by: Sunday) +5. [E][ ] project meeting (at: Mon 2-4pm) +Invalid command given. +Cannot create a task with an empty title! +Invalid command given. +Could not parse deadline. To create a deadline, please use the format in this example: deadline return book /by Sunday +Invalid command given. +Could not parse event. To create an event, please use the format in this example: event project meeting /at Mon 2-4pm +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +Noted. I've removed this task: + [T][ ] read book + +Here are the tasks in your list: +1. [T][ ] borrow book +2. [T][ ] return book +3. [D][ ] return book (by: Sunday) +4. [E][ ] project meeting (at: Mon 2-4pm) +Invalid command given. +No task found. You only have 4 tasks but you referenced a task at index 100 +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +Nice! I've marked this task as done: + [T][X] return book +Here are the tasks in your list: +1. [T][ ] borrow book +2. [T][X] return book +3. [D][ ] return book (by: Sunday) +4. [E][ ] project meeting (at: Mon 2-4pm) +Invalid command given. +No task found. You only have 4 tasks but you referenced a task at index 100 +Invalid command given. +Command should be followed by a number. For example: mark 2, unmark 3, delete 4. +OK, I've marked this task as not done yet: + [T][ ] return book +Here are the tasks in your list: +1. [T][ ] borrow book +2. [T][ ] return book +3. [D][ ] return book (by: Sunday) +4. [E][ ] project meeting (at: Mon 2-4pm) +Invalid command given. +Could not determine the command. Valid commands include: todo, deadline, event, mark, unmark, delete +Bye. Hope to see you again soon! From 49c9a18dac4668abd657350db31bf6b2590338de Mon Sep 17 00:00:00 2001 From: beetee17 Date: Tue, 6 Sep 2022 00:02:24 +0800 Subject: [PATCH 25/31] Use assert feature (not JUnit assertions) to document important assumptions that should hold at various points in the code --- src/main/java/duke/task/Task.java | 4 ++++ src/main/java/duke/task/TaskList.java | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java index 4fc036840a..21726f6a54 100644 --- a/src/main/java/duke/task/Task.java +++ b/src/main/java/duke/task/Task.java @@ -13,11 +13,15 @@ public class Task { private boolean completed; Task(String title) { + assert !title.isBlank() : "Task title cannot be empty"; + this.title = title; this.completed = false; } Task(String title, boolean completed) { + assert !title.isBlank() : "Task title cannot be empty"; + this.title = title; this.completed = completed; } diff --git a/src/main/java/duke/task/TaskList.java b/src/main/java/duke/task/TaskList.java index 487d0559da..9a1ad4666a 100644 --- a/src/main/java/duke/task/TaskList.java +++ b/src/main/java/duke/task/TaskList.java @@ -8,6 +8,8 @@ public class TaskList { private List tasks; public TaskList(List tasks) { + assert tasks != null : "Task list cannot be null"; + this.tasks = tasks; } From 3d97211a52140a5ef6762c6b04c652fc66a98a1f Mon Sep 17 00:00:00 2001 From: beetee17 Date: Tue, 6 Sep 2022 00:31:09 +0800 Subject: [PATCH 26/31] Remove dead code and replace if-else branching with switch statement for readability. --- src/main/java/duke/storage/Storage.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/duke/storage/Storage.java b/src/main/java/duke/storage/Storage.java index ea38e215a7..c2192755d8 100644 --- a/src/main/java/duke/storage/Storage.java +++ b/src/main/java/duke/storage/Storage.java @@ -33,13 +33,14 @@ private static Task parse(String data) throws ParsingTaskException { String type = components[0]; - if (type.equals("T")) { + switch (type) { + case "T": return Todo.parse(data); - } else if (type.equals("D")) { + case "D": return Deadline.parse(data); - } else if (type.equals("E")) { + case "E": return Event.parse(data); - } else { + default: throw new ParsingTaskException(String.format("duke.Task.Task was of unknown type: %s", type)); } } @@ -64,8 +65,7 @@ public List load() { try { tasks.add(Storage.parse(data)); } catch (ParsingTaskException e) { - System.out.println(e); - continue; + System.out.println(e.getMessage()); } } @@ -106,7 +106,7 @@ public void save(TaskList tasks) { System.out.println("Saved tasks list successfully!"); } catch (Exception e) { - System.out.println(e + this.dataPath); + System.out.println(e.getMessage() + this.dataPath); } } } From c01b0893eee089ad07fbd924c67eaeffdae226ed Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 6 Sep 2022 11:48:46 +0800 Subject: [PATCH 27/31] Provide a way to tag items For example, users can now tag a task as "fun" via the command "tag #fun". Current limitation is that the feature does not allow users to add multiple tags at once. For example, it does not allow the command "tag #urgent, #important, #school". --- data/duke.txt | 2 +- .../java/duke/commands/TagTaskCommand.java | 33 +++++++++++++ src/main/java/duke/enums/Action.java | 3 +- src/main/java/duke/parser/Parser.java | 10 +++- src/main/java/duke/task/Task.java | 12 +++++ .../duke/commands/TagTaskCommandTest.java | 48 +++++++++++++++++++ 6 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 src/main/java/duke/commands/TagTaskCommand.java create mode 100644 src/test/java/duke/commands/TagTaskCommandTest.java diff --git a/data/duke.txt b/data/duke.txt index 1f75acba01..eacb83bfa9 100644 --- a/data/duke.txt +++ b/data/duke.txt @@ -1,3 +1,3 @@ T,1,return book -E,1,project meeting ,2022-10-11 +E,0,project meeting ,2022-10-11 D,0,finish project,2022-09-15 diff --git a/src/main/java/duke/commands/TagTaskCommand.java b/src/main/java/duke/commands/TagTaskCommand.java new file mode 100644 index 0000000000..bf06415227 --- /dev/null +++ b/src/main/java/duke/commands/TagTaskCommand.java @@ -0,0 +1,33 @@ +package duke.commands; + +import duke.Response; +import duke.exceptions.NoSuchTaskException; +import duke.storage.Storage; +import duke.task.Task; +import duke.task.TaskList; + +public class TagTaskCommand extends Command { + private final int index; + private final String tag; + + /** + * Constructor for MarkCommand. + * @param index the index specifying the task to be deleted. + */ + public TagTaskCommand(int index, String tag) { + this.index = index; + this.tag = tag; + } + + @Override + public Response execute(TaskList taskList, Storage storage) throws NoSuchTaskException { + Task task = taskList.get(index); + String message = String.format("OK, I've added the tag '#%s' to this task:\n\t%s", + tag, + task); + + task.addTag(tag); + storage.save(taskList); + return new Response(message); + } +} diff --git a/src/main/java/duke/enums/Action.java b/src/main/java/duke/enums/Action.java index 47888ff3c0..78cfad3692 100644 --- a/src/main/java/duke/enums/Action.java +++ b/src/main/java/duke/enums/Action.java @@ -13,7 +13,8 @@ public enum Action { Mark("mark"), Unmark("unmark"), Delete("delete"), - Find("find"); + Find("find"), + Tag("tag"); public final String label; diff --git a/src/main/java/duke/parser/Parser.java b/src/main/java/duke/parser/Parser.java index f39e828bdc..472525ea5a 100644 --- a/src/main/java/duke/parser/Parser.java +++ b/src/main/java/duke/parser/Parser.java @@ -25,7 +25,6 @@ public static Command parse(String fullCommand) throws InvalidCommandException { } else { Action action = Action.parseCommand(fullCommand); - String[] components = fullCommand.split(" "); String contents = fullCommand.substring(action.label.length()).trim(); try { @@ -38,6 +37,15 @@ public static Command parse(String fullCommand) throws InvalidCommandException { return new DeleteTaskCommand(Integer.parseInt(contents) - 1); case Find: return new FindCommand(contents); + case Tag: + String[] components = contents.split(" "); + if (components.length != 2 || !components[1].trim().startsWith("#")) { + throw new InvalidCommandException("Invalid tag command. Tag tasks with the following syntax: 'tag 1 #fun'."); + } else { + int index = Integer.parseInt(components[0]) - 1; + String tag = components[1].replaceAll("#", ""); + return new TagTaskCommand(index, tag); + } case Todo: if (contents.isBlank()) { throw new EmptyTitleException(); diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java index 21726f6a54..735b21556d 100644 --- a/src/main/java/duke/task/Task.java +++ b/src/main/java/duke/task/Task.java @@ -2,6 +2,8 @@ import java.io.File; import java.time.format.FormatStyle; +import java.util.ArrayList; +import java.util.List; /** * An object representing a task in a checklist. This is the application's primary class. @@ -12,6 +14,8 @@ public class Task { private String title; private boolean completed; + private List tags = new ArrayList<>(); + Task(String title) { assert !title.isBlank() : "Task title cannot be empty"; @@ -42,6 +46,14 @@ public void markAsIncomplete() { this.completed = false; } + public List getTags() { + return tags; + } + + public final void addTag(String tag) { + this.tags.add(tag); + } + private String getStatusIcon() { return (this.completed ? "X" : " "); // mark done task with X } diff --git a/src/test/java/duke/commands/TagTaskCommandTest.java b/src/test/java/duke/commands/TagTaskCommandTest.java new file mode 100644 index 0000000000..3b331a1192 --- /dev/null +++ b/src/test/java/duke/commands/TagTaskCommandTest.java @@ -0,0 +1,48 @@ +package duke.commands; + +import duke.exceptions.NoSuchTaskException; +import duke.storage.Storage; +import duke.task.TaskList; +import duke.task.Todo; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +public class TagTaskCommandTest { + private static TaskList getTaskList() { + return new TaskList(new ArrayList<>() {{ + add(new Todo("Test", false)); + }}); + } + + @Test + public void execution_invalidIndex_exceptionThrown() { + + TagTaskCommand command = new TagTaskCommand(1, "IGNORE_ME"); + + assertThrows(NoSuchTaskException.class, + () -> command.execute(getTaskList(), new Storage(""))); + } + + @Test + public void execution_validIndex_exceptionNotThrown() { + TagTaskCommand command = new TagTaskCommand(0, "IGNORE_ME"); + + assertDoesNotThrow(() -> command.execute(getTaskList(), new Storage(""))); + } + + @Test + public void execution_validIndex_singleTaskRemoved() throws NoSuchTaskException { + String tag = "fun"; + TagTaskCommand command = new TagTaskCommand(0, tag); + TaskList taskList = getTaskList(); + + assertEquals(taskList.get(0).getTags().contains(tag), false); + + command.execute(taskList, new Storage("")); + + assertEquals(taskList.get(0).getTags().contains(tag), true); + } +} From b00c6c3d1340872e5c448a7efaad63099cb918d6 Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 16 Sep 2022 22:17:50 +0800 Subject: [PATCH 28/31] Add a User Guide to the project --- README.md | 114 +++++++++++++++++++++++++++++++++++++++++----------- docs/Ui.png | Bin 0 -> 116052 bytes 2 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 docs/Ui.png diff --git a/README.md b/README.md index 0f2208ab64..6ca8c5a8fc 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,90 @@ -# duke.Duke project template - -This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. - -## Setting up in Intellij - -Prerequisites: JDK 11, update Intellij to the most recent version. - -1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first) -1. Open the project into Intellij as follows: - 1. Click `Open`. - 1. Select the project directory, and click `OK`. - 1. If there are any further prompts, accept the defaults. -1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
- In the same dialog, set the **Project language level** field to the `SDK default` option. -3. After that, locate the `src/main/java/duke.Duke.java` file, right-click it, and choose `Run duke.Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` +# Commands + +## Add a todo task: `todo DESCRIPTION` +Adds a task with the given `DESCRIPTION`. + +**Example of usage:** + ``` + todo return book + ``` + +## Add a deadline task: `deadline DESCRIPTION /by yyyy-MM-dd` +Adds a task with the given `DESCRIPTION` and a specified deadline. + +**Example of usage:** +``` +deadline read book /by 17-09-2022 +``` + + ## Add an event: `event DESCRIPTION /at yyyy-MM-dd` + Adds an event with the given `DESCRIPTION` and a specified date. + + Date and time format is the same as the deadline command. + +**Example of usage:** +``` +event CS2103T lecture /at 17-09-2022 +``` + +## List all tasks: `list` +Lists all tasks in the task list. + + + ## Marking a task as done: `mark INDEX` — + Marks the task at the given `INDEX` as done. + +You may want to use the `list` command to find the index of the task you want to mark as done. + +**Example of usage:** +``` +mark 1 + ``` + +## Marking a task as not done: `unmark INDEX` — +Marks the task at the given `INDEX` as not done. + +**Example of usage:** + ``` + unmark 1 + ``` + +## Delete a task - `delete INDEX` — +Deletes the task at the given `INDEX`. + +**Example of usage:** + ``` +delete 1 + ``` + +## Find tasks by keyword: `find KEYWORD` — +Finds all tasks whose description contains the given `KEYWORD`. +The search is case-insensitive. + +**Example of usage:** + +``` +find book +``` + + ## Exit the program: `exit` + Exits the program with a goodbye message. + + # Advanced + ## Data Storage + All the data is saved in the file `./data/duke.txt`. You can modify task list by directly editing + this file. Each line of the file describes one task. The format is the following: + ``` +,,, + ``` + - `TASK Type` is `T` for todo task, `D` for deadline, and `E` for event tasks. + - `COMPLETION STATUS` is 0 or 1 depending on whether the task is completed or not. + - `DESCRIPTION` is the description of the task. + - Currently, `ARGS...` only take in a date for deadline and events. The date must be in the + format `yyyy-MM-dd`. Specifically, day and month number should not have leading zeros. + + Example data file: + ``` +T,1,return book +E,0,project meeting ,2022-10-11 +D,0,finish project,2022-09-15 + ``` \ No newline at end of file diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000000000000000000000000000000000..8634d3e74864b91bb9990d328b9d3eaf47b451a8 GIT binary patch literal 116052 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%0AI1zhoKve1qX}A5gc>5UeaynV7gf|7QJp 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 j5})V2TkLN-f*#R~3#w1VzhsF)J^iG_ Date: Fri, 16 Sep 2022 22:19:33 +0800 Subject: [PATCH 29/31] Move Readme to docs folder --- README.md | 90 -------------------------------------------------- docs/README.md | 89 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 75 insertions(+), 104 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 6ca8c5a8fc..0000000000 --- a/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# Commands - -## Add a todo task: `todo DESCRIPTION` -Adds a task with the given `DESCRIPTION`. - -**Example of usage:** - ``` - todo return book - ``` - -## Add a deadline task: `deadline DESCRIPTION /by yyyy-MM-dd` -Adds a task with the given `DESCRIPTION` and a specified deadline. - -**Example of usage:** -``` -deadline read book /by 17-09-2022 -``` - - ## Add an event: `event DESCRIPTION /at yyyy-MM-dd` - Adds an event with the given `DESCRIPTION` and a specified date. - - Date and time format is the same as the deadline command. - -**Example of usage:** -``` -event CS2103T lecture /at 17-09-2022 -``` - -## List all tasks: `list` -Lists all tasks in the task list. - - - ## Marking a task as done: `mark INDEX` — - Marks the task at the given `INDEX` as done. - -You may want to use the `list` command to find the index of the task you want to mark as done. - -**Example of usage:** -``` -mark 1 - ``` - -## Marking a task as not done: `unmark INDEX` — -Marks the task at the given `INDEX` as not done. - -**Example of usage:** - ``` - unmark 1 - ``` - -## Delete a task - `delete INDEX` — -Deletes the task at the given `INDEX`. - -**Example of usage:** - ``` -delete 1 - ``` - -## Find tasks by keyword: `find KEYWORD` — -Finds all tasks whose description contains the given `KEYWORD`. -The search is case-insensitive. - -**Example of usage:** - -``` -find book -``` - - ## Exit the program: `exit` - Exits the program with a goodbye message. - - # Advanced - ## Data Storage - All the data is saved in the file `./data/duke.txt`. You can modify task list by directly editing - this file. Each line of the file describes one task. The format is the following: - ``` -,,, - ``` - - `TASK Type` is `T` for todo task, `D` for deadline, and `E` for event tasks. - - `COMPLETION STATUS` is 0 or 1 depending on whether the task is completed or not. - - `DESCRIPTION` is the description of the task. - - Currently, `ARGS...` only take in a date for deadline and events. The date must be in the - format `yyyy-MM-dd`. Specifically, day and month number should not have leading zeros. - - Example data file: - ``` -T,1,return book -E,0,project meeting ,2022-10-11 -D,0,finish project,2022-09-15 - ``` \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 8077118ebe..12447da694 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,90 @@ -# User Guide +# Commands -## Features +## Add a todo task: `todo DESCRIPTION` +Adds a task with the given `DESCRIPTION`. -### Feature-ABC +**Example of usage:** + ``` + todo return book + ``` -Description of the feature. +## Add a deadline task: `deadline DESCRIPTION /by yyyy-MM-dd` +Adds a task with the given `DESCRIPTION` and a specified deadline. -### Feature-XYZ +**Example of usage:** +``` +deadline read book /by 17-09-2022 +``` + +## Add an event: `event DESCRIPTION /at yyyy-MM-dd` +Adds an event with the given `DESCRIPTION` and a specified date. + +Date and time format is the same as the deadline command. + +**Example of usage:** +``` +event CS2103T lecture /at 17-09-2022 +``` -Description of the feature. +## List all tasks: `list` +Lists all tasks in the task list. -## Usage -### `Keyword` - Describe action +## Marking a task as done: `mark INDEX` — +Marks the task at the given `INDEX` as done. -Describe the action and its outcome. +You may want to use the `list` command to find the index of the task you want to mark as done. -Example of usage: +**Example of usage:** +``` +mark 1 + ``` + +## Marking a task as not done: `unmark INDEX` — +Marks the task at the given `INDEX` as not done. + +**Example of usage:** + ``` + unmark 1 + ``` + +## Delete a task - `delete INDEX` — +Deletes the task at the given `INDEX`. -`keyword (optional arguments)` +**Example of usage:** + ``` +delete 1 + ``` -Expected outcome: +## Find tasks by keyword: `find KEYWORD` — +Finds all tasks whose description contains the given `KEYWORD`. +The search is case-insensitive. -Description of the outcome. +**Example of usage:** ``` -expected output +find book ``` + +## Exit the program: `exit` +Exits the program with a goodbye message. + +# Advanced +## Data Storage +All the data is saved in the file `./data/duke.txt`. You can modify task list by directly editing +this file. Each line of the file describes one task. The format is the following: + ``` +,,, + ``` +- `TASK Type` is `T` for todo task, `D` for deadline, and `E` for event tasks. +- `COMPLETION STATUS` is 0 or 1 depending on whether the task is completed or not. +- `DESCRIPTION` is the description of the task. +- Currently, `ARGS...` only take in a date for deadline and events. The date must be in the + format `yyyy-MM-dd`. Specifically, day and month number should not have leading zeros. + +Example data file: + ``` +T,1,return book +E,0,project meeting ,2022-10-11 +D,0,finish project,2022-09-15 + ``` \ No newline at end of file From b6b87134ba927b7048cdc3d14fec5728dce4603c Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 16 Sep 2022 22:23:46 +0800 Subject: [PATCH 30/31] Update the main Readme page --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000000..6afcc71ac5 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# duke.Duke project template + +This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. + +## Setting up in Intellij + +Prerequisites: JDK 11, update Intellij to the most recent version. + +1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first) +1. Open the project into Intellij as follows: + 1. Click `Open`. + 1. Select the project directory, and click `OK`. + 1. If there are any further prompts, accept the defaults. +1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
    + In the same dialog, set the **Project language level** field to the `SDK default` option. +3. After that, locate the `src/main/java/duke/Launcher.java` file, right-click it, and choose `Run Launcher.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: + ![Duke](docs/Ui.png) \ No newline at end of file From 7ce06ba33db9d4362dedff206efb4d811753478c Mon Sep 17 00:00:00 2001 From: Brandon Date: Sat, 17 Sep 2022 00:26:36 +0800 Subject: [PATCH 31/31] Release the product to be used by potential users --- build.gradle | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index b252d78408..c97d0f76ba 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ plugins { id 'application' id 'com.github.johnrengelman.shadow' version '5.1.0' id 'checkstyle' + id 'org.openjfx.javafxplugin' version '0.0.10' } repositories { @@ -10,11 +11,11 @@ repositories { } dependencies { - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' - testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' - String javaFxVersion = '11' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.9.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.9.0' + String javaFxVersion = '11' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' @@ -48,7 +49,7 @@ checkstyle { } application { - mainClassName = "seedu.duke.Duke" + mainClassName = "duke.Launcher" } shadowJar { @@ -56,6 +57,6 @@ shadowJar { archiveClassifier = null } -run{ +run { standardInput = System.in }