From c70ffe012e17e4621820fdd43803df1cf483063a Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Tue, 24 Aug 2021 11:22:45 +0800 Subject: [PATCH 01/59] Level-1: add greet, echo, exit --- src/main/java/Duke.java | 23 ++++++++++++++++------ src/main/java/DukePrompt.java | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 src/main/java/DukePrompt.java diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5d313334..8f3bce98 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,21 @@ +import java.util.Scanner; + public class Duke { public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + System.out.println(DukePrompt.getPrompt(DukePrompt.Prompts.START)); + + Scanner in = new Scanner(System.in); + + while (in.hasNext()) { + String input = in.nextLine(); + + if (input.equalsIgnoreCase("bye")) { + break; + } else { + System.out.println(DukePrompt.getPrompt(DukePrompt.Prompts.MANUAL, input)); + } + } + + System.out.println(DukePrompt.getPrompt(DukePrompt.Prompts.END)); } } diff --git a/src/main/java/DukePrompt.java b/src/main/java/DukePrompt.java new file mode 100644 index 00000000..11067abe --- /dev/null +++ b/src/main/java/DukePrompt.java @@ -0,0 +1,37 @@ +public class DukePrompt { + public enum Prompts { + START, + END, + MANUAL + } + + private static final String HORIZONTAL_LINE = " _________________________________________"; + private static final String START = " Hello! I'm Duke\n What can I do for you?\n"; + private static final String END = " Bye. Hope to see you again soon!\n"; + + public static String getPrompt(Prompts decision) { + return getPrompt(decision, null); + } + + public static String getPrompt(Prompts decision, String man) { + String output = ""; + + output += HORIZONTAL_LINE + System.lineSeparator(); + + switch (decision) { + case START: + output += START; + break; + case END: + output += END; + break; + default: + output += " " + man + System.lineSeparator(); + break; + } + + output += HORIZONTAL_LINE; + + return output; + } +} \ No newline at end of file From 776438f96fe9a28f8aa243873f301c8c87aca60c Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Tue, 24 Aug 2021 12:18:49 +0800 Subject: [PATCH 02/59] Level-2: Add task add and list --- src/main/java/Duke.java | 17 ++++++++++++++++- src/main/java/DukePrompt.java | 35 ++++++++++++++++++++++++++++------- src/main/java/Task.java | 19 +++++++++++++++++++ 3 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 src/main/java/Task.java diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 8f3bce98..99ac7f80 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,18 +1,33 @@ +import java.util.ArrayList; import java.util.Scanner; public class Duke { + public static String printTasks(ArrayList tasks) { + ArrayList outputs = new ArrayList(); + + for (Task t : tasks) { + outputs.add(t.getDescription()); + } + + return DukePrompt.getPrompt(DukePrompt.Prompts.MANUAL, outputs); + } + public static void main(String[] args) { System.out.println(DukePrompt.getPrompt(DukePrompt.Prompts.START)); Scanner in = new Scanner(System.in); + ArrayList tasks = new ArrayList(); while (in.hasNext()) { String input = in.nextLine(); if (input.equalsIgnoreCase("bye")) { break; + } else if (input.equalsIgnoreCase("list")) { + System.out.println(printTasks(tasks)); } else { - System.out.println(DukePrompt.getPrompt(DukePrompt.Prompts.MANUAL, input)); + System.out.println(DukePrompt.getPrompt(DukePrompt.Prompts.ADD, input)); + tasks.add(new Task(input)); } } diff --git a/src/main/java/DukePrompt.java b/src/main/java/DukePrompt.java index 11067abe..1430b75f 100644 --- a/src/main/java/DukePrompt.java +++ b/src/main/java/DukePrompt.java @@ -1,22 +1,27 @@ +import java.util.ArrayList; + public class DukePrompt { public enum Prompts { START, END, + ADD, MANUAL } - private static final String HORIZONTAL_LINE = " _________________________________________"; - private static final String START = " Hello! I'm Duke\n What can I do for you?\n"; - private static final String END = " Bye. Hope to see you again soon!\n"; + private static final String PADDING = " "; + private static final String HORIZONTAL_LINE = PADDING + "_________________________________________"; + private static final String START = "Hello! I'm Duke\n What can I do for you?"; + private static final String END = "Bye. Hope to see you again soon!"; + private static final String ADD = "added: "; public static String getPrompt(Prompts decision) { - return getPrompt(decision, null); + return getPrompt(decision, ""); } public static String getPrompt(Prompts decision, String man) { String output = ""; - output += HORIZONTAL_LINE + System.lineSeparator(); + output += HORIZONTAL_LINE + System.lineSeparator() + PADDING; switch (decision) { case START: @@ -25,13 +30,29 @@ public static String getPrompt(Prompts decision, String man) { case END: output += END; break; + case ADD: + output += ADD + man; + break; default: - output += " " + man + System.lineSeparator(); + output += man; break; } - output += HORIZONTAL_LINE; + output += System.lineSeparator() + HORIZONTAL_LINE; return output; } + + public static String getPrompt(Prompts decision, ArrayList outputs) { + String formattedInput = ""; + + for (int i=0;i < outputs.size();i++) { + if (i > 0) { + formattedInput += System.lineSeparator() + PADDING; + } + formattedInput += (i + 1) + ". " + outputs.get(i); + } + + return getPrompt(Prompts.MANUAL, formattedInput); + } } \ No newline at end of file diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 00000000..b80d8878 --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,19 @@ +public class Task { + private String description; + + public Task() { + + } + + public Task(String description) { + setDescription(description); + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } +} From 7fe63e16f60560eb15d0ed98a740189e5924f0b6 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Tue, 24 Aug 2021 19:37:56 +0800 Subject: [PATCH 03/59] Level-2: Add print list and task class --- src/main/java/Duke.java | 4 ++-- src/main/java/DukePrompt.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 99ac7f80..9cf6b78c 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -3,7 +3,7 @@ public class Duke { public static String printTasks(ArrayList tasks) { - ArrayList outputs = new ArrayList(); + ArrayList outputs = new ArrayList(); for (Task t : tasks) { outputs.add(t.getDescription()); @@ -16,7 +16,7 @@ public static void main(String[] args) { System.out.println(DukePrompt.getPrompt(DukePrompt.Prompts.START)); Scanner in = new Scanner(System.in); - ArrayList tasks = new ArrayList(); + ArrayList tasks = new ArrayList(); while (in.hasNext()) { String input = in.nextLine(); diff --git a/src/main/java/DukePrompt.java b/src/main/java/DukePrompt.java index 1430b75f..910f3d5c 100644 --- a/src/main/java/DukePrompt.java +++ b/src/main/java/DukePrompt.java @@ -53,6 +53,6 @@ public static String getPrompt(Prompts decision, ArrayList outputs) { formattedInput += (i + 1) + ". " + outputs.get(i); } - return getPrompt(Prompts.MANUAL, formattedInput); + return getPrompt(decision, formattedInput); } } \ No newline at end of file From 767b99f4b6c1dbf619f510b0f4b556437914b2ab Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Tue, 24 Aug 2021 20:38:50 +0800 Subject: [PATCH 04/59] Level-3: Add task completion --- src/main/java/Duke.java | 17 ++++++++--- src/main/java/DukePrompt.java | 54 +++++++++++++++-------------------- src/main/java/Task.java | 22 ++++++++++++++ 3 files changed, 58 insertions(+), 35 deletions(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 9cf6b78c..a4b7677d 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -3,13 +3,17 @@ public class Duke { public static String printTasks(ArrayList tasks) { - ArrayList outputs = new ArrayList(); + String output = ""; - for (Task t : tasks) { - outputs.add(t.getDescription()); + for (int i=0;i < tasks.size();i++) { + output += DukePrompt.DEFAULT_PADDING + (i + 1) + ". "; + output += tasks.get(i).toString(); + if (i < tasks.size() - 1) { + output += System.lineSeparator(); + } } - return DukePrompt.getPrompt(DukePrompt.Prompts.MANUAL, outputs); + return DukePrompt.getPrompt(DukePrompt.Prompts.MANUAL, output); } public static void main(String[] args) { @@ -25,6 +29,11 @@ public static void main(String[] args) { break; } else if (input.equalsIgnoreCase("list")) { System.out.println(printTasks(tasks)); + } else if (input.matches("^done \\d+")) { + String[] inputs = input.split(" "); + int taskNum = Integer.parseInt(inputs[1]) - 1; + tasks.get(taskNum).setDone(true); + System.out.println(DukePrompt.getPrompt(DukePrompt.Prompts.DONE, tasks.get(taskNum).toString())); } else { System.out.println(DukePrompt.getPrompt(DukePrompt.Prompts.ADD, input)); tasks.add(new Task(input)); diff --git a/src/main/java/DukePrompt.java b/src/main/java/DukePrompt.java index 910f3d5c..64f7c80a 100644 --- a/src/main/java/DukePrompt.java +++ b/src/main/java/DukePrompt.java @@ -5,14 +5,16 @@ public enum Prompts { START, END, ADD, + DONE, MANUAL } - private static final String PADDING = " "; - private static final String HORIZONTAL_LINE = PADDING + "_________________________________________"; - private static final String START = "Hello! I'm Duke\n What can I do for you?"; - private static final String END = "Bye. Hope to see you again soon!"; - private static final String ADD = "added: "; + public static final String DEFAULT_PADDING = " "; + private static final String HORIZONTAL_LINE = DEFAULT_PADDING + "_________________________________________"; + private static final String START = DEFAULT_PADDING + "Hello! I'm Duke\n What can I do for you?"; + private static final String END = DEFAULT_PADDING + "Bye. Hope to see you again soon!"; + private static final String TASK_ADD = DEFAULT_PADDING + "added: "; + private static final String TASK_DONE = DEFAULT_PADDING + "Nice! I've marked this task as done:\n" + DEFAULT_PADDING + " "; public static String getPrompt(Prompts decision) { return getPrompt(decision, ""); @@ -21,38 +23,28 @@ public static String getPrompt(Prompts decision) { public static String getPrompt(Prompts decision, String man) { String output = ""; - output += HORIZONTAL_LINE + System.lineSeparator() + PADDING; + output += HORIZONTAL_LINE + System.lineSeparator(); switch (decision) { - case START: - output += START; - break; - case END: - output += END; - break; - case ADD: - output += ADD + man; - break; - default: - output += man; - break; + case START: + output += START; + break; + case END: + output += END; + break; + case ADD: + output += TASK_ADD + man; + break; + case DONE: + output += TASK_DONE + man; + break; + default: + output += man; + break; } output += System.lineSeparator() + HORIZONTAL_LINE; return output; } - - public static String getPrompt(Prompts decision, ArrayList outputs) { - String formattedInput = ""; - - for (int i=0;i < outputs.size();i++) { - if (i > 0) { - formattedInput += System.lineSeparator() + PADDING; - } - formattedInput += (i + 1) + ". " + outputs.get(i); - } - - return getPrompt(decision, formattedInput); - } } \ No newline at end of file diff --git a/src/main/java/Task.java b/src/main/java/Task.java index b80d8878..de33180f 100644 --- a/src/main/java/Task.java +++ b/src/main/java/Task.java @@ -1,5 +1,6 @@ public class Task { private String description; + private boolean isDone; public Task() { @@ -16,4 +17,25 @@ public String getDescription() { public void setDescription(String description) { this.description = description; } + + public boolean isDone() { + return this.isDone; + } + + public void setDone(boolean done) { + this.isDone = done; + } + + @Override + public String toString() { + String output = ""; + + if (isDone()) { + output += "[X] "; + } else { + output += "[ ] "; + } + output += getDescription(); + return output; + } } From 2fbee98af38bced850cd2439d7a939b4f0eb0979 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Tue, 7 Sep 2021 20:30:48 +0800 Subject: [PATCH 05/59] Refactor to use interfaces and generics --- src/main/java/Duke.java | 24 ++++----- src/main/java/DukePrompt.java | 58 ---------------------- src/main/java/Prompt.java | 63 ++++++++++++++++++++++++ src/main/java/Task.java | 14 +++++- src/main/java/interfaces/Printable.java | 6 +++ src/main/java/interfaces/Promptable.java | 9 ++++ 6 files changed, 103 insertions(+), 71 deletions(-) delete mode 100644 src/main/java/DukePrompt.java create mode 100644 src/main/java/Prompt.java create mode 100644 src/main/java/interfaces/Printable.java create mode 100644 src/main/java/interfaces/Promptable.java diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 9cf6b78c..311ff018 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,19 +1,18 @@ +import interfaces.Promptable; + import java.util.ArrayList; import java.util.Scanner; public class Duke { - public static String printTasks(ArrayList tasks) { - ArrayList outputs = new ArrayList(); - - for (Task t : tasks) { - outputs.add(t.getDescription()); - } + private Promptable _prompt; - return DukePrompt.getPrompt(DukePrompt.Prompts.MANUAL, outputs); + public Duke(Promptable prompt) { + this._prompt = prompt; } public static void main(String[] args) { - System.out.println(DukePrompt.getPrompt(DukePrompt.Prompts.START)); + Duke d = new Duke(new Prompt()); + System.out.println(d._prompt.start()); Scanner in = new Scanner(System.in); ArrayList tasks = new ArrayList(); @@ -24,13 +23,14 @@ public static void main(String[] args) { if (input.equalsIgnoreCase("bye")) { break; } else if (input.equalsIgnoreCase("list")) { - System.out.println(printTasks(tasks)); + System.out.println(d._prompt.list(tasks)); } else { - System.out.println(DukePrompt.getPrompt(DukePrompt.Prompts.ADD, input)); - tasks.add(new Task(input)); + Task newTask = new Task(input); + tasks.add(newTask); + System.out.println(d._prompt.add(newTask)); } } - System.out.println(DukePrompt.getPrompt(DukePrompt.Prompts.END)); + System.out.println(d._prompt.exit()); } } diff --git a/src/main/java/DukePrompt.java b/src/main/java/DukePrompt.java deleted file mode 100644 index 910f3d5c..00000000 --- a/src/main/java/DukePrompt.java +++ /dev/null @@ -1,58 +0,0 @@ -import java.util.ArrayList; - -public class DukePrompt { - public enum Prompts { - START, - END, - ADD, - MANUAL - } - - private static final String PADDING = " "; - private static final String HORIZONTAL_LINE = PADDING + "_________________________________________"; - private static final String START = "Hello! I'm Duke\n What can I do for you?"; - private static final String END = "Bye. Hope to see you again soon!"; - private static final String ADD = "added: "; - - public static String getPrompt(Prompts decision) { - return getPrompt(decision, ""); - } - - public static String getPrompt(Prompts decision, String man) { - String output = ""; - - output += HORIZONTAL_LINE + System.lineSeparator() + PADDING; - - switch (decision) { - case START: - output += START; - break; - case END: - output += END; - break; - case ADD: - output += ADD + man; - break; - default: - output += man; - break; - } - - output += System.lineSeparator() + HORIZONTAL_LINE; - - return output; - } - - public static String getPrompt(Prompts decision, ArrayList outputs) { - String formattedInput = ""; - - for (int i=0;i < outputs.size();i++) { - if (i > 0) { - formattedInput += System.lineSeparator() + PADDING; - } - formattedInput += (i + 1) + ". " + outputs.get(i); - } - - return getPrompt(decision, formattedInput); - } -} \ No newline at end of file diff --git a/src/main/java/Prompt.java b/src/main/java/Prompt.java new file mode 100644 index 00000000..992e7355 --- /dev/null +++ b/src/main/java/Prompt.java @@ -0,0 +1,63 @@ +import interfaces.Printable; +import interfaces.Promptable; + +import java.util.Iterator; + +public class Prompt implements Promptable { + private static final String PADDING = " "; + private static final String HORIZONTAL_LINE = "_________________________________________"; + private static final String START = "Hello! I'm Duke\n What can I do for you?"; + private static final String END = "Bye. Hope to see you again soon!"; + private static final String ADD = "added: "; + + public enum Prompts { + START, + END, + ADD, + MANUAL + } + + @Override + public String start() { + return formatOutput(formatLine(START)); + } + + @Override + public String add(Printable printable) { + return formatOutput(formatLine(ADD + printable.toString())); + } + + @Override + public String list(Iterable inputs) { + String output = ""; + int count = 1; + Iterator iter = inputs.iterator(); + while (iter.hasNext()) { + output += formatLine(count + ". " + iter.next().toStatusString()); + count++; + } + return formatOutput(output); + } + + @Override + public String list(Printable[] inputs) { + String output = ""; + for (int i=0;i < inputs.length;i++) { + output += formatLine((i+1) + ". " + inputs[i].toStatusString()); + } + return output; + } + + @Override + public String exit() { + return formatOutput(formatLine(END)); + } + + private String formatLine(String output) { + return PADDING + output + System.lineSeparator(); + } + + private String formatOutput(String output) { + return formatLine(HORIZONTAL_LINE) + output + PADDING + HORIZONTAL_LINE; + } +} \ No newline at end of file diff --git a/src/main/java/Task.java b/src/main/java/Task.java index b80d8878..23b34cf6 100644 --- a/src/main/java/Task.java +++ b/src/main/java/Task.java @@ -1,4 +1,6 @@ -public class Task { +import interfaces.Printable; + +public class Task implements Printable { private String description; public Task() { @@ -16,4 +18,14 @@ public String getDescription() { public void setDescription(String description) { this.description = description; } + + @Override + public String toStatusString() { + return "[ ] " + toString(); + } + + @Override + public String toString() { + return getDescription(); + } } diff --git a/src/main/java/interfaces/Printable.java b/src/main/java/interfaces/Printable.java new file mode 100644 index 00000000..f5ddaf6a --- /dev/null +++ b/src/main/java/interfaces/Printable.java @@ -0,0 +1,6 @@ +package interfaces; + +public interface Printable { + public String toStatusString(); + public String toString(); +} diff --git a/src/main/java/interfaces/Promptable.java b/src/main/java/interfaces/Promptable.java new file mode 100644 index 00000000..68f34a8a --- /dev/null +++ b/src/main/java/interfaces/Promptable.java @@ -0,0 +1,9 @@ +package interfaces; + +public interface Promptable { + public String start(); + public String add(T printable); + public String list(Iterable inputs); + public String list(T[] inputs); + public String exit(); +} From ff1f2726e5aac761575031fedd76b93bb6c47625 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Tue, 7 Sep 2021 21:08:11 +0800 Subject: [PATCH 06/59] Refactor to use concrete task and fix testing --- src/main/java/Duke.java | 12 ++++++--- src/main/java/Prompt.java | 30 +++++++++++++--------- src/main/java/{ => classes}/Task.java | 17 +++++++++++-- src/main/java/interfaces/Promptable.java | 5 +++- text-ui-test/EXPECTED.TXT | 32 ++++++++++++++++++------ text-ui-test/input.txt | 5 ++++ text-ui-test/runtest.sh | 2 +- 7 files changed, 77 insertions(+), 26 deletions(-) rename src/main/java/{ => classes}/Task.java (63%) mode change 100644 => 100755 text-ui-test/runtest.sh diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 311ff018..203428b3 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,12 +1,14 @@ +import classes.Task; +import interfaces.Printable; import interfaces.Promptable; import java.util.ArrayList; import java.util.Scanner; public class Duke { - private Promptable _prompt; + private final Promptable _prompt; - public Duke(Promptable prompt) { + public Duke(Promptable prompt) { this._prompt = prompt; } @@ -15,7 +17,7 @@ public static void main(String[] args) { System.out.println(d._prompt.start()); Scanner in = new Scanner(System.in); - ArrayList tasks = new ArrayList(); + ArrayList tasks = new ArrayList<>(); while (in.hasNext()) { String input = in.nextLine(); @@ -24,6 +26,10 @@ public static void main(String[] args) { break; } else if (input.equalsIgnoreCase("list")) { System.out.println(d._prompt.list(tasks)); + } else if (input.startsWith("done")) { + int idx = Integer.parseInt(input.split(" ")[1]); + tasks.get(idx - 1).setDone(true); + System.out.println(d._prompt.done(tasks.get(idx - 1))); } else { Task newTask = new Task(input); tasks.add(newTask); diff --git a/src/main/java/Prompt.java b/src/main/java/Prompt.java index 992e7355..446365d9 100644 --- a/src/main/java/Prompt.java +++ b/src/main/java/Prompt.java @@ -1,13 +1,15 @@ +import classes.Task; import interfaces.Printable; import interfaces.Promptable; import java.util.Iterator; -public class Prompt implements Promptable { +public class Prompt implements Promptable { private static final String PADDING = " "; private static final String HORIZONTAL_LINE = "_________________________________________"; private static final String START = "Hello! I'm Duke\n What can I do for you?"; private static final String END = "Bye. Hope to see you again soon!"; + private static final String DONE = "Nice! I've marked this task as done:"; private static final String ADD = "added: "; public enum Prompts { @@ -23,29 +25,33 @@ public String start() { } @Override - public String add(Printable printable) { + public String add(Task printable) { return formatOutput(formatLine(ADD + printable.toString())); } @Override - public String list(Iterable inputs) { - String output = ""; + public String done(Task printable) { + return formatOutput(formatLine(DONE) + formatLine(" " + printable.toStatusString())); + } + + @Override + public String list(Iterable inputs) { + StringBuilder output = new StringBuilder(); int count = 1; - Iterator iter = inputs.iterator(); - while (iter.hasNext()) { - output += formatLine(count + ". " + iter.next().toStatusString()); + for (Task input : inputs) { + output.append(formatLine(count + ". " + input.toStatusString())); count++; } - return formatOutput(output); + return formatOutput(output.toString()); } @Override - public String list(Printable[] inputs) { - String output = ""; + public String list(Task[] inputs) { + StringBuilder output = new StringBuilder(); for (int i=0;i < inputs.length;i++) { - output += formatLine((i+1) + ". " + inputs[i].toStatusString()); + output.append(formatLine((i + 1) + ". " + inputs[i].toStatusString())); } - return output; + return output.toString(); } @Override diff --git a/src/main/java/Task.java b/src/main/java/classes/Task.java similarity index 63% rename from src/main/java/Task.java rename to src/main/java/classes/Task.java index 23b34cf6..e546f83f 100644 --- a/src/main/java/Task.java +++ b/src/main/java/classes/Task.java @@ -1,13 +1,17 @@ +package classes; + import interfaces.Printable; public class Task implements Printable { private String description; + private Boolean isDone; public Task() { - + isDone = false; } public Task(String description) { + this(); setDescription(description); } @@ -19,9 +23,18 @@ public void setDescription(String description) { this.description = description; } + public Boolean getDone() { + return isDone; + } + + public void setDone(Boolean done) { + isDone = done; + } + @Override public String toStatusString() { - return "[ ] " + toString(); + String doneStr = (isDone) ? "X" : " "; + return "[" + doneStr + "] " + toString(); } @Override diff --git a/src/main/java/interfaces/Promptable.java b/src/main/java/interfaces/Promptable.java index 68f34a8a..8412b0a5 100644 --- a/src/main/java/interfaces/Promptable.java +++ b/src/main/java/interfaces/Promptable.java @@ -1,8 +1,11 @@ package interfaces; -public interface Promptable { +import classes.Task; + +public interface Promptable { public String start(); public String add(T printable); + public String done(T printable); public String list(Iterable inputs); public String list(T[] inputs); public String exit(); diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6..8606de4c 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,25 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - + _________________________________________ + Hello! I'm Duke + What can I do for you? + _________________________________________ + _________________________________________ + added: read book + _________________________________________ + _________________________________________ + added: return book + _________________________________________ + _________________________________________ + 1. [ ] read book + 2. [ ] return book + _________________________________________ + _________________________________________ + Nice! I've marked this task as done: + [X] return book + _________________________________________ + _________________________________________ + 1. [ ] read book + 2. [X] return book + _________________________________________ + _________________________________________ + Bye. Hope to see you again soon! + _________________________________________ diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29b..6a6ebe1c 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,5 @@ +read book +return book +list +done 2 +list \ No newline at end of file diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh old mode 100644 new mode 100755 index c9ec8700..34c62b6c --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,7 +13,7 @@ then fi # compile the code into the bin folder, terminates if error occurred -if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java +if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java ../src/main/java/interfaces/*.java ../src/main/java/classes/*.java then echo "********** BUILD FAILURE **********" exit 1 From 0f8811d9d0834d60e19fc375557cb4e7f4a828f7 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Wed, 8 Sep 2021 09:37:39 +0800 Subject: [PATCH 07/59] Add task children, refactor parsing and testing --- src/main/java/classes/Command.java | 33 +++++++++++++++++++ src/main/java/classes/Parser.java | 2 ++ src/main/java/{ => classes}/Prompt.java | 3 -- src/main/java/classes/enums/CommandType.java | 8 +++++ src/main/java/classes/enums/TaskType.java | 2 ++ src/main/java/classes/tasks/Deadline.java | 25 ++++++++++++++ src/main/java/classes/tasks/Event.java | 25 ++++++++++++++ src/main/java/classes/{ => tasks}/Task.java | 17 +++++++--- src/main/java/classes/tasks/Todo.java | 14 ++++++++ .../exceptions/InvalidCommandException.java | 2 ++ src/main/java/interfaces/Printable.java | 6 ---- text-ui-test/EXPECTED.TXT | 10 +++--- text-ui-test/input.txt | 4 +-- text-ui-test/runtest.sh | 10 ++++-- 14 files changed, 139 insertions(+), 22 deletions(-) create mode 100644 src/main/java/classes/Command.java create mode 100644 src/main/java/classes/Parser.java rename src/main/java/{ => classes}/Prompt.java (97%) create mode 100644 src/main/java/classes/enums/CommandType.java create mode 100644 src/main/java/classes/enums/TaskType.java create mode 100644 src/main/java/classes/tasks/Deadline.java create mode 100644 src/main/java/classes/tasks/Event.java rename src/main/java/classes/{ => tasks}/Task.java (71%) create mode 100644 src/main/java/classes/tasks/Todo.java create mode 100644 src/main/java/exceptions/InvalidCommandException.java delete mode 100644 src/main/java/interfaces/Printable.java diff --git a/src/main/java/classes/Command.java b/src/main/java/classes/Command.java new file mode 100644 index 00000000..69eb8f98 --- /dev/null +++ b/src/main/java/classes/Command.java @@ -0,0 +1,33 @@ +package classes; + +public class UserCommand { + private CommandType type; + private String keyword; + private String args; + + public UserCommand(CommandType type, String keyword) { + this.keyword = keyword; + this.type = type; + } + + public UserCommand(CommandType type, String keyword, String args) { + this(type, keyword); + this.args = args; + } + + public CommandType getType() { + return type; + } + + public void setType(CommandType type) { + this.type = type; + } + + public String getArgs() { + return args; + } + + public void setArgs(String args) { + this.args = args; + } +} diff --git a/src/main/java/classes/Parser.java b/src/main/java/classes/Parser.java new file mode 100644 index 00000000..0d301bf2 --- /dev/null +++ b/src/main/java/classes/Parser.java @@ -0,0 +1,2 @@ +package classes;public class Parser { +} diff --git a/src/main/java/Prompt.java b/src/main/java/classes/Prompt.java similarity index 97% rename from src/main/java/Prompt.java rename to src/main/java/classes/Prompt.java index 446365d9..b35d6f74 100644 --- a/src/main/java/Prompt.java +++ b/src/main/java/classes/Prompt.java @@ -1,9 +1,6 @@ import classes.Task; -import interfaces.Printable; import interfaces.Promptable; -import java.util.Iterator; - public class Prompt implements Promptable { private static final String PADDING = " "; private static final String HORIZONTAL_LINE = "_________________________________________"; diff --git a/src/main/java/classes/enums/CommandType.java b/src/main/java/classes/enums/CommandType.java new file mode 100644 index 00000000..6f505612 --- /dev/null +++ b/src/main/java/classes/enums/CommandType.java @@ -0,0 +1,8 @@ +package classes; + +public enum CommandType { + ADD, + COMPLETE, + LIST, + EXIT +} diff --git a/src/main/java/classes/enums/TaskType.java b/src/main/java/classes/enums/TaskType.java new file mode 100644 index 00000000..2a48c3e8 --- /dev/null +++ b/src/main/java/classes/enums/TaskType.java @@ -0,0 +1,2 @@ +package classes.enums;public class TaskType { +} diff --git a/src/main/java/classes/tasks/Deadline.java b/src/main/java/classes/tasks/Deadline.java new file mode 100644 index 00000000..3f573625 --- /dev/null +++ b/src/main/java/classes/tasks/Deadline.java @@ -0,0 +1,25 @@ +package classes; + +import classes.enums.TaskType; + +public class Deadline extends Task { + private String dueDate; + + public Deadline(String description, String dueDate) { + super(TaskType.DEADLINE, description); + this.setDueDate(dueDate); + } + + public String getDueDate() { + return dueDate; + } + + public void setDueDate(String dueDate) { + this.dueDate = dueDate; + } + + @Override + public String toStatusString() { + return "[D]" + super.toStatusString() + " (by: " + dueDate + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/classes/tasks/Event.java b/src/main/java/classes/tasks/Event.java new file mode 100644 index 00000000..c3cc5a17 --- /dev/null +++ b/src/main/java/classes/tasks/Event.java @@ -0,0 +1,25 @@ +package classes; + +import classes.enums.TaskType; + +public class Event extends Task { + private String timing; + + public Event(String description, String timing) { + super(TaskType.EVENT, description); + this.setTiming(timing); + } + + public String getTiming() { + return timing; + } + + public void setTiming(String timing) { + this.timing = timing; + } + + @Override + public String toStatusString() { + return "[E]" + super.toStatusString() + " (at: " + timing + ")"; + } +} diff --git a/src/main/java/classes/Task.java b/src/main/java/classes/tasks/Task.java similarity index 71% rename from src/main/java/classes/Task.java rename to src/main/java/classes/tasks/Task.java index e546f83f..aa8c91ce 100644 --- a/src/main/java/classes/Task.java +++ b/src/main/java/classes/tasks/Task.java @@ -1,17 +1,19 @@ package classes; -import interfaces.Printable; +import classes.enums.TaskType; -public class Task implements Printable { +public class Task { private String description; private Boolean isDone; + private TaskType type; public Task() { isDone = false; } - public Task(String description) { + public Task(TaskType type, String description) { this(); + setType(type); setDescription(description); } @@ -31,7 +33,14 @@ public void setDone(Boolean done) { isDone = done; } - @Override + public TaskType getType() { + return type; + } + + public void setType(TaskType type) { + this.type = type; + } + public String toStatusString() { String doneStr = (isDone) ? "X" : " "; return "[" + doneStr + "] " + toString(); diff --git a/src/main/java/classes/tasks/Todo.java b/src/main/java/classes/tasks/Todo.java new file mode 100644 index 00000000..2ca8f8a2 --- /dev/null +++ b/src/main/java/classes/tasks/Todo.java @@ -0,0 +1,14 @@ +package classes; + +import classes.enums.TaskType; + +public class Todo extends Task { + public Todo(String description) { + super(TaskType.TODO, description); + } + + @Override + public String toStatusString() { + return "[T]" + super.toStatusString(); + } +} diff --git a/src/main/java/exceptions/InvalidCommandException.java b/src/main/java/exceptions/InvalidCommandException.java new file mode 100644 index 00000000..b8eed563 --- /dev/null +++ b/src/main/java/exceptions/InvalidCommandException.java @@ -0,0 +1,2 @@ +package exceptions;public class InvalidCommandException { +} diff --git a/src/main/java/interfaces/Printable.java b/src/main/java/interfaces/Printable.java deleted file mode 100644 index f5ddaf6a..00000000 --- a/src/main/java/interfaces/Printable.java +++ /dev/null @@ -1,6 +0,0 @@ -package interfaces; - -public interface Printable { - public String toStatusString(); - public String toString(); -} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 8606de4c..fcb1c355 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -9,16 +9,16 @@ added: return book _________________________________________ _________________________________________ - 1. [ ] read book - 2. [ ] return book + 1. [T][ ] read book + 2. [T][ ] return book _________________________________________ _________________________________________ Nice! I've marked this task as done: - [X] return book + [T][X] return book _________________________________________ _________________________________________ - 1. [ ] read book - 2. [X] return book + 1. [T][ ] read book + 2. [T][X] return book _________________________________________ _________________________________________ Bye. Hope to see you again soon! diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index 6a6ebe1c..583871f0 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1,5 +1,5 @@ -read book -return book +todo read book +todo return book list done 2 list \ No newline at end of file diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 34c62b6c..28f78c4e 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,7 +13,7 @@ then fi # compile the code into the bin folder, terminates if error occurred -if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java ../src/main/java/interfaces/*.java ../src/main/java/classes/*.java +if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java ../src/main/java/interfaces/*.java ../src/main/java/classes/*.java ../src/main/java/exceptions/*.java then echo "********** BUILD FAILURE **********" exit 1 @@ -24,7 +24,13 @@ java -classpath ../bin Duke < input.txt > ACTUAL.TXT # convert to UNIX format cp EXPECTED.TXT EXPECTED-UNIX.TXT -dos2unix ACTUAL.TXT EXPECTED-UNIX.TXT + +if type dos2unix &> /dev/null +then + dos2unix ACTUAL.TXT EXPECTED-UNIX.TXT +else + echo "dos2unix command not found. To prevent potential issues, please install dos2unix." +fi # compare the output to the expected output diff ACTUAL.TXT EXPECTED-UNIX.TXT From ea311720f5af462b17d387ad849a0f3bced88191 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Wed, 8 Sep 2021 10:25:16 +0800 Subject: [PATCH 08/59] Completion of level 4 tasks --- src/main/java/classes/tasks/TaskFactory.java | 2 ++ .../exceptions/InvalidCommandFormatException.java | 2 ++ text-ui-test/EXPECTED.TXT | 13 ++++++++++--- text-ui-test/input.txt | 3 ++- 4 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 src/main/java/classes/tasks/TaskFactory.java create mode 100644 src/main/java/exceptions/InvalidCommandFormatException.java diff --git a/src/main/java/classes/tasks/TaskFactory.java b/src/main/java/classes/tasks/TaskFactory.java new file mode 100644 index 00000000..913b1c2f --- /dev/null +++ b/src/main/java/classes/tasks/TaskFactory.java @@ -0,0 +1,2 @@ +package classes.tasks;public class TaskFactory { +} diff --git a/src/main/java/exceptions/InvalidCommandFormatException.java b/src/main/java/exceptions/InvalidCommandFormatException.java new file mode 100644 index 00000000..14617249 --- /dev/null +++ b/src/main/java/exceptions/InvalidCommandFormatException.java @@ -0,0 +1,2 @@ +package exceptions;public class InvalidCommandFormatException { +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index fcb1c355..958946ce 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -4,21 +4,28 @@ _________________________________________ _________________________________________ added: read book + Now you have 1 task in the list. _________________________________________ _________________________________________ added: return book + Now you have 2 tasks in the list. _________________________________________ _________________________________________ 1. [T][ ] read book - 2. [T][ ] return book + 2. [D][ ] return book (by: 2021-09-20) + _________________________________________ + _________________________________________ + added: meet friend + Now you have 3 tasks in the list. _________________________________________ _________________________________________ Nice! I've marked this task as done: - [T][X] return book + [D][X] return book (by: 2021-09-20) _________________________________________ _________________________________________ 1. [T][ ] read book - 2. [T][X] return book + 2. [D][X] return book (by: 2021-09-20) + 3. [E][ ] meet friend (at: 2021-09-15) _________________________________________ _________________________________________ Bye. Hope to see you again soon! diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index 583871f0..f650bc6e 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1,5 +1,6 @@ todo read book -todo return book +deadline return book /by 2021-09-20 list +event meet friend /at 2021-09-15 done 2 list \ No newline at end of file From 7ffd805c870af2a8121a8283a939096761634bcb Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Wed, 8 Sep 2021 10:26:15 +0800 Subject: [PATCH 09/59] Completion of level 4 tasks --- src/main/java/Duke.java | 68 ++++++++++++------- src/main/java/classes/Command.java | 22 ++++-- src/main/java/classes/Parser.java | 40 ++++++++++- src/main/java/classes/Prompt.java | 32 ++++++--- src/main/java/classes/enums/CommandType.java | 2 +- src/main/java/classes/enums/TaskType.java | 24 ++++++- src/main/java/classes/tasks/Deadline.java | 2 +- src/main/java/classes/tasks/Event.java | 2 +- src/main/java/classes/tasks/Task.java | 2 +- src/main/java/classes/tasks/TaskFactory.java | 42 +++++++++++- src/main/java/classes/tasks/Todo.java | 2 +- .../exceptions/InvalidCommandException.java | 6 +- .../InvalidCommandFormatException.java | 4 +- src/main/java/interfaces/Promptable.java | 4 +- 14 files changed, 198 insertions(+), 54 deletions(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 203428b3..b07509b5 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,42 +1,62 @@ -import classes.Task; -import interfaces.Printable; +import classes.*; +import classes.tasks.*; +import exceptions.InvalidCommandException; +import exceptions.InvalidCommandFormatException; import interfaces.Promptable; import java.util.ArrayList; import java.util.Scanner; public class Duke { - private final Promptable _prompt; + private final Promptable prompt; + private final Parser parser; - public Duke(Promptable prompt) { - this._prompt = prompt; + public Duke(Promptable prompt, Parser parser) { + this.prompt = prompt; + this.parser = parser; } public static void main(String[] args) { - Duke d = new Duke(new Prompt()); - System.out.println(d._prompt.start()); + Duke d = new Duke(new Prompt(), new Parser()); + System.out.println(d.prompt.start()); Scanner in = new Scanner(System.in); ArrayList tasks = new ArrayList<>(); - - while (in.hasNext()) { - String input = in.nextLine(); - - if (input.equalsIgnoreCase("bye")) { - break; - } else if (input.equalsIgnoreCase("list")) { - System.out.println(d._prompt.list(tasks)); - } else if (input.startsWith("done")) { - int idx = Integer.parseInt(input.split(" ")[1]); - tasks.get(idx - 1).setDone(true); - System.out.println(d._prompt.done(tasks.get(idx - 1))); - } else { - Task newTask = new Task(input); - tasks.add(newTask); - System.out.println(d._prompt.add(newTask)); + boolean receiveInput = true; + + while (receiveInput && in.hasNext()) { + try { + Command command = d.parser.readInput(in); + switch(command.getType()) { + case ADD: + Task newTask = TaskFactory.getInstance(command); + tasks.add(newTask); + System.out.println(d.prompt.add(newTask, tasks.size())); + break; + case COMPLETE: + int idx = Integer.parseInt(command.getArgs()); + Task doneTask = tasks.get(idx - 1); + doneTask.setDone(true); + System.out.println(d.prompt.done(doneTask)); + break; + case LIST: + System.out.println(d.prompt.list(tasks)); + break; + case EXIT: + receiveInput = false; + break; + } + } catch (InvalidCommandException ice) { + System.out.println("Invalid Command Exception."); + } catch (NumberFormatException nfe) { + System.out.println("Number Format Exception."); + } catch (IndexOutOfBoundsException ioobe) { + System.out.println("IndexOutOfBoundsException."); + } catch (InvalidCommandFormatException icfe) { + System.out.println("Invalid Command Format Exception."); } } - System.out.println(d._prompt.exit()); + System.out.println(d.prompt.exit()); } } diff --git a/src/main/java/classes/Command.java b/src/main/java/classes/Command.java index 69eb8f98..d1ad37d7 100644 --- a/src/main/java/classes/Command.java +++ b/src/main/java/classes/Command.java @@ -1,18 +1,20 @@ package classes; -public class UserCommand { +import classes.enums.CommandType; + +public class Command { private CommandType type; private String keyword; private String args; - public UserCommand(CommandType type, String keyword) { - this.keyword = keyword; - this.type = type; + public Command(CommandType type, String keyword) { + this.setKeyword(keyword); + this.setType(type); } - public UserCommand(CommandType type, String keyword, String args) { + public Command(CommandType type, String keyword, String args) { this(type, keyword); - this.args = args; + this.setArgs(args); } public CommandType getType() { @@ -23,6 +25,14 @@ public void setType(CommandType type) { this.type = type; } + public String getKeyword() { + return keyword; + } + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + public String getArgs() { return args; } diff --git a/src/main/java/classes/Parser.java b/src/main/java/classes/Parser.java index 0d301bf2..e5644a8f 100644 --- a/src/main/java/classes/Parser.java +++ b/src/main/java/classes/Parser.java @@ -1,2 +1,40 @@ -package classes;public class Parser { +package classes; + +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import classes.enums.CommandType; +import exceptions.InvalidCommandException; + +public class Parser { + private static final Pattern ADD_PATTERN = Pattern.compile("^(?\\bdeadline\\b|\\bevent\\b|\\btodo\\b) (?.+)$", Pattern.CASE_INSENSITIVE); + private static final Pattern DONE_PATTERN = Pattern.compile("^\\b(?done)\\b (?.+)$", Pattern.CASE_INSENSITIVE); + private static final Pattern LIST_PATTERN = Pattern.compile("^(?list)$", Pattern.CASE_INSENSITIVE); + private static final Pattern EXIT_PATTERN = Pattern.compile("^(?bye)$", Pattern.CASE_INSENSITIVE); + + public Command readInput(Scanner sc) throws InvalidCommandException { + String input = sc.nextLine(); + Matcher addMatcher = ADD_PATTERN.matcher(input); + Matcher doneMatcher = DONE_PATTERN.matcher(input); + Matcher listMatcher = LIST_PATTERN.matcher(input); + Matcher exitMatcher = EXIT_PATTERN.matcher(input); + + if (addMatcher.matches()) { + String args = addMatcher.group("args"); + String keyword = addMatcher.group("keyword"); + return new Command(CommandType.ADD, keyword, args); + } else if (doneMatcher.matches()) { + String args = doneMatcher.group("args"); + String keyword = doneMatcher.group("keyword"); + return new Command(CommandType.COMPLETE, keyword, args); + } else if (listMatcher.matches()) { + String keyword = listMatcher.group("keyword"); + return new Command(CommandType.LIST, keyword); + } else if (exitMatcher.matches()) { + String keyword = exitMatcher.group("keyword"); + return new Command(CommandType.EXIT, keyword); + } + throw new InvalidCommandException(); + } } diff --git a/src/main/java/classes/Prompt.java b/src/main/java/classes/Prompt.java index b35d6f74..285b9da5 100644 --- a/src/main/java/classes/Prompt.java +++ b/src/main/java/classes/Prompt.java @@ -1,4 +1,10 @@ -import classes.Task; +package classes; + +import classes.enums.TaskType; +import classes.tasks.Deadline; +import classes.tasks.Event; +import classes.tasks.Task; +import classes.tasks.Todo; import interfaces.Promptable; public class Prompt implements Promptable { @@ -9,21 +15,14 @@ public class Prompt implements Promptable { private static final String DONE = "Nice! I've marked this task as done:"; private static final String ADD = "added: "; - public enum Prompts { - START, - END, - ADD, - MANUAL - } - @Override public String start() { return formatOutput(formatLine(START)); } @Override - public String add(Task printable) { - return formatOutput(formatLine(ADD + printable.toString())); + public String add(Task printable, int length) { + return formatOutput(formatLine(ADD + printable.toString()) + formatLine("Now you have " + length + " task" + ((length > 1) ? "s" : "") + " in the list.")); } @Override @@ -36,7 +35,18 @@ public String list(Iterable inputs) { StringBuilder output = new StringBuilder(); int count = 1; for (Task input : inputs) { - output.append(formatLine(count + ". " + input.toStatusString())); + String taskStr = ""; + if (input.getType() == TaskType.DEADLINE) { + Deadline down = (Deadline) input; + taskStr = down.toStatusString(); + } else if (input.getType() == TaskType.EVENT) { + Event down = (Event) input; + taskStr = down.toStatusString(); + } else if (input.getType() == TaskType.TODO) { + Todo down = (Todo) input; + taskStr = down.toStatusString(); + } + output.append(formatLine(count + ". " + taskStr)); count++; } return formatOutput(output.toString()); diff --git a/src/main/java/classes/enums/CommandType.java b/src/main/java/classes/enums/CommandType.java index 6f505612..51d0ab94 100644 --- a/src/main/java/classes/enums/CommandType.java +++ b/src/main/java/classes/enums/CommandType.java @@ -1,4 +1,4 @@ -package classes; +package classes.enums; public enum CommandType { ADD, diff --git a/src/main/java/classes/enums/TaskType.java b/src/main/java/classes/enums/TaskType.java index 2a48c3e8..8593c86d 100644 --- a/src/main/java/classes/enums/TaskType.java +++ b/src/main/java/classes/enums/TaskType.java @@ -1,2 +1,24 @@ -package classes.enums;public class TaskType { +package classes.enums; + +public enum TaskType { + TODO("todo"), + EVENT("event"), + DEADLINE("deadline"); + + private String type; + TaskType(String type) { + setType(type); + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String toString() { + return getType(); + } } diff --git a/src/main/java/classes/tasks/Deadline.java b/src/main/java/classes/tasks/Deadline.java index 3f573625..f6aecb42 100644 --- a/src/main/java/classes/tasks/Deadline.java +++ b/src/main/java/classes/tasks/Deadline.java @@ -1,4 +1,4 @@ -package classes; +package classes.tasks; import classes.enums.TaskType; diff --git a/src/main/java/classes/tasks/Event.java b/src/main/java/classes/tasks/Event.java index c3cc5a17..b9eddfe8 100644 --- a/src/main/java/classes/tasks/Event.java +++ b/src/main/java/classes/tasks/Event.java @@ -1,4 +1,4 @@ -package classes; +package classes.tasks; import classes.enums.TaskType; diff --git a/src/main/java/classes/tasks/Task.java b/src/main/java/classes/tasks/Task.java index aa8c91ce..e76045d0 100644 --- a/src/main/java/classes/tasks/Task.java +++ b/src/main/java/classes/tasks/Task.java @@ -1,4 +1,4 @@ -package classes; +package classes.tasks; import classes.enums.TaskType; diff --git a/src/main/java/classes/tasks/TaskFactory.java b/src/main/java/classes/tasks/TaskFactory.java index 913b1c2f..94248ff3 100644 --- a/src/main/java/classes/tasks/TaskFactory.java +++ b/src/main/java/classes/tasks/TaskFactory.java @@ -1,2 +1,42 @@ -package classes.tasks;public class TaskFactory { +package classes.tasks; + +import classes.Command; +import classes.enums.TaskType; +import exceptions.InvalidCommandFormatException; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TaskFactory { + private static final String KEY_DESCRIPTION = "description"; + private static final String KEY_DATE = "date"; + private static final String DEADLINE_PATTERN = "^(?<" + KEY_DESCRIPTION + ">.+) /by (?<" + KEY_DATE + ">.+)$"; + private static final String EVENT_PATTERN = "^(?<" + KEY_DESCRIPTION + ">.+) /at (?<" + KEY_DATE + ">.+)$"; + + public static Task getInstance(Command cmd) throws InvalidCommandFormatException { + String keyword = cmd.getKeyword(); + String args = cmd.getArgs(); + if (keyword.equalsIgnoreCase(TaskType.DEADLINE.toString())) { + Matcher deadlineMatch = Pattern.compile(DEADLINE_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); + if (deadlineMatch.matches()) { + String description = deadlineMatch.group(KEY_DESCRIPTION); + String dueDate = deadlineMatch.group(KEY_DATE); + return new Deadline(description, dueDate); + } else { + throw new InvalidCommandFormatException(); + } + } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { + Matcher eventMatch = Pattern.compile(EVENT_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); + if (eventMatch.matches()) { + String description = eventMatch.group(KEY_DESCRIPTION); + String timing = eventMatch.group(KEY_DATE); + return new Event(description, timing); + } else { + throw new InvalidCommandFormatException(); + } + } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { + return new Todo(args); + } + throw new InvalidCommandFormatException(); + } } diff --git a/src/main/java/classes/tasks/Todo.java b/src/main/java/classes/tasks/Todo.java index 2ca8f8a2..a93a2e84 100644 --- a/src/main/java/classes/tasks/Todo.java +++ b/src/main/java/classes/tasks/Todo.java @@ -1,4 +1,4 @@ -package classes; +package classes.tasks; import classes.enums.TaskType; diff --git a/src/main/java/exceptions/InvalidCommandException.java b/src/main/java/exceptions/InvalidCommandException.java index b8eed563..7d464e6b 100644 --- a/src/main/java/exceptions/InvalidCommandException.java +++ b/src/main/java/exceptions/InvalidCommandException.java @@ -1,2 +1,4 @@ -package exceptions;public class InvalidCommandException { -} +package exceptions; + +public class InvalidCommandException extends Throwable { +} \ No newline at end of file diff --git a/src/main/java/exceptions/InvalidCommandFormatException.java b/src/main/java/exceptions/InvalidCommandFormatException.java index 14617249..61b63def 100644 --- a/src/main/java/exceptions/InvalidCommandFormatException.java +++ b/src/main/java/exceptions/InvalidCommandFormatException.java @@ -1,2 +1,4 @@ -package exceptions;public class InvalidCommandFormatException { +package exceptions; + +public class InvalidCommandFormatException extends Throwable { } diff --git a/src/main/java/interfaces/Promptable.java b/src/main/java/interfaces/Promptable.java index 8412b0a5..3e97dbf9 100644 --- a/src/main/java/interfaces/Promptable.java +++ b/src/main/java/interfaces/Promptable.java @@ -1,10 +1,10 @@ package interfaces; -import classes.Task; +import classes.tasks.Task; public interface Promptable { public String start(); - public String add(T printable); + public String add(T printable, int length); public String done(T printable); public String list(Iterable inputs); public String list(T[] inputs); From d51534715ce2554db44a0d6172dbdcca1a11701e Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Wed, 8 Sep 2021 11:30:59 +0800 Subject: [PATCH 10/59] Refactor to prep for further levels --- src/main/java/Duke.java | 12 ++++++++---- src/main/java/classes/tasks/TaskFactory.java | 2 +- src/main/java/classes/{ => ui}/Command.java | 2 +- src/main/java/classes/{ => ui}/Parser.java | 2 +- src/main/java/classes/{ => ui}/Prompt.java | 2 +- text-ui-test/runtest.sh | 2 +- 6 files changed, 13 insertions(+), 9 deletions(-) rename src/main/java/classes/{ => ui}/Command.java (97%) rename src/main/java/classes/{ => ui}/Parser.java (98%) rename src/main/java/classes/{ => ui}/Prompt.java (99%) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index b07509b5..f1c50bc5 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,5 +1,7 @@ -import classes.*; import classes.tasks.*; +import classes.ui.Command; +import classes.ui.Parser; +import classes.ui.Prompt; import exceptions.InvalidCommandException; import exceptions.InvalidCommandFormatException; import interfaces.Promptable; @@ -27,25 +29,27 @@ public static void main(String[] args) { while (receiveInput && in.hasNext()) { try { Command command = d.parser.readInput(in); + String output = ""; switch(command.getType()) { case ADD: Task newTask = TaskFactory.getInstance(command); tasks.add(newTask); - System.out.println(d.prompt.add(newTask, tasks.size())); + output = d.prompt.add(newTask, tasks.size()); break; case COMPLETE: int idx = Integer.parseInt(command.getArgs()); Task doneTask = tasks.get(idx - 1); doneTask.setDone(true); - System.out.println(d.prompt.done(doneTask)); + output = d.prompt.done(doneTask); break; case LIST: - System.out.println(d.prompt.list(tasks)); + output = d.prompt.list(tasks); break; case EXIT: receiveInput = false; break; } + System.out.println(output); } catch (InvalidCommandException ice) { System.out.println("Invalid Command Exception."); } catch (NumberFormatException nfe) { diff --git a/src/main/java/classes/tasks/TaskFactory.java b/src/main/java/classes/tasks/TaskFactory.java index 94248ff3..3a1a6125 100644 --- a/src/main/java/classes/tasks/TaskFactory.java +++ b/src/main/java/classes/tasks/TaskFactory.java @@ -1,6 +1,6 @@ package classes.tasks; -import classes.Command; +import classes.ui.Command; import classes.enums.TaskType; import exceptions.InvalidCommandFormatException; diff --git a/src/main/java/classes/Command.java b/src/main/java/classes/ui/Command.java similarity index 97% rename from src/main/java/classes/Command.java rename to src/main/java/classes/ui/Command.java index d1ad37d7..7ff9ea9d 100644 --- a/src/main/java/classes/Command.java +++ b/src/main/java/classes/ui/Command.java @@ -1,4 +1,4 @@ -package classes; +package classes.ui; import classes.enums.CommandType; diff --git a/src/main/java/classes/Parser.java b/src/main/java/classes/ui/Parser.java similarity index 98% rename from src/main/java/classes/Parser.java rename to src/main/java/classes/ui/Parser.java index e5644a8f..cc404afe 100644 --- a/src/main/java/classes/Parser.java +++ b/src/main/java/classes/ui/Parser.java @@ -1,4 +1,4 @@ -package classes; +package classes.ui; import java.util.Scanner; import java.util.regex.Matcher; diff --git a/src/main/java/classes/Prompt.java b/src/main/java/classes/ui/Prompt.java similarity index 99% rename from src/main/java/classes/Prompt.java rename to src/main/java/classes/ui/Prompt.java index 285b9da5..ed9b89eb 100644 --- a/src/main/java/classes/Prompt.java +++ b/src/main/java/classes/ui/Prompt.java @@ -1,4 +1,4 @@ -package classes; +package classes.ui; import classes.enums.TaskType; import classes.tasks.Deadline; diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 28f78c4e..3229b157 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,7 +13,7 @@ then fi # compile the code into the bin folder, terminates if error occurred -if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java ../src/main/java/interfaces/*.java ../src/main/java/classes/*.java ../src/main/java/exceptions/*.java +if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java ../src/main/java/*/*.java then echo "********** BUILD FAILURE **********" exit 1 From 615dc65763976a8fc5bb296e023e986129c67cdb Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Wed, 8 Sep 2021 11:53:16 +0800 Subject: [PATCH 11/59] Move to Duke package, refined UI --- README.md | 4 +-- src/{main/java => Duke}/Duke.java | 16 +++++---- .../classes}/interfaces/Promptable.java | 5 ++- .../java => Duke}/classes/tasks/Deadline.java | 4 +-- .../java => Duke}/classes/tasks/Event.java | 4 +-- .../java => Duke}/classes/tasks/Task.java | 4 +-- .../classes/tasks/TaskFactory.java | 8 ++--- .../java => Duke}/classes/tasks/Todo.java | 4 +-- .../java => Duke}/classes/ui/Command.java | 4 +-- .../java => Duke}/classes/ui/Parser.java | 6 ++-- .../java => Duke}/classes/ui/Prompt.java | 34 +++++++++---------- .../classes => Duke}/enums/CommandType.java | 2 +- .../java/classes => Duke}/enums/TaskType.java | 2 +- .../exceptions/InvalidCommandException.java | 2 +- .../InvalidCommandFormatException.java | 2 +- text-ui-test/runtest.bat | 2 +- text-ui-test/runtest.sh | 2 +- 17 files changed, 52 insertions(+), 53 deletions(-) rename src/{main/java => Duke}/Duke.java (88%) rename src/{main/java => Duke/classes}/interfaces/Promptable.java (73%) rename src/{main/java => Duke}/classes/tasks/Deadline.java (89%) rename src/{main/java => Duke}/classes/tasks/Event.java (89%) rename src/{main/java => Duke}/classes/tasks/Task.java (94%) rename src/{main/java => Duke}/classes/tasks/TaskFactory.java (92%) rename src/{main/java => Duke}/classes/tasks/Todo.java (80%) rename src/{main/java => Duke}/classes/ui/Command.java (93%) rename src/{main/java => Duke}/classes/ui/Parser.java (94%) rename src/{main/java => Duke}/classes/ui/Prompt.java (73%) rename src/{main/java/classes => Duke}/enums/CommandType.java (75%) rename src/{main/java/classes => Duke}/enums/TaskType.java (94%) rename src/{main/java => Duke}/exceptions/InvalidCommandException.java (70%) rename src/{main/java => Duke}/exceptions/InvalidCommandFormatException.java (72%) diff --git a/README.md b/README.md index 8715d4d9..63d0ad6b 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/src/main/java/Duke.java b/src/Duke/Duke.java similarity index 88% rename from src/main/java/Duke.java rename to src/Duke/Duke.java index f1c50bc5..cab494de 100644 --- a/src/main/java/Duke.java +++ b/src/Duke/Duke.java @@ -1,10 +1,12 @@ -import classes.tasks.*; -import classes.ui.Command; -import classes.ui.Parser; -import classes.ui.Prompt; -import exceptions.InvalidCommandException; -import exceptions.InvalidCommandFormatException; -import interfaces.Promptable; +package Duke; + +import Duke.classes.tasks.*; +import Duke.classes.ui.Command; +import Duke.classes.ui.Parser; +import Duke.classes.ui.Prompt; +import Duke.exceptions.InvalidCommandException; +import Duke.exceptions.InvalidCommandFormatException; +import Duke.classes.interfaces.Promptable; import java.util.ArrayList; import java.util.Scanner; diff --git a/src/main/java/interfaces/Promptable.java b/src/Duke/classes/interfaces/Promptable.java similarity index 73% rename from src/main/java/interfaces/Promptable.java rename to src/Duke/classes/interfaces/Promptable.java index 3e97dbf9..7426618d 100644 --- a/src/main/java/interfaces/Promptable.java +++ b/src/Duke/classes/interfaces/Promptable.java @@ -1,12 +1,11 @@ -package interfaces; +package Duke.classes.interfaces; -import classes.tasks.Task; +import Duke.classes.tasks.Task; public interface Promptable { public String start(); public String add(T printable, int length); public String done(T printable); public String list(Iterable inputs); - public String list(T[] inputs); public String exit(); } diff --git a/src/main/java/classes/tasks/Deadline.java b/src/Duke/classes/tasks/Deadline.java similarity index 89% rename from src/main/java/classes/tasks/Deadline.java rename to src/Duke/classes/tasks/Deadline.java index f6aecb42..e002b218 100644 --- a/src/main/java/classes/tasks/Deadline.java +++ b/src/Duke/classes/tasks/Deadline.java @@ -1,6 +1,6 @@ -package classes.tasks; +package Duke.classes.tasks; -import classes.enums.TaskType; +import Duke.enums.TaskType; public class Deadline extends Task { private String dueDate; diff --git a/src/main/java/classes/tasks/Event.java b/src/Duke/classes/tasks/Event.java similarity index 89% rename from src/main/java/classes/tasks/Event.java rename to src/Duke/classes/tasks/Event.java index b9eddfe8..80d19696 100644 --- a/src/main/java/classes/tasks/Event.java +++ b/src/Duke/classes/tasks/Event.java @@ -1,6 +1,6 @@ -package classes.tasks; +package Duke.classes.tasks; -import classes.enums.TaskType; +import Duke.enums.TaskType; public class Event extends Task { private String timing; diff --git a/src/main/java/classes/tasks/Task.java b/src/Duke/classes/tasks/Task.java similarity index 94% rename from src/main/java/classes/tasks/Task.java rename to src/Duke/classes/tasks/Task.java index e76045d0..0afcfcfb 100644 --- a/src/main/java/classes/tasks/Task.java +++ b/src/Duke/classes/tasks/Task.java @@ -1,6 +1,6 @@ -package classes.tasks; +package Duke.classes.tasks; -import classes.enums.TaskType; +import Duke.enums.TaskType; public class Task { private String description; diff --git a/src/main/java/classes/tasks/TaskFactory.java b/src/Duke/classes/tasks/TaskFactory.java similarity index 92% rename from src/main/java/classes/tasks/TaskFactory.java rename to src/Duke/classes/tasks/TaskFactory.java index 3a1a6125..4528f13a 100644 --- a/src/main/java/classes/tasks/TaskFactory.java +++ b/src/Duke/classes/tasks/TaskFactory.java @@ -1,8 +1,8 @@ -package classes.tasks; +package Duke.classes.tasks; -import classes.ui.Command; -import classes.enums.TaskType; -import exceptions.InvalidCommandFormatException; +import Duke.classes.ui.Command; +import Duke.enums.TaskType; +import Duke.exceptions.InvalidCommandFormatException; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/classes/tasks/Todo.java b/src/Duke/classes/tasks/Todo.java similarity index 80% rename from src/main/java/classes/tasks/Todo.java rename to src/Duke/classes/tasks/Todo.java index a93a2e84..5feff553 100644 --- a/src/main/java/classes/tasks/Todo.java +++ b/src/Duke/classes/tasks/Todo.java @@ -1,6 +1,6 @@ -package classes.tasks; +package Duke.classes.tasks; -import classes.enums.TaskType; +import Duke.enums.TaskType; public class Todo extends Task { public Todo(String description) { diff --git a/src/main/java/classes/ui/Command.java b/src/Duke/classes/ui/Command.java similarity index 93% rename from src/main/java/classes/ui/Command.java rename to src/Duke/classes/ui/Command.java index 7ff9ea9d..dd7ee8bf 100644 --- a/src/main/java/classes/ui/Command.java +++ b/src/Duke/classes/ui/Command.java @@ -1,6 +1,6 @@ -package classes.ui; +package Duke.classes.ui; -import classes.enums.CommandType; +import Duke.enums.CommandType; public class Command { private CommandType type; diff --git a/src/main/java/classes/ui/Parser.java b/src/Duke/classes/ui/Parser.java similarity index 94% rename from src/main/java/classes/ui/Parser.java rename to src/Duke/classes/ui/Parser.java index cc404afe..017508ff 100644 --- a/src/main/java/classes/ui/Parser.java +++ b/src/Duke/classes/ui/Parser.java @@ -1,11 +1,11 @@ -package classes.ui; +package Duke.classes.ui; import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; -import classes.enums.CommandType; -import exceptions.InvalidCommandException; +import Duke.enums.CommandType; +import Duke.exceptions.InvalidCommandException; public class Parser { private static final Pattern ADD_PATTERN = Pattern.compile("^(?\\bdeadline\\b|\\bevent\\b|\\btodo\\b) (?.+)$", Pattern.CASE_INSENSITIVE); diff --git a/src/main/java/classes/ui/Prompt.java b/src/Duke/classes/ui/Prompt.java similarity index 73% rename from src/main/java/classes/ui/Prompt.java rename to src/Duke/classes/ui/Prompt.java index ed9b89eb..34fc5be9 100644 --- a/src/main/java/classes/ui/Prompt.java +++ b/src/Duke/classes/ui/Prompt.java @@ -1,11 +1,11 @@ -package classes.ui; +package Duke.classes.ui; -import classes.enums.TaskType; -import classes.tasks.Deadline; -import classes.tasks.Event; -import classes.tasks.Task; -import classes.tasks.Todo; -import interfaces.Promptable; +import Duke.enums.TaskType; +import Duke.classes.tasks.Deadline; +import Duke.classes.tasks.Event; +import Duke.classes.tasks.Task; +import Duke.classes.tasks.Todo; +import Duke.classes.interfaces.Promptable; public class Prompt implements Promptable { private static final String PADDING = " "; @@ -22,12 +22,19 @@ public String start() { @Override public String add(Task printable, int length) { - return formatOutput(formatLine(ADD + printable.toString()) + formatLine("Now you have " + length + " task" + ((length > 1) ? "s" : "") + " in the list.")); + StringBuilder output = new StringBuilder(); + output.append(formatLine(ADD + printable.toString())); + output.append(formatLine("Now you have " + length + " task" + ((length > 1) ? "s" : "") + " in the list.")); + + return formatOutput(output.toString()); } @Override public String done(Task printable) { - return formatOutput(formatLine(DONE) + formatLine(" " + printable.toStatusString())); + StringBuilder output = new StringBuilder(); + output.append(formatLine(DONE) + formatLine(" " + printable.toStatusString())); + + return formatOutput(output.toString()); } @Override @@ -52,15 +59,6 @@ public String list(Iterable inputs) { return formatOutput(output.toString()); } - @Override - public String list(Task[] inputs) { - StringBuilder output = new StringBuilder(); - for (int i=0;i < inputs.length;i++) { - output.append(formatLine((i + 1) + ". " + inputs[i].toStatusString())); - } - return output.toString(); - } - @Override public String exit() { return formatOutput(formatLine(END)); diff --git a/src/main/java/classes/enums/CommandType.java b/src/Duke/enums/CommandType.java similarity index 75% rename from src/main/java/classes/enums/CommandType.java rename to src/Duke/enums/CommandType.java index 51d0ab94..5c91476b 100644 --- a/src/main/java/classes/enums/CommandType.java +++ b/src/Duke/enums/CommandType.java @@ -1,4 +1,4 @@ -package classes.enums; +package Duke.enums; public enum CommandType { ADD, diff --git a/src/main/java/classes/enums/TaskType.java b/src/Duke/enums/TaskType.java similarity index 94% rename from src/main/java/classes/enums/TaskType.java rename to src/Duke/enums/TaskType.java index 8593c86d..44788705 100644 --- a/src/main/java/classes/enums/TaskType.java +++ b/src/Duke/enums/TaskType.java @@ -1,4 +1,4 @@ -package classes.enums; +package Duke.enums; public enum TaskType { TODO("todo"), diff --git a/src/main/java/exceptions/InvalidCommandException.java b/src/Duke/exceptions/InvalidCommandException.java similarity index 70% rename from src/main/java/exceptions/InvalidCommandException.java rename to src/Duke/exceptions/InvalidCommandException.java index 7d464e6b..866d4064 100644 --- a/src/main/java/exceptions/InvalidCommandException.java +++ b/src/Duke/exceptions/InvalidCommandException.java @@ -1,4 +1,4 @@ -package exceptions; +package Duke.exceptions; public class InvalidCommandException extends Throwable { } \ No newline at end of file diff --git a/src/main/java/exceptions/InvalidCommandFormatException.java b/src/Duke/exceptions/InvalidCommandFormatException.java similarity index 72% rename from src/main/java/exceptions/InvalidCommandFormatException.java rename to src/Duke/exceptions/InvalidCommandFormatException.java index 61b63def..f22eb183 100644 --- a/src/main/java/exceptions/InvalidCommandFormatException.java +++ b/src/Duke/exceptions/InvalidCommandFormatException.java @@ -1,4 +1,4 @@ -package exceptions; +package Duke.exceptions; public class InvalidCommandFormatException extends Throwable { } diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 08737446..cecfdb11 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 diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 3229b157..ed07b635 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,7 +13,7 @@ then fi # compile the code into the bin folder, terminates if error occurred -if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java ../src/main/java/*/*.java +if ! javac -cp ../src/Duke -Xlint:none -d ../bin ../src/Duke/*.java ../src/Duke/*/*.java ../src/Duke/*/*/*.java then echo "********** BUILD FAILURE **********" exit 1 From ece4e1d4f187523e15261f78c19ca825291e89b6 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Wed, 8 Sep 2021 13:38:08 +0800 Subject: [PATCH 12/59] Refactor parsing of task, added exception handling for ICFE --- src/Duke/Duke.java | 2 +- src/Duke/classes/tasks/TaskFactory.java | 12 ++++++---- src/Duke/classes/ui/Parser.java | 5 ++-- .../InvalidCommandFormatException.java | 24 ++++++++++++++++++- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/Duke/Duke.java b/src/Duke/Duke.java index cab494de..6bf30c40 100644 --- a/src/Duke/Duke.java +++ b/src/Duke/Duke.java @@ -59,7 +59,7 @@ public static void main(String[] args) { } catch (IndexOutOfBoundsException ioobe) { System.out.println("IndexOutOfBoundsException."); } catch (InvalidCommandFormatException icfe) { - System.out.println("Invalid Command Format Exception."); + System.out.println(icfe); } } diff --git a/src/Duke/classes/tasks/TaskFactory.java b/src/Duke/classes/tasks/TaskFactory.java index 4528f13a..4b50cf07 100644 --- a/src/Duke/classes/tasks/TaskFactory.java +++ b/src/Duke/classes/tasks/TaskFactory.java @@ -23,7 +23,7 @@ public static Task getInstance(Command cmd) throws InvalidCommandFormatException String dueDate = deadlineMatch.group(KEY_DATE); return new Deadline(description, dueDate); } else { - throw new InvalidCommandFormatException(); + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); } } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { Matcher eventMatch = Pattern.compile(EVENT_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); @@ -32,11 +32,15 @@ public static Task getInstance(Command cmd) throws InvalidCommandFormatException String timing = eventMatch.group(KEY_DATE); return new Event(description, timing); } else { - throw new InvalidCommandFormatException(); + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_EVENT); } } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { - return new Todo(args); + if (args.length() > 0) { + return new Todo(args); + } else { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_TODO); + } } - throw new InvalidCommandFormatException(); + throw new InvalidCommandFormatException("Invalid Command Format:\n Cannot find the appropriate task to create."); } } diff --git a/src/Duke/classes/ui/Parser.java b/src/Duke/classes/ui/Parser.java index 017508ff..fb24db06 100644 --- a/src/Duke/classes/ui/Parser.java +++ b/src/Duke/classes/ui/Parser.java @@ -6,14 +6,15 @@ import Duke.enums.CommandType; import Duke.exceptions.InvalidCommandException; +import Duke.exceptions.InvalidCommandFormatException; public class Parser { - private static final Pattern ADD_PATTERN = Pattern.compile("^(?\\bdeadline\\b|\\bevent\\b|\\btodo\\b) (?.+)$", Pattern.CASE_INSENSITIVE); + private static final Pattern ADD_PATTERN = Pattern.compile("^(?\\bdeadline\\b|\\bevent\\b|\\btodo\\b)\\s?(?.*)$", Pattern.CASE_INSENSITIVE); private static final Pattern DONE_PATTERN = Pattern.compile("^\\b(?done)\\b (?.+)$", Pattern.CASE_INSENSITIVE); private static final Pattern LIST_PATTERN = Pattern.compile("^(?list)$", Pattern.CASE_INSENSITIVE); private static final Pattern EXIT_PATTERN = Pattern.compile("^(?bye)$", Pattern.CASE_INSENSITIVE); - public Command readInput(Scanner sc) throws InvalidCommandException { + public Command readInput(Scanner sc) throws InvalidCommandException, InvalidCommandFormatException { String input = sc.nextLine(); Matcher addMatcher = ADD_PATTERN.matcher(input); Matcher doneMatcher = DONE_PATTERN.matcher(input); diff --git a/src/Duke/exceptions/InvalidCommandFormatException.java b/src/Duke/exceptions/InvalidCommandFormatException.java index f22eb183..30c81884 100644 --- a/src/Duke/exceptions/InvalidCommandFormatException.java +++ b/src/Duke/exceptions/InvalidCommandFormatException.java @@ -1,4 +1,26 @@ package Duke.exceptions; public class InvalidCommandFormatException extends Throwable { -} + private static final String ERROR_PREFIX = "Invalid Command Format:\n"; + public static final String ERROR_DEADLINE = "Deadline should be in the format: deadline /by ."; + public static final String ERROR_EVENT = "Event should be in the format: event /at ."; + public static final String ERROR_TODO = "Todo should be in the format: todo ."; + + private String errorMessage; + + public InvalidCommandFormatException(String errorMessage) { + setErrorMessage(errorMessage); + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String toString() { + return ERROR_PREFIX + getErrorMessage(); + } +} \ No newline at end of file From f323cf678654be7db413c48a0442f7de66811b04 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Wed, 8 Sep 2021 15:08:25 +0800 Subject: [PATCH 13/59] Gradle setup --- .gitignore | 3 +++ build.gradle | 24 ++++++++++++++++++++++++ settings.gradle | 1 + 3 files changed, 28 insertions(+) create mode 100644 build.gradle create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore index f69985ef..6da7ffc6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,11 @@ # Gradle build files /.gradle/ +gradle/ /build/ src/main/resources/docs/ +gradlew +gradlew.bat # MacOS custom attributes files created by Finder .DS_Store diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..d250b79c --- /dev/null +++ b/build.gradle @@ -0,0 +1,24 @@ +plugins { + id 'java' +} + +group 'com.alexooi.duke' +version '0.1' + +repositories { + mavenCentral() +} +sourceSets { + Duke { + java { + srcDirs = ['src'] + } + } +} +test { + useJUnitPlatform() +} +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..4b97cac2 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'Duke-Alex' \ No newline at end of file From 9f42238d5b1ad5f5ad6b9b0fb93ab0c48a6ed2ff Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Wed, 8 Sep 2021 20:11:09 +0800 Subject: [PATCH 14/59] Modified package, add TaskTest --- build.gradle | 5 +- src/Duke/Duke.java | 68 --------------- src/Duke/classes/interfaces/Promptable.java | 11 --- src/Duke/classes/tasks/Deadline.java | 25 ------ src/Duke/classes/tasks/Event.java | 25 ------ src/Duke/classes/tasks/Task.java | 53 ------------ src/Duke/classes/tasks/TaskFactory.java | 46 ---------- src/Duke/classes/tasks/Todo.java | 14 ---- src/Duke/classes/ui/Command.java | 43 ---------- src/Duke/classes/ui/Parser.java | 41 --------- src/Duke/classes/ui/Prompt.java | 74 ----------------- src/Duke/enums/CommandType.java | 8 -- src/Duke/enums/TaskType.java | 24 ------ .../exceptions/InvalidCommandException.java | 4 - .../InvalidCommandFormatException.java | 26 ------ src/main/java/Duke.java | 65 +++++++++++++++ src/main/java/classes/enums/CommandType.java | 8 ++ src/main/java/classes/enums/TaskType.java | 26 ++++++ src/main/java/classes/tasks/Deadline.java | 26 ++++++ src/main/java/classes/tasks/Event.java | 25 ++++++ src/main/java/classes/tasks/Task.java | 53 ++++++++++++ src/main/java/classes/tasks/TaskFactory.java | 50 +++++++++++ src/main/java/classes/tasks/Todo.java | 14 ++++ src/main/java/classes/ui/Command.java | 43 ++++++++++ src/main/java/classes/ui/Parser.java | 48 +++++++++++ src/main/java/classes/ui/Prompt.java | 83 +++++++++++++++++++ .../exceptions/InvalidCommandException.java | 14 ++++ .../InvalidCommandFormatException.java | 25 ++++++ src/main/java/interfaces/Promptable.java | 19 +++++ src/test/java/classes/tasks/TaskTest.java | 12 +++ 30 files changed, 514 insertions(+), 464 deletions(-) delete mode 100644 src/Duke/Duke.java delete mode 100644 src/Duke/classes/interfaces/Promptable.java delete mode 100644 src/Duke/classes/tasks/Deadline.java delete mode 100644 src/Duke/classes/tasks/Event.java delete mode 100644 src/Duke/classes/tasks/Task.java delete mode 100644 src/Duke/classes/tasks/TaskFactory.java delete mode 100644 src/Duke/classes/tasks/Todo.java delete mode 100644 src/Duke/classes/ui/Command.java delete mode 100644 src/Duke/classes/ui/Parser.java delete mode 100644 src/Duke/classes/ui/Prompt.java delete mode 100644 src/Duke/enums/CommandType.java delete mode 100644 src/Duke/enums/TaskType.java delete mode 100644 src/Duke/exceptions/InvalidCommandException.java delete mode 100644 src/Duke/exceptions/InvalidCommandFormatException.java create mode 100644 src/main/java/Duke.java create mode 100644 src/main/java/classes/enums/CommandType.java create mode 100644 src/main/java/classes/enums/TaskType.java create mode 100644 src/main/java/classes/tasks/Deadline.java create mode 100644 src/main/java/classes/tasks/Event.java create mode 100644 src/main/java/classes/tasks/Task.java create mode 100644 src/main/java/classes/tasks/TaskFactory.java create mode 100644 src/main/java/classes/tasks/Todo.java create mode 100644 src/main/java/classes/ui/Command.java create mode 100644 src/main/java/classes/ui/Parser.java create mode 100644 src/main/java/classes/ui/Prompt.java create mode 100644 src/main/java/exceptions/InvalidCommandException.java create mode 100644 src/main/java/exceptions/InvalidCommandFormatException.java create mode 100644 src/main/java/interfaces/Promptable.java create mode 100644 src/test/java/classes/tasks/TaskTest.java diff --git a/build.gradle b/build.gradle index d250b79c..4b2d52a2 100644 --- a/build.gradle +++ b/build.gradle @@ -9,9 +9,9 @@ repositories { mavenCentral() } sourceSets { - Duke { + main { java { - srcDirs = ['src'] + srcDirs = ['src/main/java'] } } } @@ -19,6 +19,7 @@ test { useJUnitPlatform() } dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' } \ No newline at end of file diff --git a/src/Duke/Duke.java b/src/Duke/Duke.java deleted file mode 100644 index 6bf30c40..00000000 --- a/src/Duke/Duke.java +++ /dev/null @@ -1,68 +0,0 @@ -package Duke; - -import Duke.classes.tasks.*; -import Duke.classes.ui.Command; -import Duke.classes.ui.Parser; -import Duke.classes.ui.Prompt; -import Duke.exceptions.InvalidCommandException; -import Duke.exceptions.InvalidCommandFormatException; -import Duke.classes.interfaces.Promptable; - -import java.util.ArrayList; -import java.util.Scanner; - -public class Duke { - private final Promptable prompt; - private final Parser parser; - - public Duke(Promptable prompt, Parser parser) { - this.prompt = prompt; - this.parser = parser; - } - - public static void main(String[] args) { - Duke d = new Duke(new Prompt(), new Parser()); - System.out.println(d.prompt.start()); - - Scanner in = new Scanner(System.in); - ArrayList tasks = new ArrayList<>(); - boolean receiveInput = true; - - while (receiveInput && in.hasNext()) { - try { - Command command = d.parser.readInput(in); - String output = ""; - switch(command.getType()) { - case ADD: - Task newTask = TaskFactory.getInstance(command); - tasks.add(newTask); - output = d.prompt.add(newTask, tasks.size()); - break; - case COMPLETE: - int idx = Integer.parseInt(command.getArgs()); - Task doneTask = tasks.get(idx - 1); - doneTask.setDone(true); - output = d.prompt.done(doneTask); - break; - case LIST: - output = d.prompt.list(tasks); - break; - case EXIT: - receiveInput = false; - break; - } - System.out.println(output); - } catch (InvalidCommandException ice) { - System.out.println("Invalid Command Exception."); - } catch (NumberFormatException nfe) { - System.out.println("Number Format Exception."); - } catch (IndexOutOfBoundsException ioobe) { - System.out.println("IndexOutOfBoundsException."); - } catch (InvalidCommandFormatException icfe) { - System.out.println(icfe); - } - } - - System.out.println(d.prompt.exit()); - } -} diff --git a/src/Duke/classes/interfaces/Promptable.java b/src/Duke/classes/interfaces/Promptable.java deleted file mode 100644 index 7426618d..00000000 --- a/src/Duke/classes/interfaces/Promptable.java +++ /dev/null @@ -1,11 +0,0 @@ -package Duke.classes.interfaces; - -import Duke.classes.tasks.Task; - -public interface Promptable { - public String start(); - public String add(T printable, int length); - public String done(T printable); - public String list(Iterable inputs); - public String exit(); -} diff --git a/src/Duke/classes/tasks/Deadline.java b/src/Duke/classes/tasks/Deadline.java deleted file mode 100644 index e002b218..00000000 --- a/src/Duke/classes/tasks/Deadline.java +++ /dev/null @@ -1,25 +0,0 @@ -package Duke.classes.tasks; - -import Duke.enums.TaskType; - -public class Deadline extends Task { - private String dueDate; - - public Deadline(String description, String dueDate) { - super(TaskType.DEADLINE, description); - this.setDueDate(dueDate); - } - - public String getDueDate() { - return dueDate; - } - - public void setDueDate(String dueDate) { - this.dueDate = dueDate; - } - - @Override - public String toStatusString() { - return "[D]" + super.toStatusString() + " (by: " + dueDate + ")"; - } -} \ No newline at end of file diff --git a/src/Duke/classes/tasks/Event.java b/src/Duke/classes/tasks/Event.java deleted file mode 100644 index 80d19696..00000000 --- a/src/Duke/classes/tasks/Event.java +++ /dev/null @@ -1,25 +0,0 @@ -package Duke.classes.tasks; - -import Duke.enums.TaskType; - -public class Event extends Task { - private String timing; - - public Event(String description, String timing) { - super(TaskType.EVENT, description); - this.setTiming(timing); - } - - public String getTiming() { - return timing; - } - - public void setTiming(String timing) { - this.timing = timing; - } - - @Override - public String toStatusString() { - return "[E]" + super.toStatusString() + " (at: " + timing + ")"; - } -} diff --git a/src/Duke/classes/tasks/Task.java b/src/Duke/classes/tasks/Task.java deleted file mode 100644 index 0afcfcfb..00000000 --- a/src/Duke/classes/tasks/Task.java +++ /dev/null @@ -1,53 +0,0 @@ -package Duke.classes.tasks; - -import Duke.enums.TaskType; - -public class Task { - private String description; - private Boolean isDone; - private TaskType type; - - public Task() { - isDone = false; - } - - public Task(TaskType type, String description) { - this(); - setType(type); - setDescription(description); - } - - public String getDescription() { - return this.description; - } - - public void setDescription(String description) { - this.description = description; - } - - public Boolean getDone() { - return isDone; - } - - public void setDone(Boolean done) { - isDone = done; - } - - public TaskType getType() { - return type; - } - - public void setType(TaskType type) { - this.type = type; - } - - public String toStatusString() { - String doneStr = (isDone) ? "X" : " "; - return "[" + doneStr + "] " + toString(); - } - - @Override - public String toString() { - return getDescription(); - } -} diff --git a/src/Duke/classes/tasks/TaskFactory.java b/src/Duke/classes/tasks/TaskFactory.java deleted file mode 100644 index 4b50cf07..00000000 --- a/src/Duke/classes/tasks/TaskFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -package Duke.classes.tasks; - -import Duke.classes.ui.Command; -import Duke.enums.TaskType; -import Duke.exceptions.InvalidCommandFormatException; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class TaskFactory { - private static final String KEY_DESCRIPTION = "description"; - private static final String KEY_DATE = "date"; - private static final String DEADLINE_PATTERN = "^(?<" + KEY_DESCRIPTION + ">.+) /by (?<" + KEY_DATE + ">.+)$"; - private static final String EVENT_PATTERN = "^(?<" + KEY_DESCRIPTION + ">.+) /at (?<" + KEY_DATE + ">.+)$"; - - public static Task getInstance(Command cmd) throws InvalidCommandFormatException { - String keyword = cmd.getKeyword(); - String args = cmd.getArgs(); - if (keyword.equalsIgnoreCase(TaskType.DEADLINE.toString())) { - Matcher deadlineMatch = Pattern.compile(DEADLINE_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); - if (deadlineMatch.matches()) { - String description = deadlineMatch.group(KEY_DESCRIPTION); - String dueDate = deadlineMatch.group(KEY_DATE); - return new Deadline(description, dueDate); - } else { - throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); - } - } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { - Matcher eventMatch = Pattern.compile(EVENT_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); - if (eventMatch.matches()) { - String description = eventMatch.group(KEY_DESCRIPTION); - String timing = eventMatch.group(KEY_DATE); - return new Event(description, timing); - } else { - throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_EVENT); - } - } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { - if (args.length() > 0) { - return new Todo(args); - } else { - throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_TODO); - } - } - throw new InvalidCommandFormatException("Invalid Command Format:\n Cannot find the appropriate task to create."); - } -} diff --git a/src/Duke/classes/tasks/Todo.java b/src/Duke/classes/tasks/Todo.java deleted file mode 100644 index 5feff553..00000000 --- a/src/Duke/classes/tasks/Todo.java +++ /dev/null @@ -1,14 +0,0 @@ -package Duke.classes.tasks; - -import Duke.enums.TaskType; - -public class Todo extends Task { - public Todo(String description) { - super(TaskType.TODO, description); - } - - @Override - public String toStatusString() { - return "[T]" + super.toStatusString(); - } -} diff --git a/src/Duke/classes/ui/Command.java b/src/Duke/classes/ui/Command.java deleted file mode 100644 index dd7ee8bf..00000000 --- a/src/Duke/classes/ui/Command.java +++ /dev/null @@ -1,43 +0,0 @@ -package Duke.classes.ui; - -import Duke.enums.CommandType; - -public class Command { - private CommandType type; - private String keyword; - private String args; - - public Command(CommandType type, String keyword) { - this.setKeyword(keyword); - this.setType(type); - } - - public Command(CommandType type, String keyword, String args) { - this(type, keyword); - this.setArgs(args); - } - - public CommandType getType() { - return type; - } - - public void setType(CommandType type) { - this.type = type; - } - - public String getKeyword() { - return keyword; - } - - public void setKeyword(String keyword) { - this.keyword = keyword; - } - - public String getArgs() { - return args; - } - - public void setArgs(String args) { - this.args = args; - } -} diff --git a/src/Duke/classes/ui/Parser.java b/src/Duke/classes/ui/Parser.java deleted file mode 100644 index fb24db06..00000000 --- a/src/Duke/classes/ui/Parser.java +++ /dev/null @@ -1,41 +0,0 @@ -package Duke.classes.ui; - -import java.util.Scanner; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import Duke.enums.CommandType; -import Duke.exceptions.InvalidCommandException; -import Duke.exceptions.InvalidCommandFormatException; - -public class Parser { - private static final Pattern ADD_PATTERN = Pattern.compile("^(?\\bdeadline\\b|\\bevent\\b|\\btodo\\b)\\s?(?.*)$", Pattern.CASE_INSENSITIVE); - private static final Pattern DONE_PATTERN = Pattern.compile("^\\b(?done)\\b (?.+)$", Pattern.CASE_INSENSITIVE); - private static final Pattern LIST_PATTERN = Pattern.compile("^(?list)$", Pattern.CASE_INSENSITIVE); - private static final Pattern EXIT_PATTERN = Pattern.compile("^(?bye)$", Pattern.CASE_INSENSITIVE); - - public Command readInput(Scanner sc) throws InvalidCommandException, InvalidCommandFormatException { - String input = sc.nextLine(); - Matcher addMatcher = ADD_PATTERN.matcher(input); - Matcher doneMatcher = DONE_PATTERN.matcher(input); - Matcher listMatcher = LIST_PATTERN.matcher(input); - Matcher exitMatcher = EXIT_PATTERN.matcher(input); - - if (addMatcher.matches()) { - String args = addMatcher.group("args"); - String keyword = addMatcher.group("keyword"); - return new Command(CommandType.ADD, keyword, args); - } else if (doneMatcher.matches()) { - String args = doneMatcher.group("args"); - String keyword = doneMatcher.group("keyword"); - return new Command(CommandType.COMPLETE, keyword, args); - } else if (listMatcher.matches()) { - String keyword = listMatcher.group("keyword"); - return new Command(CommandType.LIST, keyword); - } else if (exitMatcher.matches()) { - String keyword = exitMatcher.group("keyword"); - return new Command(CommandType.EXIT, keyword); - } - throw new InvalidCommandException(); - } -} diff --git a/src/Duke/classes/ui/Prompt.java b/src/Duke/classes/ui/Prompt.java deleted file mode 100644 index 34fc5be9..00000000 --- a/src/Duke/classes/ui/Prompt.java +++ /dev/null @@ -1,74 +0,0 @@ -package Duke.classes.ui; - -import Duke.enums.TaskType; -import Duke.classes.tasks.Deadline; -import Duke.classes.tasks.Event; -import Duke.classes.tasks.Task; -import Duke.classes.tasks.Todo; -import Duke.classes.interfaces.Promptable; - -public class Prompt implements Promptable { - private static final String PADDING = " "; - private static final String HORIZONTAL_LINE = "_________________________________________"; - private static final String START = "Hello! I'm Duke\n What can I do for you?"; - private static final String END = "Bye. Hope to see you again soon!"; - private static final String DONE = "Nice! I've marked this task as done:"; - private static final String ADD = "added: "; - - @Override - public String start() { - return formatOutput(formatLine(START)); - } - - @Override - public String add(Task printable, int length) { - StringBuilder output = new StringBuilder(); - output.append(formatLine(ADD + printable.toString())); - output.append(formatLine("Now you have " + length + " task" + ((length > 1) ? "s" : "") + " in the list.")); - - return formatOutput(output.toString()); - } - - @Override - public String done(Task printable) { - StringBuilder output = new StringBuilder(); - output.append(formatLine(DONE) + formatLine(" " + printable.toStatusString())); - - return formatOutput(output.toString()); - } - - @Override - public String list(Iterable inputs) { - StringBuilder output = new StringBuilder(); - int count = 1; - for (Task input : inputs) { - String taskStr = ""; - if (input.getType() == TaskType.DEADLINE) { - Deadline down = (Deadline) input; - taskStr = down.toStatusString(); - } else if (input.getType() == TaskType.EVENT) { - Event down = (Event) input; - taskStr = down.toStatusString(); - } else if (input.getType() == TaskType.TODO) { - Todo down = (Todo) input; - taskStr = down.toStatusString(); - } - output.append(formatLine(count + ". " + taskStr)); - count++; - } - return formatOutput(output.toString()); - } - - @Override - public String exit() { - return formatOutput(formatLine(END)); - } - - private String formatLine(String output) { - return PADDING + output + System.lineSeparator(); - } - - private String formatOutput(String output) { - return formatLine(HORIZONTAL_LINE) + output + PADDING + HORIZONTAL_LINE; - } -} \ No newline at end of file diff --git a/src/Duke/enums/CommandType.java b/src/Duke/enums/CommandType.java deleted file mode 100644 index 5c91476b..00000000 --- a/src/Duke/enums/CommandType.java +++ /dev/null @@ -1,8 +0,0 @@ -package Duke.enums; - -public enum CommandType { - ADD, - COMPLETE, - LIST, - EXIT -} diff --git a/src/Duke/enums/TaskType.java b/src/Duke/enums/TaskType.java deleted file mode 100644 index 44788705..00000000 --- a/src/Duke/enums/TaskType.java +++ /dev/null @@ -1,24 +0,0 @@ -package Duke.enums; - -public enum TaskType { - TODO("todo"), - EVENT("event"), - DEADLINE("deadline"); - - private String type; - TaskType(String type) { - setType(type); - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String toString() { - return getType(); - } -} diff --git a/src/Duke/exceptions/InvalidCommandException.java b/src/Duke/exceptions/InvalidCommandException.java deleted file mode 100644 index 866d4064..00000000 --- a/src/Duke/exceptions/InvalidCommandException.java +++ /dev/null @@ -1,4 +0,0 @@ -package Duke.exceptions; - -public class InvalidCommandException extends Throwable { -} \ No newline at end of file diff --git a/src/Duke/exceptions/InvalidCommandFormatException.java b/src/Duke/exceptions/InvalidCommandFormatException.java deleted file mode 100644 index 30c81884..00000000 --- a/src/Duke/exceptions/InvalidCommandFormatException.java +++ /dev/null @@ -1,26 +0,0 @@ -package Duke.exceptions; - -public class InvalidCommandFormatException extends Throwable { - private static final String ERROR_PREFIX = "Invalid Command Format:\n"; - public static final String ERROR_DEADLINE = "Deadline should be in the format: deadline /by ."; - public static final String ERROR_EVENT = "Event should be in the format: event /at ."; - public static final String ERROR_TODO = "Todo should be in the format: todo ."; - - private String errorMessage; - - public InvalidCommandFormatException(String errorMessage) { - setErrorMessage(errorMessage); - } - - public String getErrorMessage() { - return errorMessage; - } - - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } - - public String toString() { - return ERROR_PREFIX + getErrorMessage(); - } -} \ No newline at end of file diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java new file mode 100644 index 00000000..19edc2b6 --- /dev/null +++ b/src/main/java/Duke.java @@ -0,0 +1,65 @@ +import classes.tasks.Task; +import classes.tasks.TaskFactory; +import interfaces.Promptable; +import classes.ui.Command; +import classes.ui.Parser; +import classes.ui.Prompt; +import exceptions.InvalidCommandException; +import exceptions.InvalidCommandFormatException; + +import java.util.ArrayList; +import java.util.Scanner; + +public class Duke { + private final Promptable prompt; + private final Parser parser; + + public Duke(Promptable prompt, Parser parser) { + this.prompt = prompt; + this.parser = parser; + } + + public static void main(String[] args) { + Duke d = new Duke(new Prompt(), new Parser()); + System.out.println(d.prompt.start()); + + Scanner in = new Scanner(System.in); + ArrayList tasks = new ArrayList<>(); + boolean receiveInput = true; + + while (receiveInput && in.hasNext()) { + try { + Command command = d.parser.readInput(in); + String output = ""; + switch (command.getType()) { + case ADD: + Task newTask = TaskFactory.getInstance(command); + tasks.add(newTask); + output = d.prompt.add(newTask, tasks.size()); + break; + case COMPLETE: + int idx = Integer.parseInt(command.getArgs()); + Task doneTask = tasks.get(idx - 1); + doneTask.setDone(true); + output = d.prompt.done(doneTask); + break; + case LIST: + output = d.prompt.list(tasks); + break; + case EXIT: + receiveInput = false; + break; + } + System.out.println(output); + } catch (InvalidCommandException ice) { + System.out.println(d.prompt.error(ice.getErrorHeader(), ice.getMessage())); + } catch (InvalidCommandFormatException icfe) { + System.out.println(d.prompt.error(icfe.getErrorHeader(), icfe.getMessage())); + } catch (NumberFormatException | IndexOutOfBoundsException ex) { + System.out.println(d.prompt.error(ex.getMessage())); + } + } + + System.out.println(d.prompt.exit()); + } +} diff --git a/src/main/java/classes/enums/CommandType.java b/src/main/java/classes/enums/CommandType.java new file mode 100644 index 00000000..090e5aa2 --- /dev/null +++ b/src/main/java/classes/enums/CommandType.java @@ -0,0 +1,8 @@ +package classes.enums; + +public enum CommandType { + ADD, + COMPLETE, + LIST, + EXIT +} diff --git a/src/main/java/classes/enums/TaskType.java b/src/main/java/classes/enums/TaskType.java new file mode 100644 index 00000000..7c76ce7f --- /dev/null +++ b/src/main/java/classes/enums/TaskType.java @@ -0,0 +1,26 @@ +package classes.enums; + +public enum TaskType { + TODO("todo"), + EVENT("event"), + DEADLINE("deadline"), + TASK("task"); + + private String type; + + TaskType(String type) { + setType(type); + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String toString() { + return getType(); + } +} diff --git a/src/main/java/classes/tasks/Deadline.java b/src/main/java/classes/tasks/Deadline.java new file mode 100644 index 00000000..2b4b60b6 --- /dev/null +++ b/src/main/java/classes/tasks/Deadline.java @@ -0,0 +1,26 @@ +package classes.tasks; + +import classes.enums.TaskType; +import classes.tasks.Task; + +public class Deadline extends Task { + private String dueDate; + + public Deadline(String description, String dueDate) { + super(TaskType.DEADLINE, description); + this.setDueDate(dueDate); + } + + public String getDueDate() { + return dueDate; + } + + public void setDueDate(String dueDate) { + this.dueDate = dueDate; + } + + @Override + public String toStatusString() { + return "[D]" + super.toStatusString() + " (by: " + dueDate + ")"; + } +} diff --git a/src/main/java/classes/tasks/Event.java b/src/main/java/classes/tasks/Event.java new file mode 100644 index 00000000..7cfc0313 --- /dev/null +++ b/src/main/java/classes/tasks/Event.java @@ -0,0 +1,25 @@ +package classes.tasks; + +import classes.enums.TaskType; + +public class Event extends Task { + private String timing; + + public Event(String description, String timing) { + super(TaskType.EVENT, description); + this.setTiming(timing); + } + + public String getTiming() { + return timing; + } + + public void setTiming(String timing) { + this.timing = timing; + } + + @Override + public String toStatusString() { + return "[E]" + super.toStatusString() + " (at: " + timing + ")"; + } +} diff --git a/src/main/java/classes/tasks/Task.java b/src/main/java/classes/tasks/Task.java new file mode 100644 index 00000000..92331f37 --- /dev/null +++ b/src/main/java/classes/tasks/Task.java @@ -0,0 +1,53 @@ +package classes.tasks; + +import classes.enums.TaskType; + +public class Task { + private String description; + private Boolean isDone; + private TaskType type; + + public Task() { + isDone = false; + } + + public Task(TaskType type, String description) { + this(); + setType(type); + setDescription(description); + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Boolean getDone() { + return isDone; + } + + public void setDone(Boolean done) { + isDone = done; + } + + public TaskType getType() { + return type; + } + + public void setType(TaskType type) { + this.type = type; + } + + public String toStatusString() { + String doneStr = (isDone) ? "X" : " "; + return "[" + doneStr + "] " + this; + } + + @Override + public String toString() { + return getDescription(); + } +} diff --git a/src/main/java/classes/tasks/TaskFactory.java b/src/main/java/classes/tasks/TaskFactory.java new file mode 100644 index 00000000..bf8c3b27 --- /dev/null +++ b/src/main/java/classes/tasks/TaskFactory.java @@ -0,0 +1,50 @@ +package classes.tasks; + +import classes.ui.Command; +import classes.enums.TaskType; +import exceptions.InvalidCommandFormatException; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TaskFactory { + private static final String KEY_DESCRIPTION = "description"; + private static final String KEY_DATE = "date"; + private static final String DEADLINE_PATTERN = + "^(?<" + KEY_DESCRIPTION + ">.+) /by (?<" + KEY_DATE + ">.+)$"; + private static final String EVENT_PATTERN = + "^(?<" + KEY_DESCRIPTION + ">.+) /at (?<" + KEY_DATE + ">.+)$"; + + public static Task getInstance(Command cmd) throws InvalidCommandFormatException { + String keyword = cmd.getKeyword(); + String args = cmd.getArgs(); + if (keyword.equalsIgnoreCase(TaskType.DEADLINE.toString())) { + Matcher deadlineMatch = + Pattern.compile(DEADLINE_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); + if (deadlineMatch.matches()) { + String description = deadlineMatch.group(KEY_DESCRIPTION); + String dueDate = deadlineMatch.group(KEY_DATE); + return new Deadline(description, dueDate); + } else { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); + } + } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { + Matcher eventMatch = Pattern.compile(EVENT_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); + if (eventMatch.matches()) { + String description = eventMatch.group(KEY_DESCRIPTION); + String timing = eventMatch.group(KEY_DATE); + return new Event(description, timing); + } else { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_EVENT); + } + } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { + if (args.length() > 0) { + return new Todo(args); + } else { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_TODO); + } + } + throw new InvalidCommandFormatException( + "Invalid Command Format:\n Cannot find the appropriate task to create."); + } +} diff --git a/src/main/java/classes/tasks/Todo.java b/src/main/java/classes/tasks/Todo.java new file mode 100644 index 00000000..37b1739a --- /dev/null +++ b/src/main/java/classes/tasks/Todo.java @@ -0,0 +1,14 @@ +package classes.tasks; + +import classes.enums.TaskType; + +public class Todo extends Task { + public Todo(String description) { + super(TaskType.TODO, description); + } + + @Override + public String toStatusString() { + return "[T]" + super.toStatusString(); + } +} diff --git a/src/main/java/classes/ui/Command.java b/src/main/java/classes/ui/Command.java new file mode 100644 index 00000000..f663c52e --- /dev/null +++ b/src/main/java/classes/ui/Command.java @@ -0,0 +1,43 @@ +package classes.ui; + +import classes.enums.CommandType; + +public class Command { + private CommandType type; + private String keyword; + private String args; + + public Command(CommandType type, String keyword) { + this.setKeyword(keyword); + this.setType(type); + } + + public Command(CommandType type, String keyword, String args) { + this(type, keyword); + this.setArgs(args); + } + + public CommandType getType() { + return type; + } + + public void setType(CommandType type) { + this.type = type; + } + + public String getKeyword() { + return keyword; + } + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + + public String getArgs() { + return args; + } + + public void setArgs(String args) { + this.args = args; + } +} diff --git a/src/main/java/classes/ui/Parser.java b/src/main/java/classes/ui/Parser.java new file mode 100644 index 00000000..2631ee6d --- /dev/null +++ b/src/main/java/classes/ui/Parser.java @@ -0,0 +1,48 @@ +package classes.ui; + +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import classes.enums.CommandType; +import exceptions.InvalidCommandException; +import exceptions.InvalidCommandFormatException; + +public class Parser { + private static final Pattern ADD_PATTERN = + Pattern.compile( + "^(?\\bdeadline\\b|\\bevent\\b|\\btodo\\b)\\s?(?.*)$", + Pattern.CASE_INSENSITIVE); + private static final Pattern DONE_PATTERN = + Pattern.compile("^\\b(?done)\\b (?.+)$", Pattern.CASE_INSENSITIVE); + private static final Pattern LIST_PATTERN = + Pattern.compile("^(?list)$", Pattern.CASE_INSENSITIVE); + private static final Pattern EXIT_PATTERN = + Pattern.compile("^(?bye)$", Pattern.CASE_INSENSITIVE); + + public Command readInput(Scanner sc) + throws InvalidCommandException, InvalidCommandFormatException { + String input = sc.nextLine(); + Matcher addMatcher = ADD_PATTERN.matcher(input); + Matcher doneMatcher = DONE_PATTERN.matcher(input); + Matcher listMatcher = LIST_PATTERN.matcher(input); + Matcher exitMatcher = EXIT_PATTERN.matcher(input); + + if (addMatcher.matches()) { + String args = addMatcher.group("args"); + String keyword = addMatcher.group("keyword"); + return new Command(CommandType.ADD, keyword, args); + } else if (doneMatcher.matches()) { + String args = doneMatcher.group("args"); + String keyword = doneMatcher.group("keyword"); + return new Command(CommandType.COMPLETE, keyword, args); + } else if (listMatcher.matches()) { + String keyword = listMatcher.group("keyword"); + return new Command(CommandType.LIST, keyword); + } else if (exitMatcher.matches()) { + String keyword = exitMatcher.group("keyword"); + return new Command(CommandType.EXIT, keyword); + } + throw new InvalidCommandException(InvalidCommandException.ERROR_GENERIC); + } +} diff --git a/src/main/java/classes/ui/Prompt.java b/src/main/java/classes/ui/Prompt.java new file mode 100644 index 00000000..1ed89c53 --- /dev/null +++ b/src/main/java/classes/ui/Prompt.java @@ -0,0 +1,83 @@ +package classes.ui; + +import classes.enums.TaskType; +import classes.tasks.Deadline; +import classes.tasks.Event; +import classes.tasks.Task; +import classes.tasks.Todo; +import interfaces.Promptable; + +public class Prompt implements Promptable { + private static final String PADDING = " "; + private static final String HORIZONTAL_LINE = "_________________________________________"; + private static final String START = "Hello! I'm Duke\n What can I do for you?"; + private static final String END = "Bye. Hope to see you again soon!"; + private static final String DONE = "Nice! I've marked this task as done:"; + private static final String ADD = "added: "; + + @Override + public String start() { + return formatOutput(formatLine(START)); + } + + @Override + public String add(Task printable, int length) { + String output = + formatLine(ADD + printable.toString()) + + formatLine( + "Now you have " + length + " task" + ((length > 1) ? "s" : "") + " in the list."); + + return formatOutput(output); + } + + @Override + public String done(Task printable) { + return formatOutput(formatLine(DONE) + formatLine(" " + printable.toStatusString())); + } + + @Override + public String error(String header, String errorMessage) { + String output = formatLine(header) + formatLine(errorMessage); + return formatOutput(output); + } + + @Override + public String error(String errorMessage) { + return formatOutput(formatLine(errorMessage)); + } + + @Override + public String list(Iterable inputs) { + StringBuilder output = new StringBuilder(); + int count = 1; + for (Task input : inputs) { + String taskStr = ""; + if (input.getType() == TaskType.DEADLINE) { + Deadline down = (Deadline) input; + taskStr = down.toStatusString(); + } else if (input.getType() == TaskType.EVENT) { + Event down = (Event) input; + taskStr = down.toStatusString(); + } else if (input.getType() == TaskType.TODO) { + Todo down = (Todo) input; + taskStr = down.toStatusString(); + } + output.append(formatLine(count + ". " + taskStr)); + count++; + } + return formatOutput(output.toString()); + } + + @Override + public String exit() { + return formatOutput(formatLine(END)); + } + + private String formatLine(String output) { + return PADDING + output + System.lineSeparator(); + } + + private String formatOutput(String output) { + return formatLine(HORIZONTAL_LINE) + output + PADDING + HORIZONTAL_LINE; + } +} diff --git a/src/main/java/exceptions/InvalidCommandException.java b/src/main/java/exceptions/InvalidCommandException.java new file mode 100644 index 00000000..8aa7af04 --- /dev/null +++ b/src/main/java/exceptions/InvalidCommandException.java @@ -0,0 +1,14 @@ +package exceptions; + +public class InvalidCommandException extends Throwable { + private static final String ERROR_PREFIX = "Invalid Command:"; + public static final String ERROR_GENERIC = "I don't know what that command means!"; + + public InvalidCommandException(String errorMessage) { + super(errorMessage); + } + + public String getErrorHeader() { + return ERROR_PREFIX; + } +} diff --git a/src/main/java/exceptions/InvalidCommandFormatException.java b/src/main/java/exceptions/InvalidCommandFormatException.java new file mode 100644 index 00000000..3fd969d1 --- /dev/null +++ b/src/main/java/exceptions/InvalidCommandFormatException.java @@ -0,0 +1,25 @@ +package exceptions; + +public class InvalidCommandFormatException extends Throwable { + private static final String ERROR_PREFIX = "Invalid Command Format:"; + public static final String ERROR_DEADLINE = + "classes.tasks.Deadline should be in the format: deadline /by ."; + public static final String ERROR_EVENT = + "classes.tasks.Event should be in the format: event /at ."; + public static final String ERROR_TODO = + "classes.tasks.Todo should be in the format: todo ."; + + private String errorMessage; + + public InvalidCommandFormatException(String errorMessage) { + super(errorMessage); + } + + public String getErrorHeader() { + return ERROR_PREFIX; + } + + public String toString() { + return getMessage(); + } +} diff --git a/src/main/java/interfaces/Promptable.java b/src/main/java/interfaces/Promptable.java new file mode 100644 index 00000000..86407878 --- /dev/null +++ b/src/main/java/interfaces/Promptable.java @@ -0,0 +1,19 @@ +package interfaces; + +import classes.tasks.Task; + +public interface Promptable { + String start(); + + String add(T printable, int length); + + String done(T printable); + + String error(String header, String errorMessage); + + String error(String errorMessage); + + String list(Iterable inputs); + + String exit(); +} diff --git a/src/test/java/classes/tasks/TaskTest.java b/src/test/java/classes/tasks/TaskTest.java new file mode 100644 index 00000000..db25314b --- /dev/null +++ b/src/test/java/classes/tasks/TaskTest.java @@ -0,0 +1,12 @@ +package classes.tasks; + +import classes.enums.TaskType; +import org.junit.jupiter.api.Test; + +public class TaskTest { + @Test + void testGetDescription() { + Task task = new Task(TaskType.TASK, "read book"); + assert task.getDescription().equalsIgnoreCase("read book"); + } +} From 9e55263ed41e24feb954bcecb3dac7c585a6772a Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Wed, 8 Sep 2021 20:22:29 +0800 Subject: [PATCH 15/59] Fix indentation --- src/main/java/Duke.java | 93 +++++++------ src/main/java/classes/enums/CommandType.java | 8 +- src/main/java/classes/enums/TaskType.java | 34 ++--- src/main/java/classes/tasks/Deadline.java | 31 +++-- src/main/java/classes/tasks/Event.java | 30 ++--- src/main/java/classes/tasks/Task.java | 94 +++++++------- src/main/java/classes/tasks/TaskFactory.java | 74 +++++------ src/main/java/classes/tasks/Todo.java | 14 +- src/main/java/classes/ui/Command.java | 74 +++++------ src/main/java/classes/ui/Parser.java | 66 +++++----- src/main/java/classes/ui/Prompt.java | 122 +++++++++--------- .../exceptions/InvalidCommandException.java | 16 +-- .../InvalidCommandFormatException.java | 34 ++--- src/main/java/interfaces/Promptable.java | 14 +- src/test/java/classes/tasks/TaskTest.java | 10 +- 15 files changed, 356 insertions(+), 358 deletions(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 19edc2b6..57c04713 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,65 +1,64 @@ import classes.tasks.Task; import classes.tasks.TaskFactory; -import interfaces.Promptable; import classes.ui.Command; import classes.ui.Parser; import classes.ui.Prompt; import exceptions.InvalidCommandException; import exceptions.InvalidCommandFormatException; - +import interfaces.Promptable; import java.util.ArrayList; import java.util.Scanner; public class Duke { - private final Promptable prompt; - private final Parser parser; + private final Promptable prompt; + private final Parser parser; - public Duke(Promptable prompt, Parser parser) { - this.prompt = prompt; - this.parser = parser; - } + public Duke(Promptable prompt, Parser parser) { + this.prompt = prompt; + this.parser = parser; + } - public static void main(String[] args) { - Duke d = new Duke(new Prompt(), new Parser()); - System.out.println(d.prompt.start()); + public static void main(String[] args) { + Duke d = new Duke(new Prompt(), new Parser()); + System.out.println(d.prompt.start()); - Scanner in = new Scanner(System.in); - ArrayList tasks = new ArrayList<>(); - boolean receiveInput = true; + Scanner in = new Scanner(System.in); + ArrayList tasks = new ArrayList<>(); + boolean receiveInput = true; - while (receiveInput && in.hasNext()) { - try { - Command command = d.parser.readInput(in); - String output = ""; - switch (command.getType()) { - case ADD: - Task newTask = TaskFactory.getInstance(command); - tasks.add(newTask); - output = d.prompt.add(newTask, tasks.size()); - break; - case COMPLETE: - int idx = Integer.parseInt(command.getArgs()); - Task doneTask = tasks.get(idx - 1); - doneTask.setDone(true); - output = d.prompt.done(doneTask); - break; - case LIST: - output = d.prompt.list(tasks); - break; - case EXIT: - receiveInput = false; - break; + while (receiveInput && in.hasNext()) { + try { + Command command = d.parser.readInput(in); + String output = ""; + switch (command.getType()) { + case ADD: + Task newTask = TaskFactory.getInstance(command); + tasks.add(newTask); + output = d.prompt.add(newTask, tasks.size()); + break; + case COMPLETE: + int idx = Integer.parseInt(command.getArgs()); + Task doneTask = tasks.get(idx - 1); + doneTask.setDone(true); + output = d.prompt.done(doneTask); + break; + case LIST: + output = d.prompt.list(tasks); + break; + case EXIT: + receiveInput = false; + break; + } + System.out.println(output); + } catch (InvalidCommandException ice) { + System.out.println(d.prompt.error(ice.getErrorHeader(), ice.getMessage())); + } catch (InvalidCommandFormatException icfe) { + System.out.println(d.prompt.error(icfe.getErrorHeader(), icfe.getMessage())); + } catch (NumberFormatException | IndexOutOfBoundsException ex) { + System.out.println(d.prompt.error(ex.getMessage())); + } } - System.out.println(output); - } catch (InvalidCommandException ice) { - System.out.println(d.prompt.error(ice.getErrorHeader(), ice.getMessage())); - } catch (InvalidCommandFormatException icfe) { - System.out.println(d.prompt.error(icfe.getErrorHeader(), icfe.getMessage())); - } catch (NumberFormatException | IndexOutOfBoundsException ex) { - System.out.println(d.prompt.error(ex.getMessage())); - } - } - System.out.println(d.prompt.exit()); - } + System.out.println(d.prompt.exit()); + } } diff --git a/src/main/java/classes/enums/CommandType.java b/src/main/java/classes/enums/CommandType.java index 090e5aa2..51d0ab94 100644 --- a/src/main/java/classes/enums/CommandType.java +++ b/src/main/java/classes/enums/CommandType.java @@ -1,8 +1,8 @@ package classes.enums; public enum CommandType { - ADD, - COMPLETE, - LIST, - EXIT + ADD, + COMPLETE, + LIST, + EXIT } diff --git a/src/main/java/classes/enums/TaskType.java b/src/main/java/classes/enums/TaskType.java index 7c76ce7f..fcecfd11 100644 --- a/src/main/java/classes/enums/TaskType.java +++ b/src/main/java/classes/enums/TaskType.java @@ -1,26 +1,26 @@ package classes.enums; public enum TaskType { - TODO("todo"), - EVENT("event"), - DEADLINE("deadline"), - TASK("task"); + TODO("todo"), + EVENT("event"), + DEADLINE("deadline"), + TASK("task"); - private String type; + private String type; - TaskType(String type) { - setType(type); - } + TaskType(String type) { + setType(type); + } - public String getType() { - return type; - } + public String getType() { + return type; + } - public void setType(String type) { - this.type = type; - } + public void setType(String type) { + this.type = type; + } - public String toString() { - return getType(); - } + public String toString() { + return getType(); + } } diff --git a/src/main/java/classes/tasks/Deadline.java b/src/main/java/classes/tasks/Deadline.java index 2b4b60b6..64b14060 100644 --- a/src/main/java/classes/tasks/Deadline.java +++ b/src/main/java/classes/tasks/Deadline.java @@ -1,26 +1,25 @@ package classes.tasks; import classes.enums.TaskType; -import classes.tasks.Task; public class Deadline extends Task { - private String dueDate; + private String dueDate; - public Deadline(String description, String dueDate) { - super(TaskType.DEADLINE, description); - this.setDueDate(dueDate); - } + public Deadline(String description, String dueDate) { + super(TaskType.DEADLINE, description); + this.setDueDate(dueDate); + } - public String getDueDate() { - return dueDate; - } + public String getDueDate() { + return dueDate; + } - public void setDueDate(String dueDate) { - this.dueDate = dueDate; - } + public void setDueDate(String dueDate) { + this.dueDate = dueDate; + } - @Override - public String toStatusString() { - return "[D]" + super.toStatusString() + " (by: " + dueDate + ")"; - } + @Override + public String toStatusString() { + return "[D]" + super.toStatusString() + " (by: " + dueDate + ")"; + } } diff --git a/src/main/java/classes/tasks/Event.java b/src/main/java/classes/tasks/Event.java index 7cfc0313..b9eddfe8 100644 --- a/src/main/java/classes/tasks/Event.java +++ b/src/main/java/classes/tasks/Event.java @@ -3,23 +3,23 @@ import classes.enums.TaskType; public class Event extends Task { - private String timing; + private String timing; - public Event(String description, String timing) { - super(TaskType.EVENT, description); - this.setTiming(timing); - } + public Event(String description, String timing) { + super(TaskType.EVENT, description); + this.setTiming(timing); + } - public String getTiming() { - return timing; - } + public String getTiming() { + return timing; + } - public void setTiming(String timing) { - this.timing = timing; - } + public void setTiming(String timing) { + this.timing = timing; + } - @Override - public String toStatusString() { - return "[E]" + super.toStatusString() + " (at: " + timing + ")"; - } + @Override + public String toStatusString() { + return "[E]" + super.toStatusString() + " (at: " + timing + ")"; + } } diff --git a/src/main/java/classes/tasks/Task.java b/src/main/java/classes/tasks/Task.java index 92331f37..6077016a 100644 --- a/src/main/java/classes/tasks/Task.java +++ b/src/main/java/classes/tasks/Task.java @@ -3,51 +3,51 @@ import classes.enums.TaskType; public class Task { - private String description; - private Boolean isDone; - private TaskType type; - - public Task() { - isDone = false; - } - - public Task(TaskType type, String description) { - this(); - setType(type); - setDescription(description); - } - - public String getDescription() { - return this.description; - } - - public void setDescription(String description) { - this.description = description; - } - - public Boolean getDone() { - return isDone; - } - - public void setDone(Boolean done) { - isDone = done; - } - - public TaskType getType() { - return type; - } - - public void setType(TaskType type) { - this.type = type; - } - - public String toStatusString() { - String doneStr = (isDone) ? "X" : " "; - return "[" + doneStr + "] " + this; - } - - @Override - public String toString() { - return getDescription(); - } + private String description; + private Boolean isDone; + private TaskType type; + + public Task() { + isDone = false; + } + + public Task(TaskType type, String description) { + this(); + setType(type); + setDescription(description); + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Boolean getDone() { + return isDone; + } + + public void setDone(Boolean done) { + isDone = done; + } + + public TaskType getType() { + return type; + } + + public void setType(TaskType type) { + this.type = type; + } + + public String toStatusString() { + String doneStr = (isDone) ? "X" : " "; + return "[" + doneStr + "] " + this; + } + + @Override + public String toString() { + return getDescription(); + } } diff --git a/src/main/java/classes/tasks/TaskFactory.java b/src/main/java/classes/tasks/TaskFactory.java index bf8c3b27..044ea3ec 100644 --- a/src/main/java/classes/tasks/TaskFactory.java +++ b/src/main/java/classes/tasks/TaskFactory.java @@ -8,43 +8,43 @@ import java.util.regex.Pattern; public class TaskFactory { - private static final String KEY_DESCRIPTION = "description"; - private static final String KEY_DATE = "date"; - private static final String DEADLINE_PATTERN = - "^(?<" + KEY_DESCRIPTION + ">.+) /by (?<" + KEY_DATE + ">.+)$"; - private static final String EVENT_PATTERN = - "^(?<" + KEY_DESCRIPTION + ">.+) /at (?<" + KEY_DATE + ">.+)$"; + private static final String KEY_DESCRIPTION = "description"; + private static final String KEY_DATE = "date"; + private static final String DEADLINE_PATTERN = + "^(?<" + KEY_DESCRIPTION + ">.+) /by (?<" + KEY_DATE + ">.+)$"; + private static final String EVENT_PATTERN = + "^(?<" + KEY_DESCRIPTION + ">.+) /at (?<" + KEY_DATE + ">.+)$"; - public static Task getInstance(Command cmd) throws InvalidCommandFormatException { - String keyword = cmd.getKeyword(); - String args = cmd.getArgs(); - if (keyword.equalsIgnoreCase(TaskType.DEADLINE.toString())) { - Matcher deadlineMatch = - Pattern.compile(DEADLINE_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); - if (deadlineMatch.matches()) { - String description = deadlineMatch.group(KEY_DESCRIPTION); - String dueDate = deadlineMatch.group(KEY_DATE); - return new Deadline(description, dueDate); - } else { - throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); - } - } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { - Matcher eventMatch = Pattern.compile(EVENT_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); - if (eventMatch.matches()) { - String description = eventMatch.group(KEY_DESCRIPTION); - String timing = eventMatch.group(KEY_DATE); - return new Event(description, timing); - } else { - throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_EVENT); - } - } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { - if (args.length() > 0) { - return new Todo(args); - } else { - throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_TODO); - } + public static Task getInstance(Command cmd) throws InvalidCommandFormatException { + String keyword = cmd.getKeyword(); + String args = cmd.getArgs(); + if (keyword.equalsIgnoreCase(TaskType.DEADLINE.toString())) { + Matcher deadlineMatch = + Pattern.compile(DEADLINE_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); + if (deadlineMatch.matches()) { + String description = deadlineMatch.group(KEY_DESCRIPTION); + String dueDate = deadlineMatch.group(KEY_DATE); + return new Deadline(description, dueDate); + } else { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); + } + } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { + Matcher eventMatch = Pattern.compile(EVENT_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); + if (eventMatch.matches()) { + String description = eventMatch.group(KEY_DESCRIPTION); + String timing = eventMatch.group(KEY_DATE); + return new Event(description, timing); + } else { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_EVENT); + } + } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { + if (args.length() > 0) { + return new Todo(args); + } else { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_TODO); + } + } + throw new InvalidCommandFormatException( + "Invalid Command Format:\n Cannot find the appropriate task to create."); } - throw new InvalidCommandFormatException( - "Invalid Command Format:\n Cannot find the appropriate task to create."); - } } diff --git a/src/main/java/classes/tasks/Todo.java b/src/main/java/classes/tasks/Todo.java index 37b1739a..a93a2e84 100644 --- a/src/main/java/classes/tasks/Todo.java +++ b/src/main/java/classes/tasks/Todo.java @@ -3,12 +3,12 @@ import classes.enums.TaskType; public class Todo extends Task { - public Todo(String description) { - super(TaskType.TODO, description); - } + public Todo(String description) { + super(TaskType.TODO, description); + } - @Override - public String toStatusString() { - return "[T]" + super.toStatusString(); - } + @Override + public String toStatusString() { + return "[T]" + super.toStatusString(); + } } diff --git a/src/main/java/classes/ui/Command.java b/src/main/java/classes/ui/Command.java index f663c52e..7ff9ea9d 100644 --- a/src/main/java/classes/ui/Command.java +++ b/src/main/java/classes/ui/Command.java @@ -3,41 +3,41 @@ import classes.enums.CommandType; public class Command { - private CommandType type; - private String keyword; - private String args; - - public Command(CommandType type, String keyword) { - this.setKeyword(keyword); - this.setType(type); - } - - public Command(CommandType type, String keyword, String args) { - this(type, keyword); - this.setArgs(args); - } - - public CommandType getType() { - return type; - } - - public void setType(CommandType type) { - this.type = type; - } - - public String getKeyword() { - return keyword; - } - - public void setKeyword(String keyword) { - this.keyword = keyword; - } - - public String getArgs() { - return args; - } - - public void setArgs(String args) { - this.args = args; - } + private CommandType type; + private String keyword; + private String args; + + public Command(CommandType type, String keyword) { + this.setKeyword(keyword); + this.setType(type); + } + + public Command(CommandType type, String keyword, String args) { + this(type, keyword); + this.setArgs(args); + } + + public CommandType getType() { + return type; + } + + public void setType(CommandType type) { + this.type = type; + } + + public String getKeyword() { + return keyword; + } + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + + public String getArgs() { + return args; + } + + public void setArgs(String args) { + this.args = args; + } } diff --git a/src/main/java/classes/ui/Parser.java b/src/main/java/classes/ui/Parser.java index 2631ee6d..4ee16d69 100644 --- a/src/main/java/classes/ui/Parser.java +++ b/src/main/java/classes/ui/Parser.java @@ -9,40 +9,40 @@ import exceptions.InvalidCommandFormatException; public class Parser { - private static final Pattern ADD_PATTERN = - Pattern.compile( - "^(?\\bdeadline\\b|\\bevent\\b|\\btodo\\b)\\s?(?.*)$", - Pattern.CASE_INSENSITIVE); - private static final Pattern DONE_PATTERN = - Pattern.compile("^\\b(?done)\\b (?.+)$", Pattern.CASE_INSENSITIVE); - private static final Pattern LIST_PATTERN = - Pattern.compile("^(?list)$", Pattern.CASE_INSENSITIVE); - private static final Pattern EXIT_PATTERN = - Pattern.compile("^(?bye)$", Pattern.CASE_INSENSITIVE); + private static final Pattern ADD_PATTERN = + Pattern.compile( + "^(?\\bdeadline\\b|\\bevent\\b|\\btodo\\b)\\s?(?.*)$", + Pattern.CASE_INSENSITIVE); + private static final Pattern DONE_PATTERN = + Pattern.compile("^\\b(?done)\\b (?.+)$", Pattern.CASE_INSENSITIVE); + private static final Pattern LIST_PATTERN = + Pattern.compile("^(?list)$", Pattern.CASE_INSENSITIVE); + private static final Pattern EXIT_PATTERN = + Pattern.compile("^(?bye)$", Pattern.CASE_INSENSITIVE); - public Command readInput(Scanner sc) - throws InvalidCommandException, InvalidCommandFormatException { - String input = sc.nextLine(); - Matcher addMatcher = ADD_PATTERN.matcher(input); - Matcher doneMatcher = DONE_PATTERN.matcher(input); - Matcher listMatcher = LIST_PATTERN.matcher(input); - Matcher exitMatcher = EXIT_PATTERN.matcher(input); + public Command readInput(Scanner sc) + throws InvalidCommandException, InvalidCommandFormatException { + String input = sc.nextLine(); + Matcher addMatcher = ADD_PATTERN.matcher(input); + Matcher doneMatcher = DONE_PATTERN.matcher(input); + Matcher listMatcher = LIST_PATTERN.matcher(input); + Matcher exitMatcher = EXIT_PATTERN.matcher(input); - if (addMatcher.matches()) { - String args = addMatcher.group("args"); - String keyword = addMatcher.group("keyword"); - return new Command(CommandType.ADD, keyword, args); - } else if (doneMatcher.matches()) { - String args = doneMatcher.group("args"); - String keyword = doneMatcher.group("keyword"); - return new Command(CommandType.COMPLETE, keyword, args); - } else if (listMatcher.matches()) { - String keyword = listMatcher.group("keyword"); - return new Command(CommandType.LIST, keyword); - } else if (exitMatcher.matches()) { - String keyword = exitMatcher.group("keyword"); - return new Command(CommandType.EXIT, keyword); + if (addMatcher.matches()) { + String args = addMatcher.group("args"); + String keyword = addMatcher.group("keyword"); + return new Command(CommandType.ADD, keyword, args); + } else if (doneMatcher.matches()) { + String args = doneMatcher.group("args"); + String keyword = doneMatcher.group("keyword"); + return new Command(CommandType.COMPLETE, keyword, args); + } else if (listMatcher.matches()) { + String keyword = listMatcher.group("keyword"); + return new Command(CommandType.LIST, keyword); + } else if (exitMatcher.matches()) { + String keyword = exitMatcher.group("keyword"); + return new Command(CommandType.EXIT, keyword); + } + throw new InvalidCommandException(InvalidCommandException.ERROR_GENERIC); } - throw new InvalidCommandException(InvalidCommandException.ERROR_GENERIC); - } } diff --git a/src/main/java/classes/ui/Prompt.java b/src/main/java/classes/ui/Prompt.java index 1ed89c53..92516e8d 100644 --- a/src/main/java/classes/ui/Prompt.java +++ b/src/main/java/classes/ui/Prompt.java @@ -8,76 +8,76 @@ import interfaces.Promptable; public class Prompt implements Promptable { - private static final String PADDING = " "; - private static final String HORIZONTAL_LINE = "_________________________________________"; - private static final String START = "Hello! I'm Duke\n What can I do for you?"; - private static final String END = "Bye. Hope to see you again soon!"; - private static final String DONE = "Nice! I've marked this task as done:"; - private static final String ADD = "added: "; + private static final String PADDING = " "; + private static final String HORIZONTAL_LINE = "_________________________________________"; + private static final String START = "Hello! I'm Duke\n What can I do for you?"; + private static final String END = "Bye. Hope to see you again soon!"; + private static final String DONE = "Nice! I've marked this task as done:"; + private static final String ADD = "added: "; - @Override - public String start() { - return formatOutput(formatLine(START)); - } + @Override + public String start() { + return formatOutput(formatLine(START)); + } - @Override - public String add(Task printable, int length) { - String output = - formatLine(ADD + printable.toString()) - + formatLine( - "Now you have " + length + " task" + ((length > 1) ? "s" : "") + " in the list."); + @Override + public String add(Task printable, int length) { + String output = + formatLine(ADD + printable.toString()) + + formatLine( + "Now you have " + length + " task" + ((length > 1) ? "s" : "") + " in the list."); - return formatOutput(output); - } + return formatOutput(output); + } - @Override - public String done(Task printable) { - return formatOutput(formatLine(DONE) + formatLine(" " + printable.toStatusString())); - } + @Override + public String done(Task printable) { + return formatOutput(formatLine(DONE) + formatLine(" " + printable.toStatusString())); + } - @Override - public String error(String header, String errorMessage) { - String output = formatLine(header) + formatLine(errorMessage); - return formatOutput(output); - } + @Override + public String error(String header, String errorMessage) { + String output = formatLine(header) + formatLine(errorMessage); + return formatOutput(output); + } - @Override - public String error(String errorMessage) { - return formatOutput(formatLine(errorMessage)); - } + @Override + public String error(String errorMessage) { + return formatOutput(formatLine(errorMessage)); + } - @Override - public String list(Iterable inputs) { - StringBuilder output = new StringBuilder(); - int count = 1; - for (Task input : inputs) { - String taskStr = ""; - if (input.getType() == TaskType.DEADLINE) { - Deadline down = (Deadline) input; - taskStr = down.toStatusString(); - } else if (input.getType() == TaskType.EVENT) { - Event down = (Event) input; - taskStr = down.toStatusString(); - } else if (input.getType() == TaskType.TODO) { - Todo down = (Todo) input; - taskStr = down.toStatusString(); - } - output.append(formatLine(count + ". " + taskStr)); - count++; + @Override + public String list(Iterable inputs) { + StringBuilder output = new StringBuilder(); + int count = 1; + for (Task input : inputs) { + String taskStr = ""; + if (input.getType() == TaskType.DEADLINE) { + Deadline down = (Deadline) input; + taskStr = down.toStatusString(); + } else if (input.getType() == TaskType.EVENT) { + Event down = (Event) input; + taskStr = down.toStatusString(); + } else if (input.getType() == TaskType.TODO) { + Todo down = (Todo) input; + taskStr = down.toStatusString(); + } + output.append(formatLine(count + ". " + taskStr)); + count++; + } + return formatOutput(output.toString()); } - return formatOutput(output.toString()); - } - @Override - public String exit() { - return formatOutput(formatLine(END)); - } + @Override + public String exit() { + return formatOutput(formatLine(END)); + } - private String formatLine(String output) { - return PADDING + output + System.lineSeparator(); - } + private String formatLine(String output) { + return PADDING + output + System.lineSeparator(); + } - private String formatOutput(String output) { - return formatLine(HORIZONTAL_LINE) + output + PADDING + HORIZONTAL_LINE; - } + private String formatOutput(String output) { + return formatLine(HORIZONTAL_LINE) + output + PADDING + HORIZONTAL_LINE; + } } diff --git a/src/main/java/exceptions/InvalidCommandException.java b/src/main/java/exceptions/InvalidCommandException.java index 8aa7af04..ef8727e7 100644 --- a/src/main/java/exceptions/InvalidCommandException.java +++ b/src/main/java/exceptions/InvalidCommandException.java @@ -1,14 +1,14 @@ package exceptions; public class InvalidCommandException extends Throwable { - private static final String ERROR_PREFIX = "Invalid Command:"; - public static final String ERROR_GENERIC = "I don't know what that command means!"; + private static final String ERROR_PREFIX = "Invalid Command:"; + public static final String ERROR_GENERIC = "I don't know what that command means!"; - public InvalidCommandException(String errorMessage) { - super(errorMessage); - } + public InvalidCommandException(String errorMessage) { + super(errorMessage); + } - public String getErrorHeader() { - return ERROR_PREFIX; - } + public String getErrorHeader() { + return ERROR_PREFIX; + } } diff --git a/src/main/java/exceptions/InvalidCommandFormatException.java b/src/main/java/exceptions/InvalidCommandFormatException.java index 3fd969d1..b36bf537 100644 --- a/src/main/java/exceptions/InvalidCommandFormatException.java +++ b/src/main/java/exceptions/InvalidCommandFormatException.java @@ -1,25 +1,25 @@ package exceptions; public class InvalidCommandFormatException extends Throwable { - private static final String ERROR_PREFIX = "Invalid Command Format:"; - public static final String ERROR_DEADLINE = - "classes.tasks.Deadline should be in the format: deadline /by ."; - public static final String ERROR_EVENT = - "classes.tasks.Event should be in the format: event /at ."; - public static final String ERROR_TODO = - "classes.tasks.Todo should be in the format: todo ."; + private static final String ERROR_PREFIX = "Invalid Command Format:"; + public static final String ERROR_DEADLINE = + "classes.tasks.Deadline should be in the format: deadline /by ."; + public static final String ERROR_EVENT = + "classes.tasks.Event should be in the format: event /at ."; + public static final String ERROR_TODO = + "classes.tasks.Todo should be in the format: todo ."; - private String errorMessage; + private String errorMessage; - public InvalidCommandFormatException(String errorMessage) { - super(errorMessage); - } + public InvalidCommandFormatException(String errorMessage) { + super(errorMessage); + } - public String getErrorHeader() { - return ERROR_PREFIX; - } + public String getErrorHeader() { + return ERROR_PREFIX; + } - public String toString() { - return getMessage(); - } + public String toString() { + return getMessage(); + } } diff --git a/src/main/java/interfaces/Promptable.java b/src/main/java/interfaces/Promptable.java index 86407878..b82179b6 100644 --- a/src/main/java/interfaces/Promptable.java +++ b/src/main/java/interfaces/Promptable.java @@ -3,17 +3,17 @@ import classes.tasks.Task; public interface Promptable { - String start(); + String start(); - String add(T printable, int length); + String add(T printable, int length); - String done(T printable); + String done(T printable); - String error(String header, String errorMessage); + String error(String header, String errorMessage); - String error(String errorMessage); + String error(String errorMessage); - String list(Iterable inputs); + String list(Iterable inputs); - String exit(); + String exit(); } diff --git a/src/test/java/classes/tasks/TaskTest.java b/src/test/java/classes/tasks/TaskTest.java index db25314b..7ccf33f6 100644 --- a/src/test/java/classes/tasks/TaskTest.java +++ b/src/test/java/classes/tasks/TaskTest.java @@ -4,9 +4,9 @@ import org.junit.jupiter.api.Test; public class TaskTest { - @Test - void testGetDescription() { - Task task = new Task(TaskType.TASK, "read book"); - assert task.getDescription().equalsIgnoreCase("read book"); - } + @Test + void testGetDescription() { + Task task = new Task(TaskType.TASK, "read book"); + assert task.getDescription().equalsIgnoreCase("read book"); + } } From 1a3e1e35e00aa79b51dd32b25de599049d721d2a Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Wed, 8 Sep 2021 20:39:06 +0800 Subject: [PATCH 16/59] Improved readability --- src/main/java/Duke.java | 35 ++++++++++---------- src/main/java/classes/tasks/TaskFactory.java | 4 +-- src/main/java/classes/ui/Parser.java | 7 ++-- src/main/java/classes/ui/Prompt.java | 12 ++++--- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 57c04713..566e07f0 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -6,6 +6,7 @@ import exceptions.InvalidCommandException; import exceptions.InvalidCommandFormatException; import interfaces.Promptable; + import java.util.ArrayList; import java.util.Scanner; @@ -31,23 +32,23 @@ public static void main(String[] args) { Command command = d.parser.readInput(in); String output = ""; switch (command.getType()) { - case ADD: - Task newTask = TaskFactory.getInstance(command); - tasks.add(newTask); - output = d.prompt.add(newTask, tasks.size()); - break; - case COMPLETE: - int idx = Integer.parseInt(command.getArgs()); - Task doneTask = tasks.get(idx - 1); - doneTask.setDone(true); - output = d.prompt.done(doneTask); - break; - case LIST: - output = d.prompt.list(tasks); - break; - case EXIT: - receiveInput = false; - break; + case ADD: + Task newTask = TaskFactory.getInstance(command); + tasks.add(newTask); + output = d.prompt.add(newTask, tasks.size()); + break; + case COMPLETE: + int idx = Integer.parseInt(command.getArgs()); + Task doneTask = tasks.get(idx - 1); + doneTask.setDone(true); + output = d.prompt.done(doneTask); + break; + case LIST: + output = d.prompt.list(tasks); + break; + case EXIT: + receiveInput = false; + break; } System.out.println(output); } catch (InvalidCommandException ice) { diff --git a/src/main/java/classes/tasks/TaskFactory.java b/src/main/java/classes/tasks/TaskFactory.java index 044ea3ec..bf9e75a9 100644 --- a/src/main/java/classes/tasks/TaskFactory.java +++ b/src/main/java/classes/tasks/TaskFactory.java @@ -1,7 +1,7 @@ package classes.tasks; -import classes.ui.Command; import classes.enums.TaskType; +import classes.ui.Command; import exceptions.InvalidCommandFormatException; import java.util.regex.Matcher; @@ -45,6 +45,6 @@ public static Task getInstance(Command cmd) throws InvalidCommandFormatException } } throw new InvalidCommandFormatException( - "Invalid Command Format:\n Cannot find the appropriate task to create."); + "Cannot find the appropriate task to create."); } } diff --git a/src/main/java/classes/ui/Parser.java b/src/main/java/classes/ui/Parser.java index 4ee16d69..6d47b32d 100644 --- a/src/main/java/classes/ui/Parser.java +++ b/src/main/java/classes/ui/Parser.java @@ -1,12 +1,11 @@ package classes.ui; -import java.util.Scanner; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import classes.enums.CommandType; import exceptions.InvalidCommandException; import exceptions.InvalidCommandFormatException; +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class Parser { private static final Pattern ADD_PATTERN = diff --git a/src/main/java/classes/ui/Prompt.java b/src/main/java/classes/ui/Prompt.java index 92516e8d..5a00681d 100644 --- a/src/main/java/classes/ui/Prompt.java +++ b/src/main/java/classes/ui/Prompt.java @@ -22,17 +22,19 @@ public String start() { @Override public String add(Task printable, int length) { - String output = - formatLine(ADD + printable.toString()) - + formatLine( - "Now you have " + length + " task" + ((length > 1) ? "s" : "") + " in the list."); + String multipleStr = (length > 1) ? "tasks" : "task"; + String taskStr = length + " " + multipleStr; + String output = formatLine(ADD + printable.toString()); + output += formatLine("Now you have " + taskStr + " in the list."); return formatOutput(output); } @Override public String done(Task printable) { - return formatOutput(formatLine(DONE) + formatLine(" " + printable.toStatusString())); + String output = formatLine(DONE); + output += formatLine(" " + printable.toStatusString()); + return formatOutput(output); } @Override From e53a6a82858d58a4feb7194eb2c00fe69bb04035 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Thu, 9 Sep 2021 09:58:00 +0800 Subject: [PATCH 17/59] Add IOParser interface for DI, increase test coverage --- src/main/java/Duke.java | 5 +- src/main/java/classes/tasks/TaskFactory.java | 1 - src/main/java/classes/ui/Parser.java | 6 +- .../exceptions/InvalidCommandException.java | 2 +- src/main/java/interfaces/IOParser.java | 5 ++ .../java/classes/tasks/TaskFactoryTest.java | 84 +++++++++++++++++++ src/test/java/classes/tasks/TaskTest.java | 5 +- src/test/java/classes/ui/ParserTest.java | 51 +++++++++++ 8 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 src/main/java/interfaces/IOParser.java create mode 100644 src/test/java/classes/tasks/TaskFactoryTest.java create mode 100644 src/test/java/classes/ui/ParserTest.java diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 566e07f0..bf01a631 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -5,6 +5,7 @@ import classes.ui.Prompt; import exceptions.InvalidCommandException; import exceptions.InvalidCommandFormatException; +import interfaces.IOParser; import interfaces.Promptable; import java.util.ArrayList; @@ -12,7 +13,7 @@ public class Duke { private final Promptable prompt; - private final Parser parser; + private final IOParser parser; public Duke(Promptable prompt, Parser parser) { this.prompt = prompt; @@ -57,6 +58,8 @@ public static void main(String[] args) { System.out.println(d.prompt.error(icfe.getErrorHeader(), icfe.getMessage())); } catch (NumberFormatException | IndexOutOfBoundsException ex) { System.out.println(d.prompt.error(ex.getMessage())); + } catch (Exception ex) { + ex.printStackTrace(); } } diff --git a/src/main/java/classes/tasks/TaskFactory.java b/src/main/java/classes/tasks/TaskFactory.java index bf9e75a9..1b183563 100644 --- a/src/main/java/classes/tasks/TaskFactory.java +++ b/src/main/java/classes/tasks/TaskFactory.java @@ -3,7 +3,6 @@ import classes.enums.TaskType; import classes.ui.Command; import exceptions.InvalidCommandFormatException; - import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/classes/ui/Parser.java b/src/main/java/classes/ui/Parser.java index 6d47b32d..c825985a 100644 --- a/src/main/java/classes/ui/Parser.java +++ b/src/main/java/classes/ui/Parser.java @@ -3,11 +3,13 @@ import classes.enums.CommandType; import exceptions.InvalidCommandException; import exceptions.InvalidCommandFormatException; +import interfaces.IOParser; + import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class Parser { +public class Parser implements IOParser { private static final Pattern ADD_PATTERN = Pattern.compile( "^(?\\bdeadline\\b|\\bevent\\b|\\btodo\\b)\\s?(?.*)$", @@ -20,7 +22,7 @@ public class Parser { Pattern.compile("^(?bye)$", Pattern.CASE_INSENSITIVE); public Command readInput(Scanner sc) - throws InvalidCommandException, InvalidCommandFormatException { + throws InvalidCommandException { String input = sc.nextLine(); Matcher addMatcher = ADD_PATTERN.matcher(input); Matcher doneMatcher = DONE_PATTERN.matcher(input); diff --git a/src/main/java/exceptions/InvalidCommandException.java b/src/main/java/exceptions/InvalidCommandException.java index ef8727e7..c615e9a2 100644 --- a/src/main/java/exceptions/InvalidCommandException.java +++ b/src/main/java/exceptions/InvalidCommandException.java @@ -1,6 +1,6 @@ package exceptions; -public class InvalidCommandException extends Throwable { +public class InvalidCommandException extends Exception { private static final String ERROR_PREFIX = "Invalid Command:"; public static final String ERROR_GENERIC = "I don't know what that command means!"; diff --git a/src/main/java/interfaces/IOParser.java b/src/main/java/interfaces/IOParser.java new file mode 100644 index 00000000..52fe1879 --- /dev/null +++ b/src/main/java/interfaces/IOParser.java @@ -0,0 +1,5 @@ +package interfaces; + +public interface IOParser { + T readInput(S sc) throws Exception; +} diff --git a/src/test/java/classes/tasks/TaskFactoryTest.java b/src/test/java/classes/tasks/TaskFactoryTest.java new file mode 100644 index 00000000..ddeb2993 --- /dev/null +++ b/src/test/java/classes/tasks/TaskFactoryTest.java @@ -0,0 +1,84 @@ +package classes.tasks; + +import classes.enums.CommandType; +import classes.enums.TaskType; +import classes.ui.Command; +import exceptions.InvalidCommandFormatException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class TaskFactoryTest { + + @Test + void getInstanceTodoSuccess() throws InvalidCommandFormatException { + Command addCommand = new Command(CommandType.ADD, + TaskType.TODO.toString(), "read book"); + + Task task = TaskFactory.getInstance(addCommand); + Assertions.assertEquals(TaskType.TODO, task.getType(), "TaskFactory assertEquals Todo failed"); + } + + @Test + void getInstanceEventSuccess() throws InvalidCommandFormatException { + Command addCommand = new Command(CommandType.ADD, + TaskType.EVENT.toString(), + "meet friend /at 2020-09-15"); + + Task task = TaskFactory.getInstance(addCommand); + Assertions.assertEquals(TaskType.EVENT, task.getType(), "TaskFactory assertEquals Event failed"); + } + + @Test + void getInstanceDeadlineSuccess() throws InvalidCommandFormatException { + Command addCommand = new Command(CommandType.ADD, + TaskType.DEADLINE.toString(), + "return book /by 2021-09-20"); + + Task task = TaskFactory.getInstance(addCommand); + Assertions.assertEquals(TaskType.DEADLINE, task.getType(), "TaskFactory assertEquals Deadline failed"); + } + + @Test + void getInstanceTodoFail() { + Command addCommand = new Command(CommandType.ADD, + TaskType.TODO.toString(), ""); + + InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { + TaskFactory.getInstance(addCommand); + }, "TaskFactory assertThrows Todo failed."); + + Assertions.assertEquals(InvalidCommandFormatException.ERROR_TODO, + icfe.toString(), + "TaskFactory assertEquals ERROR_TODO failed."); + } + + @Test + void getInstanceEventFail() { + Command addCommand = new Command(CommandType.ADD, + TaskType.EVENT.toString(), + "meet friend"); + + InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { + TaskFactory.getInstance(addCommand); + }, "TaskFactory assertThrows Event failed."); + + Assertions.assertEquals(InvalidCommandFormatException.ERROR_EVENT, + icfe.toString(), + "TaskFactory assertEquals ERROR_EVENT failed."); + } + + @Test + void getInstanceDeadlineFail() { + Command addCommand = new Command(CommandType.ADD, + TaskType.DEADLINE.toString(), + "return book"); + + InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { + TaskFactory.getInstance(addCommand); + }, "TaskFactory assertThrows Deadline failed."); + + Assertions.assertEquals(InvalidCommandFormatException.ERROR_DEADLINE, + icfe.toString(), + "TaskFactory assertEquals ERROR_EVENT failed."); + } +} \ No newline at end of file diff --git a/src/test/java/classes/tasks/TaskTest.java b/src/test/java/classes/tasks/TaskTest.java index 7ccf33f6..acbaa764 100644 --- a/src/test/java/classes/tasks/TaskTest.java +++ b/src/test/java/classes/tasks/TaskTest.java @@ -1,12 +1,13 @@ package classes.tasks; import classes.enums.TaskType; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class TaskTest { @Test - void testGetDescription() { + void getDescriptionSuccess() { Task task = new Task(TaskType.TASK, "read book"); - assert task.getDescription().equalsIgnoreCase("read book"); + Assertions.assertEquals("read book", task.getDescription()); } } diff --git a/src/test/java/classes/ui/ParserTest.java b/src/test/java/classes/ui/ParserTest.java new file mode 100644 index 00000000..9fce136e --- /dev/null +++ b/src/test/java/classes/ui/ParserTest.java @@ -0,0 +1,51 @@ +package classes.ui; + +import classes.enums.CommandType; +import exceptions.InvalidCommandException; +import org.junit.jupiter.api.Test; + +import java.util.Scanner; + +import static org.junit.jupiter.api.Assertions.*; + +class ParserTest { + private static Parser parser = new Parser(); + + @Test + void readInput_CommandTodoSuccess() throws InvalidCommandException { + String input = "todo read book"; + Scanner sc = new Scanner(input).useDelimiter("\n"); + Command cmd = parser.readInput(sc); + assertEquals(CommandType.ADD, cmd.getType(), "Parser assertEquals Todo CommandType ADD failed."); + assertEquals("todo", cmd.getKeyword(), "Parser assertEquals Todo Keyword failed."); + assertEquals("read book", cmd.getArgs(), "Parser assertEquals Todo Args failed."); + } + + @Test + void readInput_CommandTodoFail() { + String input = "todo1"; + Scanner sc = new Scanner(input).useDelimiter("\n"); + assertThrows(InvalidCommandException.class, () -> { + parser.readInput(sc); + }); + } + + @Test + void readInput_CommandEventSuccess() throws InvalidCommandException { + String input = "event meet friend /at 2021-09-15"; + Scanner sc = new Scanner(input).useDelimiter("\n"); + Command cmd = parser.readInput(sc); + assertEquals(CommandType.ADD, cmd.getType(), "Parser assertEquals Event CommandType ADD failed."); + assertEquals("event", cmd.getKeyword(), "Parser assertEquals Event Keyword failed."); + assertEquals("meet friend /at 2021-09-15", cmd.getArgs(), "Parser assertEquals Event Args failed."); + } + + @Test + void readInput_CommandEventFail() { + String input = "eventmeet friend /at 2021-09-15"; + Scanner sc = new Scanner(input).useDelimiter("\n"); + assertThrows(InvalidCommandException.class, () -> { + parser.readInput(sc); + }); + } +} \ No newline at end of file From 09e9129f44d8e5a6d50685cc4e4ea4dedbdec0d5 Mon Sep 17 00:00:00 2001 From: Strider1990 Date: Thu, 9 Sep 2021 10:37:26 +0800 Subject: [PATCH 18/59] Create CI.yml --- .github/workflows/CI.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/CI.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 00000000..05bba864 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,35 @@ +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + + workflow_dispatch: + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build + - name: Grant execute permission to text-ui-test + run: chmod +x text-ui-test/runtest.sh + - name: Test with runtest.sh + working-directory: ./text-ui-test + run: ./runtest.sh From 49cc616dd5648c31c78f9ded9ef11a520003a078 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Thu, 9 Sep 2021 10:39:15 +0800 Subject: [PATCH 19/59] refactor to use StringBuilder, fix ui test --- text-ui-test/runtest.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index ed07b635..77494a4e 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,7 +13,7 @@ then fi # compile the code into the bin folder, terminates if error occurred -if ! javac -cp ../src/Duke -Xlint:none -d ../bin ../src/Duke/*.java ../src/Duke/*/*.java ../src/Duke/*/*/*.java +if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java ../src/main/java/*/*.java ../src/main/java/*/*/*.java then echo "********** BUILD FAILURE **********" exit 1 From 32073d4a22b9eefe0a73a72b1b9b3dba7a40e2b7 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Thu, 9 Sep 2021 10:40:17 +0800 Subject: [PATCH 20/59] Add uncommitted filed --- src/main/java/Duke.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index bf01a631..5571edcd 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -15,7 +15,7 @@ public class Duke { private final Promptable prompt; private final IOParser parser; - public Duke(Promptable prompt, Parser parser) { + public Duke(Promptable prompt, IOParser parser) { this.prompt = prompt; this.parser = parser; } @@ -31,27 +31,27 @@ public static void main(String[] args) { while (receiveInput && in.hasNext()) { try { Command command = d.parser.readInput(in); - String output = ""; + StringBuilder output = new StringBuilder(); switch (command.getType()) { case ADD: Task newTask = TaskFactory.getInstance(command); tasks.add(newTask); - output = d.prompt.add(newTask, tasks.size()); + output.append(d.prompt.add(newTask, tasks.size())); break; case COMPLETE: int idx = Integer.parseInt(command.getArgs()); Task doneTask = tasks.get(idx - 1); doneTask.setDone(true); - output = d.prompt.done(doneTask); + output.append(d.prompt.done(doneTask)); break; case LIST: - output = d.prompt.list(tasks); + output.append(d.prompt.list(tasks)); break; case EXIT: receiveInput = false; break; } - System.out.println(output); + System.out.println(output.toString()); } catch (InvalidCommandException ice) { System.out.println(d.prompt.error(ice.getErrorHeader(), ice.getMessage())); } catch (InvalidCommandFormatException icfe) { From 6d094578f64cefe8bfccda75f1744fc42bb3abe2 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Thu, 9 Sep 2021 10:45:48 +0800 Subject: [PATCH 21/59] Add gradlew and batch for CI --- .gitignore | 2 - gradlew | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++ gradlew.bat | 89 +++++++++++++++++++++++++ 3 files changed, 274 insertions(+), 2 deletions(-) create mode 100755 gradlew create mode 100644 gradlew.bat diff --git a/.gitignore b/.gitignore index 6da7ffc6..90eec150 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,6 @@ gradle/ /build/ src/main/resources/docs/ -gradlew -gradlew.bat # MacOS custom attributes files created by Finder .DS_Store diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..744e882e --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/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 + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + 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 00000000..ac1b06f9 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@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 execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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 c9d0aa2a77561aea0b9ea74e68dc4d0b561c35ea Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Thu, 9 Sep 2021 10:46:58 +0800 Subject: [PATCH 22/59] Add gradle folder --- .gitignore | 1 - gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 5 +++++ 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties diff --git a/.gitignore b/.gitignore index 90eec150..f69985ef 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ # Gradle build files /.gradle/ -gradle/ /build/ src/main/resources/docs/ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..69a97150 --- /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-7.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists From 2d2c0d000f55239ba51c2dc7d020eef44e01d52e Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Thu, 9 Sep 2021 10:53:05 +0800 Subject: [PATCH 23/59] Break tests to two --- .github/workflows/Gradle-CI.yml | 30 ++++++++++++++++++++ .github/workflows/{CI.yml => Text-UI-CI.yml} | 7 +---- 2 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/Gradle-CI.yml rename .github/workflows/{CI.yml => Text-UI-CI.yml} (80%) diff --git a/.github/workflows/Gradle-CI.yml b/.github/workflows/Gradle-CI.yml new file mode 100644 index 00000000..f7a36b0d --- /dev/null +++ b/.github/workflows/Gradle-CI.yml @@ -0,0 +1,30 @@ +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + + workflow_dispatch: + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build diff --git a/.github/workflows/CI.yml b/.github/workflows/Text-UI-CI.yml similarity index 80% rename from .github/workflows/CI.yml rename to .github/workflows/Text-UI-CI.yml index 05bba864..25a664a6 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/Text-UI-CI.yml @@ -1,7 +1,7 @@ # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle -name: Java CI with Gradle +name: Java CI on: push: @@ -23,11 +23,6 @@ jobs: with: java-version: '11' distribution: 'adopt' - cache: gradle - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build with Gradle - run: ./gradlew build - name: Grant execute permission to text-ui-test run: chmod +x text-ui-test/runtest.sh - name: Test with runtest.sh From c69cc7b0d71d2ecf7eb2cf9eb7abef6f18ac0974 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Fri, 10 Sep 2021 08:05:25 +0800 Subject: [PATCH 24/59] Fix gradle CLI run --- build.gradle | 7 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 257 ++++++++++++++--------- 3 files changed, 160 insertions(+), 106 deletions(-) diff --git a/build.gradle b/build.gradle index 4b2d52a2..379fe5ff 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id 'application' } group 'com.alexooi.duke' @@ -22,4 +23,8 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' -} \ No newline at end of file +} +run { + standardInput = System.in +} +mainClassName = 'Duke' \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 69a97150..ffed3a25 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882e..1b6c7873 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -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 +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${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" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 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" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + 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 @@ -106,80 +140,95 @@ 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 +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac 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 +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "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 +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - 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\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - 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; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# 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" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" From 17c1af7efe123fd20074de049fa0249949b4a9ab Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Fri, 10 Sep 2021 08:31:14 +0800 Subject: [PATCH 25/59] Abstract TaskList to singleton --- src/main/java/Duke.java | 9 +++-- src/main/java/classes/tasks/TaskList.java | 48 +++++++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 src/main/java/classes/tasks/TaskList.java diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5571edcd..92568b2d 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,5 +1,6 @@ import classes.tasks.Task; import classes.tasks.TaskFactory; +import classes.tasks.TaskList; import classes.ui.Command; import classes.ui.Parser; import classes.ui.Prompt; @@ -22,10 +23,10 @@ public Duke(Promptable prompt, IOParser parser) { public static void main(String[] args) { Duke d = new Duke(new Prompt(), new Parser()); + TaskList tasks = TaskList.getInstance(); System.out.println(d.prompt.start()); Scanner in = new Scanner(System.in); - ArrayList tasks = new ArrayList<>(); boolean receiveInput = true; while (receiveInput && in.hasNext()) { @@ -40,18 +41,18 @@ public static void main(String[] args) { break; case COMPLETE: int idx = Integer.parseInt(command.getArgs()); - Task doneTask = tasks.get(idx - 1); + Task doneTask = tasks.getTask(idx - 1); doneTask.setDone(true); output.append(d.prompt.done(doneTask)); break; case LIST: - output.append(d.prompt.list(tasks)); + output.append(d.prompt.list(tasks.get())); break; case EXIT: receiveInput = false; break; } - System.out.println(output.toString()); + System.out.println(output); } catch (InvalidCommandException ice) { System.out.println(d.prompt.error(ice.getErrorHeader(), ice.getMessage())); } catch (InvalidCommandFormatException icfe) { diff --git a/src/main/java/classes/tasks/TaskList.java b/src/main/java/classes/tasks/TaskList.java new file mode 100644 index 00000000..9a618f0c --- /dev/null +++ b/src/main/java/classes/tasks/TaskList.java @@ -0,0 +1,48 @@ +package classes.tasks; + +import java.util.ArrayList; +import java.util.Optional; + +public class TaskList { + private static TaskList instance; + private ArrayList taskList; + + private TaskList() { + taskList = new ArrayList<>(); + } + + public static TaskList getInstance() { + if (instance == null) { + instance = new TaskList(); + } + + return instance; + } + + public void add(Task task) { + taskList.add(task); + } + + public Optional remove(Task task) { + if (taskList.remove(task)) { + return Optional.ofNullable(task); + } + return Optional.empty(); + } + + public Optional remove(int idx) { + return Optional.ofNullable(taskList.remove(idx)); + } + + public ArrayList get() { + return taskList; + } + + public Task getTask(int idx) { + return taskList.get(idx); + } + + public int size() { + return taskList.size(); + } +} From b57893eadfbc94000302f2a84c97abc80ce255b0 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Fri, 10 Sep 2021 09:32:22 +0800 Subject: [PATCH 26/59] Add delete feature --- src/main/java/Duke.java | 72 +++++++++++++++----- src/main/java/classes/enums/CommandType.java | 3 +- src/main/java/classes/tasks/TaskList.java | 11 +-- src/main/java/classes/ui/Parser.java | 9 ++- src/main/java/classes/ui/Prompt.java | 20 ++++-- src/main/java/interfaces/Promptable.java | 2 + text-ui-test/EXPECTED.TXT | 9 +++ text-ui-test/input.txt | 2 + 8 files changed, 96 insertions(+), 32 deletions(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 92568b2d..12f5350f 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -10,43 +10,83 @@ import interfaces.Promptable; import java.util.ArrayList; +import java.util.Optional; import java.util.Scanner; public class Duke { + private static Duke instance; private final Promptable prompt; private final IOParser parser; - public Duke(Promptable prompt, IOParser parser) { + private Duke(Promptable prompt, IOParser parser) { this.prompt = prompt; this.parser = parser; } + public static Duke getInstance() { + if (instance == null) { + instance = new Duke(new Prompt(), new Parser()); + } + + return instance; + } + + private static String handleAdd(Command cmd) throws InvalidCommandFormatException { + Task newTask = TaskFactory.getInstance(cmd); + Duke main = Duke.getInstance(); + TaskList tasks = TaskList.getInstance(); + tasks.add(newTask); + + return main.prompt.add(newTask, tasks.size()); + } + + private static String handleComplete(Command cmd) throws NumberFormatException { + int idx = Integer.parseInt(cmd.getArgs()) - 1; + TaskList tasks = TaskList.getInstance(); + Duke main = Duke.getInstance(); + Task doneTask = tasks.getTask(idx); + doneTask.setDone(!doneTask.getDone()); + return main.prompt.done(doneTask); + } + + private static String handleRemove(Command cmd) throws NumberFormatException { + int idx = Integer.parseInt(cmd.getArgs()) - 1; + TaskList tasks = TaskList.getInstance(); + Duke main = Duke.getInstance(); + Task removedTask = tasks.remove(idx); + return main.prompt.remove(removedTask, tasks.size()); + } + + private static String handleList() { + TaskList tasks = TaskList.getInstance(); + Duke main = Duke.getInstance(); + return main.prompt.list(tasks.get()); + } + public static void main(String[] args) { - Duke d = new Duke(new Prompt(), new Parser()); + Duke main = Duke.getInstance(); TaskList tasks = TaskList.getInstance(); - System.out.println(d.prompt.start()); + System.out.println(main.prompt.start()); Scanner in = new Scanner(System.in); boolean receiveInput = true; while (receiveInput && in.hasNext()) { try { - Command command = d.parser.readInput(in); + Command command = main.parser.readInput(in); StringBuilder output = new StringBuilder(); switch (command.getType()) { case ADD: - Task newTask = TaskFactory.getInstance(command); - tasks.add(newTask); - output.append(d.prompt.add(newTask, tasks.size())); + output.append(handleAdd(command)); break; case COMPLETE: - int idx = Integer.parseInt(command.getArgs()); - Task doneTask = tasks.getTask(idx - 1); - doneTask.setDone(true); - output.append(d.prompt.done(doneTask)); + output.append(handleComplete(command)); break; case LIST: - output.append(d.prompt.list(tasks.get())); + output.append(handleList()); + break; + case REMOVE: + output.append(handleRemove(command)); break; case EXIT: receiveInput = false; @@ -54,16 +94,16 @@ public static void main(String[] args) { } System.out.println(output); } catch (InvalidCommandException ice) { - System.out.println(d.prompt.error(ice.getErrorHeader(), ice.getMessage())); + System.out.println(main.prompt.error(ice.getErrorHeader(), ice.getMessage())); } catch (InvalidCommandFormatException icfe) { - System.out.println(d.prompt.error(icfe.getErrorHeader(), icfe.getMessage())); + System.out.println(main.prompt.error(icfe.getErrorHeader(), icfe.getMessage())); } catch (NumberFormatException | IndexOutOfBoundsException ex) { - System.out.println(d.prompt.error(ex.getMessage())); + System.out.println(main.prompt.error(ex.getMessage())); } catch (Exception ex) { ex.printStackTrace(); } } - System.out.println(d.prompt.exit()); + System.out.println(main.prompt.exit()); } } diff --git a/src/main/java/classes/enums/CommandType.java b/src/main/java/classes/enums/CommandType.java index 51d0ab94..e0292a04 100644 --- a/src/main/java/classes/enums/CommandType.java +++ b/src/main/java/classes/enums/CommandType.java @@ -4,5 +4,6 @@ public enum CommandType { ADD, COMPLETE, LIST, - EXIT + EXIT, + REMOVE } diff --git a/src/main/java/classes/tasks/TaskList.java b/src/main/java/classes/tasks/TaskList.java index 9a618f0c..456fa480 100644 --- a/src/main/java/classes/tasks/TaskList.java +++ b/src/main/java/classes/tasks/TaskList.java @@ -23,15 +23,8 @@ public void add(Task task) { taskList.add(task); } - public Optional remove(Task task) { - if (taskList.remove(task)) { - return Optional.ofNullable(task); - } - return Optional.empty(); - } - - public Optional remove(int idx) { - return Optional.ofNullable(taskList.remove(idx)); + public Task remove(int idx) { + return taskList.remove(idx); } public ArrayList get() { diff --git a/src/main/java/classes/ui/Parser.java b/src/main/java/classes/ui/Parser.java index c825985a..ae2d765e 100644 --- a/src/main/java/classes/ui/Parser.java +++ b/src/main/java/classes/ui/Parser.java @@ -2,9 +2,7 @@ import classes.enums.CommandType; import exceptions.InvalidCommandException; -import exceptions.InvalidCommandFormatException; import interfaces.IOParser; - import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -20,6 +18,8 @@ public class Parser implements IOParser { Pattern.compile("^(?list)$", Pattern.CASE_INSENSITIVE); private static final Pattern EXIT_PATTERN = Pattern.compile("^(?bye)$", Pattern.CASE_INSENSITIVE); + private static final Pattern DELETE_PATTERN = + Pattern.compile("^\\b(?delete)\\b (?.+)$", Pattern.CASE_INSENSITIVE); public Command readInput(Scanner sc) throws InvalidCommandException { @@ -28,6 +28,7 @@ public Command readInput(Scanner sc) Matcher doneMatcher = DONE_PATTERN.matcher(input); Matcher listMatcher = LIST_PATTERN.matcher(input); Matcher exitMatcher = EXIT_PATTERN.matcher(input); + Matcher deleteMatcher = DELETE_PATTERN.matcher(input); if (addMatcher.matches()) { String args = addMatcher.group("args"); @@ -43,6 +44,10 @@ public Command readInput(Scanner sc) } else if (exitMatcher.matches()) { String keyword = exitMatcher.group("keyword"); return new Command(CommandType.EXIT, keyword); + } else if (deleteMatcher.matches()) { + String args = deleteMatcher.group("args"); + String keyword = deleteMatcher.group("keyword"); + return new Command(CommandType.REMOVE, keyword, args); } throw new InvalidCommandException(InvalidCommandException.ERROR_GENERIC); } diff --git a/src/main/java/classes/ui/Prompt.java b/src/main/java/classes/ui/Prompt.java index 5a00681d..ec5744f7 100644 --- a/src/main/java/classes/ui/Prompt.java +++ b/src/main/java/classes/ui/Prompt.java @@ -14,6 +14,7 @@ public class Prompt implements Promptable { private static final String END = "Bye. Hope to see you again soon!"; private static final String DONE = "Nice! I've marked this task as done:"; private static final String ADD = "added: "; + private static final String REMOVE = "Noted. I've removed this task:"; @Override public String start() { @@ -22,11 +23,8 @@ public String start() { @Override public String add(Task printable, int length) { - String multipleStr = (length > 1) ? "tasks" : "task"; - String taskStr = length + " " + multipleStr; String output = formatLine(ADD + printable.toString()); - output += formatLine("Now you have " + taskStr + " in the list."); - + output += count(length); return formatOutput(output); } @@ -37,6 +35,14 @@ public String done(Task printable) { return formatOutput(output); } + @Override + public String remove(Task printable, int length) { + String output = formatLine(REMOVE); + output += formatLine(" " + printable.toStatusString()); + output += count(length); + return formatOutput(output); + } + @Override public String error(String header, String errorMessage) { String output = formatLine(header) + formatLine(errorMessage); @@ -75,6 +81,12 @@ public String exit() { return formatOutput(formatLine(END)); } + private String count(int length) { + String multipleStr = (length > 1) ? "tasks" : "task"; + String taskStr = length + " " + multipleStr; + return formatLine("Now you have " + taskStr + " in the list."); + } + private String formatLine(String output) { return PADDING + output + System.lineSeparator(); } diff --git a/src/main/java/interfaces/Promptable.java b/src/main/java/interfaces/Promptable.java index b82179b6..0712c76f 100644 --- a/src/main/java/interfaces/Promptable.java +++ b/src/main/java/interfaces/Promptable.java @@ -9,6 +9,8 @@ public interface Promptable { String done(T printable); + String remove(T printable, int length); + String error(String header, String errorMessage); String error(String errorMessage); diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 958946ce..cfef2cbd 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -28,5 +28,14 @@ 3. [E][ ] meet friend (at: 2021-09-15) _________________________________________ _________________________________________ + Noted. I've removed this task: + [D][X] return book (by: 2021-09-20) + Now you have 2 tasks in the list. + _________________________________________ + _________________________________________ + 1. [T][ ] read book + 2. [E][ ] meet friend (at: 2021-09-15) + _________________________________________ + _________________________________________ Bye. Hope to see you again soon! _________________________________________ diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f650bc6e..2d9e89da 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -3,4 +3,6 @@ deadline return book /by 2021-09-20 list event meet friend /at 2021-09-15 done 2 +list +delete 2 list \ No newline at end of file From 6fc3397df9d4e44643b909515ddf4e77b4d4183c Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Fri, 10 Sep 2021 09:42:56 +0800 Subject: [PATCH 27/59] Fix README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 63d0ad6b..8715d4d9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke.Duke project template +# 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.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: +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: ``` Hello from ____ _ From b459038335e060a719895c8180aee987f432685d Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Fri, 10 Sep 2021 17:34:00 +0800 Subject: [PATCH 28/59] Abstract commands and code clean up --- src/main/java/Duke.java | 57 ++----------------- .../java/classes/commands/AddCommand.java | 26 +++++++++ .../classes/{ui => commands}/Command.java | 10 +++- .../classes/commands/CompleteCommand.java | 25 ++++++++ .../java/classes/commands/DeleteCommand.java | 24 ++++++++ .../java/classes/commands/ExitCommand.java | 18 ++++++ .../java/classes/commands/ListCommand.java | 22 +++++++ src/main/java/classes/tasks/Task.java | 2 +- src/main/java/classes/tasks/TaskFactory.java | 2 +- src/main/java/classes/ui/Parser.java | 29 +++++----- src/main/java/classes/ui/Prompt.java | 12 +--- .../java/classes/tasks/TaskFactoryTest.java | 15 ++--- src/test/java/classes/tasks/TaskTest.java | 2 +- src/test/java/classes/ui/ParserTest.java | 1 + 14 files changed, 156 insertions(+), 89 deletions(-) create mode 100644 src/main/java/classes/commands/AddCommand.java rename src/main/java/classes/{ui => commands}/Command.java (71%) create mode 100644 src/main/java/classes/commands/CompleteCommand.java create mode 100644 src/main/java/classes/commands/DeleteCommand.java create mode 100644 src/main/java/classes/commands/ExitCommand.java create mode 100644 src/main/java/classes/commands/ListCommand.java diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 12f5350f..8bb29c1c 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,7 +1,8 @@ +import classes.enums.CommandType; import classes.tasks.Task; import classes.tasks.TaskFactory; import classes.tasks.TaskList; -import classes.ui.Command; +import classes.commands.Command; import classes.ui.Parser; import classes.ui.Prompt; import exceptions.InvalidCommandException; @@ -9,8 +10,6 @@ import interfaces.IOParser; import interfaces.Promptable; -import java.util.ArrayList; -import java.util.Optional; import java.util.Scanner; public class Duke { @@ -31,38 +30,6 @@ public static Duke getInstance() { return instance; } - private static String handleAdd(Command cmd) throws InvalidCommandFormatException { - Task newTask = TaskFactory.getInstance(cmd); - Duke main = Duke.getInstance(); - TaskList tasks = TaskList.getInstance(); - tasks.add(newTask); - - return main.prompt.add(newTask, tasks.size()); - } - - private static String handleComplete(Command cmd) throws NumberFormatException { - int idx = Integer.parseInt(cmd.getArgs()) - 1; - TaskList tasks = TaskList.getInstance(); - Duke main = Duke.getInstance(); - Task doneTask = tasks.getTask(idx); - doneTask.setDone(!doneTask.getDone()); - return main.prompt.done(doneTask); - } - - private static String handleRemove(Command cmd) throws NumberFormatException { - int idx = Integer.parseInt(cmd.getArgs()) - 1; - TaskList tasks = TaskList.getInstance(); - Duke main = Duke.getInstance(); - Task removedTask = tasks.remove(idx); - return main.prompt.remove(removedTask, tasks.size()); - } - - private static String handleList() { - TaskList tasks = TaskList.getInstance(); - Duke main = Duke.getInstance(); - return main.prompt.list(tasks.get()); - } - public static void main(String[] args) { Duke main = Duke.getInstance(); TaskList tasks = TaskList.getInstance(); @@ -74,24 +41,10 @@ public static void main(String[] args) { while (receiveInput && in.hasNext()) { try { Command command = main.parser.readInput(in); - StringBuilder output = new StringBuilder(); - switch (command.getType()) { - case ADD: - output.append(handleAdd(command)); - break; - case COMPLETE: - output.append(handleComplete(command)); - break; - case LIST: - output.append(handleList()); - break; - case REMOVE: - output.append(handleRemove(command)); - break; - case EXIT: + if (command.getType() == CommandType.EXIT) { receiveInput = false; - break; } + String output = command.execute(tasks, main.prompt); System.out.println(output); } catch (InvalidCommandException ice) { System.out.println(main.prompt.error(ice.getErrorHeader(), ice.getMessage())); @@ -103,7 +56,5 @@ public static void main(String[] args) { ex.printStackTrace(); } } - - System.out.println(main.prompt.exit()); } } diff --git a/src/main/java/classes/commands/AddCommand.java b/src/main/java/classes/commands/AddCommand.java new file mode 100644 index 00000000..1c2d62ce --- /dev/null +++ b/src/main/java/classes/commands/AddCommand.java @@ -0,0 +1,26 @@ +package classes.commands; + +import classes.enums.CommandType; +import classes.tasks.Task; +import classes.tasks.TaskFactory; +import classes.tasks.TaskList; +import exceptions.InvalidCommandFormatException; +import interfaces.Promptable; + +public class AddCommand extends Command { + public AddCommand(CommandType type, String keyword) { + super(type, keyword); + } + + public AddCommand(CommandType type, String keyword, String args) { + super(type, keyword, args); + } + + @Override + public String execute(TaskList tasks, Promptable prompt) throws InvalidCommandFormatException { + Task newTask = TaskFactory.getInstance(this); + tasks.add(newTask); + + return prompt.add(newTask, tasks.size()); + } +} diff --git a/src/main/java/classes/ui/Command.java b/src/main/java/classes/commands/Command.java similarity index 71% rename from src/main/java/classes/ui/Command.java rename to src/main/java/classes/commands/Command.java index 7ff9ea9d..b623933d 100644 --- a/src/main/java/classes/ui/Command.java +++ b/src/main/java/classes/commands/Command.java @@ -1,8 +1,12 @@ -package classes.ui; +package classes.commands; import classes.enums.CommandType; +import classes.tasks.Task; +import classes.tasks.TaskList; +import exceptions.InvalidCommandFormatException; +import interfaces.Promptable; -public class Command { +public abstract class Command { private CommandType type; private String keyword; private String args; @@ -40,4 +44,6 @@ public String getArgs() { public void setArgs(String args) { this.args = args; } + + public abstract String execute(TaskList tasks, Promptable prompt) throws InvalidCommandFormatException; } diff --git a/src/main/java/classes/commands/CompleteCommand.java b/src/main/java/classes/commands/CompleteCommand.java new file mode 100644 index 00000000..9df6c207 --- /dev/null +++ b/src/main/java/classes/commands/CompleteCommand.java @@ -0,0 +1,25 @@ +package classes.commands; + +import classes.enums.CommandType; +import classes.tasks.Task; +import classes.tasks.TaskList; +import exceptions.InvalidCommandFormatException; +import interfaces.Promptable; + +public class CompleteCommand extends Command { + public CompleteCommand(CommandType type, String keyword) { + super(type, keyword); + } + + public CompleteCommand(CommandType type, String keyword, String args) { + super(type, keyword, args); + } + + @Override + public String execute(TaskList tasks, Promptable prompt) throws InvalidCommandFormatException { + int idx = Integer.parseInt(getArgs()) - 1; + Task doneTask = tasks.getTask(idx); + doneTask.setDone(!doneTask.getDone()); + return prompt.done(doneTask); + } +} diff --git a/src/main/java/classes/commands/DeleteCommand.java b/src/main/java/classes/commands/DeleteCommand.java new file mode 100644 index 00000000..4a72eb7a --- /dev/null +++ b/src/main/java/classes/commands/DeleteCommand.java @@ -0,0 +1,24 @@ +package classes.commands; + +import classes.enums.CommandType; +import classes.tasks.Task; +import classes.tasks.TaskList; +import exceptions.InvalidCommandFormatException; +import interfaces.Promptable; + +public class DeleteCommand extends Command { + public DeleteCommand(CommandType type, String keyword) { + super(type, keyword); + } + + public DeleteCommand(CommandType type, String keyword, String args) { + super(type, keyword, args); + } + + @Override + public String execute(TaskList tasks, Promptable prompt) throws InvalidCommandFormatException { + int idx = Integer.parseInt(getArgs()) - 1; + Task removedTask = tasks.remove(idx); + return prompt.remove(removedTask, tasks.size()); + } +} diff --git a/src/main/java/classes/commands/ExitCommand.java b/src/main/java/classes/commands/ExitCommand.java new file mode 100644 index 00000000..23f631af --- /dev/null +++ b/src/main/java/classes/commands/ExitCommand.java @@ -0,0 +1,18 @@ +package classes.commands; + +import classes.enums.CommandType; +import classes.tasks.Task; +import classes.tasks.TaskList; +import exceptions.InvalidCommandFormatException; +import interfaces.Promptable; + +public class ExitCommand extends Command { + public ExitCommand(CommandType type, String keyword) { + super(type, keyword); + } + + @Override + public String execute(TaskList tasks, Promptable prompt) throws InvalidCommandFormatException { + return prompt.exit(); + } +} diff --git a/src/main/java/classes/commands/ListCommand.java b/src/main/java/classes/commands/ListCommand.java new file mode 100644 index 00000000..d9a60f24 --- /dev/null +++ b/src/main/java/classes/commands/ListCommand.java @@ -0,0 +1,22 @@ +package classes.commands; + +import classes.enums.CommandType; +import classes.tasks.Task; +import classes.tasks.TaskList; +import exceptions.InvalidCommandFormatException; +import interfaces.Promptable; + +public class ListCommand extends Command { + public ListCommand(CommandType type, String keyword) { + super(type, keyword); + } + + public ListCommand(CommandType type, String keyword, String args) { + super(type, keyword, args); + } + + @Override + public String execute(TaskList tasks, Promptable prompt) throws InvalidCommandFormatException { + return prompt.list(tasks.get()); + } +} diff --git a/src/main/java/classes/tasks/Task.java b/src/main/java/classes/tasks/Task.java index 6077016a..f30d02b9 100644 --- a/src/main/java/classes/tasks/Task.java +++ b/src/main/java/classes/tasks/Task.java @@ -2,7 +2,7 @@ import classes.enums.TaskType; -public class Task { +public abstract class Task { private String description; private Boolean isDone; private TaskType type; diff --git a/src/main/java/classes/tasks/TaskFactory.java b/src/main/java/classes/tasks/TaskFactory.java index 1b183563..3dbea5e8 100644 --- a/src/main/java/classes/tasks/TaskFactory.java +++ b/src/main/java/classes/tasks/TaskFactory.java @@ -1,7 +1,7 @@ package classes.tasks; import classes.enums.TaskType; -import classes.ui.Command; +import classes.commands.Command; import exceptions.InvalidCommandFormatException; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/classes/ui/Parser.java b/src/main/java/classes/ui/Parser.java index ae2d765e..ff26b7ea 100644 --- a/src/main/java/classes/ui/Parser.java +++ b/src/main/java/classes/ui/Parser.java @@ -1,5 +1,6 @@ package classes.ui; +import classes.commands.*; import classes.enums.CommandType; import exceptions.InvalidCommandException; import interfaces.IOParser; @@ -20,6 +21,8 @@ public class Parser implements IOParser { Pattern.compile("^(?bye)$", Pattern.CASE_INSENSITIVE); private static final Pattern DELETE_PATTERN = Pattern.compile("^\\b(?delete)\\b (?.+)$", Pattern.CASE_INSENSITIVE); + private final String KEY_ARGS = "args"; + private final String KEY_WORD = "keyword"; public Command readInput(Scanner sc) throws InvalidCommandException { @@ -31,23 +34,23 @@ public Command readInput(Scanner sc) Matcher deleteMatcher = DELETE_PATTERN.matcher(input); if (addMatcher.matches()) { - String args = addMatcher.group("args"); - String keyword = addMatcher.group("keyword"); - return new Command(CommandType.ADD, keyword, args); + String args = addMatcher.group(KEY_ARGS); + String keyword = addMatcher.group(KEY_WORD); + return new AddCommand(CommandType.ADD, keyword, args); } else if (doneMatcher.matches()) { - String args = doneMatcher.group("args"); - String keyword = doneMatcher.group("keyword"); - return new Command(CommandType.COMPLETE, keyword, args); + String args = doneMatcher.group(KEY_ARGS); + String keyword = doneMatcher.group(KEY_WORD); + return new CompleteCommand(CommandType.COMPLETE, keyword, args); } else if (listMatcher.matches()) { - String keyword = listMatcher.group("keyword"); - return new Command(CommandType.LIST, keyword); + String keyword = listMatcher.group(KEY_WORD); + return new ListCommand(CommandType.LIST, keyword); } else if (exitMatcher.matches()) { - String keyword = exitMatcher.group("keyword"); - return new Command(CommandType.EXIT, keyword); + String keyword = exitMatcher.group(KEY_WORD); + return new ExitCommand(CommandType.EXIT, keyword); } else if (deleteMatcher.matches()) { - String args = deleteMatcher.group("args"); - String keyword = deleteMatcher.group("keyword"); - return new Command(CommandType.REMOVE, keyword, args); + String args = deleteMatcher.group(KEY_ARGS); + String keyword = deleteMatcher.group(KEY_WORD); + return new DeleteCommand(CommandType.REMOVE, keyword, args); } throw new InvalidCommandException(InvalidCommandException.ERROR_GENERIC); } diff --git a/src/main/java/classes/ui/Prompt.java b/src/main/java/classes/ui/Prompt.java index ec5744f7..caabf6d0 100644 --- a/src/main/java/classes/ui/Prompt.java +++ b/src/main/java/classes/ui/Prompt.java @@ -59,17 +59,7 @@ public String list(Iterable inputs) { StringBuilder output = new StringBuilder(); int count = 1; for (Task input : inputs) { - String taskStr = ""; - if (input.getType() == TaskType.DEADLINE) { - Deadline down = (Deadline) input; - taskStr = down.toStatusString(); - } else if (input.getType() == TaskType.EVENT) { - Event down = (Event) input; - taskStr = down.toStatusString(); - } else if (input.getType() == TaskType.TODO) { - Todo down = (Todo) input; - taskStr = down.toStatusString(); - } + String taskStr = input.toStatusString(); output.append(formatLine(count + ". " + taskStr)); count++; } diff --git a/src/test/java/classes/tasks/TaskFactoryTest.java b/src/test/java/classes/tasks/TaskFactoryTest.java index ddeb2993..139039bf 100644 --- a/src/test/java/classes/tasks/TaskFactoryTest.java +++ b/src/test/java/classes/tasks/TaskFactoryTest.java @@ -1,8 +1,9 @@ package classes.tasks; +import classes.commands.AddCommand; import classes.enums.CommandType; import classes.enums.TaskType; -import classes.ui.Command; +import classes.commands.Command; import exceptions.InvalidCommandFormatException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -11,7 +12,7 @@ class TaskFactoryTest { @Test void getInstanceTodoSuccess() throws InvalidCommandFormatException { - Command addCommand = new Command(CommandType.ADD, + Command addCommand = new AddCommand(CommandType.ADD, TaskType.TODO.toString(), "read book"); Task task = TaskFactory.getInstance(addCommand); @@ -20,7 +21,7 @@ void getInstanceTodoSuccess() throws InvalidCommandFormatException { @Test void getInstanceEventSuccess() throws InvalidCommandFormatException { - Command addCommand = new Command(CommandType.ADD, + Command addCommand = new AddCommand(CommandType.ADD, TaskType.EVENT.toString(), "meet friend /at 2020-09-15"); @@ -30,7 +31,7 @@ void getInstanceEventSuccess() throws InvalidCommandFormatException { @Test void getInstanceDeadlineSuccess() throws InvalidCommandFormatException { - Command addCommand = new Command(CommandType.ADD, + Command addCommand = new AddCommand(CommandType.ADD, TaskType.DEADLINE.toString(), "return book /by 2021-09-20"); @@ -40,7 +41,7 @@ void getInstanceDeadlineSuccess() throws InvalidCommandFormatException { @Test void getInstanceTodoFail() { - Command addCommand = new Command(CommandType.ADD, + Command addCommand = new AddCommand(CommandType.ADD, TaskType.TODO.toString(), ""); InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { @@ -54,7 +55,7 @@ void getInstanceTodoFail() { @Test void getInstanceEventFail() { - Command addCommand = new Command(CommandType.ADD, + Command addCommand = new AddCommand(CommandType.ADD, TaskType.EVENT.toString(), "meet friend"); @@ -69,7 +70,7 @@ void getInstanceEventFail() { @Test void getInstanceDeadlineFail() { - Command addCommand = new Command(CommandType.ADD, + Command addCommand = new AddCommand(CommandType.ADD, TaskType.DEADLINE.toString(), "return book"); diff --git a/src/test/java/classes/tasks/TaskTest.java b/src/test/java/classes/tasks/TaskTest.java index acbaa764..15e81f80 100644 --- a/src/test/java/classes/tasks/TaskTest.java +++ b/src/test/java/classes/tasks/TaskTest.java @@ -7,7 +7,7 @@ public class TaskTest { @Test void getDescriptionSuccess() { - Task task = new Task(TaskType.TASK, "read book"); + Task task = new Todo("read book"); Assertions.assertEquals("read book", task.getDescription()); } } diff --git a/src/test/java/classes/ui/ParserTest.java b/src/test/java/classes/ui/ParserTest.java index 9fce136e..e708b42b 100644 --- a/src/test/java/classes/ui/ParserTest.java +++ b/src/test/java/classes/ui/ParserTest.java @@ -1,5 +1,6 @@ package classes.ui; +import classes.commands.Command; import classes.enums.CommandType; import exceptions.InvalidCommandException; import org.junit.jupiter.api.Test; From 1a2deb35d11ac534ec54b6f816822e9234e082d9 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Fri, 10 Sep 2021 17:40:20 +0800 Subject: [PATCH 29/59] Fix failing test --- src/main/java/Duke.java | 7 +++++-- src/main/java/classes/ui/Prompt.java | 4 ---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 8bb29c1c..832baac4 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -43,9 +43,10 @@ public static void main(String[] args) { Command command = main.parser.readInput(in); if (command.getType() == CommandType.EXIT) { receiveInput = false; + } else { + String output = command.execute(tasks, main.prompt); + System.out.println(output); } - String output = command.execute(tasks, main.prompt); - System.out.println(output); } catch (InvalidCommandException ice) { System.out.println(main.prompt.error(ice.getErrorHeader(), ice.getMessage())); } catch (InvalidCommandFormatException icfe) { @@ -56,5 +57,7 @@ public static void main(String[] args) { ex.printStackTrace(); } } + + System.out.println(main.prompt.exit()); } } diff --git a/src/main/java/classes/ui/Prompt.java b/src/main/java/classes/ui/Prompt.java index caabf6d0..b2e5687e 100644 --- a/src/main/java/classes/ui/Prompt.java +++ b/src/main/java/classes/ui/Prompt.java @@ -1,10 +1,6 @@ package classes.ui; -import classes.enums.TaskType; -import classes.tasks.Deadline; -import classes.tasks.Event; import classes.tasks.Task; -import classes.tasks.Todo; import interfaces.Promptable; public class Prompt implements Promptable { From 645de51d1cfa2e3fdc5221d83b5f792f2aeb0a8f Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 11 Sep 2021 14:34:04 +0800 Subject: [PATCH 30/59] Refactor to use Service pattern for Command, move to package com.alexooi.duke --- build.gradle | 2 +- .../java/classes/commands/AddCommand.java | 26 ------ src/main/java/classes/commands/Command.java | 49 ----------- .../classes/commands/CompleteCommand.java | 25 ------ .../java/classes/commands/DeleteCommand.java | 24 ------ .../java/classes/commands/ExitCommand.java | 18 ---- .../java/classes/commands/ListCommand.java | 22 ----- src/main/java/classes/enums/CommandType.java | 9 -- src/main/java/classes/ui/Parser.java | 57 ------------- .../java/{ => com/alexooi/duke}/Duke.java | 34 ++++---- .../com/alexooi/duke/commands/AddCommand.java | 39 +++++++++ .../com/alexooi/duke/commands/Command.java | 51 +++++++++++ .../duke/commands/CompleteCommand.java | 37 ++++++++ .../alexooi/duke/commands/DeleteCommand.java | 36 ++++++++ .../alexooi/duke/commands/ExitCommand.java | 39 +++++++++ .../alexooi/duke/commands/ListCommand.java | 34 ++++++++ .../alexooi/duke}/enums/TaskType.java | 2 +- .../exceptions/InvalidCommandException.java | 2 +- .../InvalidCommandFormatException.java | 8 +- .../com/alexooi/duke/interfaces/IOParser.java | 5 ++ .../duke/interfaces/OutputFormat.java} | 6 +- .../alexooi/duke}/tasks/Deadline.java | 4 +- .../alexooi/duke}/tasks/Event.java | 4 +- .../alexooi/duke}/tasks/Task.java | 4 +- .../alexooi/duke}/tasks/TaskFactory.java | 8 +- .../alexooi/duke}/tasks/TaskList.java | 3 +- .../alexooi/duke}/tasks/Todo.java | 4 +- .../alexooi/duke/ui/OutputFormatter.java} | 8 +- src/main/java/com/alexooi/duke/ui/Parser.java | 33 +++++++ src/main/java/interfaces/IOParser.java | 5 -- .../com.alexooi.duke.commands.Command | 5 ++ .../java/classes/tasks/TaskFactoryTest.java | 85 ------------------- src/test/java/classes/ui/ParserTest.java | 52 ------------ .../alexooi/duke/tasks/TaskFactoryTest.java | 77 +++++++++++++++++ .../alexooi/duke}/tasks/TaskTest.java | 3 +- .../java/com/alexooi/duke/ui/ParserTest.java | 62 ++++++++++++++ .../com.alexooi.duke.commands.Command | 5 ++ text-ui-test/runtest.bat | 2 +- text-ui-test/runtest.sh | 2 +- 39 files changed, 470 insertions(+), 421 deletions(-) delete mode 100644 src/main/java/classes/commands/AddCommand.java delete mode 100644 src/main/java/classes/commands/Command.java delete mode 100644 src/main/java/classes/commands/CompleteCommand.java delete mode 100644 src/main/java/classes/commands/DeleteCommand.java delete mode 100644 src/main/java/classes/commands/ExitCommand.java delete mode 100644 src/main/java/classes/commands/ListCommand.java delete mode 100644 src/main/java/classes/enums/CommandType.java delete mode 100644 src/main/java/classes/ui/Parser.java rename src/main/java/{ => com/alexooi/duke}/Duke.java (62%) create mode 100644 src/main/java/com/alexooi/duke/commands/AddCommand.java create mode 100644 src/main/java/com/alexooi/duke/commands/Command.java create mode 100644 src/main/java/com/alexooi/duke/commands/CompleteCommand.java create mode 100644 src/main/java/com/alexooi/duke/commands/DeleteCommand.java create mode 100644 src/main/java/com/alexooi/duke/commands/ExitCommand.java create mode 100644 src/main/java/com/alexooi/duke/commands/ListCommand.java rename src/main/java/{classes => com/alexooi/duke}/enums/TaskType.java (92%) rename src/main/java/{ => com/alexooi/duke}/exceptions/InvalidCommandException.java (91%) rename src/main/java/{ => com/alexooi/duke}/exceptions/InvalidCommandFormatException.java (65%) create mode 100644 src/main/java/com/alexooi/duke/interfaces/IOParser.java rename src/main/java/{interfaces/Promptable.java => com/alexooi/duke/interfaces/OutputFormat.java} (75%) rename src/main/java/{classes => com/alexooi/duke}/tasks/Deadline.java (87%) rename src/main/java/{classes => com/alexooi/duke}/tasks/Event.java (86%) rename src/main/java/{classes => com/alexooi/duke}/tasks/Task.java (93%) rename src/main/java/{classes => com/alexooi/duke}/tasks/TaskFactory.java (92%) rename src/main/java/{classes => com/alexooi/duke}/tasks/TaskList.java (93%) rename src/main/java/{classes => com/alexooi/duke}/tasks/Todo.java (76%) rename src/main/java/{classes/ui/Prompt.java => com/alexooi/duke/ui/OutputFormatter.java} (93%) create mode 100644 src/main/java/com/alexooi/duke/ui/Parser.java delete mode 100644 src/main/java/interfaces/IOParser.java create mode 100644 src/main/resources/META-INF/services/com.alexooi.duke.commands.Command delete mode 100644 src/test/java/classes/tasks/TaskFactoryTest.java delete mode 100644 src/test/java/classes/ui/ParserTest.java create mode 100644 src/test/java/com/alexooi/duke/tasks/TaskFactoryTest.java rename src/test/java/{classes => com/alexooi/duke}/tasks/TaskTest.java (83%) create mode 100644 src/test/java/com/alexooi/duke/ui/ParserTest.java create mode 100644 src/test/resources/META-INF/services/com.alexooi.duke.commands.Command diff --git a/build.gradle b/build.gradle index 379fe5ff..2d41afe3 100644 --- a/build.gradle +++ b/build.gradle @@ -27,4 +27,4 @@ dependencies { run { standardInput = System.in } -mainClassName = 'Duke' \ No newline at end of file +mainClassName = 'com.alexooi.duke.Duke' \ No newline at end of file diff --git a/src/main/java/classes/commands/AddCommand.java b/src/main/java/classes/commands/AddCommand.java deleted file mode 100644 index 1c2d62ce..00000000 --- a/src/main/java/classes/commands/AddCommand.java +++ /dev/null @@ -1,26 +0,0 @@ -package classes.commands; - -import classes.enums.CommandType; -import classes.tasks.Task; -import classes.tasks.TaskFactory; -import classes.tasks.TaskList; -import exceptions.InvalidCommandFormatException; -import interfaces.Promptable; - -public class AddCommand extends Command { - public AddCommand(CommandType type, String keyword) { - super(type, keyword); - } - - public AddCommand(CommandType type, String keyword, String args) { - super(type, keyword, args); - } - - @Override - public String execute(TaskList tasks, Promptable prompt) throws InvalidCommandFormatException { - Task newTask = TaskFactory.getInstance(this); - tasks.add(newTask); - - return prompt.add(newTask, tasks.size()); - } -} diff --git a/src/main/java/classes/commands/Command.java b/src/main/java/classes/commands/Command.java deleted file mode 100644 index b623933d..00000000 --- a/src/main/java/classes/commands/Command.java +++ /dev/null @@ -1,49 +0,0 @@ -package classes.commands; - -import classes.enums.CommandType; -import classes.tasks.Task; -import classes.tasks.TaskList; -import exceptions.InvalidCommandFormatException; -import interfaces.Promptable; - -public abstract class Command { - private CommandType type; - private String keyword; - private String args; - - public Command(CommandType type, String keyword) { - this.setKeyword(keyword); - this.setType(type); - } - - public Command(CommandType type, String keyword, String args) { - this(type, keyword); - this.setArgs(args); - } - - public CommandType getType() { - return type; - } - - public void setType(CommandType type) { - this.type = type; - } - - public String getKeyword() { - return keyword; - } - - public void setKeyword(String keyword) { - this.keyword = keyword; - } - - public String getArgs() { - return args; - } - - public void setArgs(String args) { - this.args = args; - } - - public abstract String execute(TaskList tasks, Promptable prompt) throws InvalidCommandFormatException; -} diff --git a/src/main/java/classes/commands/CompleteCommand.java b/src/main/java/classes/commands/CompleteCommand.java deleted file mode 100644 index 9df6c207..00000000 --- a/src/main/java/classes/commands/CompleteCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package classes.commands; - -import classes.enums.CommandType; -import classes.tasks.Task; -import classes.tasks.TaskList; -import exceptions.InvalidCommandFormatException; -import interfaces.Promptable; - -public class CompleteCommand extends Command { - public CompleteCommand(CommandType type, String keyword) { - super(type, keyword); - } - - public CompleteCommand(CommandType type, String keyword, String args) { - super(type, keyword, args); - } - - @Override - public String execute(TaskList tasks, Promptable prompt) throws InvalidCommandFormatException { - int idx = Integer.parseInt(getArgs()) - 1; - Task doneTask = tasks.getTask(idx); - doneTask.setDone(!doneTask.getDone()); - return prompt.done(doneTask); - } -} diff --git a/src/main/java/classes/commands/DeleteCommand.java b/src/main/java/classes/commands/DeleteCommand.java deleted file mode 100644 index 4a72eb7a..00000000 --- a/src/main/java/classes/commands/DeleteCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package classes.commands; - -import classes.enums.CommandType; -import classes.tasks.Task; -import classes.tasks.TaskList; -import exceptions.InvalidCommandFormatException; -import interfaces.Promptable; - -public class DeleteCommand extends Command { - public DeleteCommand(CommandType type, String keyword) { - super(type, keyword); - } - - public DeleteCommand(CommandType type, String keyword, String args) { - super(type, keyword, args); - } - - @Override - public String execute(TaskList tasks, Promptable prompt) throws InvalidCommandFormatException { - int idx = Integer.parseInt(getArgs()) - 1; - Task removedTask = tasks.remove(idx); - return prompt.remove(removedTask, tasks.size()); - } -} diff --git a/src/main/java/classes/commands/ExitCommand.java b/src/main/java/classes/commands/ExitCommand.java deleted file mode 100644 index 23f631af..00000000 --- a/src/main/java/classes/commands/ExitCommand.java +++ /dev/null @@ -1,18 +0,0 @@ -package classes.commands; - -import classes.enums.CommandType; -import classes.tasks.Task; -import classes.tasks.TaskList; -import exceptions.InvalidCommandFormatException; -import interfaces.Promptable; - -public class ExitCommand extends Command { - public ExitCommand(CommandType type, String keyword) { - super(type, keyword); - } - - @Override - public String execute(TaskList tasks, Promptable prompt) throws InvalidCommandFormatException { - return prompt.exit(); - } -} diff --git a/src/main/java/classes/commands/ListCommand.java b/src/main/java/classes/commands/ListCommand.java deleted file mode 100644 index d9a60f24..00000000 --- a/src/main/java/classes/commands/ListCommand.java +++ /dev/null @@ -1,22 +0,0 @@ -package classes.commands; - -import classes.enums.CommandType; -import classes.tasks.Task; -import classes.tasks.TaskList; -import exceptions.InvalidCommandFormatException; -import interfaces.Promptable; - -public class ListCommand extends Command { - public ListCommand(CommandType type, String keyword) { - super(type, keyword); - } - - public ListCommand(CommandType type, String keyword, String args) { - super(type, keyword, args); - } - - @Override - public String execute(TaskList tasks, Promptable prompt) throws InvalidCommandFormatException { - return prompt.list(tasks.get()); - } -} diff --git a/src/main/java/classes/enums/CommandType.java b/src/main/java/classes/enums/CommandType.java deleted file mode 100644 index e0292a04..00000000 --- a/src/main/java/classes/enums/CommandType.java +++ /dev/null @@ -1,9 +0,0 @@ -package classes.enums; - -public enum CommandType { - ADD, - COMPLETE, - LIST, - EXIT, - REMOVE -} diff --git a/src/main/java/classes/ui/Parser.java b/src/main/java/classes/ui/Parser.java deleted file mode 100644 index ff26b7ea..00000000 --- a/src/main/java/classes/ui/Parser.java +++ /dev/null @@ -1,57 +0,0 @@ -package classes.ui; - -import classes.commands.*; -import classes.enums.CommandType; -import exceptions.InvalidCommandException; -import interfaces.IOParser; -import java.util.Scanner; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class Parser implements IOParser { - private static final Pattern ADD_PATTERN = - Pattern.compile( - "^(?\\bdeadline\\b|\\bevent\\b|\\btodo\\b)\\s?(?.*)$", - Pattern.CASE_INSENSITIVE); - private static final Pattern DONE_PATTERN = - Pattern.compile("^\\b(?done)\\b (?.+)$", Pattern.CASE_INSENSITIVE); - private static final Pattern LIST_PATTERN = - Pattern.compile("^(?list)$", Pattern.CASE_INSENSITIVE); - private static final Pattern EXIT_PATTERN = - Pattern.compile("^(?bye)$", Pattern.CASE_INSENSITIVE); - private static final Pattern DELETE_PATTERN = - Pattern.compile("^\\b(?delete)\\b (?.+)$", Pattern.CASE_INSENSITIVE); - private final String KEY_ARGS = "args"; - private final String KEY_WORD = "keyword"; - - public Command readInput(Scanner sc) - throws InvalidCommandException { - String input = sc.nextLine(); - Matcher addMatcher = ADD_PATTERN.matcher(input); - Matcher doneMatcher = DONE_PATTERN.matcher(input); - Matcher listMatcher = LIST_PATTERN.matcher(input); - Matcher exitMatcher = EXIT_PATTERN.matcher(input); - Matcher deleteMatcher = DELETE_PATTERN.matcher(input); - - if (addMatcher.matches()) { - String args = addMatcher.group(KEY_ARGS); - String keyword = addMatcher.group(KEY_WORD); - return new AddCommand(CommandType.ADD, keyword, args); - } else if (doneMatcher.matches()) { - String args = doneMatcher.group(KEY_ARGS); - String keyword = doneMatcher.group(KEY_WORD); - return new CompleteCommand(CommandType.COMPLETE, keyword, args); - } else if (listMatcher.matches()) { - String keyword = listMatcher.group(KEY_WORD); - return new ListCommand(CommandType.LIST, keyword); - } else if (exitMatcher.matches()) { - String keyword = exitMatcher.group(KEY_WORD); - return new ExitCommand(CommandType.EXIT, keyword); - } else if (deleteMatcher.matches()) { - String args = deleteMatcher.group(KEY_ARGS); - String keyword = deleteMatcher.group(KEY_WORD); - return new DeleteCommand(CommandType.REMOVE, keyword, args); - } - throw new InvalidCommandException(InvalidCommandException.ERROR_GENERIC); - } -} diff --git a/src/main/java/Duke.java b/src/main/java/com/alexooi/duke/Duke.java similarity index 62% rename from src/main/java/Duke.java rename to src/main/java/com/alexooi/duke/Duke.java index 832baac4..ea400329 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/com/alexooi/duke/Duke.java @@ -1,30 +1,31 @@ -import classes.enums.CommandType; -import classes.tasks.Task; -import classes.tasks.TaskFactory; -import classes.tasks.TaskList; -import classes.commands.Command; -import classes.ui.Parser; -import classes.ui.Prompt; -import exceptions.InvalidCommandException; -import exceptions.InvalidCommandFormatException; -import interfaces.IOParser; -import interfaces.Promptable; +package com.alexooi.duke; + +import com.alexooi.duke.commands.Command; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.ui.Parser; +import com.alexooi.duke.ui.OutputFormatter; +import com.alexooi.duke.exceptions.InvalidCommandException; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.IOParser; +import com.alexooi.duke.interfaces.OutputFormat; import java.util.Scanner; +import java.util.ServiceLoader; public class Duke { private static Duke instance; - private final Promptable prompt; + private final OutputFormat prompt; private final IOParser parser; - private Duke(Promptable prompt, IOParser parser) { + private Duke(OutputFormat prompt, IOParser parser) { this.prompt = prompt; this.parser = parser; } public static Duke getInstance() { if (instance == null) { - instance = new Duke(new Prompt(), new Parser()); + instance = new Duke(new OutputFormatter(), new Parser()); } return instance; @@ -33,6 +34,7 @@ public static Duke getInstance() { public static void main(String[] args) { Duke main = Duke.getInstance(); TaskList tasks = TaskList.getInstance(); + ServiceLoader commandLoader = ServiceLoader.load(Command.class); System.out.println(main.prompt.start()); Scanner in = new Scanner(System.in); @@ -40,8 +42,8 @@ public static void main(String[] args) { while (receiveInput && in.hasNext()) { try { - Command command = main.parser.readInput(in); - if (command.getType() == CommandType.EXIT) { + Command command = main.parser.readInput(in, commandLoader); + if (command.isExit()) { receiveInput = false; } else { String output = command.execute(tasks, main.prompt); diff --git a/src/main/java/com/alexooi/duke/commands/AddCommand.java b/src/main/java/com/alexooi/duke/commands/AddCommand.java new file mode 100644 index 00000000..3719e287 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/AddCommand.java @@ -0,0 +1,39 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskFactory; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.util.regex.Pattern; + +public class AddCommand extends Command { + private final String REGEX_PATTERN = "^(\\bdeadline\\b|\\bevent\\b|\\btodo\\b)\\s?(.*)$"; + + public AddCommand() { + super(); + } + + public AddCommand(String keyword, String args) { + super(keyword, args); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public AddCommand build() { + return new AddCommand(getKeyword(), getArgs()); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + Task newTask = TaskFactory.getInstance(this); + tasks.add(newTask); + + return prompt.add(newTask, tasks.size()); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/Command.java b/src/main/java/com/alexooi/duke/commands/Command.java new file mode 100644 index 00000000..980b07fa --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/Command.java @@ -0,0 +1,51 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; +import java.util.regex.Pattern; + +public abstract class Command { + private String keyword; + private String args; + + public Command() { + + } + + public Command(String keyword) { + this.setKeyword(keyword); + } + + public Command(String keyword, String args) { + this(keyword); + this.setArgs(args); + } + + public String getKeyword() { + return keyword; + } + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + + public String getArgs() { + return args; + } + + public void setArgs(String args) { + this.args = args; + } + + public abstract String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException; + + public abstract Pattern getRegexPattern(); + + public abstract Command build(); + + public boolean isExit() { + return false; + }; +} diff --git a/src/main/java/com/alexooi/duke/commands/CompleteCommand.java b/src/main/java/com/alexooi/duke/commands/CompleteCommand.java new file mode 100644 index 00000000..efb9c46f --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/CompleteCommand.java @@ -0,0 +1,37 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.util.regex.Pattern; + +public class CompleteCommand extends Command { + private final String REGEX_PATTERN = "^\\b(done)\\b (.+)$"; + + public CompleteCommand() { + } + + public CompleteCommand(String keyword, String args) { + super(keyword, args); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public CompleteCommand build() { + return new CompleteCommand(getKeyword(), getArgs()); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + int idx = Integer.parseInt(getArgs()) - 1; + Task doneTask = tasks.getTask(idx); + doneTask.setDone(!doneTask.getDone()); + return prompt.done(doneTask); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/DeleteCommand.java b/src/main/java/com/alexooi/duke/commands/DeleteCommand.java new file mode 100644 index 00000000..77d82903 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/DeleteCommand.java @@ -0,0 +1,36 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.util.regex.Pattern; + +public class DeleteCommand extends Command { + private final String REGEX_PATTERN = "^\\b(delete)\\b (.+)$"; + + public DeleteCommand() { + } + + public DeleteCommand(String keyword, String args) { + super(keyword, args); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public DeleteCommand build() { + return new DeleteCommand(getKeyword(), getArgs()); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + int idx = Integer.parseInt(getArgs()) - 1; + Task removedTask = tasks.remove(idx); + return prompt.remove(removedTask, tasks.size()); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/ExitCommand.java b/src/main/java/com/alexooi/duke/commands/ExitCommand.java new file mode 100644 index 00000000..9ff678e0 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/ExitCommand.java @@ -0,0 +1,39 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.util.regex.Pattern; + +public class ExitCommand extends Command { + private final String REGEX_PATTERN = "^(bye)$"; + + public ExitCommand() { + } + + public ExitCommand(String keyword) { + super(keyword); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public ExitCommand build() { + return new ExitCommand(getKeyword()); + } + + @Override + public boolean isExit() { + return true; + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + return prompt.exit(); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/ListCommand.java b/src/main/java/com/alexooi/duke/commands/ListCommand.java new file mode 100644 index 00000000..9753e099 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/ListCommand.java @@ -0,0 +1,34 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.util.regex.Pattern; + +public class ListCommand extends Command { + private final String REGEX_PATTERN = "^(list)$"; + + public ListCommand() { + } + + public ListCommand(String keyword) { + super(keyword); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public ListCommand build() { + return new ListCommand(getKeyword()); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + return prompt.list(tasks.get()); + } +} diff --git a/src/main/java/classes/enums/TaskType.java b/src/main/java/com/alexooi/duke/enums/TaskType.java similarity index 92% rename from src/main/java/classes/enums/TaskType.java rename to src/main/java/com/alexooi/duke/enums/TaskType.java index fcecfd11..f2349b53 100644 --- a/src/main/java/classes/enums/TaskType.java +++ b/src/main/java/com/alexooi/duke/enums/TaskType.java @@ -1,4 +1,4 @@ -package classes.enums; +package com.alexooi.duke.enums; public enum TaskType { TODO("todo"), diff --git a/src/main/java/exceptions/InvalidCommandException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandException.java similarity index 91% rename from src/main/java/exceptions/InvalidCommandException.java rename to src/main/java/com/alexooi/duke/exceptions/InvalidCommandException.java index c615e9a2..f67ffb9c 100644 --- a/src/main/java/exceptions/InvalidCommandException.java +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandException.java @@ -1,4 +1,4 @@ -package exceptions; +package com.alexooi.duke.exceptions; public class InvalidCommandException extends Exception { private static final String ERROR_PREFIX = "Invalid Command:"; diff --git a/src/main/java/exceptions/InvalidCommandFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java similarity index 65% rename from src/main/java/exceptions/InvalidCommandFormatException.java rename to src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java index b36bf537..8108a9e0 100644 --- a/src/main/java/exceptions/InvalidCommandFormatException.java +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java @@ -1,13 +1,13 @@ -package exceptions; +package com.alexooi.duke.exceptions; public class InvalidCommandFormatException extends Throwable { private static final String ERROR_PREFIX = "Invalid Command Format:"; public static final String ERROR_DEADLINE = - "classes.tasks.Deadline should be in the format: deadline /by ."; + "Deadline should be in the format: deadline /by ."; public static final String ERROR_EVENT = - "classes.tasks.Event should be in the format: event /at ."; + "Event should be in the format: event /at ."; public static final String ERROR_TODO = - "classes.tasks.Todo should be in the format: todo ."; + "Todo should be in the format: todo ."; private String errorMessage; diff --git a/src/main/java/com/alexooi/duke/interfaces/IOParser.java b/src/main/java/com/alexooi/duke/interfaces/IOParser.java new file mode 100644 index 00000000..c044b499 --- /dev/null +++ b/src/main/java/com/alexooi/duke/interfaces/IOParser.java @@ -0,0 +1,5 @@ +package com.alexooi.duke.interfaces; + +public interface IOParser { + T readInput(S sc, Iterable it) throws Exception; +} diff --git a/src/main/java/interfaces/Promptable.java b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java similarity index 75% rename from src/main/java/interfaces/Promptable.java rename to src/main/java/com/alexooi/duke/interfaces/OutputFormat.java index 0712c76f..a6b087c3 100644 --- a/src/main/java/interfaces/Promptable.java +++ b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java @@ -1,8 +1,6 @@ -package interfaces; +package com.alexooi.duke.interfaces; -import classes.tasks.Task; - -public interface Promptable { +public interface OutputFormat { String start(); String add(T printable, int length); diff --git a/src/main/java/classes/tasks/Deadline.java b/src/main/java/com/alexooi/duke/tasks/Deadline.java similarity index 87% rename from src/main/java/classes/tasks/Deadline.java rename to src/main/java/com/alexooi/duke/tasks/Deadline.java index 64b14060..c165d8bb 100644 --- a/src/main/java/classes/tasks/Deadline.java +++ b/src/main/java/com/alexooi/duke/tasks/Deadline.java @@ -1,6 +1,6 @@ -package classes.tasks; +package com.alexooi.duke.tasks; -import classes.enums.TaskType; +import com.alexooi.duke.enums.TaskType; public class Deadline extends Task { private String dueDate; diff --git a/src/main/java/classes/tasks/Event.java b/src/main/java/com/alexooi/duke/tasks/Event.java similarity index 86% rename from src/main/java/classes/tasks/Event.java rename to src/main/java/com/alexooi/duke/tasks/Event.java index b9eddfe8..09995849 100644 --- a/src/main/java/classes/tasks/Event.java +++ b/src/main/java/com/alexooi/duke/tasks/Event.java @@ -1,6 +1,6 @@ -package classes.tasks; +package com.alexooi.duke.tasks; -import classes.enums.TaskType; +import com.alexooi.duke.enums.TaskType; public class Event extends Task { private String timing; diff --git a/src/main/java/classes/tasks/Task.java b/src/main/java/com/alexooi/duke/tasks/Task.java similarity index 93% rename from src/main/java/classes/tasks/Task.java rename to src/main/java/com/alexooi/duke/tasks/Task.java index f30d02b9..64797906 100644 --- a/src/main/java/classes/tasks/Task.java +++ b/src/main/java/com/alexooi/duke/tasks/Task.java @@ -1,6 +1,6 @@ -package classes.tasks; +package com.alexooi.duke.tasks; -import classes.enums.TaskType; +import com.alexooi.duke.enums.TaskType; public abstract class Task { private String description; diff --git a/src/main/java/classes/tasks/TaskFactory.java b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java similarity index 92% rename from src/main/java/classes/tasks/TaskFactory.java rename to src/main/java/com/alexooi/duke/tasks/TaskFactory.java index 3dbea5e8..c772402b 100644 --- a/src/main/java/classes/tasks/TaskFactory.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java @@ -1,8 +1,8 @@ -package classes.tasks; +package com.alexooi.duke.tasks; -import classes.enums.TaskType; -import classes.commands.Command; -import exceptions.InvalidCommandFormatException; +import com.alexooi.duke.enums.TaskType; +import com.alexooi.duke.commands.Command; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/classes/tasks/TaskList.java b/src/main/java/com/alexooi/duke/tasks/TaskList.java similarity index 93% rename from src/main/java/classes/tasks/TaskList.java rename to src/main/java/com/alexooi/duke/tasks/TaskList.java index 456fa480..3a6ff466 100644 --- a/src/main/java/classes/tasks/TaskList.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskList.java @@ -1,7 +1,6 @@ -package classes.tasks; +package com.alexooi.duke.tasks; import java.util.ArrayList; -import java.util.Optional; public class TaskList { private static TaskList instance; diff --git a/src/main/java/classes/tasks/Todo.java b/src/main/java/com/alexooi/duke/tasks/Todo.java similarity index 76% rename from src/main/java/classes/tasks/Todo.java rename to src/main/java/com/alexooi/duke/tasks/Todo.java index a93a2e84..67636ff7 100644 --- a/src/main/java/classes/tasks/Todo.java +++ b/src/main/java/com/alexooi/duke/tasks/Todo.java @@ -1,6 +1,6 @@ -package classes.tasks; +package com.alexooi.duke.tasks; -import classes.enums.TaskType; +import com.alexooi.duke.enums.TaskType; public class Todo extends Task { public Todo(String description) { diff --git a/src/main/java/classes/ui/Prompt.java b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java similarity index 93% rename from src/main/java/classes/ui/Prompt.java rename to src/main/java/com/alexooi/duke/ui/OutputFormatter.java index b2e5687e..70892644 100644 --- a/src/main/java/classes/ui/Prompt.java +++ b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java @@ -1,9 +1,9 @@ -package classes.ui; +package com.alexooi.duke.ui; -import classes.tasks.Task; -import interfaces.Promptable; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.interfaces.OutputFormat; -public class Prompt implements Promptable { +public class OutputFormatter implements OutputFormat { private static final String PADDING = " "; private static final String HORIZONTAL_LINE = "_________________________________________"; private static final String START = "Hello! I'm Duke\n What can I do for you?"; diff --git a/src/main/java/com/alexooi/duke/ui/Parser.java b/src/main/java/com/alexooi/duke/ui/Parser.java new file mode 100644 index 00000000..62323a5f --- /dev/null +++ b/src/main/java/com/alexooi/duke/ui/Parser.java @@ -0,0 +1,33 @@ +package com.alexooi.duke.ui; + +import com.alexooi.duke.commands.Command; +import com.alexooi.duke.exceptions.InvalidCommandException; +import com.alexooi.duke.interfaces.IOParser; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Parser implements IOParser { + public Command readInput(Scanner sc, Iterable commands) + throws InvalidCommandException { + String input = sc.nextLine(); + + for (Command cmd : commands) { + Pattern servicePattern = cmd.getRegexPattern(); + Matcher patternMatcher = servicePattern.matcher(input); + + if (patternMatcher.matches()) { + String keyword = patternMatcher.group(1); + cmd.setKeyword(keyword); + if (patternMatcher.groupCount() > 1) { + String args = patternMatcher.group(2); + cmd.setArgs(args); + } + return cmd.build(); + } + } + + throw new InvalidCommandException(InvalidCommandException.ERROR_GENERIC); + } +} diff --git a/src/main/java/interfaces/IOParser.java b/src/main/java/interfaces/IOParser.java deleted file mode 100644 index 52fe1879..00000000 --- a/src/main/java/interfaces/IOParser.java +++ /dev/null @@ -1,5 +0,0 @@ -package interfaces; - -public interface IOParser { - T readInput(S sc) throws Exception; -} diff --git a/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command b/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command new file mode 100644 index 00000000..8f97627e --- /dev/null +++ b/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command @@ -0,0 +1,5 @@ +com.alexooi.duke.commands.AddCommand +com.alexooi.duke.commands.CompleteCommand +com.alexooi.duke.commands.DeleteCommand +com.alexooi.duke.commands.ExitCommand +com.alexooi.duke.commands.ListCommand \ No newline at end of file diff --git a/src/test/java/classes/tasks/TaskFactoryTest.java b/src/test/java/classes/tasks/TaskFactoryTest.java deleted file mode 100644 index 139039bf..00000000 --- a/src/test/java/classes/tasks/TaskFactoryTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package classes.tasks; - -import classes.commands.AddCommand; -import classes.enums.CommandType; -import classes.enums.TaskType; -import classes.commands.Command; -import exceptions.InvalidCommandFormatException; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class TaskFactoryTest { - - @Test - void getInstanceTodoSuccess() throws InvalidCommandFormatException { - Command addCommand = new AddCommand(CommandType.ADD, - TaskType.TODO.toString(), "read book"); - - Task task = TaskFactory.getInstance(addCommand); - Assertions.assertEquals(TaskType.TODO, task.getType(), "TaskFactory assertEquals Todo failed"); - } - - @Test - void getInstanceEventSuccess() throws InvalidCommandFormatException { - Command addCommand = new AddCommand(CommandType.ADD, - TaskType.EVENT.toString(), - "meet friend /at 2020-09-15"); - - Task task = TaskFactory.getInstance(addCommand); - Assertions.assertEquals(TaskType.EVENT, task.getType(), "TaskFactory assertEquals Event failed"); - } - - @Test - void getInstanceDeadlineSuccess() throws InvalidCommandFormatException { - Command addCommand = new AddCommand(CommandType.ADD, - TaskType.DEADLINE.toString(), - "return book /by 2021-09-20"); - - Task task = TaskFactory.getInstance(addCommand); - Assertions.assertEquals(TaskType.DEADLINE, task.getType(), "TaskFactory assertEquals Deadline failed"); - } - - @Test - void getInstanceTodoFail() { - Command addCommand = new AddCommand(CommandType.ADD, - TaskType.TODO.toString(), ""); - - InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { - TaskFactory.getInstance(addCommand); - }, "TaskFactory assertThrows Todo failed."); - - Assertions.assertEquals(InvalidCommandFormatException.ERROR_TODO, - icfe.toString(), - "TaskFactory assertEquals ERROR_TODO failed."); - } - - @Test - void getInstanceEventFail() { - Command addCommand = new AddCommand(CommandType.ADD, - TaskType.EVENT.toString(), - "meet friend"); - - InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { - TaskFactory.getInstance(addCommand); - }, "TaskFactory assertThrows Event failed."); - - Assertions.assertEquals(InvalidCommandFormatException.ERROR_EVENT, - icfe.toString(), - "TaskFactory assertEquals ERROR_EVENT failed."); - } - - @Test - void getInstanceDeadlineFail() { - Command addCommand = new AddCommand(CommandType.ADD, - TaskType.DEADLINE.toString(), - "return book"); - - InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { - TaskFactory.getInstance(addCommand); - }, "TaskFactory assertThrows Deadline failed."); - - Assertions.assertEquals(InvalidCommandFormatException.ERROR_DEADLINE, - icfe.toString(), - "TaskFactory assertEquals ERROR_EVENT failed."); - } -} \ No newline at end of file diff --git a/src/test/java/classes/ui/ParserTest.java b/src/test/java/classes/ui/ParserTest.java deleted file mode 100644 index e708b42b..00000000 --- a/src/test/java/classes/ui/ParserTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package classes.ui; - -import classes.commands.Command; -import classes.enums.CommandType; -import exceptions.InvalidCommandException; -import org.junit.jupiter.api.Test; - -import java.util.Scanner; - -import static org.junit.jupiter.api.Assertions.*; - -class ParserTest { - private static Parser parser = new Parser(); - - @Test - void readInput_CommandTodoSuccess() throws InvalidCommandException { - String input = "todo read book"; - Scanner sc = new Scanner(input).useDelimiter("\n"); - Command cmd = parser.readInput(sc); - assertEquals(CommandType.ADD, cmd.getType(), "Parser assertEquals Todo CommandType ADD failed."); - assertEquals("todo", cmd.getKeyword(), "Parser assertEquals Todo Keyword failed."); - assertEquals("read book", cmd.getArgs(), "Parser assertEquals Todo Args failed."); - } - - @Test - void readInput_CommandTodoFail() { - String input = "todo1"; - Scanner sc = new Scanner(input).useDelimiter("\n"); - assertThrows(InvalidCommandException.class, () -> { - parser.readInput(sc); - }); - } - - @Test - void readInput_CommandEventSuccess() throws InvalidCommandException { - String input = "event meet friend /at 2021-09-15"; - Scanner sc = new Scanner(input).useDelimiter("\n"); - Command cmd = parser.readInput(sc); - assertEquals(CommandType.ADD, cmd.getType(), "Parser assertEquals Event CommandType ADD failed."); - assertEquals("event", cmd.getKeyword(), "Parser assertEquals Event Keyword failed."); - assertEquals("meet friend /at 2021-09-15", cmd.getArgs(), "Parser assertEquals Event Args failed."); - } - - @Test - void readInput_CommandEventFail() { - String input = "eventmeet friend /at 2021-09-15"; - Scanner sc = new Scanner(input).useDelimiter("\n"); - assertThrows(InvalidCommandException.class, () -> { - parser.readInput(sc); - }); - } -} \ No newline at end of file diff --git a/src/test/java/com/alexooi/duke/tasks/TaskFactoryTest.java b/src/test/java/com/alexooi/duke/tasks/TaskFactoryTest.java new file mode 100644 index 00000000..fbaa3fb7 --- /dev/null +++ b/src/test/java/com/alexooi/duke/tasks/TaskFactoryTest.java @@ -0,0 +1,77 @@ +package com.alexooi.duke.tasks; + +class TaskFactoryTest { + +// @Test +// void getInstanceTodoSuccess() throws InvalidCommandFormatException { +// Command addCommand = new AddCommand(CommandType.ADD, +// TaskType.TODO.toString(), "read book"); +// +// Task task = TaskFactory.getInstance(addCommand); +// Assertions.assertEquals(TaskType.TODO, task.getType(), "TaskFactory assertEquals Todo failed"); +// } +// +// @Test +// void getInstanceEventSuccess() throws InvalidCommandFormatException { +// Command addCommand = new AddCommand(CommandType.ADD, +// TaskType.EVENT.toString(), +// "meet friend /at 2020-09-15"); +// +// Task task = TaskFactory.getInstance(addCommand); +// Assertions.assertEquals(TaskType.EVENT, task.getType(), "TaskFactory assertEquals Event failed"); +// } +// +// @Test +// void getInstanceDeadlineSuccess() throws InvalidCommandFormatException { +// Command addCommand = new AddCommand(CommandType.ADD, +// TaskType.DEADLINE.toString(), +// "return book /by 2021-09-20"); +// +// Task task = TaskFactory.getInstance(addCommand); +// Assertions.assertEquals(TaskType.DEADLINE, task.getType(), "TaskFactory assertEquals Deadline failed"); +// } +// +// @Test +// void getInstanceTodoFail() { +// Command addCommand = new AddCommand(CommandType.ADD, +// TaskType.TODO.toString(), ""); +// +// InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { +// TaskFactory.getInstance(addCommand); +// }, "TaskFactory assertThrows Todo failed."); +// +// Assertions.assertEquals(InvalidCommandFormatException.ERROR_TODO, +// icfe.toString(), +// "TaskFactory assertEquals ERROR_TODO failed."); +// } +// +// @Test +// void getInstanceEventFail() { +// Command addCommand = new AddCommand(CommandType.ADD, +// TaskType.EVENT.toString(), +// "meet friend"); +// +// InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { +// TaskFactory.getInstance(addCommand); +// }, "TaskFactory assertThrows Event failed."); +// +// Assertions.assertEquals(InvalidCommandFormatException.ERROR_EVENT, +// icfe.toString(), +// "TaskFactory assertEquals ERROR_EVENT failed."); +// } +// +// @Test +// void getInstanceDeadlineFail() { +// Command addCommand = new AddCommand(CommandType.ADD, +// TaskType.DEADLINE.toString(), +// "return book"); +// +// InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { +// TaskFactory.getInstance(addCommand); +// }, "TaskFactory assertThrows Deadline failed."); +// +// Assertions.assertEquals(InvalidCommandFormatException.ERROR_DEADLINE, +// icfe.toString(), +// "TaskFactory assertEquals ERROR_EVENT failed."); +// } +} \ No newline at end of file diff --git a/src/test/java/classes/tasks/TaskTest.java b/src/test/java/com/alexooi/duke/tasks/TaskTest.java similarity index 83% rename from src/test/java/classes/tasks/TaskTest.java rename to src/test/java/com/alexooi/duke/tasks/TaskTest.java index 15e81f80..abbeab70 100644 --- a/src/test/java/classes/tasks/TaskTest.java +++ b/src/test/java/com/alexooi/duke/tasks/TaskTest.java @@ -1,6 +1,5 @@ -package classes.tasks; +package com.alexooi.duke.tasks; -import classes.enums.TaskType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/alexooi/duke/ui/ParserTest.java b/src/test/java/com/alexooi/duke/ui/ParserTest.java new file mode 100644 index 00000000..b5c1537d --- /dev/null +++ b/src/test/java/com/alexooi/duke/ui/ParserTest.java @@ -0,0 +1,62 @@ +package com.alexooi.duke.ui; + +import com.alexooi.duke.commands.Command; +import com.alexooi.duke.exceptions.InvalidCommandException; +import org.junit.jupiter.api.Test; +import java.util.Scanner; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.*; + +class ParserTest { + private static final Parser parser = new Parser(); + private final ServiceLoader commandLoader = ServiceLoader.load(Command.class); + + @Test + void readInput_CommandTodoSuccess() throws InvalidCommandException { + String input = "todo read book"; + Scanner sc = new Scanner(input).useDelimiter("\n"); + Command cmd = parser.readInput(sc, commandLoader); + assertEquals("todo", cmd.getKeyword(), "Parser assertEquals Todo Keyword failed."); + assertEquals("read book", cmd.getArgs(), "Parser assertEquals Todo Args failed."); + } + + @Test + void readInput_CommandTodoFail() { + String input = "todo1"; + Scanner sc = new Scanner(input).useDelimiter("\n"); + assertThrows(InvalidCommandException.class, () -> parser.readInput(sc, commandLoader)); + } + + @Test + void readInput_CommandDeadlineSuccess() throws InvalidCommandException { + String input = "deadline return book /by 2021-09-15"; + Scanner sc = new Scanner(input).useDelimiter("\n"); + Command cmd = parser.readInput(sc, commandLoader); + assertEquals("deadline", cmd.getKeyword(), "Parser assertEquals Deadline Keyword failed."); + assertEquals("return book /by 2021-09-15", cmd.getArgs(), "Parser assertEquals Deadline Args failed."); + } + + @Test + void readInput_CommandDeadlineFail() { + String input = "deadlinereturn book /by 2021-09-15"; + Scanner sc = new Scanner(input).useDelimiter("\n"); + assertThrows(InvalidCommandException.class, () -> parser.readInput(sc, commandLoader)); + } + + @Test + void readInput_CommandEventSuccess() throws InvalidCommandException { + String input = "event meet friend /at 2021-09-15"; + Scanner sc = new Scanner(input).useDelimiter("\n"); + Command cmd = parser.readInput(sc, commandLoader); + assertEquals("event", cmd.getKeyword(), "Parser assertEquals Event Keyword failed."); + assertEquals("meet friend /at 2021-09-15", cmd.getArgs(), "Parser assertEquals Event Args failed."); + } + + @Test + void readInput_CommandEventFail() { + String input = "eventmeet friend /at 2021-09-15"; + Scanner sc = new Scanner(input).useDelimiter("\n"); + assertThrows(InvalidCommandException.class, () -> parser.readInput(sc, commandLoader)); + } +} \ No newline at end of file diff --git a/src/test/resources/META-INF/services/com.alexooi.duke.commands.Command b/src/test/resources/META-INF/services/com.alexooi.duke.commands.Command new file mode 100644 index 00000000..8f97627e --- /dev/null +++ b/src/test/resources/META-INF/services/com.alexooi.duke.commands.Command @@ -0,0 +1,5 @@ +com.alexooi.duke.commands.AddCommand +com.alexooi.duke.commands.CompleteCommand +com.alexooi.duke.commands.DeleteCommand +com.alexooi.duke.commands.ExitCommand +com.alexooi.duke.commands.ListCommand \ No newline at end of file diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index cecfdb11..dcd80e64 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.Duke < input.txt > ACTUAL.TXT +java -classpath ..\bin com.alexooi.duke.Duke.com.alexooi.duke.Duke < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 77494a4e..6bf32a52 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,7 +13,7 @@ then fi # compile the code into the bin folder, terminates if error occurred -if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java ../src/main/java/*/*.java ../src/main/java/*/*/*.java +if ! find ../src/main/java -name "*.java" -print | xargs javac -cp ../src/main/java -Xlint:none -d ../bin then echo "********** BUILD FAILURE **********" exit 1 From 7d0ee2f5678e9236b3d386ff03104227ae0978e1 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 11 Sep 2021 16:27:26 +0800 Subject: [PATCH 31/59] Fix shellscript to also copy resources --- text-ui-test/runtest.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 6bf32a52..8f597ab7 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,6 +13,7 @@ then fi # compile the code into the bin folder, terminates if error occurred +cp -R ../src/main/resources/ ../bin if ! find ../src/main/java -name "*.java" -print | xargs javac -cp ../src/main/java -Xlint:none -d ../bin then echo "********** BUILD FAILURE **********" @@ -20,7 +21,7 @@ then fi # 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 -cp ../bin com.alexooi.duke.Duke < input.txt > ACTUAL.TXT # convert to UNIX format cp EXPECTED.TXT EXPECTED-UNIX.TXT From f3a4c137742a086047852371e7f4d69592e32233 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 11 Sep 2021 16:35:26 +0800 Subject: [PATCH 32/59] Add view files step to workflow --- .github/workflows/Text-UI-CI.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/Text-UI-CI.yml b/.github/workflows/Text-UI-CI.yml index 25a664a6..51599696 100644 --- a/.github/workflows/Text-UI-CI.yml +++ b/.github/workflows/Text-UI-CI.yml @@ -28,3 +28,5 @@ jobs: - name: Test with runtest.sh working-directory: ./text-ui-test run: ./runtest.sh + - name: List all files + run: find ./bin type -f "." From 48c0ee0b4d7fa63eb9b3c7d9250c624494b5d895 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 11 Sep 2021 16:39:35 +0800 Subject: [PATCH 33/59] Remove fail-fast --- .github/workflows/Text-UI-CI.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Text-UI-CI.yml b/.github/workflows/Text-UI-CI.yml index 51599696..4840274c 100644 --- a/.github/workflows/Text-UI-CI.yml +++ b/.github/workflows/Text-UI-CI.yml @@ -13,9 +13,11 @@ on: jobs: build: - runs-on: ubuntu-latest - + strategy: + matrix: + operating-system: [ubuntu-16.04, ubuntu-18.04, ubuntu-20.04] + fail-fast: false steps: - uses: actions/checkout@v2 - name: Set up JDK 11 From 0b93f471fc668a60de19184f38bd0c22a8dbb4be Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 11 Sep 2021 16:42:32 +0800 Subject: [PATCH 34/59] Add find to shellscript to view copied files --- .github/workflows/Text-UI-CI.yml | 4 ---- text-ui-test/runtest.sh | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Text-UI-CI.yml b/.github/workflows/Text-UI-CI.yml index 4840274c..9397a2cb 100644 --- a/.github/workflows/Text-UI-CI.yml +++ b/.github/workflows/Text-UI-CI.yml @@ -14,10 +14,6 @@ on: jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - operating-system: [ubuntu-16.04, ubuntu-18.04, ubuntu-20.04] - fail-fast: false steps: - uses: actions/checkout@v2 - name: Set up JDK 11 diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 8f597ab7..989b8c22 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -33,6 +33,8 @@ else echo "dos2unix command not found. To prevent potential issues, please install dos2unix." fi +find ../bin -type f + # compare the output to the expected output diff ACTUAL.TXT EXPECTED-UNIX.TXT if [ $? -eq 0 ] From 5b10d940dc4043652c72346cebd48af20898017a Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 11 Sep 2021 16:45:49 +0800 Subject: [PATCH 35/59] Remove TaskFactoryTest --- .../alexooi/duke/tasks/TaskFactoryTest.java | 77 ------------------- 1 file changed, 77 deletions(-) delete mode 100644 src/test/java/com/alexooi/duke/tasks/TaskFactoryTest.java diff --git a/src/test/java/com/alexooi/duke/tasks/TaskFactoryTest.java b/src/test/java/com/alexooi/duke/tasks/TaskFactoryTest.java deleted file mode 100644 index fbaa3fb7..00000000 --- a/src/test/java/com/alexooi/duke/tasks/TaskFactoryTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.alexooi.duke.tasks; - -class TaskFactoryTest { - -// @Test -// void getInstanceTodoSuccess() throws InvalidCommandFormatException { -// Command addCommand = new AddCommand(CommandType.ADD, -// TaskType.TODO.toString(), "read book"); -// -// Task task = TaskFactory.getInstance(addCommand); -// Assertions.assertEquals(TaskType.TODO, task.getType(), "TaskFactory assertEquals Todo failed"); -// } -// -// @Test -// void getInstanceEventSuccess() throws InvalidCommandFormatException { -// Command addCommand = new AddCommand(CommandType.ADD, -// TaskType.EVENT.toString(), -// "meet friend /at 2020-09-15"); -// -// Task task = TaskFactory.getInstance(addCommand); -// Assertions.assertEquals(TaskType.EVENT, task.getType(), "TaskFactory assertEquals Event failed"); -// } -// -// @Test -// void getInstanceDeadlineSuccess() throws InvalidCommandFormatException { -// Command addCommand = new AddCommand(CommandType.ADD, -// TaskType.DEADLINE.toString(), -// "return book /by 2021-09-20"); -// -// Task task = TaskFactory.getInstance(addCommand); -// Assertions.assertEquals(TaskType.DEADLINE, task.getType(), "TaskFactory assertEquals Deadline failed"); -// } -// -// @Test -// void getInstanceTodoFail() { -// Command addCommand = new AddCommand(CommandType.ADD, -// TaskType.TODO.toString(), ""); -// -// InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { -// TaskFactory.getInstance(addCommand); -// }, "TaskFactory assertThrows Todo failed."); -// -// Assertions.assertEquals(InvalidCommandFormatException.ERROR_TODO, -// icfe.toString(), -// "TaskFactory assertEquals ERROR_TODO failed."); -// } -// -// @Test -// void getInstanceEventFail() { -// Command addCommand = new AddCommand(CommandType.ADD, -// TaskType.EVENT.toString(), -// "meet friend"); -// -// InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { -// TaskFactory.getInstance(addCommand); -// }, "TaskFactory assertThrows Event failed."); -// -// Assertions.assertEquals(InvalidCommandFormatException.ERROR_EVENT, -// icfe.toString(), -// "TaskFactory assertEquals ERROR_EVENT failed."); -// } -// -// @Test -// void getInstanceDeadlineFail() { -// Command addCommand = new AddCommand(CommandType.ADD, -// TaskType.DEADLINE.toString(), -// "return book"); -// -// InvalidCommandFormatException icfe = Assertions.assertThrows(InvalidCommandFormatException.class, () -> { -// TaskFactory.getInstance(addCommand); -// }, "TaskFactory assertThrows Deadline failed."); -// -// Assertions.assertEquals(InvalidCommandFormatException.ERROR_DEADLINE, -// icfe.toString(), -// "TaskFactory assertEquals ERROR_EVENT failed."); -// } -} \ No newline at end of file From 9de076366eb91965d66ca87e009bf0850ec75ced Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 11 Sep 2021 16:55:20 +0800 Subject: [PATCH 36/59] Fixed cp command to copy META-INF folder --- text-ui-test/runtest.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 989b8c22..ac85fd4b 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,7 +13,7 @@ then fi # compile the code into the bin folder, terminates if error occurred -cp -R ../src/main/resources/ ../bin +cp -R ../src/main/resources/META-INF ../bin if ! find ../src/main/java -name "*.java" -print | xargs javac -cp ../src/main/java -Xlint:none -d ../bin then echo "********** BUILD FAILURE **********" From d2d08bc865b1fee7a38490d41224aa6dde1b16df Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 11 Sep 2021 16:57:31 +0800 Subject: [PATCH 37/59] Remove find command --- text-ui-test/runtest.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index ac85fd4b..21d43df7 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -33,8 +33,6 @@ else echo "dos2unix command not found. To prevent potential issues, please install dos2unix." fi -find ../bin -type f - # compare the output to the expected output diff ACTUAL.TXT EXPECTED-UNIX.TXT if [ $? -eq 0 ] From 3370bb5fd1f16c0981030bb7b2b38cf7c528ad57 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 11 Sep 2021 16:59:23 +0800 Subject: [PATCH 38/59] Remove unneeded step from workflow --- .github/workflows/Text-UI-CI.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/Text-UI-CI.yml b/.github/workflows/Text-UI-CI.yml index 9397a2cb..b0e8939a 100644 --- a/.github/workflows/Text-UI-CI.yml +++ b/.github/workflows/Text-UI-CI.yml @@ -26,5 +26,3 @@ jobs: - name: Test with runtest.sh working-directory: ./text-ui-test run: ./runtest.sh - - name: List all files - run: find ./bin type -f "." From cecca29d1b33bffb2a14fc8060d8b8278a93d178 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 23 Oct 2021 14:46:50 +0800 Subject: [PATCH 39/59] Add Storage for Level-7 --- src/main/java/com/alexooi/duke/Duke.java | 9 ++-- .../InvalidFileFormatException.java | 4 ++ .../duke/interfaces/StorageClient.java | 6 +++ .../com/alexooi/duke/storage/FileStorage.java | 45 +++++++++++++++++++ .../java/com/alexooi/duke/tasks/Deadline.java | 5 +++ .../java/com/alexooi/duke/tasks/Event.java | 5 +++ .../java/com/alexooi/duke/tasks/Task.java | 7 ++- .../com/alexooi/duke/tasks/TaskFactory.java | 26 +++++++++++ .../java/com/alexooi/duke/tasks/TaskList.java | 41 +++++++++++++++-- .../{Parser.java => CommandLineParser.java} | 2 +- ...erTest.java => CommandLineParserTest.java} | 16 +++---- 11 files changed, 150 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java create mode 100644 src/main/java/com/alexooi/duke/interfaces/StorageClient.java create mode 100644 src/main/java/com/alexooi/duke/storage/FileStorage.java rename src/main/java/com/alexooi/duke/ui/{Parser.java => CommandLineParser.java} (93%) rename src/test/java/com/alexooi/duke/ui/{ParserTest.java => CommandLineParserTest.java} (75%) diff --git a/src/main/java/com/alexooi/duke/Duke.java b/src/main/java/com/alexooi/duke/Duke.java index ea400329..8265a805 100644 --- a/src/main/java/com/alexooi/duke/Duke.java +++ b/src/main/java/com/alexooi/duke/Duke.java @@ -1,9 +1,10 @@ package com.alexooi.duke; import com.alexooi.duke.commands.Command; +import com.alexooi.duke.storage.FileStorage; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; -import com.alexooi.duke.ui.Parser; +import com.alexooi.duke.ui.CommandLineParser; import com.alexooi.duke.ui.OutputFormatter; import com.alexooi.duke.exceptions.InvalidCommandException; import com.alexooi.duke.exceptions.InvalidCommandFormatException; @@ -25,7 +26,7 @@ private Duke(OutputFormat prompt, IOParser parser) { public static Duke getInstance() { if (instance == null) { - instance = new Duke(new OutputFormatter(), new Parser()); + instance = new Duke(new OutputFormatter(), new CommandLineParser()); } return instance; @@ -33,7 +34,8 @@ public static Duke getInstance() { public static void main(String[] args) { Duke main = Duke.getInstance(); - TaskList tasks = TaskList.getInstance(); + FileStorage client = new FileStorage("state.txt"); + TaskList tasks = TaskList.getInstance(client); ServiceLoader commandLoader = ServiceLoader.load(Command.class); System.out.println(main.prompt.start()); @@ -58,6 +60,7 @@ public static void main(String[] args) { } catch (Exception ex) { ex.printStackTrace(); } + tasks.save(); } System.out.println(main.prompt.exit()); diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java new file mode 100644 index 00000000..5777ac7e --- /dev/null +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java @@ -0,0 +1,4 @@ +package com.alexooi.duke.exceptions; + +public class InvalidFileFormatException extends Throwable { +} diff --git a/src/main/java/com/alexooi/duke/interfaces/StorageClient.java b/src/main/java/com/alexooi/duke/interfaces/StorageClient.java new file mode 100644 index 00000000..2207a06d --- /dev/null +++ b/src/main/java/com/alexooi/duke/interfaces/StorageClient.java @@ -0,0 +1,6 @@ +package com.alexooi.duke.interfaces; + +public interface StorageClient { + T load(); + void save(String input); +} diff --git a/src/main/java/com/alexooi/duke/storage/FileStorage.java b/src/main/java/com/alexooi/duke/storage/FileStorage.java new file mode 100644 index 00000000..950de8d8 --- /dev/null +++ b/src/main/java/com/alexooi/duke/storage/FileStorage.java @@ -0,0 +1,45 @@ +package com.alexooi.duke.storage; + +import com.alexooi.duke.interfaces.StorageClient; + +import java.io.*; + +public class FileStorage implements StorageClient { + String filePath; + + public FileStorage(String filePath) { + this.filePath = filePath; + } + + public BufferedReader load() { + try { + File file = new File(this.filePath); + if (file.exists()) { + FileReader reader = new FileReader(file); + return new BufferedReader(reader); + } else if (file.createNewFile()) { + FileReader reader = new FileReader(file); + return new BufferedReader(reader); + } else { + throw new IOException(); + } + } catch (FileNotFoundException fnfe) { + System.out.println("File Not Found Exception: Shouldn't have hit this!"); + } catch (IOException ioe) { + System.out.println("IOException: Had an issue creating state file"); + } + return null; + } + + public void save(String input) { + try { + File file = new File(this.filePath); + FileWriter writer = new FileWriter(file); + BufferedWriter out = new BufferedWriter(writer); + out.write(input); + out.close(); + } catch (IOException ioe) { + System.out.println("Had trouble saving!"); + } + } +} diff --git a/src/main/java/com/alexooi/duke/tasks/Deadline.java b/src/main/java/com/alexooi/duke/tasks/Deadline.java index c165d8bb..8a967886 100644 --- a/src/main/java/com/alexooi/duke/tasks/Deadline.java +++ b/src/main/java/com/alexooi/duke/tasks/Deadline.java @@ -22,4 +22,9 @@ public void setDueDate(String dueDate) { public String toStatusString() { return "[D]" + super.toStatusString() + " (by: " + dueDate + ")"; } + + @Override + public String toSaveString() { + return super.toSaveString() + "|" + dueDate; + } } diff --git a/src/main/java/com/alexooi/duke/tasks/Event.java b/src/main/java/com/alexooi/duke/tasks/Event.java index 09995849..8658120c 100644 --- a/src/main/java/com/alexooi/duke/tasks/Event.java +++ b/src/main/java/com/alexooi/duke/tasks/Event.java @@ -22,4 +22,9 @@ public void setTiming(String timing) { public String toStatusString() { return "[E]" + super.toStatusString() + " (at: " + timing + ")"; } + + @Override + public String toSaveString() { + return super.toSaveString() + "|" + timing; + } } diff --git a/src/main/java/com/alexooi/duke/tasks/Task.java b/src/main/java/com/alexooi/duke/tasks/Task.java index 64797906..07dbef8c 100644 --- a/src/main/java/com/alexooi/duke/tasks/Task.java +++ b/src/main/java/com/alexooi/duke/tasks/Task.java @@ -42,7 +42,7 @@ public void setType(TaskType type) { } public String toStatusString() { - String doneStr = (isDone) ? "X" : " "; + String doneStr = (getDone()) ? "X" : " "; return "[" + doneStr + "] " + this; } @@ -50,4 +50,9 @@ public String toStatusString() { public String toString() { return getDescription(); } + + public String toSaveString() { + String doneStr = (getDone()) ? "T" : "F"; + return getType() + "|" + doneStr + "|" + getDescription(); + } } diff --git a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java index c772402b..f2dbd45b 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java @@ -3,6 +3,8 @@ import com.alexooi.duke.enums.TaskType; import com.alexooi.duke.commands.Command; import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.exceptions.InvalidFileFormatException; + import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -46,4 +48,28 @@ public static Task getInstance(Command cmd) throws InvalidCommandFormatException throw new InvalidCommandFormatException( "Cannot find the appropriate task to create."); } + + public static Task getInstance(String s) throws InvalidFileFormatException { + String[] line = s.split("\\|"); + boolean doneState = line[1].equalsIgnoreCase("t"); + Task currentTask; + + String keyword = line[0]; + if (keyword.equalsIgnoreCase(TaskType.DEADLINE.toString())) { + String description = line[2]; + String dueDate = line[3]; + currentTask = new Deadline(description, dueDate); + } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { + String description = line[2]; + String timing = line[3]; + currentTask = new Event(description, timing); + } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { + currentTask = new Todo(line[2]); + } else { + throw new InvalidFileFormatException(); + } + + currentTask.setDone(doneState); + return currentTask; + } } diff --git a/src/main/java/com/alexooi/duke/tasks/TaskList.java b/src/main/java/com/alexooi/duke/tasks/TaskList.java index 3a6ff466..e7e737f8 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskList.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskList.java @@ -1,18 +1,27 @@ package com.alexooi.duke.tasks; +import com.alexooi.duke.exceptions.InvalidFileFormatException; +import com.alexooi.duke.interfaces.StorageClient; + +import java.io.BufferedReader; +import java.io.IOException; import java.util.ArrayList; public class TaskList { private static TaskList instance; private ArrayList taskList; + private StorageClient client; - private TaskList() { + private TaskList(StorageClient client) { taskList = new ArrayList<>(); + this.client = client; + BufferedReader input = client.load(); + load(input); } - public static TaskList getInstance() { + public static TaskList getInstance(StorageClient client) { if (instance == null) { - instance = new TaskList(); + instance = new TaskList(client); } return instance; @@ -37,4 +46,30 @@ public Task getTask(int idx) { public int size() { return taskList.size(); } + + public void save() { + String output = taskList.stream().reduce("", + (partialOutput, task) -> partialOutput + task.toSaveString() + System.lineSeparator(), + String::concat + ); + client.save(output); + } + + private void load(BufferedReader in) { + boolean endOfInput = false; + while (!endOfInput) { + try { + String taskStr = in.readLine(); + if (taskStr == null) { + endOfInput = true; + } else { + taskList.add(TaskFactory.getInstance(taskStr)); + } + } catch (IOException e) { + System.out.println("Unable to read file!"); + } catch (InvalidFileFormatException iffe) { + System.out.println("Invalid File Format detected!"); + } + } + } } diff --git a/src/main/java/com/alexooi/duke/ui/Parser.java b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java similarity index 93% rename from src/main/java/com/alexooi/duke/ui/Parser.java rename to src/main/java/com/alexooi/duke/ui/CommandLineParser.java index 62323a5f..d64f6780 100644 --- a/src/main/java/com/alexooi/duke/ui/Parser.java +++ b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java @@ -8,7 +8,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class Parser implements IOParser { +public class CommandLineParser implements IOParser { public Command readInput(Scanner sc, Iterable commands) throws InvalidCommandException { String input = sc.nextLine(); diff --git a/src/test/java/com/alexooi/duke/ui/ParserTest.java b/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java similarity index 75% rename from src/test/java/com/alexooi/duke/ui/ParserTest.java rename to src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java index b5c1537d..b9fcaa23 100644 --- a/src/test/java/com/alexooi/duke/ui/ParserTest.java +++ b/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java @@ -8,15 +8,15 @@ import static org.junit.jupiter.api.Assertions.*; -class ParserTest { - private static final Parser parser = new Parser(); +class CommandLineParserTest { + private static final CommandLineParser COMMAND_LINE_PARSER = new CommandLineParser(); private final ServiceLoader commandLoader = ServiceLoader.load(Command.class); @Test void readInput_CommandTodoSuccess() throws InvalidCommandException { String input = "todo read book"; Scanner sc = new Scanner(input).useDelimiter("\n"); - Command cmd = parser.readInput(sc, commandLoader); + Command cmd = COMMAND_LINE_PARSER.readInput(sc, commandLoader); assertEquals("todo", cmd.getKeyword(), "Parser assertEquals Todo Keyword failed."); assertEquals("read book", cmd.getArgs(), "Parser assertEquals Todo Args failed."); } @@ -25,14 +25,14 @@ void readInput_CommandTodoSuccess() throws InvalidCommandException { void readInput_CommandTodoFail() { String input = "todo1"; Scanner sc = new Scanner(input).useDelimiter("\n"); - assertThrows(InvalidCommandException.class, () -> parser.readInput(sc, commandLoader)); + assertThrows(InvalidCommandException.class, () -> COMMAND_LINE_PARSER.readInput(sc, commandLoader)); } @Test void readInput_CommandDeadlineSuccess() throws InvalidCommandException { String input = "deadline return book /by 2021-09-15"; Scanner sc = new Scanner(input).useDelimiter("\n"); - Command cmd = parser.readInput(sc, commandLoader); + Command cmd = COMMAND_LINE_PARSER.readInput(sc, commandLoader); assertEquals("deadline", cmd.getKeyword(), "Parser assertEquals Deadline Keyword failed."); assertEquals("return book /by 2021-09-15", cmd.getArgs(), "Parser assertEquals Deadline Args failed."); } @@ -41,14 +41,14 @@ void readInput_CommandDeadlineSuccess() throws InvalidCommandException { void readInput_CommandDeadlineFail() { String input = "deadlinereturn book /by 2021-09-15"; Scanner sc = new Scanner(input).useDelimiter("\n"); - assertThrows(InvalidCommandException.class, () -> parser.readInput(sc, commandLoader)); + assertThrows(InvalidCommandException.class, () -> COMMAND_LINE_PARSER.readInput(sc, commandLoader)); } @Test void readInput_CommandEventSuccess() throws InvalidCommandException { String input = "event meet friend /at 2021-09-15"; Scanner sc = new Scanner(input).useDelimiter("\n"); - Command cmd = parser.readInput(sc, commandLoader); + Command cmd = COMMAND_LINE_PARSER.readInput(sc, commandLoader); assertEquals("event", cmd.getKeyword(), "Parser assertEquals Event Keyword failed."); assertEquals("meet friend /at 2021-09-15", cmd.getArgs(), "Parser assertEquals Event Args failed."); } @@ -57,6 +57,6 @@ void readInput_CommandEventSuccess() throws InvalidCommandException { void readInput_CommandEventFail() { String input = "eventmeet friend /at 2021-09-15"; Scanner sc = new Scanner(input).useDelimiter("\n"); - assertThrows(InvalidCommandException.class, () -> parser.readInput(sc, commandLoader)); + assertThrows(InvalidCommandException.class, () -> COMMAND_LINE_PARSER.readInput(sc, commandLoader)); } } \ No newline at end of file From 8d3f624b47a5e16c6cd221f5031ca235ecfcc1f0 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 23 Oct 2021 15:59:58 +0800 Subject: [PATCH 40/59] Add LocalDate and LocalDateTime handling for Level-8 --- .gitignore | 1 + src/main/java/com/alexooi/duke/Duke.java | 1 + .../alexooi/duke/interfaces/DateParser.java | 7 +++ .../java/com/alexooi/duke/tasks/Deadline.java | 21 +++++-- .../java/com/alexooi/duke/tasks/Event.java | 23 ++++++-- .../com/alexooi/duke/tasks/TaskFactory.java | 11 ++-- .../alexooi/duke/utility/InputDateParser.java | 56 +++++++++++++++++++ text-ui-test/EXPECTED.TXT | 12 ++-- 8 files changed, 113 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/alexooi/duke/interfaces/DateParser.java create mode 100644 src/main/java/com/alexooi/duke/utility/InputDateParser.java diff --git a/.gitignore b/.gitignore index f69985ef..4ede840f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT +state.txt \ No newline at end of file diff --git a/src/main/java/com/alexooi/duke/Duke.java b/src/main/java/com/alexooi/duke/Duke.java index 8265a805..d320f7de 100644 --- a/src/main/java/com/alexooi/duke/Duke.java +++ b/src/main/java/com/alexooi/duke/Duke.java @@ -35,6 +35,7 @@ public static Duke getInstance() { public static void main(String[] args) { Duke main = Duke.getInstance(); FileStorage client = new FileStorage("state.txt"); + TaskList tasks = TaskList.getInstance(client); ServiceLoader commandLoader = ServiceLoader.load(Command.class); System.out.println(main.prompt.start()); diff --git a/src/main/java/com/alexooi/duke/interfaces/DateParser.java b/src/main/java/com/alexooi/duke/interfaces/DateParser.java new file mode 100644 index 00000000..95e74e20 --- /dev/null +++ b/src/main/java/com/alexooi/duke/interfaces/DateParser.java @@ -0,0 +1,7 @@ +package com.alexooi.duke.interfaces; + +import java.time.LocalDateTime; + +public interface DateParser { + LocalDateTime parseInput(String input); +} \ No newline at end of file diff --git a/src/main/java/com/alexooi/duke/tasks/Deadline.java b/src/main/java/com/alexooi/duke/tasks/Deadline.java index 8a967886..e457970d 100644 --- a/src/main/java/com/alexooi/duke/tasks/Deadline.java +++ b/src/main/java/com/alexooi/duke/tasks/Deadline.java @@ -1,26 +1,39 @@ package com.alexooi.duke.tasks; import com.alexooi.duke.enums.TaskType; +import com.alexooi.duke.interfaces.DateParser; + +import java.time.LocalDateTime; public class Deadline extends Task { - private String dueDate; + private String dueDateStr; + private LocalDateTime dueDate; + private DateParser dateParser; public Deadline(String description, String dueDate) { super(TaskType.DEADLINE, description); this.setDueDate(dueDate); } - public String getDueDate() { + public Deadline(String description, String dueDate, DateParser parser) { + super(TaskType.DEADLINE, description); + this.dateParser = parser; + this.setDueDate(dueDate); + } + + public LocalDateTime getDueDate() { return dueDate; } public void setDueDate(String dueDate) { - this.dueDate = dueDate; + LocalDateTime dateTime = dateParser.parseInput(dueDate); + this.dueDateStr = dueDate; + this.dueDate = dateTime; } @Override public String toStatusString() { - return "[D]" + super.toStatusString() + " (by: " + dueDate + ")"; + return "[D]" + super.toStatusString() + " (by: " + getDueDate() + ")"; } @Override diff --git a/src/main/java/com/alexooi/duke/tasks/Event.java b/src/main/java/com/alexooi/duke/tasks/Event.java index 8658120c..f9ad7452 100644 --- a/src/main/java/com/alexooi/duke/tasks/Event.java +++ b/src/main/java/com/alexooi/duke/tasks/Event.java @@ -1,30 +1,43 @@ package com.alexooi.duke.tasks; import com.alexooi.duke.enums.TaskType; +import com.alexooi.duke.interfaces.DateParser; + +import java.time.LocalDateTime; public class Event extends Task { - private String timing; + private String timingStr; + private LocalDateTime timing; + private DateParser dateParser; public Event(String description, String timing) { super(TaskType.EVENT, description); this.setTiming(timing); } - public String getTiming() { + public Event(String description, String timing, DateParser parser) { + super(TaskType.EVENT, description); + this.dateParser = parser; + this.setTiming(timing); + } + + public LocalDateTime getTiming() { return timing; } public void setTiming(String timing) { - this.timing = timing; + LocalDateTime dateTime = dateParser.parseInput(timing); + this.timingStr = timing; + this.timing = dateTime; } @Override public String toStatusString() { - return "[E]" + super.toStatusString() + " (at: " + timing + ")"; + return "[E]" + super.toStatusString() + " (at: " + getTiming() + ")"; } @Override public String toSaveString() { - return super.toSaveString() + "|" + timing; + return super.toSaveString() + "|" + getTiming(); } } diff --git a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java index f2dbd45b..25005ac4 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java @@ -4,6 +4,8 @@ import com.alexooi.duke.commands.Command; import com.alexooi.duke.exceptions.InvalidCommandFormatException; import com.alexooi.duke.exceptions.InvalidFileFormatException; +import com.alexooi.duke.interfaces.DateParser; +import com.alexooi.duke.utility.InputDateParser; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -15,6 +17,7 @@ public class TaskFactory { "^(?<" + KEY_DESCRIPTION + ">.+) /by (?<" + KEY_DATE + ">.+)$"; private static final String EVENT_PATTERN = "^(?<" + KEY_DESCRIPTION + ">.+) /at (?<" + KEY_DATE + ">.+)$"; + private static final DateParser dateParser = new InputDateParser(); public static Task getInstance(Command cmd) throws InvalidCommandFormatException { String keyword = cmd.getKeyword(); @@ -25,7 +28,7 @@ public static Task getInstance(Command cmd) throws InvalidCommandFormatException if (deadlineMatch.matches()) { String description = deadlineMatch.group(KEY_DESCRIPTION); String dueDate = deadlineMatch.group(KEY_DATE); - return new Deadline(description, dueDate); + return new Deadline(description, dueDate, dateParser); } else { throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); } @@ -34,7 +37,7 @@ public static Task getInstance(Command cmd) throws InvalidCommandFormatException if (eventMatch.matches()) { String description = eventMatch.group(KEY_DESCRIPTION); String timing = eventMatch.group(KEY_DATE); - return new Event(description, timing); + return new Event(description, timing, dateParser); } else { throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_EVENT); } @@ -58,11 +61,11 @@ public static Task getInstance(String s) throws InvalidFileFormatException { if (keyword.equalsIgnoreCase(TaskType.DEADLINE.toString())) { String description = line[2]; String dueDate = line[3]; - currentTask = new Deadline(description, dueDate); + currentTask = new Deadline(description, dueDate, dateParser); } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { String description = line[2]; String timing = line[3]; - currentTask = new Event(description, timing); + currentTask = new Event(description, timing, dateParser); } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { currentTask = new Todo(line[2]); } else { diff --git a/src/main/java/com/alexooi/duke/utility/InputDateParser.java b/src/main/java/com/alexooi/duke/utility/InputDateParser.java new file mode 100644 index 00000000..1ac9f194 --- /dev/null +++ b/src/main/java/com/alexooi/duke/utility/InputDateParser.java @@ -0,0 +1,56 @@ +package com.alexooi.duke.utility; + +import com.alexooi.duke.interfaces.DateParser; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; + +public class InputDateParser implements DateParser { + private final DateTimeFormatter[] ACCEPTED_DATE_FORMATS = { + DateTimeFormatter.ISO_DATE + }; + private final DateTimeFormatter[] ACCEPTED_DATE_TIME_FORMATS = { + DateTimeFormatter.ISO_DATE_TIME + }; + + boolean isDate(String input, DateTimeFormatter acceptedFormats) { + try { + LocalDate.parse(input, acceptedFormats); + } catch (DateTimeParseException dtpe) { + return false; + } + return true; + } + + boolean isDateTime(String input, DateTimeFormatter acceptedFormats) { + try { + LocalDateTime.parse(input, acceptedFormats); + } catch (DateTimeParseException dtpe) { + return false; + } + return true; + } + + DateTimeFormatter formatBuilder(DateTimeFormatter[] formats) { + DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); + for (DateTimeFormatter formatter : formats) { + builder.appendOptional(formatter); + } + return builder.toFormatter(); + } + + @Override + public LocalDateTime parseInput(String input) { + DateTimeFormatter dateTimeFormats = formatBuilder(ACCEPTED_DATE_TIME_FORMATS); + DateTimeFormatter dateFormats = formatBuilder(ACCEPTED_DATE_FORMATS); + if (isDateTime(input, dateTimeFormats)) { + return LocalDateTime.parse(input, dateTimeFormats); + } else if (isDate(input, dateFormats)) { + return LocalDate.parse(input, dateFormats).atStartOfDay(); + } + return null; + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index cfef2cbd..fc89e8a4 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -12,7 +12,7 @@ _________________________________________ _________________________________________ 1. [T][ ] read book - 2. [D][ ] return book (by: 2021-09-20) + 2. [D][ ] return book (by: 2021-09-20T00:00) _________________________________________ _________________________________________ added: meet friend @@ -20,21 +20,21 @@ _________________________________________ _________________________________________ Nice! I've marked this task as done: - [D][X] return book (by: 2021-09-20) + [D][X] return book (by: 2021-09-20T00:00) _________________________________________ _________________________________________ 1. [T][ ] read book - 2. [D][X] return book (by: 2021-09-20) - 3. [E][ ] meet friend (at: 2021-09-15) + 2. [D][X] return book (by: 2021-09-20T00:00) + 3. [E][ ] meet friend (at: 2021-09-15T00:00) _________________________________________ _________________________________________ Noted. I've removed this task: - [D][X] return book (by: 2021-09-20) + [D][X] return book (by: 2021-09-20T00:00) Now you have 2 tasks in the list. _________________________________________ _________________________________________ 1. [T][ ] read book - 2. [E][ ] meet friend (at: 2021-09-15) + 2. [E][ ] meet friend (at: 2021-09-15T00:00) _________________________________________ _________________________________________ Bye. Hope to see you again soon! From e152182a211abcb5c3673d64c5fa5f243fdce35d Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 6 Nov 2021 18:16:39 +0800 Subject: [PATCH 41/59] Complete ViewSchedules --- .../com/alexooi/duke/commands/Command.java | 3 +- .../duke/commands/ViewScheduleCommand.java | 74 +++++++++++++++++++ .../InvalidCommandFormatException.java | 2 + .../alexooi/duke/interfaces/DateParser.java | 3 + .../com/alexooi/duke/interfaces/IOParser.java | 4 +- .../alexooi/duke/interfaces/OutputFormat.java | 4 + .../alexooi/duke/ui/CommandLineParser.java | 3 +- .../com/alexooi/duke/ui/OutputFormatter.java | 13 ++++ .../alexooi/duke/utility/InputDateParser.java | 6 +- .../com.alexooi.duke.commands.Command | 3 +- .../duke/ui/CommandLineParserTest.java | 7 +- text-ui-test/EXPECTED.TXT | 5 ++ text-ui-test/input.txt | 1 + 13 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java diff --git a/src/main/java/com/alexooi/duke/commands/Command.java b/src/main/java/com/alexooi/duke/commands/Command.java index 980b07fa..5cea7db8 100644 --- a/src/main/java/com/alexooi/duke/commands/Command.java +++ b/src/main/java/com/alexooi/duke/commands/Command.java @@ -1,5 +1,6 @@ package com.alexooi.duke.commands; +import com.alexooi.duke.exceptions.InvalidCommandException; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; import com.alexooi.duke.exceptions.InvalidCommandFormatException; @@ -43,7 +44,7 @@ public void setArgs(String args) { public abstract Pattern getRegexPattern(); - public abstract Command build(); + public abstract Command build() throws InvalidCommandFormatException; public boolean isExit() { return false; diff --git a/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java b/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java new file mode 100644 index 00000000..6367e191 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java @@ -0,0 +1,74 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.DateParser; +import com.alexooi.duke.interfaces.OutputFormat; +import com.alexooi.duke.tasks.Deadline; +import com.alexooi.duke.tasks.Event; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.utility.InputDateParser; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class ViewScheduleCommand extends Command { + private final String REGEX_PATTERN = "^(\\bview\\b) (.*)$"; + DateParser parser; + + public ViewScheduleCommand() { + this.parser = new InputDateParser(); + } + + public ViewScheduleCommand(String keyword, String args) { + super(keyword, args); + this.parser = new InputDateParser(); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + ArrayList taskList = tasks.get(); + LocalDateTime dateTime = parser.parseInput(getArgs()); + if (dateTime == null) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_VIEW_SCHEDULE); + } + + List filteredList = taskList.stream().filter((element) -> { + boolean isScheduled = false; + switch (element.getType()) { + case EVENT: + Event event = (Event) element; + if (event.getTiming().toLocalDate().equals(dateTime.toLocalDate())) { + isScheduled = true; + } + break; + case DEADLINE: + Deadline deadline = (Deadline) element; + if (deadline.getDueDate().toLocalDate().equals(dateTime.toLocalDate())) { + isScheduled = true; + } + break; + } + return isScheduled; + }).collect(Collectors.toList()); + + return prompt.viewSchedule(filteredList, dateTime.toLocalDate()); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public ViewScheduleCommand build() throws InvalidCommandFormatException { + if (!parser.isDate(getArgs(), DateTimeFormatter.ISO_DATE)) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_VIEW_SCHEDULE); + } + return new ViewScheduleCommand(getKeyword(), getArgs()); + } +} diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java index 8108a9e0..95154745 100644 --- a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java @@ -8,6 +8,8 @@ public class InvalidCommandFormatException extends Throwable { "Event should be in the format: event /at ."; public static final String ERROR_TODO = "Todo should be in the format: todo ."; + public static final String ERROR_VIEW_SCHEDULE = + "View schedule should be in the format: view schedule /for ."; private String errorMessage; diff --git a/src/main/java/com/alexooi/duke/interfaces/DateParser.java b/src/main/java/com/alexooi/duke/interfaces/DateParser.java index 95e74e20..3493da7c 100644 --- a/src/main/java/com/alexooi/duke/interfaces/DateParser.java +++ b/src/main/java/com/alexooi/duke/interfaces/DateParser.java @@ -1,7 +1,10 @@ package com.alexooi.duke.interfaces; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; public interface DateParser { + boolean isDate(String input, DateTimeFormatter formatter); + boolean isDateTime(String input, DateTimeFormatter formatter); LocalDateTime parseInput(String input); } \ No newline at end of file diff --git a/src/main/java/com/alexooi/duke/interfaces/IOParser.java b/src/main/java/com/alexooi/duke/interfaces/IOParser.java index c044b499..8bb2de70 100644 --- a/src/main/java/com/alexooi/duke/interfaces/IOParser.java +++ b/src/main/java/com/alexooi/duke/interfaces/IOParser.java @@ -1,5 +1,7 @@ package com.alexooi.duke.interfaces; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; + public interface IOParser { - T readInput(S sc, Iterable it) throws Exception; + T readInput(S sc, Iterable it) throws Exception, InvalidCommandFormatException; } diff --git a/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java index a6b087c3..db021f21 100644 --- a/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java +++ b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java @@ -1,5 +1,7 @@ package com.alexooi.duke.interfaces; +import java.time.LocalDate; + public interface OutputFormat { String start(); @@ -15,5 +17,7 @@ public interface OutputFormat { String list(Iterable inputs); + String viewSchedule(Iterable inputs, LocalDate date); + String exit(); } diff --git a/src/main/java/com/alexooi/duke/ui/CommandLineParser.java b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java index d64f6780..ddcaa3f2 100644 --- a/src/main/java/com/alexooi/duke/ui/CommandLineParser.java +++ b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java @@ -2,6 +2,7 @@ import com.alexooi.duke.commands.Command; import com.alexooi.duke.exceptions.InvalidCommandException; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; import com.alexooi.duke.interfaces.IOParser; import java.util.*; @@ -10,7 +11,7 @@ public class CommandLineParser implements IOParser { public Command readInput(Scanner sc, Iterable commands) - throws InvalidCommandException { + throws InvalidCommandException, InvalidCommandFormatException { String input = sc.nextLine(); for (Command cmd : commands) { diff --git a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java index 70892644..b37b5c38 100644 --- a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java +++ b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java @@ -3,6 +3,8 @@ import com.alexooi.duke.tasks.Task; import com.alexooi.duke.interfaces.OutputFormat; +import java.time.LocalDate; + public class OutputFormatter implements OutputFormat { private static final String PADDING = " "; private static final String HORIZONTAL_LINE = "_________________________________________"; @@ -11,6 +13,7 @@ public class OutputFormatter implements OutputFormat { private static final String DONE = "Nice! I've marked this task as done:"; private static final String ADD = "added: "; private static final String REMOVE = "Noted. I've removed this task:"; + private static final String VIEW_SCHEDULE = "You have the following tasks scheduled on "; @Override public String start() { @@ -62,6 +65,16 @@ public String list(Iterable inputs) { return formatOutput(output.toString()); } + @Override + public String viewSchedule(Iterable inputs, LocalDate date) { + StringBuilder output = new StringBuilder(); + output.append(formatLine(VIEW_SCHEDULE + date.toString() + System.lineSeparator())); + for (Task input : inputs) { + output.append(formatLine(input.toStatusString())); + } + return formatOutput(output.toString()); + } + @Override public String exit() { return formatOutput(formatLine(END)); diff --git a/src/main/java/com/alexooi/duke/utility/InputDateParser.java b/src/main/java/com/alexooi/duke/utility/InputDateParser.java index 1ac9f194..dacd2cf8 100644 --- a/src/main/java/com/alexooi/duke/utility/InputDateParser.java +++ b/src/main/java/com/alexooi/duke/utility/InputDateParser.java @@ -16,7 +16,8 @@ public class InputDateParser implements DateParser { DateTimeFormatter.ISO_DATE_TIME }; - boolean isDate(String input, DateTimeFormatter acceptedFormats) { + @Override + public boolean isDate(String input, DateTimeFormatter acceptedFormats) { try { LocalDate.parse(input, acceptedFormats); } catch (DateTimeParseException dtpe) { @@ -25,7 +26,8 @@ boolean isDate(String input, DateTimeFormatter acceptedFormats) { return true; } - boolean isDateTime(String input, DateTimeFormatter acceptedFormats) { + @Override + public boolean isDateTime(String input, DateTimeFormatter acceptedFormats) { try { LocalDateTime.parse(input, acceptedFormats); } catch (DateTimeParseException dtpe) { diff --git a/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command b/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command index 8f97627e..9800571e 100644 --- a/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command +++ b/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command @@ -2,4 +2,5 @@ com.alexooi.duke.commands.AddCommand com.alexooi.duke.commands.CompleteCommand com.alexooi.duke.commands.DeleteCommand com.alexooi.duke.commands.ExitCommand -com.alexooi.duke.commands.ListCommand \ No newline at end of file +com.alexooi.duke.commands.ListCommand +com.alexooi.duke.commands.ViewScheduleCommand diff --git a/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java b/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java index b9fcaa23..144e0413 100644 --- a/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java +++ b/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java @@ -2,6 +2,7 @@ import com.alexooi.duke.commands.Command; import com.alexooi.duke.exceptions.InvalidCommandException; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; import org.junit.jupiter.api.Test; import java.util.Scanner; import java.util.ServiceLoader; @@ -13,7 +14,7 @@ class CommandLineParserTest { private final ServiceLoader commandLoader = ServiceLoader.load(Command.class); @Test - void readInput_CommandTodoSuccess() throws InvalidCommandException { + void readInput_CommandTodoSuccess() throws InvalidCommandException, InvalidCommandFormatException { String input = "todo read book"; Scanner sc = new Scanner(input).useDelimiter("\n"); Command cmd = COMMAND_LINE_PARSER.readInput(sc, commandLoader); @@ -29,7 +30,7 @@ void readInput_CommandTodoFail() { } @Test - void readInput_CommandDeadlineSuccess() throws InvalidCommandException { + void readInput_CommandDeadlineSuccess() throws InvalidCommandException, InvalidCommandFormatException { String input = "deadline return book /by 2021-09-15"; Scanner sc = new Scanner(input).useDelimiter("\n"); Command cmd = COMMAND_LINE_PARSER.readInput(sc, commandLoader); @@ -45,7 +46,7 @@ void readInput_CommandDeadlineFail() { } @Test - void readInput_CommandEventSuccess() throws InvalidCommandException { + void readInput_CommandEventSuccess() throws InvalidCommandException, InvalidCommandFormatException { String input = "event meet friend /at 2021-09-15"; Scanner sc = new Scanner(input).useDelimiter("\n"); Command cmd = COMMAND_LINE_PARSER.readInput(sc, commandLoader); diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index fc89e8a4..a90cfd19 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -28,6 +28,11 @@ 3. [E][ ] meet friend (at: 2021-09-15T00:00) _________________________________________ _________________________________________ + You have the following tasks scheduled on 2021-09-20 + + [D][X] return book (by: 2021-09-20T00:00) + _________________________________________ + _________________________________________ Noted. I've removed this task: [D][X] return book (by: 2021-09-20T00:00) Now you have 2 tasks in the list. diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index 2d9e89da..ac859799 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -4,5 +4,6 @@ list event meet friend /at 2021-09-15 done 2 list +view 2021-09-20 delete 2 list \ No newline at end of file From 3becf4633814ba2a1baedafe07cc5bffa5a0085c Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 6 Nov 2021 18:18:22 +0800 Subject: [PATCH 42/59] Refactor to follow boolean variable naming convention --- src/main/java/com/alexooi/duke/Duke.java | 6 +++--- src/main/java/com/alexooi/duke/tasks/TaskList.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/alexooi/duke/Duke.java b/src/main/java/com/alexooi/duke/Duke.java index d320f7de..6d08fd52 100644 --- a/src/main/java/com/alexooi/duke/Duke.java +++ b/src/main/java/com/alexooi/duke/Duke.java @@ -41,13 +41,13 @@ public static void main(String[] args) { System.out.println(main.prompt.start()); Scanner in = new Scanner(System.in); - boolean receiveInput = true; + boolean isInputReceived = true; - while (receiveInput && in.hasNext()) { + while (isInputReceived && in.hasNext()) { try { Command command = main.parser.readInput(in, commandLoader); if (command.isExit()) { - receiveInput = false; + isInputReceived = false; } else { String output = command.execute(tasks, main.prompt); System.out.println(output); diff --git a/src/main/java/com/alexooi/duke/tasks/TaskList.java b/src/main/java/com/alexooi/duke/tasks/TaskList.java index e7e737f8..93e334d0 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskList.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskList.java @@ -56,12 +56,12 @@ public void save() { } private void load(BufferedReader in) { - boolean endOfInput = false; - while (!endOfInput) { + boolean isEndOfInput = false; + while (!isEndOfInput) { try { String taskStr = in.readLine(); if (taskStr == null) { - endOfInput = true; + isEndOfInput = true; } else { taskList.add(TaskFactory.getInstance(taskStr)); } From 3464887d0c957179fdbf9abe4f3f8a2c4daf2383 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 6 Nov 2021 18:34:28 +0800 Subject: [PATCH 43/59] Added find command --- .../alexooi/duke/commands/FindCommand.java | 42 +++++++++++++++++++ .../alexooi/duke/interfaces/OutputFormat.java | 2 + .../com/alexooi/duke/ui/OutputFormatter.java | 32 +++++++++----- .../com.alexooi.duke.commands.Command | 1 + text-ui-test/EXPECTED.TXT | 8 +++- text-ui-test/input.txt | 1 + 6 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/alexooi/duke/commands/FindCommand.java diff --git a/src/main/java/com/alexooi/duke/commands/FindCommand.java b/src/main/java/com/alexooi/duke/commands/FindCommand.java new file mode 100644 index 00000000..9ff4fb71 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/FindCommand.java @@ -0,0 +1,42 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class FindCommand extends Command { + private final String REGEX_PATTERN = "^(\\bfind\\b) (.*)$"; + + public FindCommand() { + + } + + public FindCommand(String keyword, String args) { + super(keyword, args); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + ArrayList taskList = tasks.get(); + + List filteredList = taskList.stream().filter((task) -> task.getDescription().contains(getArgs())).collect(Collectors.toList()); + + return prompt.find(filteredList); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public Command build() throws InvalidCommandFormatException { + return new FindCommand(getKeyword(), getArgs()); + } +} diff --git a/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java index db021f21..34b26014 100644 --- a/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java +++ b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java @@ -19,5 +19,7 @@ public interface OutputFormat { String viewSchedule(Iterable inputs, LocalDate date); + String find(Iterable inputs); + String exit(); } diff --git a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java index b37b5c38..71d6af33 100644 --- a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java +++ b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java @@ -14,6 +14,7 @@ public class OutputFormatter implements OutputFormat { private static final String ADD = "added: "; private static final String REMOVE = "Noted. I've removed this task:"; private static final String VIEW_SCHEDULE = "You have the following tasks scheduled on "; + private static final String FIND = "Here are the matching tasks in your list:"; @Override public String start() { @@ -56,22 +57,23 @@ public String error(String errorMessage) { @Override public String list(Iterable inputs) { StringBuilder output = new StringBuilder(); - int count = 1; - for (Task input : inputs) { - String taskStr = input.toStatusString(); - output.append(formatLine(count + ". " + taskStr)); - count++; - } + output.append(formatList(inputs)); return formatOutput(output.toString()); } @Override public String viewSchedule(Iterable inputs, LocalDate date) { StringBuilder output = new StringBuilder(); - output.append(formatLine(VIEW_SCHEDULE + date.toString() + System.lineSeparator())); - for (Task input : inputs) { - output.append(formatLine(input.toStatusString())); - } + output.append(formatLine(VIEW_SCHEDULE + date.toString())); + output.append(formatList(inputs)); + return formatOutput(output.toString()); + } + + @Override + public String find(Iterable inputs) { + StringBuilder output = new StringBuilder(); + output.append(formatLine(FIND)); + output.append(formatList(inputs)); return formatOutput(output.toString()); } @@ -93,4 +95,14 @@ private String formatLine(String output) { private String formatOutput(String output) { return formatLine(HORIZONTAL_LINE) + output + PADDING + HORIZONTAL_LINE; } + + private String formatList(Iterable inputs) { + StringBuilder output = new StringBuilder(); + int count = 1; + for (Task input : inputs) { + output.append(formatLine(count + ". " + input.toStatusString())); + count++; + } + return output.toString(); + } } diff --git a/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command b/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command index 9800571e..26223e5b 100644 --- a/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command +++ b/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command @@ -4,3 +4,4 @@ com.alexooi.duke.commands.DeleteCommand com.alexooi.duke.commands.ExitCommand com.alexooi.duke.commands.ListCommand com.alexooi.duke.commands.ViewScheduleCommand +com.alexooi.duke.commands.FindCommand \ No newline at end of file diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index a90cfd19..6d243a38 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -29,8 +29,12 @@ _________________________________________ _________________________________________ You have the following tasks scheduled on 2021-09-20 - - [D][X] return book (by: 2021-09-20T00:00) + 1. [D][X] return book (by: 2021-09-20T00:00) + _________________________________________ + _________________________________________ + Here are the matching tasks in your list: + 1. [T][ ] read book + 2. [D][X] return book (by: 2021-09-20T00:00) _________________________________________ _________________________________________ Noted. I've removed this task: diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index ac859799..dec4f8e1 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -5,5 +5,6 @@ event meet friend /at 2021-09-15 done 2 list view 2021-09-20 +find book delete 2 list \ No newline at end of file From aa81a9b56a4899d701b2e543ef0aeced4f1aa6f6 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 6 Nov 2021 20:55:31 +0800 Subject: [PATCH 44/59] Add archival --- .gitignore | 3 +- src/main/java/com/alexooi/duke/Duke.java | 5 +- .../alexooi/duke/commands/ArchiveCommand.java | 50 +++++++++++++++++++ .../InvalidCommandFormatException.java | 4 +- .../alexooi/duke/interfaces/OutputFormat.java | 4 ++ .../duke/interfaces/StorageClient.java | 2 +- .../com/alexooi/duke/storage/FileStorage.java | 5 +- .../java/com/alexooi/duke/tasks/TaskList.java | 35 ++++++++++--- .../com/alexooi/duke/ui/OutputFormatter.java | 15 ++++++ .../com.alexooi.duke.commands.Command | 3 +- text-ui-test/EXPECTED.TXT | 13 +++++ text-ui-test/input.txt | 10 ---- 12 files changed, 124 insertions(+), 25 deletions(-) create mode 100644 src/main/java/com/alexooi/duke/commands/ArchiveCommand.java delete mode 100644 text-ui-test/input.txt diff --git a/.gitignore b/.gitignore index 4ede840f..3f8bcafa 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT -state.txt \ No newline at end of file +state.txt +archive.txt \ No newline at end of file diff --git a/src/main/java/com/alexooi/duke/Duke.java b/src/main/java/com/alexooi/duke/Duke.java index 6d08fd52..3c20503c 100644 --- a/src/main/java/com/alexooi/duke/Duke.java +++ b/src/main/java/com/alexooi/duke/Duke.java @@ -34,9 +34,10 @@ public static Duke getInstance() { public static void main(String[] args) { Duke main = Duke.getInstance(); - FileStorage client = new FileStorage("state.txt"); + FileStorage state = new FileStorage("state.txt"); + FileStorage archive = new FileStorage("archive.txt"); - TaskList tasks = TaskList.getInstance(client); + TaskList tasks = TaskList.getInstance(state, archive); ServiceLoader commandLoader = ServiceLoader.load(Command.class); System.out.println(main.prompt.start()); diff --git a/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java b/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java new file mode 100644 index 00000000..5b9621f6 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java @@ -0,0 +1,50 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; + +import java.util.regex.Pattern; + +public class ArchiveCommand extends Command { + private final String REGEX_PATTERN = "^(\\barchive\\b)\\s+(\\d+|all)$"; + private final String ARGS_ALL = "all"; + + public ArchiveCommand() { + + } + + public ArchiveCommand(String keyword, String args) { + super(keyword, args); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + String output; + if (getArgs().equalsIgnoreCase(ARGS_ALL)) { + tasks.archive(); + output = prompt.archive(tasks.get()); + tasks.removeAll(); + } else { + int idx = Integer.parseUnsignedInt(getArgs()) - 1; + if (idx > tasks.size() + 1) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_ARCHIVE_NO_SUCH_TASK); + } + tasks.archive(idx); + Task archived = tasks.remove(idx); + output = prompt.archive(archived); + } + return output; + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public Command build() throws InvalidCommandFormatException { + return new ArchiveCommand(getKeyword(), getArgs()); + } +} diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java index 95154745..1a0cab2d 100644 --- a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java @@ -9,7 +9,9 @@ public class InvalidCommandFormatException extends Throwable { public static final String ERROR_TODO = "Todo should be in the format: todo ."; public static final String ERROR_VIEW_SCHEDULE = - "View schedule should be in the format: view schedule /for ."; + "View schedule should be in the format: view ."; + public static final String ERROR_ARCHIVE_NO_SUCH_TASK = + "The task number to archive is not valid. Archive should be in the format: archive where num is a valid index"; private String errorMessage; diff --git a/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java index 34b26014..86f30600 100644 --- a/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java +++ b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java @@ -21,5 +21,9 @@ public interface OutputFormat { String find(Iterable inputs); + String archive(T printable); + + String archive(Iterable inputs); + String exit(); } diff --git a/src/main/java/com/alexooi/duke/interfaces/StorageClient.java b/src/main/java/com/alexooi/duke/interfaces/StorageClient.java index 2207a06d..8ed57e53 100644 --- a/src/main/java/com/alexooi/duke/interfaces/StorageClient.java +++ b/src/main/java/com/alexooi/duke/interfaces/StorageClient.java @@ -2,5 +2,5 @@ public interface StorageClient { T load(); - void save(String input); + void save(String input, boolean isAppend); } diff --git a/src/main/java/com/alexooi/duke/storage/FileStorage.java b/src/main/java/com/alexooi/duke/storage/FileStorage.java index 950de8d8..fbad36a9 100644 --- a/src/main/java/com/alexooi/duke/storage/FileStorage.java +++ b/src/main/java/com/alexooi/duke/storage/FileStorage.java @@ -31,10 +31,11 @@ public BufferedReader load() { return null; } - public void save(String input) { + @Override + public void save(String input, boolean isAppend) { try { File file = new File(this.filePath); - FileWriter writer = new FileWriter(file); + FileWriter writer = new FileWriter(file, isAppend); BufferedWriter out = new BufferedWriter(writer); out.write(input); out.close(); diff --git a/src/main/java/com/alexooi/duke/tasks/TaskList.java b/src/main/java/com/alexooi/duke/tasks/TaskList.java index 93e334d0..a8de95cc 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskList.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskList.java @@ -10,18 +10,20 @@ public class TaskList { private static TaskList instance; private ArrayList taskList; - private StorageClient client; + private StorageClient stateClient; + private StorageClient archiveClient; - private TaskList(StorageClient client) { + private TaskList(StorageClient stateClient, StorageClient archiveClient) { taskList = new ArrayList<>(); - this.client = client; - BufferedReader input = client.load(); + this.stateClient = stateClient; + this.archiveClient = archiveClient; + BufferedReader input = stateClient.load(); load(input); } - public static TaskList getInstance(StorageClient client) { + public static TaskList getInstance(StorageClient stateClient, StorageClient archiveClient) { if (instance == null) { - instance = new TaskList(client); + instance = new TaskList(stateClient, archiveClient); } return instance; @@ -35,6 +37,10 @@ public Task remove(int idx) { return taskList.remove(idx); } + public void removeAll() { + taskList.clear(); + } + public ArrayList get() { return taskList; } @@ -47,12 +53,27 @@ public int size() { return taskList.size(); } + public void archive(int idx) { + String output = taskList.get(idx).toSaveString() + System.lineSeparator(); + archiveClient.save(output, true); + } + + public void archive() { + String output = toSaveListString(); + archiveClient.save(output, true); + } + public void save() { + String output = toSaveListString(); + stateClient.save(output, false); + } + + private String toSaveListString() { String output = taskList.stream().reduce("", (partialOutput, task) -> partialOutput + task.toSaveString() + System.lineSeparator(), String::concat ); - client.save(output); + return output; } private void load(BufferedReader in) { diff --git a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java index 71d6af33..df0e280e 100644 --- a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java +++ b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java @@ -15,6 +15,8 @@ public class OutputFormatter implements OutputFormat { private static final String REMOVE = "Noted. I've removed this task:"; private static final String VIEW_SCHEDULE = "You have the following tasks scheduled on "; private static final String FIND = "Here are the matching tasks in your list:"; + private static final String ARCHIVE_SINGLE = "We successfully archived the following task:"; + private static final String ARCHIVE_ALL = "We successfully archived all of your tasks!"; @Override public String start() { @@ -77,6 +79,19 @@ public String find(Iterable inputs) { return formatOutput(output.toString()); } + @Override + public String archive(Task printable) { + String output = formatLine(ARCHIVE_SINGLE) + formatLine(printable.toStatusString()); + return formatOutput(output); + } + + @Override + public String archive(Iterable inputs) { + String output = formatLine(ARCHIVE_ALL) + + formatList(inputs); + return formatOutput(output); + } + @Override public String exit() { return formatOutput(formatLine(END)); diff --git a/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command b/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command index 26223e5b..5778a4e1 100644 --- a/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command +++ b/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command @@ -4,4 +4,5 @@ com.alexooi.duke.commands.DeleteCommand com.alexooi.duke.commands.ExitCommand com.alexooi.duke.commands.ListCommand com.alexooi.duke.commands.ViewScheduleCommand -com.alexooi.duke.commands.FindCommand \ No newline at end of file +com.alexooi.duke.commands.FindCommand +com.alexooi.duke.commands.ArchiveCommand \ No newline at end of file diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 6d243a38..471c9945 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -46,5 +46,18 @@ 2. [E][ ] meet friend (at: 2021-09-15T00:00) _________________________________________ _________________________________________ + We successfully archived the following task: + [E][ ] meet friend (at: 2021-09-15T00:00) + _________________________________________ + _________________________________________ + 1. [T][ ] read book + _________________________________________ + _________________________________________ + We successfully archived all of your tasks! + 1. [T][ ] read book + _________________________________________ + _________________________________________ + _________________________________________ + _________________________________________ Bye. Hope to see you again soon! _________________________________________ diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt deleted file mode 100644 index dec4f8e1..00000000 --- a/text-ui-test/input.txt +++ /dev/null @@ -1,10 +0,0 @@ -todo read book -deadline return book /by 2021-09-20 -list -event meet friend /at 2021-09-15 -done 2 -list -view 2021-09-20 -find book -delete 2 -list \ No newline at end of file From cad70b6fdc5c0711bdcfd7f405fb56f3904136e6 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 6 Nov 2021 20:59:14 +0800 Subject: [PATCH 45/59] Add missing input.txt file --- text-ui-test/input.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 text-ui-test/input.txt diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt new file mode 100644 index 00000000..4e96e6d3 --- /dev/null +++ b/text-ui-test/input.txt @@ -0,0 +1,14 @@ +todo read book +deadline return book /by 2021-09-20 +list +event meet friend /at 2021-09-15 +done 2 +list +view 2021-09-20 +find book +delete 2 +list +archive 2 +list +archive all +list \ No newline at end of file From 3f19e974cf83e9c1c11c8b03ec99feb899e09046 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sun, 7 Nov 2021 11:11:36 +0800 Subject: [PATCH 46/59] Add JavaDoc --- src/main/java/com/alexooi/duke/Duke.java | 6 +- .../InvalidCommandFormatException.java | 2 - .../com/alexooi/duke/storage/FileStorage.java | 14 +++- .../java/com/alexooi/duke/tasks/Deadline.java | 5 -- .../java/com/alexooi/duke/tasks/Event.java | 5 -- .../com/alexooi/duke/tasks/TaskFactory.java | 14 ++++ .../java/com/alexooi/duke/tasks/TaskList.java | 53 ++++++++++-- .../alexooi/duke/ui/CommandLineParser.java | 9 ++ .../com/alexooi/duke/ui/OutputFormatter.java | 84 ++++++++++++++++--- .../alexooi/duke/utility/InputDateParser.java | 20 ++++- 10 files changed, 179 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/alexooi/duke/Duke.java b/src/main/java/com/alexooi/duke/Duke.java index 3c20503c..220b4729 100644 --- a/src/main/java/com/alexooi/duke/Duke.java +++ b/src/main/java/com/alexooi/duke/Duke.java @@ -42,13 +42,13 @@ public static void main(String[] args) { System.out.println(main.prompt.start()); Scanner in = new Scanner(System.in); - boolean isInputReceived = true; + boolean isReceivingInput = true; - while (isInputReceived && in.hasNext()) { + while (isReceivingInput && in.hasNext()) { try { Command command = main.parser.readInput(in, commandLoader); if (command.isExit()) { - isInputReceived = false; + isReceivingInput = false; } else { String output = command.execute(tasks, main.prompt); System.out.println(output); diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java index 1a0cab2d..2b419a47 100644 --- a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java @@ -13,8 +13,6 @@ public class InvalidCommandFormatException extends Throwable { public static final String ERROR_ARCHIVE_NO_SUCH_TASK = "The task number to archive is not valid. Archive should be in the format: archive where num is a valid index"; - private String errorMessage; - public InvalidCommandFormatException(String errorMessage) { super(errorMessage); } diff --git a/src/main/java/com/alexooi/duke/storage/FileStorage.java b/src/main/java/com/alexooi/duke/storage/FileStorage.java index fbad36a9..4b8e0ee4 100644 --- a/src/main/java/com/alexooi/duke/storage/FileStorage.java +++ b/src/main/java/com/alexooi/duke/storage/FileStorage.java @@ -4,6 +4,9 @@ import java.io.*; +/** + * The concrete implementation of the StorageClient for storing in files. + */ public class FileStorage implements StorageClient { String filePath; @@ -11,6 +14,10 @@ public FileStorage(String filePath) { this.filePath = filePath; } + /** + * Loads the specific file at the file path and returns a reader for use by the parser + * @return A buffered reader that can be used to scan the file. + */ public BufferedReader load() { try { File file = new File(this.filePath); @@ -26,11 +33,16 @@ public BufferedReader load() { } catch (FileNotFoundException fnfe) { System.out.println("File Not Found Exception: Shouldn't have hit this!"); } catch (IOException ioe) { - System.out.println("IOException: Had an issue creating state file"); + System.out.println("IOException: Had an issue creating file"); } return null; } + /** + * Save the input into the file at the file path. + * @param input The string to write into the file + * @param isAppend Whether to append to the file or overwrite the file + */ @Override public void save(String input, boolean isAppend) { try { diff --git a/src/main/java/com/alexooi/duke/tasks/Deadline.java b/src/main/java/com/alexooi/duke/tasks/Deadline.java index e457970d..0a7bd113 100644 --- a/src/main/java/com/alexooi/duke/tasks/Deadline.java +++ b/src/main/java/com/alexooi/duke/tasks/Deadline.java @@ -10,11 +10,6 @@ public class Deadline extends Task { private LocalDateTime dueDate; private DateParser dateParser; - public Deadline(String description, String dueDate) { - super(TaskType.DEADLINE, description); - this.setDueDate(dueDate); - } - public Deadline(String description, String dueDate, DateParser parser) { super(TaskType.DEADLINE, description); this.dateParser = parser; diff --git a/src/main/java/com/alexooi/duke/tasks/Event.java b/src/main/java/com/alexooi/duke/tasks/Event.java index f9ad7452..215d6c35 100644 --- a/src/main/java/com/alexooi/duke/tasks/Event.java +++ b/src/main/java/com/alexooi/duke/tasks/Event.java @@ -10,11 +10,6 @@ public class Event extends Task { private LocalDateTime timing; private DateParser dateParser; - public Event(String description, String timing) { - super(TaskType.EVENT, description); - this.setTiming(timing); - } - public Event(String description, String timing, DateParser parser) { super(TaskType.EVENT, description); this.dateParser = parser; diff --git a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java index 25005ac4..16583867 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java @@ -19,6 +19,12 @@ public class TaskFactory { "^(?<" + KEY_DESCRIPTION + ">.+) /at (?<" + KEY_DATE + ">.+)$"; private static final DateParser dateParser = new InputDateParser(); + /** + * This function takes in a command and + * @param cmd The command containing the keyword (Task name) and arguments (Date if applicable) + * @return Deadline, Event or Todo task + * @throws InvalidCommandFormatException If the date does is not parsable for Deadline and Event, this exception is thrown + */ public static Task getInstance(Command cmd) throws InvalidCommandFormatException { String keyword = cmd.getKeyword(); String args = cmd.getArgs(); @@ -52,8 +58,16 @@ public static Task getInstance(Command cmd) throws InvalidCommandFormatException "Cannot find the appropriate task to create."); } + /** + * This function parses an input string that is formatted in the save file format and returns the respective Task + * @param s The input string to parse into a task + * @return The Deadline, Event or To do Task + * @throws InvalidFileFormatException This exception gets thrown if the line being parsed does not match any Task. + * It should not occur unless the file has been modified externally. + */ public static Task getInstance(String s) throws InvalidFileFormatException { String[] line = s.split("\\|"); + assert line.length > 2; boolean doneState = line[1].equalsIgnoreCase("t"); Task currentTask; diff --git a/src/main/java/com/alexooi/duke/tasks/TaskList.java b/src/main/java/com/alexooi/duke/tasks/TaskList.java index a8de95cc..3e3109b7 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskList.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskList.java @@ -7,11 +7,14 @@ import java.io.IOException; import java.util.ArrayList; +/** + * This class follows the Singleton design pattern as it is used extensively throughout the application + */ public class TaskList { private static TaskList instance; - private ArrayList taskList; - private StorageClient stateClient; - private StorageClient archiveClient; + private final ArrayList taskList; + private final StorageClient stateClient; + private final StorageClient archiveClient; private TaskList(StorageClient stateClient, StorageClient archiveClient) { taskList = new ArrayList<>(); @@ -21,6 +24,12 @@ private TaskList(StorageClient stateClient, StorageClient stateClient, StorageClient archiveClient) { if (instance == null) { instance = new TaskList(stateClient, archiveClient); @@ -29,51 +38,85 @@ public static TaskList getInstance(StorageClient stateClient, St return instance; } + /** + * Adds a task to the task list + * @param task The task to add + */ public void add(Task task) { taskList.add(task); } + /** + * Removes a task from the task list by its index + * @param idx The index of the task to remove + * @return The task that has been removed. + */ public Task remove(int idx) { return taskList.remove(idx); } + /** + * Removes all tasks from the task list. + */ public void removeAll() { taskList.clear(); } + /** + * Get the list of tasks + * @return Returns an ArrayList containing all the tasks currently in the task list + */ public ArrayList get() { return taskList; } + /** + * Get a task by its index + * @param idx The index of the task to retrieve + * @return The task at index idx + */ public Task getTask(int idx) { return taskList.get(idx); } + /** + * Get the length of the task list + * @return The length of the task list + */ public int size() { return taskList.size(); } + /** + * Archives a single task in the task list. + * @param idx The index of the task to archive. + */ public void archive(int idx) { String output = taskList.get(idx).toSaveString() + System.lineSeparator(); archiveClient.save(output, true); } + /** + * Archive all tasks in the task list. + */ public void archive() { String output = toSaveListString(); archiveClient.save(output, true); } + /** + * Save the state of the task list. + */ public void save() { String output = toSaveListString(); stateClient.save(output, false); } private String toSaveListString() { - String output = taskList.stream().reduce("", + return taskList.stream().reduce("", (partialOutput, task) -> partialOutput + task.toSaveString() + System.lineSeparator(), String::concat ); - return output; } private void load(BufferedReader in) { diff --git a/src/main/java/com/alexooi/duke/ui/CommandLineParser.java b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java index ddcaa3f2..2bc8293d 100644 --- a/src/main/java/com/alexooi/duke/ui/CommandLineParser.java +++ b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java @@ -10,6 +10,15 @@ import java.util.regex.Pattern; public class CommandLineParser implements IOParser { + /** + * This function reads the user input and matches it against a list of commands. + * It parses the keyword and arguments of the command and returns the command found for execution. + * @param sc Scanner object to read in input + * @param commands A list of commands implemented to match against the input + * @return The command that was detected + * @throws InvalidCommandException Reaches this exception if no command is found matching the input + * @throws InvalidCommandFormatException Reaches this exception if a command is found the but the input is invalid + */ public Command readInput(Scanner sc, Iterable commands) throws InvalidCommandException, InvalidCommandFormatException { String input = sc.nextLine(); diff --git a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java index df0e280e..d9416ade 100644 --- a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java +++ b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java @@ -5,6 +5,10 @@ import java.time.LocalDate; +/** + * This class is the concrete implementation of OutputFormat. It is used for formatting the chatbot output to the + * desired output format. + */ public class OutputFormatter implements OutputFormat { private static final String PADDING = " "; private static final String HORIZONTAL_LINE = "_________________________________________"; @@ -18,11 +22,21 @@ public class OutputFormatter implements OutputFormat { private static final String ARCHIVE_SINGLE = "We successfully archived the following task:"; private static final String ARCHIVE_ALL = "We successfully archived all of your tasks!"; + /** + * Generates the output when first starting the application. + * @return formatted string containing the output to print. + */ @Override public String start() { return formatOutput(formatLine(START)); } + /** + * Generates the output when adding a new Task + * @param printable Task or Child Task that has implemented the toString() method + * @param length Length of the task list after adding the new Task + * @return formatted string containing the output to print + */ @Override public String add(Task printable, int length) { String output = formatLine(ADD + printable.toString()); @@ -30,6 +44,11 @@ public String add(Task printable, int length) { return formatOutput(output); } + /** + * Generates the output when marking a Task as done. + * @param printable Task or Child Task that has implemented the toStatusString method + * @return formatted string containing the output to print + */ @Override public String done(Task printable) { String output = formatLine(DONE); @@ -37,6 +56,12 @@ public String done(Task printable) { return formatOutput(output); } + /** + * Generates the output when removing a Task. + * @param printable The task that has been removed + * @param length Length of the task list after removing the Task + * @return formatted string containing the output to print + */ @Override public String remove(Task printable, int length) { String output = formatLine(REMOVE); @@ -45,46 +70,79 @@ public String remove(Task printable, int length) { return formatOutput(output); } + /** + * Generates the output when there is an error that has occurred. + * @param header The header to print for the error + * @param errorMessage The error message of the error that occurred + * @return formatted string containing the error header and message + */ @Override public String error(String header, String errorMessage) { String output = formatLine(header) + formatLine(errorMessage); return formatOutput(output); } + /** + * Generates the output when there is an error that has occurred. + * @param errorMessage The error message of the error that occurred + * @return formatted string containing the error message + */ @Override public String error(String errorMessage) { return formatOutput(formatLine(errorMessage)); } + /** + * Generates the output when listing the current task list + * @param inputs An iterable list of Tasks + * @return formatted string containing the list of Tasks + */ @Override public String list(Iterable inputs) { - StringBuilder output = new StringBuilder(); - output.append(formatList(inputs)); - return formatOutput(output.toString()); + return formatOutput(formatList(inputs)); } + /** + * Generates the output when viewing the schedule of a specific date + * @param inputs An iterable list of tasks on that date + * @param date Date that is being viewed + * @return formatted string containing the list of Tasks on that specific date + */ @Override public String viewSchedule(Iterable inputs, LocalDate date) { - StringBuilder output = new StringBuilder(); - output.append(formatLine(VIEW_SCHEDULE + date.toString())); - output.append(formatList(inputs)); - return formatOutput(output.toString()); + String output = formatLine(VIEW_SCHEDULE + date.toString()) + + formatList(inputs); + return formatOutput(output); } + /** + * Generates the output of a description find + * @param inputs An iterable list of tasks that matches the find + * @return formatted string containing the list of Tasks that match the keyword + */ @Override public String find(Iterable inputs) { - StringBuilder output = new StringBuilder(); - output.append(formatLine(FIND)); - output.append(formatList(inputs)); - return formatOutput(output.toString()); + String output = formatLine(FIND) + + formatList(inputs); + return formatOutput(output); } + /** + * Generates the output of a single Task archival + * @param printable The task that has been archived + * @return Formatted string containing the Task + */ @Override public String archive(Task printable) { String output = formatLine(ARCHIVE_SINGLE) + formatLine(printable.toStatusString()); return formatOutput(output); } + /** + * Generates the output of a complete Task List archival + * @param inputs The list of tasks the have been archived + * @return Formatted string containing all the tasks that are archived + */ @Override public String archive(Iterable inputs) { String output = formatLine(ARCHIVE_ALL) + @@ -92,6 +150,10 @@ public String archive(Iterable inputs) { return formatOutput(output); } + /** + * Generates the output when the user is exiting + * @return Formatted string for the exit message + */ @Override public String exit() { return formatOutput(formatLine(END)); diff --git a/src/main/java/com/alexooi/duke/utility/InputDateParser.java b/src/main/java/com/alexooi/duke/utility/InputDateParser.java index dacd2cf8..0b85f0e3 100644 --- a/src/main/java/com/alexooi/duke/utility/InputDateParser.java +++ b/src/main/java/com/alexooi/duke/utility/InputDateParser.java @@ -16,6 +16,12 @@ public class InputDateParser implements DateParser { DateTimeFormatter.ISO_DATE_TIME }; + /** + * Checks if a string matches some particular date formats, returning true if it does, false if not. + * @param input Date in string format + * @param acceptedFormats Can be a DateTimeFormatter or a list of accepted formats built using DateTimeFormatterBuilder + * @return True if the string is an accepted date format, false if not + */ @Override public boolean isDate(String input, DateTimeFormatter acceptedFormats) { try { @@ -26,6 +32,12 @@ public boolean isDate(String input, DateTimeFormatter acceptedFormats) { return true; } + /** + * Checks if a string matches some particular date formats, returning true if it does, false if not + * @param input Date and Time in string format + * @param acceptedFormats Can be a DateTimeFormatter or a list of accepted formats built using DateTimeFormatterBuilder + * @return True if the string is an accepted Date and Time format, false if not + */ @Override public boolean isDateTime(String input, DateTimeFormatter acceptedFormats) { try { @@ -36,7 +48,7 @@ public boolean isDateTime(String input, DateTimeFormatter acceptedFormats) { return true; } - DateTimeFormatter formatBuilder(DateTimeFormatter[] formats) { + private DateTimeFormatter formatBuilder(DateTimeFormatter[] formats) { DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); for (DateTimeFormatter formatter : formats) { builder.appendOptional(formatter); @@ -44,6 +56,12 @@ DateTimeFormatter formatBuilder(DateTimeFormatter[] formats) { return builder.toFormatter(); } + /** + * Parses a LocalDateTime object from an input string + * @param input Date or Date and Time in string format + * @return Returns a LocalDateTime object containing the parsed Date and parsed Time if applicable, + * otherwise time defaults to start of day 00:00. If the string cannot be parsed, then returns null + */ @Override public LocalDateTime parseInput(String input) { DateTimeFormatter dateTimeFormats = formatBuilder(ACCEPTED_DATE_TIME_FORMATS); From 5e6e13ed72f709bab34afac028c395934aea05ea Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sun, 7 Nov 2021 11:28:20 +0800 Subject: [PATCH 47/59] Add important assertions --- src/main/java/com/alexooi/duke/tasks/TaskList.java | 4 ++++ src/main/java/com/alexooi/duke/ui/CommandLineParser.java | 1 + 2 files changed, 5 insertions(+) diff --git a/src/main/java/com/alexooi/duke/tasks/TaskList.java b/src/main/java/com/alexooi/duke/tasks/TaskList.java index 3e3109b7..eca865fa 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskList.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskList.java @@ -17,6 +17,7 @@ public class TaskList { private final StorageClient archiveClient; private TaskList(StorageClient stateClient, StorageClient archiveClient) { + assert stateClient != null && archiveClient != null; taskList = new ArrayList<>(); this.stateClient = stateClient; this.archiveClient = archiveClient; @@ -93,6 +94,7 @@ public int size() { */ public void archive(int idx) { String output = taskList.get(idx).toSaveString() + System.lineSeparator(); + assert archiveClient != null; archiveClient.save(output, true); } @@ -101,6 +103,7 @@ public void archive(int idx) { */ public void archive() { String output = toSaveListString(); + assert archiveClient != null; archiveClient.save(output, true); } @@ -109,6 +112,7 @@ public void archive() { */ public void save() { String output = toSaveListString(); + assert stateClient != null; stateClient.save(output, false); } diff --git a/src/main/java/com/alexooi/duke/ui/CommandLineParser.java b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java index 2bc8293d..9c4b20b1 100644 --- a/src/main/java/com/alexooi/duke/ui/CommandLineParser.java +++ b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java @@ -21,6 +21,7 @@ public class CommandLineParser implements IOParser { */ public Command readInput(Scanner sc, Iterable commands) throws InvalidCommandException, InvalidCommandFormatException { + assert sc != null; String input = sc.nextLine(); for (Command cmd : commands) { From e477dc6a1368f28bf79b4b185db6154b9a300b18 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sun, 7 Nov 2021 15:24:55 +0800 Subject: [PATCH 48/59] Add help command and personalized Duke --- .../com/alexooi/duke/commands/AddCommand.java | 3 ++ .../alexooi/duke/commands/ArchiveCommand.java | 4 +- .../com/alexooi/duke/commands/Command.java | 22 ++++++++- .../duke/commands/CompleteCommand.java | 3 ++ .../alexooi/duke/commands/DeleteCommand.java | 3 ++ .../alexooi/duke/commands/ExitCommand.java | 3 ++ .../alexooi/duke/commands/FindCommand.java | 4 +- .../alexooi/duke/commands/HelpCommand.java | 46 +++++++++++++++++++ .../alexooi/duke/commands/ListCommand.java | 3 ++ .../duke/commands/ViewScheduleCommand.java | 3 ++ .../com/alexooi/duke/enums/CommandType.java | 41 +++++++++++++++++ .../alexooi/duke/interfaces/OutputFormat.java | 2 + .../com/alexooi/duke/ui/OutputFormatter.java | 13 +++++- .../com.alexooi.duke.commands.Command | 3 +- text-ui-test/EXPECTED.TXT | 18 +++++++- text-ui-test/input.txt | 1 + 16 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/alexooi/duke/commands/HelpCommand.java create mode 100644 src/main/java/com/alexooi/duke/enums/CommandType.java diff --git a/src/main/java/com/alexooi/duke/commands/AddCommand.java b/src/main/java/com/alexooi/duke/commands/AddCommand.java index 3719e287..7710de20 100644 --- a/src/main/java/com/alexooi/duke/commands/AddCommand.java +++ b/src/main/java/com/alexooi/duke/commands/AddCommand.java @@ -1,5 +1,6 @@ package com.alexooi.duke.commands; +import com.alexooi.duke.enums.CommandType; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskFactory; import com.alexooi.duke.tasks.TaskList; @@ -13,10 +14,12 @@ public class AddCommand extends Command { public AddCommand() { super(); + setCommandType(CommandType.ADD); } public AddCommand(String keyword, String args) { super(keyword, args); + setCommandType(CommandType.ADD); } @Override diff --git a/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java b/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java index 5b9621f6..34b62a89 100644 --- a/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java +++ b/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java @@ -1,5 +1,6 @@ package com.alexooi.duke.commands; +import com.alexooi.duke.enums.CommandType; import com.alexooi.duke.exceptions.InvalidCommandFormatException; import com.alexooi.duke.interfaces.OutputFormat; import com.alexooi.duke.tasks.Task; @@ -12,11 +13,12 @@ public class ArchiveCommand extends Command { private final String ARGS_ALL = "all"; public ArchiveCommand() { - + setCommandType(CommandType.ARCHIVE); } public ArchiveCommand(String keyword, String args) { super(keyword, args); + setCommandType(CommandType.ARCHIVE); } @Override diff --git a/src/main/java/com/alexooi/duke/commands/Command.java b/src/main/java/com/alexooi/duke/commands/Command.java index 5cea7db8..2b5b6e41 100644 --- a/src/main/java/com/alexooi/duke/commands/Command.java +++ b/src/main/java/com/alexooi/duke/commands/Command.java @@ -1,15 +1,18 @@ package com.alexooi.duke.commands; -import com.alexooi.duke.exceptions.InvalidCommandException; +import com.alexooi.duke.enums.CommandType; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; import com.alexooi.duke.exceptions.InvalidCommandFormatException; import com.alexooi.duke.interfaces.OutputFormat; + +import java.util.ArrayList; import java.util.regex.Pattern; public abstract class Command { private String keyword; private String args; + private CommandType commandType; public Command() { @@ -40,6 +43,15 @@ public void setArgs(String args) { this.args = args; } + public Iterable getCommandFormat() { + ArrayList commandFormat = new ArrayList<>(); + commandFormat.add(getCommandType().getFormat()); + if (getCommandType().getNotes() != null) { + commandFormat.add(getCommandType().getNotes()); + } + return commandFormat; + } + public abstract String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException; public abstract Pattern getRegexPattern(); @@ -49,4 +61,12 @@ public void setArgs(String args) { public boolean isExit() { return false; }; + + public CommandType getCommandType() { + return commandType; + } + + public void setCommandType(CommandType commandType) { + this.commandType = commandType; + } } diff --git a/src/main/java/com/alexooi/duke/commands/CompleteCommand.java b/src/main/java/com/alexooi/duke/commands/CompleteCommand.java index efb9c46f..799809a5 100644 --- a/src/main/java/com/alexooi/duke/commands/CompleteCommand.java +++ b/src/main/java/com/alexooi/duke/commands/CompleteCommand.java @@ -1,5 +1,6 @@ package com.alexooi.duke.commands; +import com.alexooi.duke.enums.CommandType; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; import com.alexooi.duke.exceptions.InvalidCommandFormatException; @@ -11,10 +12,12 @@ public class CompleteCommand extends Command { private final String REGEX_PATTERN = "^\\b(done)\\b (.+)$"; public CompleteCommand() { + setCommandType(CommandType.COMPLETE); } public CompleteCommand(String keyword, String args) { super(keyword, args); + setCommandType(CommandType.COMPLETE); } @Override diff --git a/src/main/java/com/alexooi/duke/commands/DeleteCommand.java b/src/main/java/com/alexooi/duke/commands/DeleteCommand.java index 77d82903..3feff6e6 100644 --- a/src/main/java/com/alexooi/duke/commands/DeleteCommand.java +++ b/src/main/java/com/alexooi/duke/commands/DeleteCommand.java @@ -1,5 +1,6 @@ package com.alexooi.duke.commands; +import com.alexooi.duke.enums.CommandType; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; import com.alexooi.duke.exceptions.InvalidCommandFormatException; @@ -11,10 +12,12 @@ public class DeleteCommand extends Command { private final String REGEX_PATTERN = "^\\b(delete)\\b (.+)$"; public DeleteCommand() { + setCommandType(CommandType.REMOVE); } public DeleteCommand(String keyword, String args) { super(keyword, args); + setCommandType(CommandType.REMOVE); } @Override diff --git a/src/main/java/com/alexooi/duke/commands/ExitCommand.java b/src/main/java/com/alexooi/duke/commands/ExitCommand.java index 9ff678e0..6a921203 100644 --- a/src/main/java/com/alexooi/duke/commands/ExitCommand.java +++ b/src/main/java/com/alexooi/duke/commands/ExitCommand.java @@ -1,5 +1,6 @@ package com.alexooi.duke.commands; +import com.alexooi.duke.enums.CommandType; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; import com.alexooi.duke.exceptions.InvalidCommandFormatException; @@ -11,10 +12,12 @@ public class ExitCommand extends Command { private final String REGEX_PATTERN = "^(bye)$"; public ExitCommand() { + setCommandType(CommandType.EXIT); } public ExitCommand(String keyword) { super(keyword); + setCommandType(CommandType.EXIT); } @Override diff --git a/src/main/java/com/alexooi/duke/commands/FindCommand.java b/src/main/java/com/alexooi/duke/commands/FindCommand.java index 9ff4fb71..49299095 100644 --- a/src/main/java/com/alexooi/duke/commands/FindCommand.java +++ b/src/main/java/com/alexooi/duke/commands/FindCommand.java @@ -1,5 +1,6 @@ package com.alexooi.duke.commands; +import com.alexooi.duke.enums.CommandType; import com.alexooi.duke.exceptions.InvalidCommandFormatException; import com.alexooi.duke.interfaces.OutputFormat; import com.alexooi.duke.tasks.Task; @@ -14,11 +15,12 @@ public class FindCommand extends Command { private final String REGEX_PATTERN = "^(\\bfind\\b) (.*)$"; public FindCommand() { - + setCommandType(CommandType.FIND); } public FindCommand(String keyword, String args) { super(keyword, args); + setCommandType(CommandType.FIND); } @Override diff --git a/src/main/java/com/alexooi/duke/commands/HelpCommand.java b/src/main/java/com/alexooi/duke/commands/HelpCommand.java new file mode 100644 index 00000000..7211aaa1 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/HelpCommand.java @@ -0,0 +1,46 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.enums.CommandType; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; + +import java.util.ArrayList; +import java.util.ServiceLoader; +import java.util.regex.Pattern; + +public class HelpCommand extends Command { + private final String REGEX_PATTERN = "^(help)$"; + + public HelpCommand() { + setCommandType(CommandType.HELP); + } + + public HelpCommand(String keyword) { + super(keyword); + setCommandType(CommandType.HELP); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + ServiceLoader commandLoader = ServiceLoader.load(Command.class); + ArrayList promptInput = new ArrayList<>(); + for (Command cmd : commandLoader) { + ArrayList commandFormat = new ArrayList<>(); + cmd.getCommandFormat().forEach(commandFormat::add); + promptInput.add(String.join(" : ", commandFormat)); + } + return prompt.help(promptInput); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public Command build() throws InvalidCommandFormatException { + return new HelpCommand(getKeyword()); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/ListCommand.java b/src/main/java/com/alexooi/duke/commands/ListCommand.java index 9753e099..4da6b4fe 100644 --- a/src/main/java/com/alexooi/duke/commands/ListCommand.java +++ b/src/main/java/com/alexooi/duke/commands/ListCommand.java @@ -1,5 +1,6 @@ package com.alexooi.duke.commands; +import com.alexooi.duke.enums.CommandType; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; import com.alexooi.duke.exceptions.InvalidCommandFormatException; @@ -11,10 +12,12 @@ public class ListCommand extends Command { private final String REGEX_PATTERN = "^(list)$"; public ListCommand() { + setCommandType(CommandType.LIST); } public ListCommand(String keyword) { super(keyword); + setCommandType(CommandType.LIST); } @Override diff --git a/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java b/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java index 6367e191..a4cb5d8b 100644 --- a/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java +++ b/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java @@ -1,5 +1,6 @@ package com.alexooi.duke.commands; +import com.alexooi.duke.enums.CommandType; import com.alexooi.duke.exceptions.InvalidCommandFormatException; import com.alexooi.duke.interfaces.DateParser; import com.alexooi.duke.interfaces.OutputFormat; @@ -21,11 +22,13 @@ public class ViewScheduleCommand extends Command { DateParser parser; public ViewScheduleCommand() { + setCommandType(CommandType.VIEW_SCHEDULE); this.parser = new InputDateParser(); } public ViewScheduleCommand(String keyword, String args) { super(keyword, args); + setCommandType(CommandType.VIEW_SCHEDULE); this.parser = new InputDateParser(); } diff --git a/src/main/java/com/alexooi/duke/enums/CommandType.java b/src/main/java/com/alexooi/duke/enums/CommandType.java new file mode 100644 index 00000000..adf44382 --- /dev/null +++ b/src/main/java/com/alexooi/duke/enums/CommandType.java @@ -0,0 +1,41 @@ +package com.alexooi.duke.enums; + +public enum CommandType { + ADD("todo \n deadline /by \n event /at ", " must be in yyyy-mm-dd or yyyy-mm-ddThh:mm format."), + REMOVE("remove ", " must be a number that currently exists in the list."), + COMPLETE("done ", " must be a number that currently exists in the list."), + ARCHIVE("archive /all", " must be a number that currently exists in the list."), + VIEW_SCHEDULE("view ", " must be in yyyy-mm-dd."), + LIST("list"), + FIND("find "), + EXIT("bye"), + HELP("help"); + + private String format; + private String notes; + + CommandType(String format, String notes) { + setFormat(format); + setNotes(notes); + } + + CommandType(String format) { + setFormat(format); + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } +} diff --git a/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java index 86f30600..32e1a3fb 100644 --- a/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java +++ b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java @@ -25,5 +25,7 @@ public interface OutputFormat { String archive(Iterable inputs); + String help(Iterable inputs); + String exit(); } diff --git a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java index d9416ade..2eee2030 100644 --- a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java +++ b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java @@ -12,7 +12,8 @@ public class OutputFormatter implements OutputFormat { private static final String PADDING = " "; private static final String HORIZONTAL_LINE = "_________________________________________"; - private static final String START = "Hello! I'm Duke\n What can I do for you?"; + private static final String START = "Hello! I'm Tassie, your virtual assistant.\n What can I do for you?\n\n Note: Enter help to see a list of commands."; + private static final String COMMAND_LIST = "The following commands are available:"; private static final String END = "Bye. Hope to see you again soon!"; private static final String DONE = "Nice! I've marked this task as done:"; private static final String ADD = "added: "; @@ -150,6 +151,16 @@ public String archive(Iterable inputs) { return formatOutput(output); } + @Override + public String help(Iterable inputs) { + StringBuilder builder = new StringBuilder(); + builder.append(formatLine(COMMAND_LIST)); + for (String input : inputs) { + builder.append(formatLine(input)); + } + return formatOutput(builder.toString()); + } + /** * Generates the output when the user is exiting * @return Formatted string for the exit message diff --git a/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command b/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command index 5778a4e1..02058463 100644 --- a/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command +++ b/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command @@ -5,4 +5,5 @@ com.alexooi.duke.commands.ExitCommand com.alexooi.duke.commands.ListCommand com.alexooi.duke.commands.ViewScheduleCommand com.alexooi.duke.commands.FindCommand -com.alexooi.duke.commands.ArchiveCommand \ No newline at end of file +com.alexooi.duke.commands.ArchiveCommand +com.alexooi.duke.commands.HelpCommand \ No newline at end of file diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 471c9945..935717db 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,6 +1,22 @@ _________________________________________ - Hello! I'm Duke + Hello! I'm Tassie, your virtual assistant. What can I do for you? + + Note: Enter help to see a list of commands. + _________________________________________ + _________________________________________ + The following commands are available: + todo + deadline /by + event /at : must be in yyyy-mm-dd or yyyy-mm-ddThh:mm format. + done : must be a number that currently exists in the list. + remove : must be a number that currently exists in the list. + bye + list + view : must be in yyyy-mm-dd. + find + archive /all : must be a number that currently exists in the list. + help _________________________________________ _________________________________________ added: read book diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index 4e96e6d3..f91f93f9 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1,3 +1,4 @@ +help todo read book deadline return book /by 2021-09-20 list From 6f50f878409b96b697e768912278e807e4d0f6a9 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sun, 7 Nov 2021 15:34:08 +0800 Subject: [PATCH 49/59] Modify gradle build to prepare for release --- build.gradle | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2d41afe3..1b212c45 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group 'com.alexooi.duke' -version '0.1' +version '1.0' repositories { mavenCentral() @@ -16,6 +16,13 @@ sourceSets { } } } +jar { + manifest { + attributes( + 'Main-Class': 'com.alexooi.duke.Duke' + ) + } +} test { useJUnitPlatform() } From 83efcf04a34ec1520769f1072981961dffe51548 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sun, 7 Nov 2021 15:49:10 +0800 Subject: [PATCH 50/59] Fix done and undo taskg --- .../com/alexooi/duke/commands/CompleteCommand.java | 2 +- .../java/com/alexooi/duke/interfaces/OutputFormat.java | 2 +- src/main/java/com/alexooi/duke/ui/OutputFormatter.java | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/alexooi/duke/commands/CompleteCommand.java b/src/main/java/com/alexooi/duke/commands/CompleteCommand.java index 799809a5..23e7eaa4 100644 --- a/src/main/java/com/alexooi/duke/commands/CompleteCommand.java +++ b/src/main/java/com/alexooi/duke/commands/CompleteCommand.java @@ -35,6 +35,6 @@ public String execute(TaskList tasks, OutputFormat prompt) throws InvalidC int idx = Integer.parseInt(getArgs()) - 1; Task doneTask = tasks.getTask(idx); doneTask.setDone(!doneTask.getDone()); - return prompt.done(doneTask); + return prompt.done(doneTask, doneTask.getDone()); } } diff --git a/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java index 32e1a3fb..b77b782b 100644 --- a/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java +++ b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java @@ -7,7 +7,7 @@ public interface OutputFormat { String add(T printable, int length); - String done(T printable); + String done(T printable, boolean isDone); String remove(T printable, int length); diff --git a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java index 2eee2030..34583c7f 100644 --- a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java +++ b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java @@ -16,6 +16,7 @@ public class OutputFormatter implements OutputFormat { private static final String COMMAND_LIST = "The following commands are available:"; private static final String END = "Bye. Hope to see you again soon!"; private static final String DONE = "Nice! I've marked this task as done:"; + private static final String UNDO = "Okay, I've unmarked this task:"; private static final String ADD = "added: "; private static final String REMOVE = "Noted. I've removed this task:"; private static final String VIEW_SCHEDULE = "You have the following tasks scheduled on "; @@ -51,8 +52,13 @@ public String add(Task printable, int length) { * @return formatted string containing the output to print */ @Override - public String done(Task printable) { - String output = formatLine(DONE); + public String done(Task printable, boolean isDone) { + String output = ""; + if (isDone) { + output += formatLine(DONE); + } else { + output += formatLine(UNDO); + } output += formatLine(" " + printable.toStatusString()); return formatOutput(output); } From 39528c239723b4ff528c8407806c1b481e160c4d Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sun, 7 Nov 2021 15:57:32 +0800 Subject: [PATCH 51/59] Improve error handling --- src/main/java/com/alexooi/duke/commands/CompleteCommand.java | 3 +++ src/main/java/com/alexooi/duke/commands/DeleteCommand.java | 3 +++ .../alexooi/duke/exceptions/InvalidCommandFormatException.java | 2 ++ 3 files changed, 8 insertions(+) diff --git a/src/main/java/com/alexooi/duke/commands/CompleteCommand.java b/src/main/java/com/alexooi/duke/commands/CompleteCommand.java index 23e7eaa4..d74d2b89 100644 --- a/src/main/java/com/alexooi/duke/commands/CompleteCommand.java +++ b/src/main/java/com/alexooi/duke/commands/CompleteCommand.java @@ -33,6 +33,9 @@ public CompleteCommand build() { @Override public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { int idx = Integer.parseInt(getArgs()) - 1; + if (idx > tasks.size() - 1) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_NO_SUCH_INDEX); + } Task doneTask = tasks.getTask(idx); doneTask.setDone(!doneTask.getDone()); return prompt.done(doneTask, doneTask.getDone()); diff --git a/src/main/java/com/alexooi/duke/commands/DeleteCommand.java b/src/main/java/com/alexooi/duke/commands/DeleteCommand.java index 3feff6e6..79932d63 100644 --- a/src/main/java/com/alexooi/duke/commands/DeleteCommand.java +++ b/src/main/java/com/alexooi/duke/commands/DeleteCommand.java @@ -33,6 +33,9 @@ public DeleteCommand build() { @Override public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { int idx = Integer.parseInt(getArgs()) - 1; + if (idx > tasks.size() - 1) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_NO_SUCH_INDEX); + } Task removedTask = tasks.remove(idx); return prompt.remove(removedTask, tasks.size()); } diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java index 2b419a47..fa69255b 100644 --- a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java @@ -12,6 +12,8 @@ public class InvalidCommandFormatException extends Throwable { "View schedule should be in the format: view ."; public static final String ERROR_ARCHIVE_NO_SUCH_TASK = "The task number to archive is not valid. Archive should be in the format: archive where num is a valid index"; + public static final String ERROR_NO_SUCH_INDEX = + "The task number is not valid. Please use the corresponding task number when listing the tasks"; public InvalidCommandFormatException(String errorMessage) { super(errorMessage); From 20d89b072ebd3ee5717b91ea1e711c4ee22f0235 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sun, 7 Nov 2021 16:32:34 +0800 Subject: [PATCH 52/59] Further improved error handling --- src/main/java/com/alexooi/duke/Duke.java | 5 ++++ .../InvalidCommandFormatException.java | 4 +-- .../InvalidFileFormatException.java | 16 ++++++++++++ .../java/com/alexooi/duke/tasks/Deadline.java | 11 +++----- .../java/com/alexooi/duke/tasks/Event.java | 11 +++----- .../com/alexooi/duke/tasks/TaskFactory.java | 25 ++++++++++++++++--- .../java/com/alexooi/duke/tasks/TaskList.java | 10 +++++--- 7 files changed, 57 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/alexooi/duke/Duke.java b/src/main/java/com/alexooi/duke/Duke.java index 220b4729..6c47a126 100644 --- a/src/main/java/com/alexooi/duke/Duke.java +++ b/src/main/java/com/alexooi/duke/Duke.java @@ -1,6 +1,7 @@ package com.alexooi.duke; import com.alexooi.duke.commands.Command; +import com.alexooi.duke.exceptions.InvalidFileFormatException; import com.alexooi.duke.storage.FileStorage; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; @@ -32,6 +33,10 @@ public static Duke getInstance() { return instance; } + public OutputFormat getPrompt() { + return this.prompt; + } + public static void main(String[] args) { Duke main = Duke.getInstance(); FileStorage state = new FileStorage("state.txt"); diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java index fa69255b..9d39051a 100644 --- a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java @@ -3,9 +3,9 @@ public class InvalidCommandFormatException extends Throwable { private static final String ERROR_PREFIX = "Invalid Command Format:"; public static final String ERROR_DEADLINE = - "Deadline should be in the format: deadline /by ."; + "Deadline should be in the format: deadline /by where is in the format of yyyy-mm-dd."; public static final String ERROR_EVENT = - "Event should be in the format: event /at ."; + "Event should be in the format: event /at where is in the format of yyyy-mm-dd."; public static final String ERROR_TODO = "Todo should be in the format: todo ."; public static final String ERROR_VIEW_SCHEDULE = diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java index 5777ac7e..923fc3ad 100644 --- a/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java @@ -1,4 +1,20 @@ package com.alexooi.duke.exceptions; public class InvalidFileFormatException extends Throwable { + private static final String ERROR_PREFIX = "Invalid File Format:"; + public static final String ERROR_DATE = "Unable to parse date from file, file may have been modified manually or corrupted."; + + public InvalidFileFormatException() {} + + public InvalidFileFormatException(String errorMessage) { + super(errorMessage); + } + + public String getErrorHeader() { + return ERROR_PREFIX; + } + + public String toString() { + return getMessage(); + } } diff --git a/src/main/java/com/alexooi/duke/tasks/Deadline.java b/src/main/java/com/alexooi/duke/tasks/Deadline.java index 0a7bd113..d06c16de 100644 --- a/src/main/java/com/alexooi/duke/tasks/Deadline.java +++ b/src/main/java/com/alexooi/duke/tasks/Deadline.java @@ -6,13 +6,10 @@ import java.time.LocalDateTime; public class Deadline extends Task { - private String dueDateStr; private LocalDateTime dueDate; - private DateParser dateParser; - public Deadline(String description, String dueDate, DateParser parser) { + public Deadline(String description, LocalDateTime dueDate) { super(TaskType.DEADLINE, description); - this.dateParser = parser; this.setDueDate(dueDate); } @@ -20,10 +17,8 @@ public LocalDateTime getDueDate() { return dueDate; } - public void setDueDate(String dueDate) { - LocalDateTime dateTime = dateParser.parseInput(dueDate); - this.dueDateStr = dueDate; - this.dueDate = dateTime; + public void setDueDate(LocalDateTime dueDate) { + this.dueDate = dueDate; } @Override diff --git a/src/main/java/com/alexooi/duke/tasks/Event.java b/src/main/java/com/alexooi/duke/tasks/Event.java index 215d6c35..68164594 100644 --- a/src/main/java/com/alexooi/duke/tasks/Event.java +++ b/src/main/java/com/alexooi/duke/tasks/Event.java @@ -6,13 +6,10 @@ import java.time.LocalDateTime; public class Event extends Task { - private String timingStr; private LocalDateTime timing; - private DateParser dateParser; - public Event(String description, String timing, DateParser parser) { + public Event(String description, LocalDateTime timing) { super(TaskType.EVENT, description); - this.dateParser = parser; this.setTiming(timing); } @@ -20,10 +17,8 @@ public LocalDateTime getTiming() { return timing; } - public void setTiming(String timing) { - LocalDateTime dateTime = dateParser.parseInput(timing); - this.timingStr = timing; - this.timing = dateTime; + public void setTiming(LocalDateTime timing) { + this.timing = timing; } @Override diff --git a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java index 16583867..037810ec 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java @@ -7,6 +7,7 @@ import com.alexooi.duke.interfaces.DateParser; import com.alexooi.duke.utility.InputDateParser; +import java.time.LocalDateTime; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -34,7 +35,11 @@ public static Task getInstance(Command cmd) throws InvalidCommandFormatException if (deadlineMatch.matches()) { String description = deadlineMatch.group(KEY_DESCRIPTION); String dueDate = deadlineMatch.group(KEY_DATE); - return new Deadline(description, dueDate, dateParser); + LocalDateTime dueBy = dateParser.parseInput(dueDate); + if (dueBy == null) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); + } + return new Deadline(description, dueBy); } else { throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); } @@ -43,7 +48,11 @@ public static Task getInstance(Command cmd) throws InvalidCommandFormatException if (eventMatch.matches()) { String description = eventMatch.group(KEY_DESCRIPTION); String timing = eventMatch.group(KEY_DATE); - return new Event(description, timing, dateParser); + LocalDateTime eventTime = dateParser.parseInput(timing); + if (eventTime == null) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); + } + return new Event(description, eventTime); } else { throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_EVENT); } @@ -75,11 +84,19 @@ public static Task getInstance(String s) throws InvalidFileFormatException { if (keyword.equalsIgnoreCase(TaskType.DEADLINE.toString())) { String description = line[2]; String dueDate = line[3]; - currentTask = new Deadline(description, dueDate, dateParser); + LocalDateTime dueBy = dateParser.parseInput(dueDate); + if (dueBy == null) { + throw new InvalidFileFormatException(InvalidFileFormatException.ERROR_DATE); + } + currentTask = new Deadline(description, dueBy); } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { String description = line[2]; String timing = line[3]; - currentTask = new Event(description, timing, dateParser); + LocalDateTime eventTime = dateParser.parseInput(timing); + if (eventTime == null) { + throw new InvalidFileFormatException(InvalidFileFormatException.ERROR_DATE); + } + currentTask = new Event(description, eventTime); } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { currentTask = new Todo(line[2]); } else { diff --git a/src/main/java/com/alexooi/duke/tasks/TaskList.java b/src/main/java/com/alexooi/duke/tasks/TaskList.java index eca865fa..b4e1444f 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskList.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskList.java @@ -1,5 +1,7 @@ package com.alexooi.duke.tasks; +import com.alexooi.duke.Duke; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; import com.alexooi.duke.exceptions.InvalidFileFormatException; import com.alexooi.duke.interfaces.StorageClient; @@ -125,6 +127,7 @@ private String toSaveListString() { private void load(BufferedReader in) { boolean isEndOfInput = false; + Duke main = Duke.getInstance(); while (!isEndOfInput) { try { String taskStr = in.readLine(); @@ -133,11 +136,12 @@ private void load(BufferedReader in) { } else { taskList.add(TaskFactory.getInstance(taskStr)); } - } catch (IOException e) { - System.out.println("Unable to read file!"); + } catch (IOException ioe) { + System.out.println(main.getPrompt().error(ioe.getMessage())); } catch (InvalidFileFormatException iffe) { - System.out.println("Invalid File Format detected!"); + System.out.println(main.getPrompt().error(iffe.getErrorHeader(), iffe.getMessage())); } + } } } From 8390c07130372572fdebe0afceaf19d63203079a Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sun, 7 Nov 2021 16:40:03 +0800 Subject: [PATCH 53/59] Fix archive out of bounds issue --- src/main/java/com/alexooi/duke/commands/ArchiveCommand.java | 4 ++-- .../duke/exceptions/InvalidCommandFormatException.java | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java b/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java index 34b62a89..a6345fc1 100644 --- a/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java +++ b/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java @@ -30,8 +30,8 @@ public String execute(TaskList tasks, OutputFormat prompt) throws InvalidC tasks.removeAll(); } else { int idx = Integer.parseUnsignedInt(getArgs()) - 1; - if (idx > tasks.size() + 1) { - throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_ARCHIVE_NO_SUCH_TASK); + if (idx >= tasks.size()) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_NO_SUCH_INDEX); } tasks.archive(idx); Task archived = tasks.remove(idx); diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java index 9d39051a..40801605 100644 --- a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java @@ -10,8 +10,6 @@ public class InvalidCommandFormatException extends Throwable { "Todo should be in the format: todo ."; public static final String ERROR_VIEW_SCHEDULE = "View schedule should be in the format: view ."; - public static final String ERROR_ARCHIVE_NO_SUCH_TASK = - "The task number to archive is not valid. Archive should be in the format: archive where num is a valid index"; public static final String ERROR_NO_SUCH_INDEX = "The task number is not valid. Please use the corresponding task number when listing the tasks"; From 039b8e643d936318ddaa1209b92d7e578bfddf4c Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Mon, 8 Nov 2021 10:49:50 +0800 Subject: [PATCH 54/59] Code clean up and refactor, no functionality changed --- src/main/java/com/alexooi/duke/Duke.java | 12 ++-- .../alexooi/duke/commands/ArchiveCommand.java | 2 +- .../com/alexooi/duke/commands/Command.java | 16 ++--- .../alexooi/duke/commands/ExitCommand.java | 3 +- .../alexooi/duke/commands/FindCommand.java | 5 +- .../alexooi/duke/commands/HelpCommand.java | 5 +- .../alexooi/duke/commands/ListCommand.java | 3 +- .../duke/commands/ViewScheduleCommand.java | 58 +++++++-------- .../java/com/alexooi/duke/enums/TaskType.java | 3 +- .../InvalidCommandFormatException.java | 2 +- .../InvalidDateFormatException.java | 4 ++ .../InvalidFileFormatException.java | 2 +- .../alexooi/duke/interfaces/DateParser.java | 10 --- .../com/alexooi/duke/interfaces/IOParser.java | 7 -- .../com/alexooi/duke/interfaces/Parser.java | 6 ++ .../com/alexooi/duke/storage/FileStorage.java | 2 +- .../java/com/alexooi/duke/tasks/Deadline.java | 1 - .../java/com/alexooi/duke/tasks/Event.java | 1 - .../java/com/alexooi/duke/tasks/Task.java | 8 +-- .../com/alexooi/duke/tasks/TaskFactory.java | 71 +++++++++++-------- .../java/com/alexooi/duke/tasks/TaskList.java | 1 - .../alexooi/duke/ui/CommandLineParser.java | 27 ++++--- .../{InputDateParser.java => DateParser.java} | 46 ++++++++---- .../duke/ui/CommandLineParserTest.java | 28 +++----- 24 files changed, 167 insertions(+), 156 deletions(-) create mode 100644 src/main/java/com/alexooi/duke/exceptions/InvalidDateFormatException.java delete mode 100644 src/main/java/com/alexooi/duke/interfaces/DateParser.java delete mode 100644 src/main/java/com/alexooi/duke/interfaces/IOParser.java create mode 100644 src/main/java/com/alexooi/duke/interfaces/Parser.java rename src/main/java/com/alexooi/duke/utility/{InputDateParser.java => DateParser.java} (60%) diff --git a/src/main/java/com/alexooi/duke/Duke.java b/src/main/java/com/alexooi/duke/Duke.java index 6c47a126..fe948d09 100644 --- a/src/main/java/com/alexooi/duke/Duke.java +++ b/src/main/java/com/alexooi/duke/Duke.java @@ -1,7 +1,6 @@ package com.alexooi.duke; import com.alexooi.duke.commands.Command; -import com.alexooi.duke.exceptions.InvalidFileFormatException; import com.alexooi.duke.storage.FileStorage; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; @@ -9,18 +8,17 @@ import com.alexooi.duke.ui.OutputFormatter; import com.alexooi.duke.exceptions.InvalidCommandException; import com.alexooi.duke.exceptions.InvalidCommandFormatException; -import com.alexooi.duke.interfaces.IOParser; +import com.alexooi.duke.interfaces.Parser; import com.alexooi.duke.interfaces.OutputFormat; import java.util.Scanner; -import java.util.ServiceLoader; public class Duke { private static Duke instance; private final OutputFormat prompt; - private final IOParser parser; + private final Parser parser; - private Duke(OutputFormat prompt, IOParser parser) { + private Duke(OutputFormat prompt, Parser parser) { this.prompt = prompt; this.parser = parser; } @@ -43,7 +41,6 @@ public static void main(String[] args) { FileStorage archive = new FileStorage("archive.txt"); TaskList tasks = TaskList.getInstance(state, archive); - ServiceLoader commandLoader = ServiceLoader.load(Command.class); System.out.println(main.prompt.start()); Scanner in = new Scanner(System.in); @@ -51,7 +48,8 @@ public static void main(String[] args) { while (isReceivingInput && in.hasNext()) { try { - Command command = main.parser.readInput(in, commandLoader); + String input = in.nextLine(); + Command command = main.parser.parseInput(input); if (command.isExit()) { isReceivingInput = false; } else { diff --git a/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java b/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java index a6345fc1..d0fbe503 100644 --- a/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java +++ b/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java @@ -46,7 +46,7 @@ public Pattern getRegexPattern() { } @Override - public Command build() throws InvalidCommandFormatException { + public Command build() { return new ArchiveCommand(getKeyword(), getArgs()); } } diff --git a/src/main/java/com/alexooi/duke/commands/Command.java b/src/main/java/com/alexooi/duke/commands/Command.java index 2b5b6e41..86a24fc9 100644 --- a/src/main/java/com/alexooi/duke/commands/Command.java +++ b/src/main/java/com/alexooi/duke/commands/Command.java @@ -43,6 +43,14 @@ public void setArgs(String args) { this.args = args; } + public CommandType getCommandType() { + return commandType; + } + + public void setCommandType(CommandType commandType) { + this.commandType = commandType; + } + public Iterable getCommandFormat() { ArrayList commandFormat = new ArrayList<>(); commandFormat.add(getCommandType().getFormat()); @@ -60,13 +68,5 @@ public Iterable getCommandFormat() { public boolean isExit() { return false; - }; - - public CommandType getCommandType() { - return commandType; - } - - public void setCommandType(CommandType commandType) { - this.commandType = commandType; } } diff --git a/src/main/java/com/alexooi/duke/commands/ExitCommand.java b/src/main/java/com/alexooi/duke/commands/ExitCommand.java index 6a921203..844b4dbc 100644 --- a/src/main/java/com/alexooi/duke/commands/ExitCommand.java +++ b/src/main/java/com/alexooi/duke/commands/ExitCommand.java @@ -3,7 +3,6 @@ import com.alexooi.duke.enums.CommandType; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; -import com.alexooi.duke.exceptions.InvalidCommandFormatException; import com.alexooi.duke.interfaces.OutputFormat; import java.util.regex.Pattern; @@ -36,7 +35,7 @@ public boolean isExit() { } @Override - public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + public String execute(TaskList tasks, OutputFormat prompt) { return prompt.exit(); } } diff --git a/src/main/java/com/alexooi/duke/commands/FindCommand.java b/src/main/java/com/alexooi/duke/commands/FindCommand.java index 49299095..f4337b33 100644 --- a/src/main/java/com/alexooi/duke/commands/FindCommand.java +++ b/src/main/java/com/alexooi/duke/commands/FindCommand.java @@ -1,7 +1,6 @@ package com.alexooi.duke.commands; import com.alexooi.duke.enums.CommandType; -import com.alexooi.duke.exceptions.InvalidCommandFormatException; import com.alexooi.duke.interfaces.OutputFormat; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; @@ -24,7 +23,7 @@ public FindCommand(String keyword, String args) { } @Override - public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + public String execute(TaskList tasks, OutputFormat prompt) { ArrayList taskList = tasks.get(); List filteredList = taskList.stream().filter((task) -> task.getDescription().contains(getArgs())).collect(Collectors.toList()); @@ -38,7 +37,7 @@ public Pattern getRegexPattern() { } @Override - public Command build() throws InvalidCommandFormatException { + public Command build() { return new FindCommand(getKeyword(), getArgs()); } } diff --git a/src/main/java/com/alexooi/duke/commands/HelpCommand.java b/src/main/java/com/alexooi/duke/commands/HelpCommand.java index 7211aaa1..53339ad1 100644 --- a/src/main/java/com/alexooi/duke/commands/HelpCommand.java +++ b/src/main/java/com/alexooi/duke/commands/HelpCommand.java @@ -1,7 +1,6 @@ package com.alexooi.duke.commands; import com.alexooi.duke.enums.CommandType; -import com.alexooi.duke.exceptions.InvalidCommandFormatException; import com.alexooi.duke.interfaces.OutputFormat; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; @@ -23,7 +22,7 @@ public HelpCommand(String keyword) { } @Override - public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + public String execute(TaskList tasks, OutputFormat prompt) { ServiceLoader commandLoader = ServiceLoader.load(Command.class); ArrayList promptInput = new ArrayList<>(); for (Command cmd : commandLoader) { @@ -40,7 +39,7 @@ public Pattern getRegexPattern() { } @Override - public Command build() throws InvalidCommandFormatException { + public Command build() { return new HelpCommand(getKeyword()); } } diff --git a/src/main/java/com/alexooi/duke/commands/ListCommand.java b/src/main/java/com/alexooi/duke/commands/ListCommand.java index 4da6b4fe..94c254a4 100644 --- a/src/main/java/com/alexooi/duke/commands/ListCommand.java +++ b/src/main/java/com/alexooi/duke/commands/ListCommand.java @@ -3,7 +3,6 @@ import com.alexooi.duke.enums.CommandType; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; -import com.alexooi.duke.exceptions.InvalidCommandFormatException; import com.alexooi.duke.interfaces.OutputFormat; import java.util.regex.Pattern; @@ -31,7 +30,7 @@ public ListCommand build() { } @Override - public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + public String execute(TaskList tasks, OutputFormat prompt) { return prompt.list(tasks.get()); } } diff --git a/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java b/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java index a4cb5d8b..21c90bdb 100644 --- a/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java +++ b/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java @@ -2,16 +2,15 @@ import com.alexooi.duke.enums.CommandType; import com.alexooi.duke.exceptions.InvalidCommandFormatException; -import com.alexooi.duke.interfaces.DateParser; import com.alexooi.duke.interfaces.OutputFormat; +import com.alexooi.duke.interfaces.Parser; import com.alexooi.duke.tasks.Deadline; import com.alexooi.duke.tasks.Event; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; -import com.alexooi.duke.utility.InputDateParser; +import com.alexooi.duke.utility.DateParser; import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; @@ -19,47 +18,48 @@ public class ViewScheduleCommand extends Command { private final String REGEX_PATTERN = "^(\\bview\\b) (.*)$"; - DateParser parser; + final Parser parser; public ViewScheduleCommand() { setCommandType(CommandType.VIEW_SCHEDULE); - this.parser = new InputDateParser(); + this.parser = new DateParser(); } public ViewScheduleCommand(String keyword, String args) { super(keyword, args); setCommandType(CommandType.VIEW_SCHEDULE); - this.parser = new InputDateParser(); + this.parser = new DateParser(); } @Override public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { ArrayList taskList = tasks.get(); - LocalDateTime dateTime = parser.parseInput(getArgs()); - if (dateTime == null) { - throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_VIEW_SCHEDULE); - } + try { + LocalDateTime dateTime = parser.parseInput(getArgs()); - List filteredList = taskList.stream().filter((element) -> { - boolean isScheduled = false; - switch (element.getType()) { - case EVENT: - Event event = (Event) element; - if (event.getTiming().toLocalDate().equals(dateTime.toLocalDate())) { - isScheduled = true; - } - break; - case DEADLINE: - Deadline deadline = (Deadline) element; - if (deadline.getDueDate().toLocalDate().equals(dateTime.toLocalDate())) { - isScheduled = true; + List filteredList = taskList.stream().filter((element) -> { + boolean isScheduled = false; + switch (element.getType()) { + case EVENT: + Event event = (Event) element; + if (event.getTiming().toLocalDate().equals(dateTime.toLocalDate())) { + isScheduled = true; + } + break; + case DEADLINE: + Deadline deadline = (Deadline) element; + if (deadline.getDueDate().toLocalDate().equals(dateTime.toLocalDate())) { + isScheduled = true; + } + break; } - break; - } - return isScheduled; - }).collect(Collectors.toList()); + return isScheduled; + }).collect(Collectors.toList()); - return prompt.viewSchedule(filteredList, dateTime.toLocalDate()); + return prompt.viewSchedule(filteredList, dateTime.toLocalDate()); + } catch (Exception e) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_VIEW_SCHEDULE); + } } @Override @@ -69,7 +69,7 @@ public Pattern getRegexPattern() { @Override public ViewScheduleCommand build() throws InvalidCommandFormatException { - if (!parser.isDate(getArgs(), DateTimeFormatter.ISO_DATE)) { + if (!parser.isValidInput(getArgs())) { throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_VIEW_SCHEDULE); } return new ViewScheduleCommand(getKeyword(), getArgs()); diff --git a/src/main/java/com/alexooi/duke/enums/TaskType.java b/src/main/java/com/alexooi/duke/enums/TaskType.java index f2349b53..4924bb0e 100644 --- a/src/main/java/com/alexooi/duke/enums/TaskType.java +++ b/src/main/java/com/alexooi/duke/enums/TaskType.java @@ -3,8 +3,7 @@ public enum TaskType { TODO("todo"), EVENT("event"), - DEADLINE("deadline"), - TASK("task"); + DEADLINE("deadline"); private String type; diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java index 40801605..7e0312bd 100644 --- a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java @@ -1,6 +1,6 @@ package com.alexooi.duke.exceptions; -public class InvalidCommandFormatException extends Throwable { +public class InvalidCommandFormatException extends Exception { private static final String ERROR_PREFIX = "Invalid Command Format:"; public static final String ERROR_DEADLINE = "Deadline should be in the format: deadline /by where is in the format of yyyy-mm-dd."; diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidDateFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidDateFormatException.java new file mode 100644 index 00000000..1206a89e --- /dev/null +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidDateFormatException.java @@ -0,0 +1,4 @@ +package com.alexooi.duke.exceptions; + +public class InvalidDateFormatException extends Exception { +} diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java index 923fc3ad..f520b1f0 100644 --- a/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java @@ -1,6 +1,6 @@ package com.alexooi.duke.exceptions; -public class InvalidFileFormatException extends Throwable { +public class InvalidFileFormatException extends Exception { private static final String ERROR_PREFIX = "Invalid File Format:"; public static final String ERROR_DATE = "Unable to parse date from file, file may have been modified manually or corrupted."; diff --git a/src/main/java/com/alexooi/duke/interfaces/DateParser.java b/src/main/java/com/alexooi/duke/interfaces/DateParser.java deleted file mode 100644 index 3493da7c..00000000 --- a/src/main/java/com/alexooi/duke/interfaces/DateParser.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.alexooi.duke.interfaces; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - -public interface DateParser { - boolean isDate(String input, DateTimeFormatter formatter); - boolean isDateTime(String input, DateTimeFormatter formatter); - LocalDateTime parseInput(String input); -} \ No newline at end of file diff --git a/src/main/java/com/alexooi/duke/interfaces/IOParser.java b/src/main/java/com/alexooi/duke/interfaces/IOParser.java deleted file mode 100644 index 8bb2de70..00000000 --- a/src/main/java/com/alexooi/duke/interfaces/IOParser.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.alexooi.duke.interfaces; - -import com.alexooi.duke.exceptions.InvalidCommandFormatException; - -public interface IOParser { - T readInput(S sc, Iterable it) throws Exception, InvalidCommandFormatException; -} diff --git a/src/main/java/com/alexooi/duke/interfaces/Parser.java b/src/main/java/com/alexooi/duke/interfaces/Parser.java new file mode 100644 index 00000000..88ffbf0c --- /dev/null +++ b/src/main/java/com/alexooi/duke/interfaces/Parser.java @@ -0,0 +1,6 @@ +package com.alexooi.duke.interfaces; + +public interface Parser { + T parseInput(S input) throws Exception; + boolean isValidInput(S input); +} \ No newline at end of file diff --git a/src/main/java/com/alexooi/duke/storage/FileStorage.java b/src/main/java/com/alexooi/duke/storage/FileStorage.java index 4b8e0ee4..90951193 100644 --- a/src/main/java/com/alexooi/duke/storage/FileStorage.java +++ b/src/main/java/com/alexooi/duke/storage/FileStorage.java @@ -8,7 +8,7 @@ * The concrete implementation of the StorageClient for storing in files. */ public class FileStorage implements StorageClient { - String filePath; + final String filePath; public FileStorage(String filePath) { this.filePath = filePath; diff --git a/src/main/java/com/alexooi/duke/tasks/Deadline.java b/src/main/java/com/alexooi/duke/tasks/Deadline.java index d06c16de..c5c99930 100644 --- a/src/main/java/com/alexooi/duke/tasks/Deadline.java +++ b/src/main/java/com/alexooi/duke/tasks/Deadline.java @@ -1,7 +1,6 @@ package com.alexooi.duke.tasks; import com.alexooi.duke.enums.TaskType; -import com.alexooi.duke.interfaces.DateParser; import java.time.LocalDateTime; diff --git a/src/main/java/com/alexooi/duke/tasks/Event.java b/src/main/java/com/alexooi/duke/tasks/Event.java index 68164594..c8131457 100644 --- a/src/main/java/com/alexooi/duke/tasks/Event.java +++ b/src/main/java/com/alexooi/duke/tasks/Event.java @@ -1,7 +1,6 @@ package com.alexooi.duke.tasks; import com.alexooi.duke.enums.TaskType; -import com.alexooi.duke.interfaces.DateParser; import java.time.LocalDateTime; diff --git a/src/main/java/com/alexooi/duke/tasks/Task.java b/src/main/java/com/alexooi/duke/tasks/Task.java index 07dbef8c..41eefcb1 100644 --- a/src/main/java/com/alexooi/duke/tasks/Task.java +++ b/src/main/java/com/alexooi/duke/tasks/Task.java @@ -4,7 +4,7 @@ public abstract class Task { private String description; - private Boolean isDone; + private boolean isDone; private TaskType type; public Task() { @@ -25,12 +25,12 @@ public void setDescription(String description) { this.description = description; } - public Boolean getDone() { + public boolean getDone() { return isDone; } - public void setDone(Boolean done) { - isDone = done; + public void setDone(boolean isDone) { + this.isDone = isDone; } public TaskType getType() { diff --git a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java index 037810ec..5f154aad 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java @@ -3,9 +3,10 @@ import com.alexooi.duke.enums.TaskType; import com.alexooi.duke.commands.Command; import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.exceptions.InvalidDateFormatException; import com.alexooi.duke.exceptions.InvalidFileFormatException; -import com.alexooi.duke.interfaces.DateParser; -import com.alexooi.duke.utility.InputDateParser; +import com.alexooi.duke.interfaces.Parser; +import com.alexooi.duke.utility.DateParser; import java.time.LocalDateTime; import java.util.regex.Matcher; @@ -18,10 +19,18 @@ public class TaskFactory { "^(?<" + KEY_DESCRIPTION + ">.+) /by (?<" + KEY_DATE + ">.+)$"; private static final String EVENT_PATTERN = "^(?<" + KEY_DESCRIPTION + ">.+) /at (?<" + KEY_DATE + ">.+)$"; - private static final DateParser dateParser = new InputDateParser(); + private static final Parser dateParser = new DateParser(); + + private static LocalDateTime handleDateInput(String input) throws InvalidDateFormatException { + try { + return dateParser.parseInput(input); + } catch (Exception e) { + throw new InvalidDateFormatException(); + } + } /** - * This function takes in a command and + * This function takes in a command and outputs the corresponding task based on the command shown. * @param cmd The command containing the keyword (Task name) and arguments (Date if applicable) * @return Deadline, Event or Todo task * @throws InvalidCommandFormatException If the date does is not parsable for Deadline and Event, this exception is thrown @@ -32,36 +41,35 @@ public static Task getInstance(Command cmd) throws InvalidCommandFormatException if (keyword.equalsIgnoreCase(TaskType.DEADLINE.toString())) { Matcher deadlineMatch = Pattern.compile(DEADLINE_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); - if (deadlineMatch.matches()) { - String description = deadlineMatch.group(KEY_DESCRIPTION); - String dueDate = deadlineMatch.group(KEY_DATE); - LocalDateTime dueBy = dateParser.parseInput(dueDate); - if (dueBy == null) { - throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); - } + if (!deadlineMatch.matches()) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); + } + String description = deadlineMatch.group(KEY_DESCRIPTION); + String dueDate = deadlineMatch.group(KEY_DATE); + try { + LocalDateTime dueBy = handleDateInput(dueDate); return new Deadline(description, dueBy); - } else { + } catch (InvalidDateFormatException idfe) { throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); } } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { Matcher eventMatch = Pattern.compile(EVENT_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); - if (eventMatch.matches()) { - String description = eventMatch.group(KEY_DESCRIPTION); - String timing = eventMatch.group(KEY_DATE); - LocalDateTime eventTime = dateParser.parseInput(timing); - if (eventTime == null) { - throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); - } - return new Event(description, eventTime); - } else { + if (!eventMatch.matches()) { throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_EVENT); } + String description = eventMatch.group(KEY_DESCRIPTION); + String timing = eventMatch.group(KEY_DATE); + try { + LocalDateTime eventTime = handleDateInput(timing); + return new Event(description, eventTime); + } catch (InvalidDateFormatException idfe) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); + } } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { - if (args.length() > 0) { - return new Todo(args); - } else { + if (args.length() <= 0) { throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_TODO); } + return new Todo(args); } throw new InvalidCommandFormatException( "Cannot find the appropriate task to create."); @@ -84,19 +92,22 @@ public static Task getInstance(String s) throws InvalidFileFormatException { if (keyword.equalsIgnoreCase(TaskType.DEADLINE.toString())) { String description = line[2]; String dueDate = line[3]; - LocalDateTime dueBy = dateParser.parseInput(dueDate); - if (dueBy == null) { + try { + LocalDateTime dueBy = handleDateInput(dueDate); + return new Deadline(description, dueBy); + } catch (InvalidDateFormatException idfe) { throw new InvalidFileFormatException(InvalidFileFormatException.ERROR_DATE); } - currentTask = new Deadline(description, dueBy); } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { String description = line[2]; String timing = line[3]; - LocalDateTime eventTime = dateParser.parseInput(timing); - if (eventTime == null) { + + try { + LocalDateTime eventTime = handleDateInput(timing); + return new Event(description, eventTime); + } catch (InvalidDateFormatException idfe) { throw new InvalidFileFormatException(InvalidFileFormatException.ERROR_DATE); } - currentTask = new Event(description, eventTime); } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { currentTask = new Todo(line[2]); } else { diff --git a/src/main/java/com/alexooi/duke/tasks/TaskList.java b/src/main/java/com/alexooi/duke/tasks/TaskList.java index b4e1444f..26cf0387 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskList.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskList.java @@ -1,7 +1,6 @@ package com.alexooi.duke.tasks; import com.alexooi.duke.Duke; -import com.alexooi.duke.exceptions.InvalidCommandFormatException; import com.alexooi.duke.exceptions.InvalidFileFormatException; import com.alexooi.duke.interfaces.StorageClient; diff --git a/src/main/java/com/alexooi/duke/ui/CommandLineParser.java b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java index 9c4b20b1..e308e4a0 100644 --- a/src/main/java/com/alexooi/duke/ui/CommandLineParser.java +++ b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java @@ -3,26 +3,25 @@ import com.alexooi.duke.commands.Command; import com.alexooi.duke.exceptions.InvalidCommandException; import com.alexooi.duke.exceptions.InvalidCommandFormatException; -import com.alexooi.duke.interfaces.IOParser; +import com.alexooi.duke.interfaces.Parser; -import java.util.*; +import java.util.ServiceLoader; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class CommandLineParser implements IOParser { +public class CommandLineParser implements Parser { + final ServiceLoader commands = ServiceLoader.load(Command.class); + /** * This function reads the user input and matches it against a list of commands. * It parses the keyword and arguments of the command and returns the command found for execution. - * @param sc Scanner object to read in input - * @param commands A list of commands implemented to match against the input + * @param input The input string to parse * @return The command that was detected * @throws InvalidCommandException Reaches this exception if no command is found matching the input * @throws InvalidCommandFormatException Reaches this exception if a command is found the but the input is invalid */ - public Command readInput(Scanner sc, Iterable commands) - throws InvalidCommandException, InvalidCommandFormatException { - assert sc != null; - String input = sc.nextLine(); + public Command parseInput(String input) + throws Exception { for (Command cmd : commands) { Pattern servicePattern = cmd.getRegexPattern(); @@ -41,4 +40,14 @@ public Command readInput(Scanner sc, Iterable commands) throw new InvalidCommandException(InvalidCommandException.ERROR_GENERIC); } + + /** + * Note: This function is not in use, but has been added here due to the interface. + * @param input String to validate + * @return + */ + @Override + public boolean isValidInput(String input) { + return false; + } } diff --git a/src/main/java/com/alexooi/duke/utility/InputDateParser.java b/src/main/java/com/alexooi/duke/utility/DateParser.java similarity index 60% rename from src/main/java/com/alexooi/duke/utility/InputDateParser.java rename to src/main/java/com/alexooi/duke/utility/DateParser.java index 0b85f0e3..8e197c86 100644 --- a/src/main/java/com/alexooi/duke/utility/InputDateParser.java +++ b/src/main/java/com/alexooi/duke/utility/DateParser.java @@ -1,6 +1,7 @@ package com.alexooi.duke.utility; -import com.alexooi.duke.interfaces.DateParser; +import com.alexooi.duke.exceptions.InvalidDateFormatException; +import com.alexooi.duke.interfaces.Parser; import java.time.LocalDate; import java.time.LocalDateTime; @@ -8,11 +9,11 @@ import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; -public class InputDateParser implements DateParser { - private final DateTimeFormatter[] ACCEPTED_DATE_FORMATS = { +public class DateParser implements Parser { + private static final DateTimeFormatter[] ALLOWED_DATE_FORMATS = { DateTimeFormatter.ISO_DATE }; - private final DateTimeFormatter[] ACCEPTED_DATE_TIME_FORMATS = { + private static final DateTimeFormatter[] ALLOWED_DATE_TIME_FORMATS = { DateTimeFormatter.ISO_DATE_TIME }; @@ -22,7 +23,6 @@ public class InputDateParser implements DateParser { * @param acceptedFormats Can be a DateTimeFormatter or a list of accepted formats built using DateTimeFormatterBuilder * @return True if the string is an accepted date format, false if not */ - @Override public boolean isDate(String input, DateTimeFormatter acceptedFormats) { try { LocalDate.parse(input, acceptedFormats); @@ -38,7 +38,6 @@ public boolean isDate(String input, DateTimeFormatter acceptedFormats) { * @param acceptedFormats Can be a DateTimeFormatter or a list of accepted formats built using DateTimeFormatterBuilder * @return True if the string is an accepted Date and Time format, false if not */ - @Override public boolean isDateTime(String input, DateTimeFormatter acceptedFormats) { try { LocalDateTime.parse(input, acceptedFormats); @@ -63,14 +62,33 @@ private DateTimeFormatter formatBuilder(DateTimeFormatter[] formats) { * otherwise time defaults to start of day 00:00. If the string cannot be parsed, then returns null */ @Override - public LocalDateTime parseInput(String input) { - DateTimeFormatter dateTimeFormats = formatBuilder(ACCEPTED_DATE_TIME_FORMATS); - DateTimeFormatter dateFormats = formatBuilder(ACCEPTED_DATE_FORMATS); - if (isDateTime(input, dateTimeFormats)) { - return LocalDateTime.parse(input, dateTimeFormats); - } else if (isDate(input, dateFormats)) { - return LocalDate.parse(input, dateFormats).atStartOfDay(); + public LocalDateTime parseInput(String input) throws Exception { + DateTimeFormatter allowedDateTimeFormats = formatBuilder(ALLOWED_DATE_TIME_FORMATS); + DateTimeFormatter allowedDateFormats = formatBuilder(ALLOWED_DATE_FORMATS); + if (isDateTime(input, allowedDateTimeFormats)) { + return LocalDateTime.parse(input, allowedDateTimeFormats); + } else if (isDate(input, allowedDateFormats)) { + return LocalDate.parse(input, allowedDateFormats).atStartOfDay(); + } else { + throw new InvalidDateFormatException(); + } + } + + /** + * This function validates if a particular date is valid for the allowed date and date time formats. + * @param input String to validate + * @return True if valid, else false + */ + @Override + public boolean isValidInput(String input) { + DateTimeFormatter allowedDateTimeFormats = formatBuilder(ALLOWED_DATE_TIME_FORMATS); + DateTimeFormatter allowedDateFormats = formatBuilder(ALLOWED_DATE_FORMATS); + boolean isValid = false; + if (isDateTime(input, allowedDateTimeFormats)) { + isValid = true; + } else if (isDate(input, allowedDateFormats)) { + isValid = true; } - return null; + return isValid; } } diff --git a/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java b/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java index 144e0413..1cfa7a08 100644 --- a/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java +++ b/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java @@ -2,22 +2,17 @@ import com.alexooi.duke.commands.Command; import com.alexooi.duke.exceptions.InvalidCommandException; -import com.alexooi.duke.exceptions.InvalidCommandFormatException; import org.junit.jupiter.api.Test; -import java.util.Scanner; -import java.util.ServiceLoader; import static org.junit.jupiter.api.Assertions.*; class CommandLineParserTest { private static final CommandLineParser COMMAND_LINE_PARSER = new CommandLineParser(); - private final ServiceLoader commandLoader = ServiceLoader.load(Command.class); @Test - void readInput_CommandTodoSuccess() throws InvalidCommandException, InvalidCommandFormatException { + void readInput_CommandTodoSuccess() throws Exception { String input = "todo read book"; - Scanner sc = new Scanner(input).useDelimiter("\n"); - Command cmd = COMMAND_LINE_PARSER.readInput(sc, commandLoader); + Command cmd = COMMAND_LINE_PARSER.parseInput(input); assertEquals("todo", cmd.getKeyword(), "Parser assertEquals Todo Keyword failed."); assertEquals("read book", cmd.getArgs(), "Parser assertEquals Todo Args failed."); } @@ -25,15 +20,13 @@ void readInput_CommandTodoSuccess() throws InvalidCommandException, InvalidComma @Test void readInput_CommandTodoFail() { String input = "todo1"; - Scanner sc = new Scanner(input).useDelimiter("\n"); - assertThrows(InvalidCommandException.class, () -> COMMAND_LINE_PARSER.readInput(sc, commandLoader)); + assertThrows(InvalidCommandException.class, () -> COMMAND_LINE_PARSER.parseInput(input)); } @Test - void readInput_CommandDeadlineSuccess() throws InvalidCommandException, InvalidCommandFormatException { + void readInput_CommandDeadlineSuccess() throws Exception { String input = "deadline return book /by 2021-09-15"; - Scanner sc = new Scanner(input).useDelimiter("\n"); - Command cmd = COMMAND_LINE_PARSER.readInput(sc, commandLoader); + Command cmd = COMMAND_LINE_PARSER.parseInput(input); assertEquals("deadline", cmd.getKeyword(), "Parser assertEquals Deadline Keyword failed."); assertEquals("return book /by 2021-09-15", cmd.getArgs(), "Parser assertEquals Deadline Args failed."); } @@ -41,15 +34,13 @@ void readInput_CommandDeadlineSuccess() throws InvalidCommandException, InvalidC @Test void readInput_CommandDeadlineFail() { String input = "deadlinereturn book /by 2021-09-15"; - Scanner sc = new Scanner(input).useDelimiter("\n"); - assertThrows(InvalidCommandException.class, () -> COMMAND_LINE_PARSER.readInput(sc, commandLoader)); + assertThrows(InvalidCommandException.class, () -> COMMAND_LINE_PARSER.parseInput(input)); } @Test - void readInput_CommandEventSuccess() throws InvalidCommandException, InvalidCommandFormatException { + void readInput_CommandEventSuccess() throws Exception { String input = "event meet friend /at 2021-09-15"; - Scanner sc = new Scanner(input).useDelimiter("\n"); - Command cmd = COMMAND_LINE_PARSER.readInput(sc, commandLoader); + Command cmd = COMMAND_LINE_PARSER.parseInput(input); assertEquals("event", cmd.getKeyword(), "Parser assertEquals Event Keyword failed."); assertEquals("meet friend /at 2021-09-15", cmd.getArgs(), "Parser assertEquals Event Args failed."); } @@ -57,7 +48,6 @@ void readInput_CommandEventSuccess() throws InvalidCommandException, InvalidComm @Test void readInput_CommandEventFail() { String input = "eventmeet friend /at 2021-09-15"; - Scanner sc = new Scanner(input).useDelimiter("\n"); - assertThrows(InvalidCommandException.class, () -> COMMAND_LINE_PARSER.readInput(sc, commandLoader)); + assertThrows(InvalidCommandException.class, () -> COMMAND_LINE_PARSER.parseInput(input)); } } \ No newline at end of file From 8d8753a392d81d93f28905e4506b448a66f30d1a Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 13 Nov 2021 15:32:35 +0800 Subject: [PATCH 55/59] Remove dependency on prompt from TaskList --- src/main/java/com/alexooi/duke/Duke.java | 24 ++++++++++++++----- .../java/com/alexooi/duke/tasks/TaskList.java | 23 +++++------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/alexooi/duke/Duke.java b/src/main/java/com/alexooi/duke/Duke.java index fe948d09..78de2e20 100644 --- a/src/main/java/com/alexooi/duke/Duke.java +++ b/src/main/java/com/alexooi/duke/Duke.java @@ -1,6 +1,7 @@ package com.alexooi.duke; import com.alexooi.duke.commands.Command; +import com.alexooi.duke.exceptions.InvalidFileFormatException; import com.alexooi.duke.storage.FileStorage; import com.alexooi.duke.tasks.Task; import com.alexooi.duke.tasks.TaskList; @@ -11,6 +12,8 @@ import com.alexooi.duke.interfaces.Parser; import com.alexooi.duke.interfaces.OutputFormat; +import java.io.BufferedReader; +import java.io.IOException; import java.util.Scanner; public class Duke { @@ -31,18 +34,27 @@ public static Duke getInstance() { return instance; } - public OutputFormat getPrompt() { - return this.prompt; - } - public static void main(String[] args) { + final String STATE_PATH = "state.txt"; + final String ARCHIVE_PATH = "archive.txt"; + Duke main = Duke.getInstance(); - FileStorage state = new FileStorage("state.txt"); - FileStorage archive = new FileStorage("archive.txt"); + FileStorage state = new FileStorage(STATE_PATH); + FileStorage archive = new FileStorage(ARCHIVE_PATH); TaskList tasks = TaskList.getInstance(state, archive); System.out.println(main.prompt.start()); + // Load previous task list state + BufferedReader stateInput = state.load(); + try { + tasks.load(stateInput); + } catch (IOException ioe) { + main.prompt.error(ioe.getMessage()); + } catch (InvalidFileFormatException iffe) { + main.prompt.error(iffe.getErrorHeader(), iffe.getMessage()); + } + Scanner in = new Scanner(System.in); boolean isReceivingInput = true; diff --git a/src/main/java/com/alexooi/duke/tasks/TaskList.java b/src/main/java/com/alexooi/duke/tasks/TaskList.java index 26cf0387..09eb1997 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskList.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskList.java @@ -1,6 +1,5 @@ package com.alexooi.duke.tasks; -import com.alexooi.duke.Duke; import com.alexooi.duke.exceptions.InvalidFileFormatException; import com.alexooi.duke.interfaces.StorageClient; @@ -22,8 +21,6 @@ private TaskList(StorageClient stateClient, StorageClient(); this.stateClient = stateClient; this.archiveClient = archiveClient; - BufferedReader input = stateClient.load(); - load(input); } /** @@ -124,23 +121,15 @@ private String toSaveListString() { ); } - private void load(BufferedReader in) { + public void load(BufferedReader in) throws IOException, InvalidFileFormatException { boolean isEndOfInput = false; - Duke main = Duke.getInstance(); while (!isEndOfInput) { - try { - String taskStr = in.readLine(); - if (taskStr == null) { - isEndOfInput = true; - } else { - taskList.add(TaskFactory.getInstance(taskStr)); - } - } catch (IOException ioe) { - System.out.println(main.getPrompt().error(ioe.getMessage())); - } catch (InvalidFileFormatException iffe) { - System.out.println(main.getPrompt().error(iffe.getErrorHeader(), iffe.getMessage())); + String taskStr = in.readLine(); + if (taskStr == null) { + isEndOfInput = true; + } else { + taskList.add(TaskFactory.getInstance(taskStr)); } - } } } From 5b093785520d366b9bf3b09fe4013c7068e58cc3 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 13 Nov 2021 15:55:11 +0800 Subject: [PATCH 56/59] Fix HelpCommand --- src/main/java/com/alexooi/duke/enums/CommandType.java | 2 +- src/main/java/com/alexooi/duke/ui/CommandLineParser.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/alexooi/duke/enums/CommandType.java b/src/main/java/com/alexooi/duke/enums/CommandType.java index adf44382..4467fd2e 100644 --- a/src/main/java/com/alexooi/duke/enums/CommandType.java +++ b/src/main/java/com/alexooi/duke/enums/CommandType.java @@ -2,7 +2,7 @@ public enum CommandType { ADD("todo \n deadline /by \n event /at ", " must be in yyyy-mm-dd or yyyy-mm-ddThh:mm format."), - REMOVE("remove ", " must be a number that currently exists in the list."), + REMOVE("delete ", " must be a number that currently exists in the list."), COMPLETE("done ", " must be a number that currently exists in the list."), ARCHIVE("archive /all", " must be a number that currently exists in the list."), VIEW_SCHEDULE("view ", " must be in yyyy-mm-dd."), diff --git a/src/main/java/com/alexooi/duke/ui/CommandLineParser.java b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java index e308e4a0..0c24d646 100644 --- a/src/main/java/com/alexooi/duke/ui/CommandLineParser.java +++ b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java @@ -44,7 +44,7 @@ public Command parseInput(String input) /** * Note: This function is not in use, but has been added here due to the interface. * @param input String to validate - * @return + * @return false always */ @Override public boolean isValidInput(String input) { From aaa821075872c7e098546386fc4587973a9037aa Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 13 Nov 2021 15:56:33 +0800 Subject: [PATCH 57/59] Fix expected test case --- text-ui-test/EXPECTED.TXT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 935717db..97deaa1c 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -10,7 +10,7 @@ deadline /by event /at : must be in yyyy-mm-dd or yyyy-mm-ddThh:mm format. done : must be a number that currently exists in the list. - remove : must be a number that currently exists in the list. + delete : must be a number that currently exists in the list. bye list view : must be in yyyy-mm-dd. From 236bc080fd5172094593070ebb7d37053fb9a4cc Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 13 Nov 2021 16:33:43 +0800 Subject: [PATCH 58/59] Fix wrong error message for event error handling --- src/main/java/com/alexooi/duke/tasks/TaskFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java index 5f154aad..8baee87d 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java @@ -63,7 +63,7 @@ public static Task getInstance(Command cmd) throws InvalidCommandFormatException LocalDateTime eventTime = handleDateInput(timing); return new Event(description, eventTime); } catch (InvalidDateFormatException idfe) { - throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_EVENT); } } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { if (args.length() <= 0) { From 5ff561753d7d5a61e3ffbc7fe77712be56cffc71 Mon Sep 17 00:00:00 2001 From: Alex Ooi Date: Sat, 13 Nov 2021 16:54:34 +0800 Subject: [PATCH 59/59] Fix Task state issue when parsing state --- src/main/java/com/alexooi/duke/tasks/TaskFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java index 8baee87d..02c9d9a5 100644 --- a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java +++ b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java @@ -94,7 +94,7 @@ public static Task getInstance(String s) throws InvalidFileFormatException { String dueDate = line[3]; try { LocalDateTime dueBy = handleDateInput(dueDate); - return new Deadline(description, dueBy); + currentTask = new Deadline(description, dueBy); } catch (InvalidDateFormatException idfe) { throw new InvalidFileFormatException(InvalidFileFormatException.ERROR_DATE); } @@ -104,7 +104,7 @@ public static Task getInstance(String s) throws InvalidFileFormatException { try { LocalDateTime eventTime = handleDateInput(timing); - return new Event(description, eventTime); + currentTask = new Event(description, eventTime); } catch (InvalidDateFormatException idfe) { throw new InvalidFileFormatException(InvalidFileFormatException.ERROR_DATE); }