From a611e8dfe58f002952486acdd141e6289f5fd56c Mon Sep 17 00:00:00 2001 From: YFshadaow <55742825+YFshadaow@users.noreply.github.com> Date: Wed, 30 Aug 2023 22:02:44 +0800 Subject: [PATCH 01/22] Feature: Bot greets user and exists. --- src/main/java/Duke.java | 9 +++------ src/main/java/XiaoAiBot.java | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 src/main/java/XiaoAiBot.java diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5d313334c..9f9b7d914 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,7 @@ public class Duke { + public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + XiaoAiBot bot = new XiaoAiBot(); + bot.start(); } } diff --git a/src/main/java/XiaoAiBot.java b/src/main/java/XiaoAiBot.java new file mode 100644 index 000000000..bd34a0f7b --- /dev/null +++ b/src/main/java/XiaoAiBot.java @@ -0,0 +1,20 @@ +public class XiaoAiBot { + private final String BOT_NAME = "XiaoAi"; + private final String SPLIT_LINE = "____________________________________________"; + private final String GREET_MESSAGE = "Welcome back, master!\n" + + BOT_NAME + " here. What can I do for you?"; + + private final String QUIT_MESSAGE = "See you next time, master!"; + + private void sendMessages(String... messages) { + for (String message : messages) { + System.out.println(message); + } + System.out.println(SPLIT_LINE); + } + + public void start() { + sendMessages(GREET_MESSAGE); + sendMessages(QUIT_MESSAGE); + } +} From 030308be8d8545ce324f38d0bf1a1c4ee5825c0e Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 30 Aug 2023 23:04:10 +0800 Subject: [PATCH 02/22] Feature: - Bot echos user input - Bot exists after bye command --- src/main/java/CommandHandler.java | 28 +++++++++++++++++++++++ src/main/java/XiaoAiBot.java | 37 ++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/main/java/CommandHandler.java diff --git a/src/main/java/CommandHandler.java b/src/main/java/CommandHandler.java new file mode 100644 index 000000000..3e2c9063b --- /dev/null +++ b/src/main/java/CommandHandler.java @@ -0,0 +1,28 @@ +public class CommandHandler { + + private final XiaoAiBot bot; + public CommandHandler(XiaoAiBot bot) { + this.bot = bot; + } + + public void handleCommand(String command){ + if (command == null) { + bot.sendMessages("Command is null!"); + return; + } + String[] splitCommand = command.split(" "); + if (splitCommand.length < 1) { + bot.sendMessages("No command detected!"); + return; + } + String[] args = new String[splitCommand.length - 1]; + System.arraycopy(splitCommand, 1, args, 0, splitCommand.length - 1); + switch (splitCommand[0]) { + case "bye": + bot.setShouldQuit(true); + break; + default: + bot.sendMessages(command); + } + } +} diff --git a/src/main/java/XiaoAiBot.java b/src/main/java/XiaoAiBot.java index bd34a0f7b..15112fd12 100644 --- a/src/main/java/XiaoAiBot.java +++ b/src/main/java/XiaoAiBot.java @@ -1,3 +1,5 @@ +import java.util.Scanner; + public class XiaoAiBot { private final String BOT_NAME = "XiaoAi"; private final String SPLIT_LINE = "____________________________________________"; @@ -6,15 +8,48 @@ public class XiaoAiBot { private final String QUIT_MESSAGE = "See you next time, master!"; - private void sendMessages(String... messages) { + public CommandHandler getCommandHandler() { + return commandHandler; + } + + private CommandHandler commandHandler = new CommandHandler(this); + private Scanner scanner = new Scanner(System.in); + + public void setShouldQuit(boolean shouldQuit) { + this.shouldQuit = shouldQuit; + } + + private boolean shouldQuit = false; + + private void initialize() { + // for further modification + } + + public void sendMessages(String... messages) { for (String message : messages) { System.out.println(message); } System.out.println(SPLIT_LINE); } + public String readLine() { + try { + return scanner.nextLine(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + public void start() { + initialize(); sendMessages(GREET_MESSAGE); + + while (!shouldQuit) { + String command = readLine(); + commandHandler.handleCommand(command); + } + sendMessages(QUIT_MESSAGE); } } From 37b8d431711bcd23beab9173ab6b63536aa03983 Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 6 Sep 2023 20:03:16 +0800 Subject: [PATCH 03/22] Fix run and test scripts --- text-ui-test/runtest.bat | 4 ++-- text-ui-test/runtest.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 087374464..5fcdaa0c2 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -7,7 +7,7 @@ REM delete output from previous run if exist ACTUAL.TXT del ACTUAL.TXT REM compile the code into the bin folder -javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\*.java +javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\cn\yfshadaow\cs2113\ip\*.java IF ERRORLEVEL 1 ( echo ********** BUILD FAILURE ********** exit /b 1 @@ -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 cn.yfshadaow.cs2113.ip.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 c9ec87003..d6a236db2 100644 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,14 +13,14 @@ 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/cn/yfshadaow/cs2113/ip/*.java then echo "********** BUILD FAILURE **********" exit 1 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 -classpath ../bin cn.yfshadaow.cs2113.ip.Duke < input.txt > ACTUAL.TXT # convert to UNIX format cp EXPECTED.TXT EXPECTED-UNIX.TXT From f4b1e20b901f8bcf09bdeb6cc5f244b3b320b550 Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 6 Sep 2023 20:03:32 +0800 Subject: [PATCH 04/22] Move code away from default package --- .../java/{ => cn/yfshadaow/cs2113/ip}/CommandHandler.java | 4 ++++ src/main/java/{ => cn/yfshadaow/cs2113/ip}/Duke.java | 2 ++ src/main/java/{ => cn/yfshadaow/cs2113/ip}/XiaoAiBot.java | 2 ++ 3 files changed, 8 insertions(+) rename src/main/java/{ => cn/yfshadaow/cs2113/ip}/CommandHandler.java (91%) rename src/main/java/{ => cn/yfshadaow/cs2113/ip}/Duke.java (80%) rename src/main/java/{ => cn/yfshadaow/cs2113/ip}/XiaoAiBot.java (97%) diff --git a/src/main/java/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java similarity index 91% rename from src/main/java/CommandHandler.java rename to src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java index 3e2c9063b..0a6a95f83 100644 --- a/src/main/java/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java @@ -1,3 +1,7 @@ +package cn.yfshadaow.cs2113.ip; + +import cn.yfshadaow.cs2113.ip.XiaoAiBot; + public class CommandHandler { private final XiaoAiBot bot; diff --git a/src/main/java/Duke.java b/src/main/java/cn/yfshadaow/cs2113/ip/Duke.java similarity index 80% rename from src/main/java/Duke.java rename to src/main/java/cn/yfshadaow/cs2113/ip/Duke.java index 9f9b7d914..da1e3d41e 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/Duke.java @@ -1,3 +1,5 @@ +package cn.yfshadaow.cs2113.ip; + public class Duke { public static void main(String[] args) { diff --git a/src/main/java/XiaoAiBot.java b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java similarity index 97% rename from src/main/java/XiaoAiBot.java rename to src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java index 15112fd12..c8709c169 100644 --- a/src/main/java/XiaoAiBot.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java @@ -1,3 +1,5 @@ +package cn.yfshadaow.cs2113.ip; + import java.util.Scanner; public class XiaoAiBot { From 9922052a33127810207f5310d410fea1e3bc6f9c Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 6 Sep 2023 20:23:14 +0800 Subject: [PATCH 05/22] Feature: - Add tasks - List tasks --- .../yfshadaow/cs2113/ip/CommandHandler.java | 16 ++++++++-- .../cn/yfshadaow/cs2113/ip/XiaoAiBot.java | 31 ++++++++++++++----- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java index 0a6a95f83..8da60a0b6 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java @@ -2,6 +2,8 @@ import cn.yfshadaow.cs2113.ip.XiaoAiBot; +import java.util.List; + public class CommandHandler { private final XiaoAiBot bot; @@ -11,12 +13,12 @@ public CommandHandler(XiaoAiBot bot) { public void handleCommand(String command){ if (command == null) { - bot.sendMessages("Command is null!"); + bot.sendMessage("Command is null!"); return; } String[] splitCommand = command.split(" "); if (splitCommand.length < 1) { - bot.sendMessages("No command detected!"); + bot.sendMessage("No command detected!"); return; } String[] args = new String[splitCommand.length - 1]; @@ -25,8 +27,16 @@ public void handleCommand(String command){ case "bye": bot.setShouldQuit(true); break; + case "list": + List tasks = bot.getTasks(); + for (int i = 0; i < tasks.size(); i += 1) { + bot.sendMessageWithoutSplit(i + ". " + tasks.get(i)); + } + bot.sendSplit(); + break; default: - bot.sendMessages(command); + bot.getTasks().add(command); + bot.sendMessage("added " + command); } } } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java index c8709c169..1d08c5554 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java @@ -1,15 +1,25 @@ package cn.yfshadaow.cs2113.ip; +import java.util.ArrayList; +import java.util.List; import java.util.Scanner; public class XiaoAiBot { private final String BOT_NAME = "XiaoAi"; - private final String SPLIT_LINE = "____________________________________________"; + private final String SPLIT = "____________________________________________"; private final String GREET_MESSAGE = "Welcome back, master!\n" + BOT_NAME + " here. What can I do for you?"; private final String QUIT_MESSAGE = "See you next time, master!"; + + private final List tasks = new ArrayList<>(); + + public List getTasks() { + return tasks; + } + + public CommandHandler getCommandHandler() { return commandHandler; } @@ -27,11 +37,16 @@ private void initialize() { // for further modification } - public void sendMessages(String... messages) { - for (String message : messages) { - System.out.println(message); - } - System.out.println(SPLIT_LINE); + public void sendMessage(String message) { + sendMessageWithoutSplit(message); + sendSplit(); + } + + public void sendMessageWithoutSplit(String message) { + System.out.println(message); + } + public void sendSplit() { + System.out.println(SPLIT); } public String readLine() { @@ -45,13 +60,13 @@ public String readLine() { public void start() { initialize(); - sendMessages(GREET_MESSAGE); + sendMessage(GREET_MESSAGE); while (!shouldQuit) { String command = readLine(); commandHandler.handleCommand(command); } - sendMessages(QUIT_MESSAGE); + sendMessage(QUIT_MESSAGE); } } From 0d227e685d0fa31d16ff6fd5adae3017e19722ed Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 6 Sep 2023 20:54:44 +0800 Subject: [PATCH 06/22] Feature: - Mark and unmark tasks --- .../yfshadaow/cs2113/ip/CommandHandler.java | 61 ++++++++++++++++--- .../java/cn/yfshadaow/cs2113/ip/Task.java | 36 +++++++++++ .../cn/yfshadaow/cs2113/ip/XiaoAiBot.java | 8 +-- 3 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 src/main/java/cn/yfshadaow/cs2113/ip/Task.java diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java index 8da60a0b6..99af36a3b 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java @@ -1,7 +1,5 @@ package cn.yfshadaow.cs2113.ip; -import cn.yfshadaow.cs2113.ip.XiaoAiBot; - import java.util.List; public class CommandHandler { @@ -23,20 +21,67 @@ public void handleCommand(String command){ } String[] args = new String[splitCommand.length - 1]; System.arraycopy(splitCommand, 1, args, 0, splitCommand.length - 1); + bot.sendSplit(); switch (splitCommand[0]) { - case "bye": + case "bye": { bot.setShouldQuit(true); break; - case "list": - List tasks = bot.getTasks(); + } + case "mark": { + if (args.length != 1) { + bot.sendMessage("Incorrect arguments"); + } + int index = 0; + try { + index = Integer.parseInt(args[0]); + } catch (Exception e) { + e.printStackTrace(); + break; + } + try { + bot.getTasks().get(index - 1).setDone(true); + } catch (Exception e) { + e.printStackTrace(); + break; + } + bot.sendMessageWithoutSplit("Nice! I've marked this task as done:"); + bot.sendMessage(bot.getTasks().get(index - 1).toStringWithIsDone()); + break; + } + case "unmark": { + if (args.length != 1) { + bot.sendMessage("Incorrect arguments"); + } + int index = 0; + try { + index = Integer.parseInt(args[0]); + } catch (Exception e) { + e.printStackTrace(); + break; + } + try { + bot.getTasks().get(index - 1).setDone(false); + } catch (Exception e) { + e.printStackTrace(); + break; + } + bot.sendMessageWithoutSplit("OK, I've marked this task as not done yet:"); + bot.sendMessage(bot.getTasks().get(index - 1).toStringWithIsDone()); + break; + } + case "list": { + List tasks = bot.getTasks(); for (int i = 0; i < tasks.size(); i += 1) { - bot.sendMessageWithoutSplit(i + ". " + tasks.get(i)); + Task task = tasks.get(i); + bot.sendMessageWithoutSplit((i + 1) + "." + task.toStringWithIsDone()); } bot.sendSplit(); break; - default: - bot.getTasks().add(command); + } + default: { + bot.getTasks().add(new Task(command)); bot.sendMessage("added " + command); + } } } } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/Task.java b/src/main/java/cn/yfshadaow/cs2113/ip/Task.java new file mode 100644 index 000000000..b919786fd --- /dev/null +++ b/src/main/java/cn/yfshadaow/cs2113/ip/Task.java @@ -0,0 +1,36 @@ +package cn.yfshadaow.cs2113.ip; + +public class Task { + private boolean isDone = false; + private String name; + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean done) { + isDone = done; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + public Task(String name) { + this.name = name; + } + + public Task(String name, boolean isDone) { + this.name = name; + this.isDone = isDone; + } + + public String toStringWithIsDone() { + return "[" + (isDone ? "X" : " ") + "] " + name; + } +} diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java index 1d08c5554..66a235ab3 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java @@ -13,9 +13,9 @@ public class XiaoAiBot { private final String QUIT_MESSAGE = "See you next time, master!"; - private final List tasks = new ArrayList<>(); + private final List tasks = new ArrayList<>(); - public List getTasks() { + public List getTasks() { return tasks; } @@ -24,8 +24,8 @@ public CommandHandler getCommandHandler() { return commandHandler; } - private CommandHandler commandHandler = new CommandHandler(this); - private Scanner scanner = new Scanner(System.in); + private final CommandHandler commandHandler = new CommandHandler(this); + private final Scanner scanner = new Scanner(System.in); public void setShouldQuit(boolean shouldQuit) { this.shouldQuit = shouldQuit; From 8ccd6b9f76eef2c6db9be959d5077b68b3f35e41 Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 6 Sep 2023 22:02:16 +0800 Subject: [PATCH 07/22] Feature: Add type to tasks --- .../yfshadaow/cs2113/ip/CommandHandler.java | 55 +++++++++++++++-- .../java/cn/yfshadaow/cs2113/ip/Deadline.java | 42 +++++++++++++ .../java/cn/yfshadaow/cs2113/ip/Event.java | 59 +++++++++++++++++++ .../java/cn/yfshadaow/cs2113/ip/Task.java | 16 +---- .../java/cn/yfshadaow/cs2113/ip/Todo.java | 16 +++++ .../cn/yfshadaow/cs2113/ip/XiaoAiBot.java | 1 + 6 files changed, 171 insertions(+), 18 deletions(-) create mode 100644 src/main/java/cn/yfshadaow/cs2113/ip/Deadline.java create mode 100644 src/main/java/cn/yfshadaow/cs2113/ip/Event.java create mode 100644 src/main/java/cn/yfshadaow/cs2113/ip/Todo.java diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java index 99af36a3b..06dd0eb84 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java @@ -5,11 +5,12 @@ public class CommandHandler { private final XiaoAiBot bot; + public CommandHandler(XiaoAiBot bot) { this.bot = bot; } - public void handleCommand(String command){ + public void handleCommand(String command) { if (command == null) { bot.sendMessage("Command is null!"); return; @@ -27,11 +28,56 @@ public void handleCommand(String command){ bot.setShouldQuit(true); break; } + case "todo": { + Todo todo; + try { + todo = Todo.parseTodo(args); + } catch (Exception e) { + bot.sendMessage("Error parsing task"); + e.printStackTrace(); + break; + } + bot.getTasks().add(todo); + bot.sendMessageWithoutSplit("Got it. I've added this task:"); + bot.sendMessageWithoutSplit(todo.toStringWithIsDone()); + bot.sendMessage("Now you have " + bot.getTasks().size() + " tasks in the list."); + break; + } + case "deadline": { + Deadline deadline; + try { + deadline = Deadline.parseDeadline(args); + } catch (Exception e) { + bot.sendMessage("Error parsing task"); + e.printStackTrace(); + break; + } + bot.getTasks().add(deadline); + bot.sendMessageWithoutSplit("Got it. I've added this task:"); + bot.sendMessageWithoutSplit(deadline.toStringWithIsDone()); + bot.sendMessage("Now you have " + bot.getTasks().size() + " tasks in the list."); + break; + } + case "event": { + Event event; + try { + event = Event.parseEvent(args); + } catch (Exception e) { + bot.sendMessage("Error parsing task"); + e.printStackTrace(); + break; + } + bot.getTasks().add(event); + bot.sendMessageWithoutSplit("Got it. I've added this task:"); + bot.sendMessageWithoutSplit(event.toStringWithIsDone()); + bot.sendMessage("Now you have " + bot.getTasks().size() + " tasks in the list."); + break; + } case "mark": { if (args.length != 1) { bot.sendMessage("Incorrect arguments"); } - int index = 0; + int index; try { index = Integer.parseInt(args[0]); } catch (Exception e) { @@ -52,7 +98,7 @@ public void handleCommand(String command){ if (args.length != 1) { bot.sendMessage("Incorrect arguments"); } - int index = 0; + int index; try { index = Integer.parseInt(args[0]); } catch (Exception e) { @@ -79,8 +125,7 @@ public void handleCommand(String command){ break; } default: { - bot.getTasks().add(new Task(command)); - bot.sendMessage("added " + command); + bot.sendMessage("Unknown command"); } } } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/Deadline.java b/src/main/java/cn/yfshadaow/cs2113/ip/Deadline.java new file mode 100644 index 000000000..e452a7e14 --- /dev/null +++ b/src/main/java/cn/yfshadaow/cs2113/ip/Deadline.java @@ -0,0 +1,42 @@ +package cn.yfshadaow.cs2113.ip; + +import java.util.ArrayList; +import java.util.List; + +public class Deadline extends Task { + public String getBy() { + return by; + } + + public void setBy(String by) { + this.by = by; + } + + private String by; + + public Deadline(String name, String by) { + this.name = name; + this.by = by; + } + + @Override + public String toStringWithIsDone() { + return "[D][" + (isDone ? "X" : " ") + "] " + name + " (by: " + by + ")"; + } + + public static Deadline parseDeadline(String[] args) { + int index = 0; + List nameFragments = new ArrayList<>(); + while (!args[index].equals("/by")) { + nameFragments.add(args[index]); + index += 1; + } + index += 1; + List byFragments = new ArrayList<>(); + while (index < args.length) { + byFragments.add(args[index]); + index += 1; + } + return new Deadline(String.join(" ", nameFragments), String.join(" ", byFragments)); + } +} diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/Event.java b/src/main/java/cn/yfshadaow/cs2113/ip/Event.java new file mode 100644 index 000000000..3f9f732a6 --- /dev/null +++ b/src/main/java/cn/yfshadaow/cs2113/ip/Event.java @@ -0,0 +1,59 @@ +package cn.yfshadaow.cs2113.ip; + +import java.util.ArrayList; +import java.util.List; + +public class Event extends Task { + + private String from; + private String to; + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public String getTo() { + return to; + } + + public void setTo(String to) { + this.to = to; + } + + public Event(String name, String from, String to) { + this.name = name; + this.from = from; + this.to = to; + } + + @Override + public String toStringWithIsDone() { + return "[E][" + (isDone ? "X" : " ") + "] " + name + " (from: " + from + " to: " + to + ")"; + } + + public static Event parseEvent(String[] args) { + int index = 0; + List nameFragments = new ArrayList<>(); + while (!args[index].equals("/from")) { + nameFragments.add(args[index]); + index += 1; + } + index += 1; + List fromFragments = new ArrayList<>(); + while (!args[index].equals("/to")) { + fromFragments.add(args[index]); + index += 1; + } + index += 1; + List toFragments = new ArrayList<>(); + while (index < args.length) { + toFragments.add(args[index]); + index += 1; + } + return new Event(String.join(" ", nameFragments), String.join(" ", fromFragments), String.join(" ", toFragments)); + } +} diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/Task.java b/src/main/java/cn/yfshadaow/cs2113/ip/Task.java index b919786fd..aa384a3a1 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/Task.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/Task.java @@ -1,8 +1,8 @@ package cn.yfshadaow.cs2113.ip; -public class Task { - private boolean isDone = false; - private String name; +public abstract class Task { + protected boolean isDone = false; + protected String name; public boolean isDone() { return isDone; @@ -20,16 +20,6 @@ public void setName(String name) { this.name = name; } - - public Task(String name) { - this.name = name; - } - - public Task(String name, boolean isDone) { - this.name = name; - this.isDone = isDone; - } - public String toStringWithIsDone() { return "[" + (isDone ? "X" : " ") + "] " + name; } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/Todo.java b/src/main/java/cn/yfshadaow/cs2113/ip/Todo.java new file mode 100644 index 000000000..70fe75e17 --- /dev/null +++ b/src/main/java/cn/yfshadaow/cs2113/ip/Todo.java @@ -0,0 +1,16 @@ +package cn.yfshadaow.cs2113.ip; + +public class Todo extends Task { + public Todo(String name) { + this.name = name; + } + + public static Todo parseTodo(String[] args) { + return new Todo(String.join(" ", args)); + } + + @Override + public String toStringWithIsDone() { + return "[T][" + (isDone ? "X" : " ") + "] " + name; + } +} diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java index 66a235ab3..70dee0af6 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java @@ -45,6 +45,7 @@ public void sendMessage(String message) { public void sendMessageWithoutSplit(String message) { System.out.println(message); } + public void sendSplit() { System.out.println(SPLIT); } From 39ef31d41ad74aae856f88954e17c5a5c69891c6 Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 20 Sep 2023 11:07:47 +0800 Subject: [PATCH 08/22] Optimize code according to github reviews --- .../yfshadaow/cs2113/ip/CommandHandler.java | 27 +++++++++---------- .../java/cn/yfshadaow/cs2113/ip/Deadline.java | 2 +- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java index 06dd0eb84..116471529 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java @@ -1,5 +1,6 @@ package cn.yfshadaow.cs2113.ip; +import java.util.Arrays; import java.util.List; public class CommandHandler { @@ -20,8 +21,7 @@ public void handleCommand(String command) { bot.sendMessage("No command detected!"); return; } - String[] args = new String[splitCommand.length - 1]; - System.arraycopy(splitCommand, 1, args, 0, splitCommand.length - 1); + String[] args = Arrays.copyOfRange(splitCommand, 1, splitCommand.length); bot.sendSplit(); switch (splitCommand[0]) { case "bye": { @@ -33,8 +33,7 @@ public void handleCommand(String command) { try { todo = Todo.parseTodo(args); } catch (Exception e) { - bot.sendMessage("Error parsing task"); - e.printStackTrace(); + bot.sendMessage(String.format("Error parsing task: %s", e.getMessage())); break; } bot.getTasks().add(todo); @@ -48,8 +47,7 @@ public void handleCommand(String command) { try { deadline = Deadline.parseDeadline(args); } catch (Exception e) { - bot.sendMessage("Error parsing task"); - e.printStackTrace(); + bot.sendMessage(String.format("Error parsing task: %s", e.getMessage())); break; } bot.getTasks().add(deadline); @@ -63,8 +61,7 @@ public void handleCommand(String command) { try { event = Event.parseEvent(args); } catch (Exception e) { - bot.sendMessage("Error parsing task"); - e.printStackTrace(); + bot.sendMessage(String.format("Error parsing task: %s", e.getMessage())); break; } bot.getTasks().add(event); @@ -76,18 +73,19 @@ public void handleCommand(String command) { case "mark": { if (args.length != 1) { bot.sendMessage("Incorrect arguments"); + break; } int index; try { index = Integer.parseInt(args[0]); - } catch (Exception e) { - e.printStackTrace(); + } catch (NumberFormatException e) { + bot.sendMessage(String.format("Error parsing int: %s", e.getMessage())); break; } try { bot.getTasks().get(index - 1).setDone(true); - } catch (Exception e) { - e.printStackTrace(); + } catch (IndexOutOfBoundsException e) { + bot.sendMessage(String.format("Error getting task: %s", e.getMessage())); break; } bot.sendMessageWithoutSplit("Nice! I've marked this task as done:"); @@ -97,18 +95,19 @@ public void handleCommand(String command) { case "unmark": { if (args.length != 1) { bot.sendMessage("Incorrect arguments"); + break; } int index; try { index = Integer.parseInt(args[0]); } catch (Exception e) { - e.printStackTrace(); + bot.sendMessage(String.format("Error parsing int: %s", e.getMessage())); break; } try { bot.getTasks().get(index - 1).setDone(false); } catch (Exception e) { - e.printStackTrace(); + bot.sendMessage(String.format("Error getting task: %s", e.getMessage())); break; } bot.sendMessageWithoutSplit("OK, I've marked this task as not done yet:"); diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/Deadline.java b/src/main/java/cn/yfshadaow/cs2113/ip/Deadline.java index e452a7e14..551ad95ba 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/Deadline.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/Deadline.java @@ -4,6 +4,7 @@ import java.util.List; public class Deadline extends Task { + private String by; public String getBy() { return by; } @@ -12,7 +13,6 @@ public void setBy(String by) { this.by = by; } - private String by; public Deadline(String name, String by) { this.name = name; From f16e73fb356238694f0b9279a7eb08b623618a09 Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 20 Sep 2023 11:38:13 +0800 Subject: [PATCH 09/22] Optimize error logging --- src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java index 70dee0af6..6b2339652 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java @@ -54,7 +54,7 @@ public String readLine() { try { return scanner.nextLine(); } catch (Exception e) { - e.printStackTrace(); + sendMessage(String.format("Error reading line: %s", e.getMessage())); return null; } } From 5d257a6bf2175ea6702e841045b2b4f4606e3630 Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 20 Sep 2023 11:49:03 +0800 Subject: [PATCH 10/22] Optimize package structure --- src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java | 3 +++ .../yfshadaow/cs2113/ip/{ => command}/CommandHandler.java | 8 +++++++- .../java/cn/yfshadaow/cs2113/ip/{ => task}/Deadline.java | 2 +- .../java/cn/yfshadaow/cs2113/ip/{ => task}/Event.java | 2 +- src/main/java/cn/yfshadaow/cs2113/ip/{ => task}/Task.java | 2 +- src/main/java/cn/yfshadaow/cs2113/ip/{ => task}/Todo.java | 2 +- 6 files changed, 14 insertions(+), 5 deletions(-) rename src/main/java/cn/yfshadaow/cs2113/ip/{ => command}/CommandHandler.java (95%) rename src/main/java/cn/yfshadaow/cs2113/ip/{ => task}/Deadline.java (96%) rename src/main/java/cn/yfshadaow/cs2113/ip/{ => task}/Event.java (97%) rename src/main/java/cn/yfshadaow/cs2113/ip/{ => task}/Task.java (92%) rename src/main/java/cn/yfshadaow/cs2113/ip/{ => task}/Todo.java (89%) diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java index 6b2339652..805e452b3 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java @@ -1,5 +1,8 @@ package cn.yfshadaow.cs2113.ip; +import cn.yfshadaow.cs2113.ip.command.CommandHandler; +import cn.yfshadaow.cs2113.ip.task.Task; + import java.util.ArrayList; import java.util.List; import java.util.Scanner; diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java similarity index 95% rename from src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java rename to src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java index 116471529..e6bcb3aab 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java @@ -1,4 +1,10 @@ -package cn.yfshadaow.cs2113.ip; +package cn.yfshadaow.cs2113.ip.command; + +import cn.yfshadaow.cs2113.ip.*; +import cn.yfshadaow.cs2113.ip.task.Deadline; +import cn.yfshadaow.cs2113.ip.task.Event; +import cn.yfshadaow.cs2113.ip.task.Task; +import cn.yfshadaow.cs2113.ip.task.Todo; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/Deadline.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java similarity index 96% rename from src/main/java/cn/yfshadaow/cs2113/ip/Deadline.java rename to src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java index 551ad95ba..ae72f5d14 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/Deadline.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java @@ -1,4 +1,4 @@ -package cn.yfshadaow.cs2113.ip; +package cn.yfshadaow.cs2113.ip.task; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/Event.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java similarity index 97% rename from src/main/java/cn/yfshadaow/cs2113/ip/Event.java rename to src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java index 3f9f732a6..71c94567f 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/Event.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java @@ -1,4 +1,4 @@ -package cn.yfshadaow.cs2113.ip; +package cn.yfshadaow.cs2113.ip.task; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/Task.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java similarity index 92% rename from src/main/java/cn/yfshadaow/cs2113/ip/Task.java rename to src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java index aa384a3a1..c66cf43cb 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/Task.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java @@ -1,4 +1,4 @@ -package cn.yfshadaow.cs2113.ip; +package cn.yfshadaow.cs2113.ip.task; public abstract class Task { protected boolean isDone = false; diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/Todo.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Todo.java similarity index 89% rename from src/main/java/cn/yfshadaow/cs2113/ip/Todo.java rename to src/main/java/cn/yfshadaow/cs2113/ip/task/Todo.java index 70fe75e17..6abf0e2a2 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/Todo.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Todo.java @@ -1,4 +1,4 @@ -package cn.yfshadaow.cs2113.ip; +package cn.yfshadaow.cs2113.ip.task; public class Todo extends Task { public Todo(String name) { From 1dc55b32616809279c1e1d12df7983b90f5f3ca4 Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 4 Oct 2023 18:30:36 +0800 Subject: [PATCH 11/22] Add Command.java: Update command parsing logic --- .../cn/yfshadaow/cs2113/ip/XiaoAiBot.java | 12 +++- .../yfshadaow/cs2113/ip/command/Command.java | 59 +++++++++++++++++++ .../cs2113/ip/command/CommandHandler.java | 31 ++++------ .../cn/yfshadaow/cs2113/ip/task/Deadline.java | 24 ++++---- .../cn/yfshadaow/cs2113/ip/task/Event.java | 35 +++++------ .../cn/yfshadaow/cs2113/ip/task/Todo.java | 9 ++- 6 files changed, 116 insertions(+), 54 deletions(-) create mode 100644 src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java index 805e452b3..e8c0ec59e 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java @@ -1,5 +1,6 @@ package cn.yfshadaow.cs2113.ip; +import cn.yfshadaow.cs2113.ip.command.Command; import cn.yfshadaow.cs2113.ip.command.CommandHandler; import cn.yfshadaow.cs2113.ip.task.Task; @@ -67,8 +68,15 @@ public void start() { sendMessage(GREET_MESSAGE); while (!shouldQuit) { - String command = readLine(); - commandHandler.handleCommand(command); + String commandString = readLine(); + Command cmd; + try { + cmd = Command.parseCommand(commandString); + } catch (IllegalArgumentException e) { + sendMessage(String.format("Error parsing command: %s", e.getMessage())); + continue; + } + commandHandler.handleCommand(cmd); } sendMessage(QUIT_MESSAGE); diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java new file mode 100644 index 000000000..cce43df1b --- /dev/null +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java @@ -0,0 +1,59 @@ +package cn.yfshadaow.cs2113.ip.command; + +import java.util.*; + +public class Command { + private String cmd; + + public String getCmd() { + return cmd; + } + + public void setCmd(String cmd) { + this.cmd = cmd; + } + + public final List args = new ArrayList<>(); + public final Map extraArgs = new HashMap<>(); + + public Command(String cmd, List args, Map extraArgs) { + this.cmd = cmd; + this.args.addAll(args); + this.extraArgs.putAll(extraArgs); + } + + public static Command parseCommand(String s) throws IllegalArgumentException{ + Iterator iterator = Arrays.stream(s.split(" ")).iterator(); + if (!iterator.hasNext()) { + throw new IllegalArgumentException("Command cannot be empty!"); + } + String cmdString = iterator.next(); + List arguments = new ArrayList<>(); + Map extraArguments = new HashMap<>(); + + // currentPart stores which part of argument the parser is reading + // For example, if currentPart is null, the parser is reading main arguments + // If it is not null, the parser is reading extra arguments associated with that string + String currentPart = null; + while (iterator.hasNext()) { + String next = iterator.next(); + if (next.startsWith("-")) { + if (next.length() == 1) { + throw new IllegalArgumentException("Extra argument identifier cannot be empty!"); + } + currentPart = next.substring(1); + if (extraArguments.containsKey(currentPart)) { + throw new IllegalArgumentException("Duplicate argument identifier!"); + } + extraArguments.put(currentPart, ""); + } else { + if (currentPart == null) { + arguments.add(next); + } else { + extraArguments.put(currentPart, extraArguments.get(currentPart) + " " + next); + } + } + } + return new Command(cmdString, arguments, extraArguments); + } +} diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java index e6bcb3aab..aa8ad1d62 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java @@ -1,12 +1,11 @@ package cn.yfshadaow.cs2113.ip.command; -import cn.yfshadaow.cs2113.ip.*; +import cn.yfshadaow.cs2113.ip.XiaoAiBot; import cn.yfshadaow.cs2113.ip.task.Deadline; import cn.yfshadaow.cs2113.ip.task.Event; import cn.yfshadaow.cs2113.ip.task.Task; import cn.yfshadaow.cs2113.ip.task.Todo; -import java.util.Arrays; import java.util.List; public class CommandHandler { @@ -17,19 +16,9 @@ public CommandHandler(XiaoAiBot bot) { this.bot = bot; } - public void handleCommand(String command) { - if (command == null) { - bot.sendMessage("Command is null!"); - return; - } - String[] splitCommand = command.split(" "); - if (splitCommand.length < 1) { - bot.sendMessage("No command detected!"); - return; - } - String[] args = Arrays.copyOfRange(splitCommand, 1, splitCommand.length); + public void handleCommand(Command cmd) { bot.sendSplit(); - switch (splitCommand[0]) { + switch (cmd.getCmd()) { case "bye": { bot.setShouldQuit(true); break; @@ -37,7 +26,7 @@ public void handleCommand(String command) { case "todo": { Todo todo; try { - todo = Todo.parseTodo(args); + todo = Todo.parseTodo(cmd); } catch (Exception e) { bot.sendMessage(String.format("Error parsing task: %s", e.getMessage())); break; @@ -51,7 +40,7 @@ public void handleCommand(String command) { case "deadline": { Deadline deadline; try { - deadline = Deadline.parseDeadline(args); + deadline = Deadline.parseDeadline(cmd); } catch (Exception e) { bot.sendMessage(String.format("Error parsing task: %s", e.getMessage())); break; @@ -65,7 +54,7 @@ public void handleCommand(String command) { case "event": { Event event; try { - event = Event.parseEvent(args); + event = Event.parseEvent(cmd); } catch (Exception e) { bot.sendMessage(String.format("Error parsing task: %s", e.getMessage())); break; @@ -77,13 +66,13 @@ public void handleCommand(String command) { break; } case "mark": { - if (args.length != 1) { + if (cmd.args.size() != 1) { bot.sendMessage("Incorrect arguments"); break; } int index; try { - index = Integer.parseInt(args[0]); + index = Integer.parseInt(cmd.args.get(0)); } catch (NumberFormatException e) { bot.sendMessage(String.format("Error parsing int: %s", e.getMessage())); break; @@ -99,13 +88,13 @@ public void handleCommand(String command) { break; } case "unmark": { - if (args.length != 1) { + if (cmd.args.size() != 1) { bot.sendMessage("Incorrect arguments"); break; } int index; try { - index = Integer.parseInt(args[0]); + index = Integer.parseInt(cmd.args.get(0)); } catch (Exception e) { bot.sendMessage(String.format("Error parsing int: %s", e.getMessage())); break; diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java index ae72f5d14..c545b21c1 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java @@ -1,5 +1,7 @@ package cn.yfshadaow.cs2113.ip.task; +import cn.yfshadaow.cs2113.ip.command.Command; + import java.util.ArrayList; import java.util.List; @@ -24,19 +26,17 @@ public String toStringWithIsDone() { return "[D][" + (isDone ? "X" : " ") + "] " + name + " (by: " + by + ")"; } - public static Deadline parseDeadline(String[] args) { - int index = 0; - List nameFragments = new ArrayList<>(); - while (!args[index].equals("/by")) { - nameFragments.add(args[index]); - index += 1; + public static Deadline parseDeadline(Command cmd) throws IllegalArgumentException{ + if (cmd.args.isEmpty()) { + throw new IllegalArgumentException("Deadline name cannot be empty!"); + } + if (!cmd.extraArgs.containsKey("by")) { + throw new IllegalArgumentException("Deadline must have -by argument!"); } - index += 1; - List byFragments = new ArrayList<>(); - while (index < args.length) { - byFragments.add(args[index]); - index += 1; + String byString = cmd.extraArgs.get("by"); + if (byString.isEmpty()) { + throw new IllegalArgumentException("-by argument cannot be empty!"); } - return new Deadline(String.join(" ", nameFragments), String.join(" ", byFragments)); + return new Deadline(String.join(" ", cmd.args), byString); } } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java index 71c94567f..1954c8c41 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java @@ -1,5 +1,7 @@ package cn.yfshadaow.cs2113.ip.task; +import cn.yfshadaow.cs2113.ip.command.Command; + import java.util.ArrayList; import java.util.List; @@ -35,25 +37,24 @@ public String toStringWithIsDone() { return "[E][" + (isDone ? "X" : " ") + "] " + name + " (from: " + from + " to: " + to + ")"; } - public static Event parseEvent(String[] args) { - int index = 0; - List nameFragments = new ArrayList<>(); - while (!args[index].equals("/from")) { - nameFragments.add(args[index]); - index += 1; + public static Event parseEvent(Command cmd) throws IllegalArgumentException{ + if (cmd.args.isEmpty()) { + throw new IllegalArgumentException("Event name cannot be empty!"); + } + if (!cmd.extraArgs.containsKey("from")) { + throw new IllegalArgumentException("Event must have -from argument!"); + } + String fromString = cmd.extraArgs.get("from"); + if (fromString.isEmpty()) { + throw new IllegalArgumentException("-from argument cannot be empty!"); } - index += 1; - List fromFragments = new ArrayList<>(); - while (!args[index].equals("/to")) { - fromFragments.add(args[index]); - index += 1; + if (!cmd.extraArgs.containsKey("to")) { + throw new IllegalArgumentException("Event must have -to argument!"); } - index += 1; - List toFragments = new ArrayList<>(); - while (index < args.length) { - toFragments.add(args[index]); - index += 1; + String toString = cmd.extraArgs.get("to"); + if (toString.isEmpty()) { + throw new IllegalArgumentException("-to argument cannot be empty!"); } - return new Event(String.join(" ", nameFragments), String.join(" ", fromFragments), String.join(" ", toFragments)); + return new Event(String.join(" ", cmd.args), fromString, toString); } } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/task/Todo.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Todo.java index 6abf0e2a2..5ab7f36aa 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/task/Todo.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Todo.java @@ -1,12 +1,17 @@ package cn.yfshadaow.cs2113.ip.task; +import cn.yfshadaow.cs2113.ip.command.Command; + public class Todo extends Task { public Todo(String name) { this.name = name; } - public static Todo parseTodo(String[] args) { - return new Todo(String.join(" ", args)); + public static Todo parseTodo(Command cmd) throws IllegalArgumentException{ + if (cmd.args.isEmpty()) { + throw new IllegalArgumentException("Todo name cannot be empty!"); + } + return new Todo(String.join(" ", cmd.args)); } @Override From e94935f04533ffdc6995f96dcec568027707f322 Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 4 Oct 2023 18:33:09 +0800 Subject: [PATCH 12/22] Eliminate warnings --- src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java | 9 +++++---- .../java/cn/yfshadaow/cs2113/ip/command/Command.java | 2 ++ src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java | 5 ++--- src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java | 7 ++++--- src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java | 3 +++ 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java index e8c0ec59e..862ff6715 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java @@ -9,12 +9,12 @@ import java.util.Scanner; public class XiaoAiBot { - private final String BOT_NAME = "XiaoAi"; - private final String SPLIT = "____________________________________________"; - private final String GREET_MESSAGE = "Welcome back, master!\n" + + private static final String BOT_NAME = "XiaoAi"; + private static final String SPLIT = "____________________________________________"; + private static final String GREET_MESSAGE = "Welcome back, master!\n" + BOT_NAME + " here. What can I do for you?"; - private final String QUIT_MESSAGE = "See you next time, master!"; + private static final String QUIT_MESSAGE = "See you next time, master!"; private final List tasks = new ArrayList<>(); @@ -24,6 +24,7 @@ public List getTasks() { } + @SuppressWarnings("unused") public CommandHandler getCommandHandler() { return commandHandler; } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java index cce43df1b..10e51a8bf 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java @@ -5,10 +5,12 @@ public class Command { private String cmd; + @SuppressWarnings("unused") public String getCmd() { return cmd; } + @SuppressWarnings("unused") public void setCmd(String cmd) { this.cmd = cmd; } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java index c545b21c1..d14b99120 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java @@ -2,15 +2,14 @@ import cn.yfshadaow.cs2113.ip.command.Command; -import java.util.ArrayList; -import java.util.List; - public class Deadline extends Task { private String by; + @SuppressWarnings("unused") public String getBy() { return by; } + @SuppressWarnings("unused") public void setBy(String by) { this.by = by; } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java index 1954c8c41..4e373964a 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java @@ -2,26 +2,27 @@ import cn.yfshadaow.cs2113.ip.command.Command; -import java.util.ArrayList; -import java.util.List; - public class Event extends Task { private String from; private String to; + @SuppressWarnings("unused") public String getFrom() { return from; } + @SuppressWarnings("unused") public void setFrom(String from) { this.from = from; } + @SuppressWarnings("unused") public String getTo() { return to; } + @SuppressWarnings("unused") public void setTo(String to) { this.to = to; } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java index c66cf43cb..2cb8bba7d 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java @@ -4,6 +4,7 @@ public abstract class Task { protected boolean isDone = false; protected String name; + @SuppressWarnings("unused") public boolean isDone() { return isDone; } @@ -12,10 +13,12 @@ public void setDone(boolean done) { isDone = done; } + @SuppressWarnings("unused") public String getName() { return name; } + @SuppressWarnings("unused") public void setName(String name) { this.name = name; } From c499c985cf4361c5f66ff13d6a5b8060dc402f03 Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 4 Oct 2023 18:46:34 +0800 Subject: [PATCH 13/22] Add delete command --- .../cn/yfshadaow/cs2113/ip/XiaoAiBot.java | 5 ++-- .../yfshadaow/cs2113/ip/command/Command.java | 3 ++- .../cs2113/ip/command/CommandHandler.java | 24 +++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java index 862ff6715..f0986f9d7 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java @@ -4,7 +4,8 @@ import cn.yfshadaow.cs2113.ip.command.CommandHandler; import cn.yfshadaow.cs2113.ip.task.Task; -import java.util.ArrayList; +import java.util.LinkedList; +import java.util.LinkedList; import java.util.List; import java.util.Scanner; @@ -17,7 +18,7 @@ public class XiaoAiBot { private static final String QUIT_MESSAGE = "See you next time, master!"; - private final List tasks = new ArrayList<>(); + private final List tasks = new LinkedList<>(); public List getTasks() { return tasks; diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java index 10e51a8bf..9b79ec69e 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java @@ -52,7 +52,8 @@ public static Command parseCommand(String s) throws IllegalArgumentException{ if (currentPart == null) { arguments.add(next); } else { - extraArguments.put(currentPart, extraArguments.get(currentPart) + " " + next); + String storedString = extraArguments.get(currentPart); + extraArguments.put(currentPart, storedString + (storedString.isEmpty()? "" : " ") + next); } } } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java index aa8ad1d62..44cf782f8 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java @@ -109,6 +109,30 @@ public void handleCommand(Command cmd) { bot.sendMessage(bot.getTasks().get(index - 1).toStringWithIsDone()); break; } + case "delete": { + if (cmd.args.size() != 1) { + bot.sendMessage("Incorrect arguments"); + break; + } + int index; + try { + index = Integer.parseInt(cmd.args.get(0)); + } catch (Exception e) { + bot.sendMessage(String.format("Error parsing int: %s", e.getMessage())); + break; + } + Task task; + try { + task = bot.getTasks().get(index - 1); + } catch (Exception e) { + bot.sendMessage(String.format("Error getting task: %s", e.getMessage())); + break; + } + bot.sendMessageWithoutSplit("Noted. I've removed this task:"); + bot.sendMessage(task.toStringWithIsDone()); + bot.getTasks().remove(index - 1); + break; + } case "list": { List tasks = bot.getTasks(); for (int i = 0; i < tasks.size(); i += 1) { From 9501edca8cdc0542186fa45d205a2c67d92225f0 Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 4 Oct 2023 22:36:54 +0800 Subject: [PATCH 14/22] Add feature of saving task data as json file --- pom.xml | 83 +++++ .../cn/yfshadaow/cs2113/ip/XiaoAiBot.java | 73 ++++- .../cs2113/ip/command/CommandHandler.java | 31 ++ .../cn/yfshadaow/cs2113/ip/task/Task.java | 4 + .../RuntimeTypeAdapterFactory.java | 297 ++++++++++++++++++ 5 files changed, 484 insertions(+), 4 deletions(-) create mode 100644 pom.xml create mode 100644 src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..5c5ba4093 --- /dev/null +++ b/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + cn.yfshadaow.cs2113.ip + Duke + 1.0.0 + jar + + Duke + + + 11 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + false + + + + + + org.apache.maven.plugins + maven-help-plugin + 3.2.0 + + + install + src/main/java + + + src/main/resources + true + + + + + + + + com.google.code.gson + gson + 2.10.1 + + + + com.google.errorprone + error_prone_annotations + 2.22.0 + + + + commons-io + commons-io + 2.14.0 + + + + diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java index 862ff6715..480cab39a 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java @@ -2,13 +2,33 @@ import cn.yfshadaow.cs2113.ip.command.Command; import cn.yfshadaow.cs2113.ip.command.CommandHandler; +import cn.yfshadaow.cs2113.ip.task.Deadline; +import cn.yfshadaow.cs2113.ip.task.Event; import cn.yfshadaow.cs2113.ip.task.Task; - +import cn.yfshadaow.cs2113.ip.task.Todo; +import com.google.gson.*; +import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class XiaoAiBot { + private static final RuntimeTypeAdapterFactory taskAdapterFactory = RuntimeTypeAdapterFactory.of(Task.class, "type") + .registerSubtype(Deadline.class, "Deadline") + .registerSubtype(Event.class, "Event") + .registerSubtype(Todo.class, "Todo"); + public static final Gson gson = new GsonBuilder() + .registerTypeAdapterFactory(taskAdapterFactory) + .setPrettyPrinting() + .create(); + private static final String BOT_NAME = "XiaoAi"; private static final String SPLIT = "____________________________________________"; private static final String GREET_MESSAGE = "Welcome back, master!\n" + @@ -16,6 +36,12 @@ public class XiaoAiBot { private static final String QUIT_MESSAGE = "See you next time, master!"; + private static final String DATA_DIRECTORY_PATH = "./data/"; + + private static final String TASK_DATA_FILE_PATH = DATA_DIRECTORY_PATH + "taskData.json"; + + private final File taskDataFile = new File(TASK_DATA_FILE_PATH); + private final List tasks = new ArrayList<>(); @@ -38,10 +64,44 @@ public void setShouldQuit(boolean shouldQuit) { private boolean shouldQuit = false; - private void initialize() { - // for further modification + private void initialize() throws IOException{ + Files.createDirectories(Paths.get(DATA_DIRECTORY_PATH)); + taskDataFile.createNewFile(); + try { + loadData(); + sendMessage("Successfully loaded data from data file"); + } catch (IllegalStateException e) { + sendMessage(String.format("Failed to load data from data file: %s", e.getMessage())); + } catch (JsonParseException e) { + sendMessage(String.format("Failed to load data becauseJSON parsing failed: %s", e.getMessage())); + } + } + + public void saveData() throws IOException { + JsonObject dataObject = new JsonObject(); + JsonArray tasksArray = new JsonArray(); + for (Task t: tasks) { + if (t instanceof Todo) { + tasksArray.add(gson.toJsonTree(t, Task.class)); + } else if (t instanceof Deadline) { + tasksArray.add(gson.toJsonTree(t, Task.class)); + } else if (t instanceof Event) { + tasksArray.add(gson.toJsonTree(t, Task.class)); + } + } + dataObject.add("tasks", tasksArray); + FileUtils.write(taskDataFile, gson.toJson(dataObject), StandardCharsets.UTF_8); } + public void loadData() throws IOException, IllegalStateException, JsonParseException { + tasks.clear(); + String dataString = FileUtils.readFileToString(taskDataFile, StandardCharsets.UTF_8); + JsonObject dataObject = JsonParser.parseString(dataString).getAsJsonObject(); + JsonArray tasksArray = dataObject.getAsJsonArray("tasks"); + for (JsonElement e : tasksArray.asList()) { + tasks.add(gson.fromJson(e, Task.class)); + } + } public void sendMessage(String message) { sendMessageWithoutSplit(message); sendSplit(); @@ -65,7 +125,12 @@ public String readLine() { } public void start() { - initialize(); + try { + initialize(); + } catch (Exception e) { + sendMessage(String.format("Bot initialization failed: %s", e.getMessage())); + return; + } sendMessage(GREET_MESSAGE); while (!shouldQuit) { diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java index aa8ad1d62..c4b505739 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java @@ -35,6 +35,13 @@ public void handleCommand(Command cmd) { bot.sendMessageWithoutSplit("Got it. I've added this task:"); bot.sendMessageWithoutSplit(todo.toStringWithIsDone()); bot.sendMessage("Now you have " + bot.getTasks().size() + " tasks in the list."); + try { + bot.saveData(); + } catch (Exception e) { + bot.sendMessage(String.format("Error saving data: %s", e.getMessage())); + e.printStackTrace(); + break; + } break; } case "deadline": { @@ -49,6 +56,12 @@ public void handleCommand(Command cmd) { bot.sendMessageWithoutSplit("Got it. I've added this task:"); bot.sendMessageWithoutSplit(deadline.toStringWithIsDone()); bot.sendMessage("Now you have " + bot.getTasks().size() + " tasks in the list."); + try { + bot.saveData(); + } catch (Exception e) { + bot.sendMessage(String.format("Error saving data: %s", e.getMessage())); + break; + } break; } case "event": { @@ -63,6 +76,12 @@ public void handleCommand(Command cmd) { bot.sendMessageWithoutSplit("Got it. I've added this task:"); bot.sendMessageWithoutSplit(event.toStringWithIsDone()); bot.sendMessage("Now you have " + bot.getTasks().size() + " tasks in the list."); + try { + bot.saveData(); + } catch (Exception e) { + bot.sendMessage(String.format("Error saving data: %s", e.getMessage())); + break; + } break; } case "mark": { @@ -85,6 +104,12 @@ public void handleCommand(Command cmd) { } bot.sendMessageWithoutSplit("Nice! I've marked this task as done:"); bot.sendMessage(bot.getTasks().get(index - 1).toStringWithIsDone()); + try { + bot.saveData(); + } catch (Exception e) { + bot.sendMessage(String.format("Error saving data: %s", e.getMessage())); + break; + } break; } case "unmark": { @@ -107,6 +132,12 @@ public void handleCommand(Command cmd) { } bot.sendMessageWithoutSplit("OK, I've marked this task as not done yet:"); bot.sendMessage(bot.getTasks().get(index - 1).toStringWithIsDone()); + try { + bot.saveData(); + } catch (Exception e) { + bot.sendMessage(String.format("Error saving data: %s", e.getMessage())); + break; + } break; } case "list": { diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java index 2cb8bba7d..b69b10521 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java @@ -1,7 +1,11 @@ package cn.yfshadaow.cs2113.ip.task; +import com.google.gson.annotations.Expose; + public abstract class Task { + @Expose protected boolean isDone = false; + @Expose protected String name; @SuppressWarnings("unused") diff --git a/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java b/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java new file mode 100644 index 000000000..29fbc6926 --- /dev/null +++ b/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * 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 + * + * http://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. + */ + +package com.google.gson.typeadapters; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Adapts values whose runtime type may differ from their declaration type. This + * is necessary when a field's type is not the same type that GSON should create + * when deserializing that field. For example, consider these types: + *
   {@code
+ *   abstract class Shape {
+ *     int x;
+ *     int y;
+ *   }
+ *   class Circle extends Shape {
+ *     int radius;
+ *   }
+ *   class Rectangle extends Shape {
+ *     int width;
+ *     int height;
+ *   }
+ *   class Diamond extends Shape {
+ *     int width;
+ *     int height;
+ *   }
+ *   class Drawing {
+ *     Shape bottomShape;
+ *     Shape topShape;
+ *   }
+ * }
+ *

Without additional type information, the serialized JSON is ambiguous. Is + * the bottom shape in this drawing a rectangle or a diamond?

   {@code
+ *   {
+ *     "bottomShape": {
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * This class addresses this problem by adding type information to the + * serialized JSON and honoring that type information when the JSON is + * deserialized:
   {@code
+ *   {
+ *     "bottomShape": {
+ *       "type": "Diamond",
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "type": "Circle",
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * Both the type field name ({@code "type"}) and the type labels ({@code + * "Rectangle"}) are configurable. + * + *

Registering Types

+ * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field + * name to the {@link #of} factory method. If you don't supply an explicit type + * field name, {@code "type"} will be used.
   {@code
+ *   RuntimeTypeAdapterFactory shapeAdapterFactory
+ *       = RuntimeTypeAdapterFactory.of(Shape.class, "type");
+ * }
+ * Next register all of your subtypes. Every subtype must be explicitly + * registered. This protects your application from injection attacks. If you + * don't supply an explicit type label, the type's simple name will be used. + *
   {@code
+ *   shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
+ *   shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
+ *   shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
+ * }
+ * Finally, register the type adapter factory in your application's GSON builder: + *
   {@code
+ *   Gson gson = new GsonBuilder()
+ *       .registerTypeAdapterFactory(shapeAdapterFactory)
+ *       .create();
+ * }
+ * Like {@code GsonBuilder}, this API supports chaining:
   {@code
+ *   RuntimeTypeAdapterFactory shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
+ *       .registerSubtype(Rectangle.class)
+ *       .registerSubtype(Circle.class)
+ *       .registerSubtype(Diamond.class);
+ * }
+ * + *

Serialization and deserialization

+ * In order to serialize and deserialize a polymorphic object, + * you must specify the base type explicitly. + *
   {@code
+ *   Diamond diamond = new Diamond();
+ *   String json = gson.toJson(diamond, Shape.class);
+ * }
+ * And then: + *
   {@code
+ *   Shape shape = gson.fromJson(json, Shape.class);
+ * }
+ */ +public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { + private final Class baseType; + private final String typeFieldName; + private final Map> labelToSubtype = new LinkedHashMap<>(); + private final Map, String> subtypeToLabel = new LinkedHashMap<>(); + private final boolean maintainType; + private boolean recognizeSubtypes; + + private RuntimeTypeAdapterFactory( + Class baseType, String typeFieldName, boolean maintainType) { + if (typeFieldName == null || baseType == null) { + throw new NullPointerException(); + } + this.baseType = baseType; + this.typeFieldName = typeFieldName; + this.maintainType = maintainType; + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code + * typeFieldName} as the type field name. Type field names are case sensitive. + * + * @param maintainType true if the type field should be included in deserialized objects + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName, boolean maintainType) { + return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType); + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code + * typeFieldName} as the type field name. Type field names are case sensitive. + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { + return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false); + } + + /** + * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as + * the type field name. + */ + public static RuntimeTypeAdapterFactory of(Class baseType) { + return new RuntimeTypeAdapterFactory<>(baseType, "type", false); + } + + /** + * Ensures that this factory will handle not just the given {@code baseType}, but any subtype + * of that type. + */ + @CanIgnoreReturnValue + public RuntimeTypeAdapterFactory recognizeSubtypes() { + this.recognizeSubtypes = true; + return this; + } + + /** + * Registers {@code type} identified by {@code label}. Labels are case + * sensitive. + * + * @throws IllegalArgumentException if either {@code type} or {@code label} + * have already been registered on this type adapter. + */ + @CanIgnoreReturnValue + public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { + if (type == null || label == null) { + throw new NullPointerException(); + } + if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { + throw new IllegalArgumentException("types and labels must be unique"); + } + labelToSubtype.put(label, type); + subtypeToLabel.put(type, label); + return this; + } + + /** + * Registers {@code type} identified by its {@link Class#getSimpleName simple + * name}. Labels are case sensitive. + * + * @throws IllegalArgumentException if either {@code type} or its simple name + * have already been registered on this type adapter. + */ + @CanIgnoreReturnValue + public RuntimeTypeAdapterFactory registerSubtype(Class type) { + return registerSubtype(type, type.getSimpleName()); + } + + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (type == null) { + return null; + } + Class rawType = type.getRawType(); + boolean handle = + recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType); + if (!handle) { + return null; + } + + final TypeAdapter jsonElementAdapter = gson.getAdapter(JsonElement.class); + final Map> labelToDelegate = new LinkedHashMap<>(); + final Map, TypeAdapter> subtypeToDelegate = new LinkedHashMap<>(); + for (Map.Entry> entry : labelToSubtype.entrySet()) { + TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); + labelToDelegate.put(entry.getKey(), delegate); + subtypeToDelegate.put(entry.getValue(), delegate); + } + + return new TypeAdapter() { + @Override public R read(JsonReader in) throws IOException { + JsonElement jsonElement = jsonElementAdapter.read(in); + JsonElement labelJsonElement; + if (maintainType) { + labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); + } else { + labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); + } + + if (labelJsonElement == null) { + throw new JsonParseException("cannot deserialize " + baseType + + " because it does not define a field named " + typeFieldName); + } + String label = labelJsonElement.getAsString(); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); + if (delegate == null) { + throw new JsonParseException("cannot deserialize " + baseType + " subtype named " + + label + "; did you forget to register a subtype?"); + } + return delegate.fromJsonTree(jsonElement); + } + + @Override public void write(JsonWriter out, R value) throws IOException { + Class srcType = value.getClass(); + String label = subtypeToLabel.get(srcType); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); + if (delegate == null) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + "; did you forget to register a subtype?"); + } + JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); + + if (maintainType) { + jsonElementAdapter.write(out, jsonObject); + return; + } + + JsonObject clone = new JsonObject(); + + if (jsonObject.has(typeFieldName)) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + " because it already defines a field named " + typeFieldName); + } + clone.add(typeFieldName, new JsonPrimitive(label)); + + for (Map.Entry e : jsonObject.entrySet()) { + clone.add(e.getKey(), e.getValue()); + } + jsonElementAdapter.write(out, clone); + } + }.nullSafe(); + } +} From 90133e03e6b199e2061493b9832f12c72a83e8ca Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 4 Oct 2023 22:38:42 +0800 Subject: [PATCH 15/22] Update .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 2873e189e..c83f1a88b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,9 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +# Bot data files +/data/ + +# Maven build files +/target/ \ No newline at end of file From c307ba658109509624a8eaefd2f7022bc63df075 Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 4 Oct 2023 22:39:18 +0800 Subject: [PATCH 16/22] Remove debug logging --- src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java index c4b505739..b07f64d1c 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java @@ -39,7 +39,6 @@ public void handleCommand(Command cmd) { bot.saveData(); } catch (Exception e) { bot.sendMessage(String.format("Error saving data: %s", e.getMessage())); - e.printStackTrace(); break; } break; From 40c399af828dddf96a1565a8b76c6f26e79a794a Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Wed, 4 Oct 2023 22:48:14 +0800 Subject: [PATCH 17/22] Update pom.xml --- pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pom.xml b/pom.xml index 5c5ba4093..462d9a3bf 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,14 @@ shade + + + + cn.yfshadaow.cs2113.ip.Duke + 1.0 + + + false From 928f9a5933827f027e392c4a8a571d6698a8219e Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Thu, 5 Oct 2023 00:06:31 +0800 Subject: [PATCH 18/22] Extract methods to new classes for better OOP --- .../cn/yfshadaow/cs2113/ip/XiaoAiBot.java | 127 +++++------------- .../yfshadaow/cs2113/ip/command/Command.java | 35 ----- .../cs2113/ip/command/CommandHandler.java | 104 +++++++------- .../yfshadaow/cs2113/ip/storage/Storage.java | 66 +++++++++ .../java/cn/yfshadaow/cs2113/ip/ui/Ui.java | 48 +++++++ .../cn/yfshadaow/cs2113/ip/utils/Parser.java | 44 ++++++ .../yfshadaow/cs2113/ip/utils/TaskList.java | 12 ++ 7 files changed, 257 insertions(+), 179 deletions(-) create mode 100644 src/main/java/cn/yfshadaow/cs2113/ip/storage/Storage.java create mode 100644 src/main/java/cn/yfshadaow/cs2113/ip/ui/Ui.java create mode 100644 src/main/java/cn/yfshadaow/cs2113/ip/utils/Parser.java create mode 100644 src/main/java/cn/yfshadaow/cs2113/ip/utils/TaskList.java diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java index 8fb26a1af..46a92626f 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java @@ -2,51 +2,36 @@ import cn.yfshadaow.cs2113.ip.command.Command; import cn.yfshadaow.cs2113.ip.command.CommandHandler; -import cn.yfshadaow.cs2113.ip.task.Deadline; -import cn.yfshadaow.cs2113.ip.task.Event; -import cn.yfshadaow.cs2113.ip.task.Task; -import cn.yfshadaow.cs2113.ip.task.Todo; -import com.google.gson.*; -import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; -import org.apache.commons.io.FileUtils; - -import java.io.File; +import cn.yfshadaow.cs2113.ip.storage.Storage; +import cn.yfshadaow.cs2113.ip.ui.Ui; +import cn.yfshadaow.cs2113.ip.utils.Parser; +import cn.yfshadaow.cs2113.ip.utils.TaskList; +import com.google.gson.JsonParseException; + import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.LinkedList; -import java.util.List; import java.util.Scanner; public class XiaoAiBot { - private static final RuntimeTypeAdapterFactory taskAdapterFactory = RuntimeTypeAdapterFactory.of(Task.class, "type") - .registerSubtype(Deadline.class, "Deadline") - .registerSubtype(Event.class, "Event") - .registerSubtype(Todo.class, "Todo"); - public static final Gson gson = new GsonBuilder() - .registerTypeAdapterFactory(taskAdapterFactory) - .setPrettyPrinting() - .create(); - - private static final String BOT_NAME = "XiaoAi"; - private static final String SPLIT = "____________________________________________"; - private static final String GREET_MESSAGE = "Welcome back, master!\n" + - BOT_NAME + " here. What can I do for you?"; - private static final String QUIT_MESSAGE = "See you next time, master!"; + private final Storage storage = new Storage(); - private static final String DATA_DIRECTORY_PATH = "./data/"; - - private static final String TASK_DATA_FILE_PATH = DATA_DIRECTORY_PATH + "taskData.json"; + @SuppressWarnings("unused") + public Storage getStorage() { + return storage; + } - private final File taskDataFile = new File(TASK_DATA_FILE_PATH); + private final Ui ui = new Ui(this); + @SuppressWarnings("unused") + public Ui getUi() { + return ui; + } - private final List tasks = new LinkedList<>(); + private final TaskList taskList = new TaskList(); - public List getTasks() { - return tasks; + @SuppressWarnings("unused") + public TaskList getTaskList() { + return taskList; } @@ -56,7 +41,6 @@ public CommandHandler getCommandHandler() { } private final CommandHandler commandHandler = new CommandHandler(this); - private final Scanner scanner = new Scanner(System.in); public void setShouldQuit(boolean shouldQuit) { this.shouldQuit = shouldQuit; @@ -65,86 +49,39 @@ public void setShouldQuit(boolean shouldQuit) { private boolean shouldQuit = false; private void initialize() throws IOException{ - Files.createDirectories(Paths.get(DATA_DIRECTORY_PATH)); - taskDataFile.createNewFile(); + storage.initialize(); try { - loadData(); - sendMessage("Successfully loaded data from data file"); - } catch (IllegalStateException e) { - sendMessage(String.format("Failed to load data from data file: %s", e.getMessage())); + taskList.tasks.addAll(storage.loadData().tasks); + ui.sendMessage("Successfully loaded data from data file"); } catch (JsonParseException e) { - sendMessage(String.format("Failed to load data becauseJSON parsing failed: %s", e.getMessage())); - } - } - - public void saveData() throws IOException { - JsonObject dataObject = new JsonObject(); - JsonArray tasksArray = new JsonArray(); - for (Task t: tasks) { - if (t instanceof Todo) { - tasksArray.add(gson.toJsonTree(t, Task.class)); - } else if (t instanceof Deadline) { - tasksArray.add(gson.toJsonTree(t, Task.class)); - } else if (t instanceof Event) { - tasksArray.add(gson.toJsonTree(t, Task.class)); - } - } - dataObject.add("tasks", tasksArray); - FileUtils.write(taskDataFile, gson.toJson(dataObject), StandardCharsets.UTF_8); - } - - public void loadData() throws IOException, IllegalStateException, JsonParseException { - tasks.clear(); - String dataString = FileUtils.readFileToString(taskDataFile, StandardCharsets.UTF_8); - JsonObject dataObject = JsonParser.parseString(dataString).getAsJsonObject(); - JsonArray tasksArray = dataObject.getAsJsonArray("tasks"); - for (JsonElement e : tasksArray.asList()) { - tasks.add(gson.fromJson(e, Task.class)); - } - } - public void sendMessage(String message) { - sendMessageWithoutSplit(message); - sendSplit(); - } - - public void sendMessageWithoutSplit(String message) { - System.out.println(message); - } - - public void sendSplit() { - System.out.println(SPLIT); - } - - public String readLine() { - try { - return scanner.nextLine(); + ui.sendMessage(String.format("Failed to load data becauseJSON parsing failed: %s", e.getMessage())); } catch (Exception e) { - sendMessage(String.format("Error reading line: %s", e.getMessage())); - return null; + ui.sendMessage(String.format("Failed to load data from data file: %s", e.getMessage())); } } + public void start() { try { initialize(); } catch (Exception e) { - sendMessage(String.format("Bot initialization failed: %s", e.getMessage())); + ui.sendMessage(String.format("Bot initialization failed: %s", e.getMessage())); return; } - sendMessage(GREET_MESSAGE); + ui.greet(); while (!shouldQuit) { - String commandString = readLine(); + String commandString = ui.readLine(); Command cmd; try { - cmd = Command.parseCommand(commandString); + cmd = Parser.parseCommand(commandString); } catch (IllegalArgumentException e) { - sendMessage(String.format("Error parsing command: %s", e.getMessage())); + ui.sendMessage(String.format("Error parsing command: %s", e.getMessage())); continue; } commandHandler.handleCommand(cmd); } - sendMessage(QUIT_MESSAGE); + ui.farewell(); } } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java index 9b79ec69e..0ee44ad8a 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java @@ -24,39 +24,4 @@ public Command(String cmd, List args, Map extraArgs) { this.extraArgs.putAll(extraArgs); } - public static Command parseCommand(String s) throws IllegalArgumentException{ - Iterator iterator = Arrays.stream(s.split(" ")).iterator(); - if (!iterator.hasNext()) { - throw new IllegalArgumentException("Command cannot be empty!"); - } - String cmdString = iterator.next(); - List arguments = new ArrayList<>(); - Map extraArguments = new HashMap<>(); - - // currentPart stores which part of argument the parser is reading - // For example, if currentPart is null, the parser is reading main arguments - // If it is not null, the parser is reading extra arguments associated with that string - String currentPart = null; - while (iterator.hasNext()) { - String next = iterator.next(); - if (next.startsWith("-")) { - if (next.length() == 1) { - throw new IllegalArgumentException("Extra argument identifier cannot be empty!"); - } - currentPart = next.substring(1); - if (extraArguments.containsKey(currentPart)) { - throw new IllegalArgumentException("Duplicate argument identifier!"); - } - extraArguments.put(currentPart, ""); - } else { - if (currentPart == null) { - arguments.add(next); - } else { - String storedString = extraArguments.get(currentPart); - extraArguments.put(currentPart, storedString + (storedString.isEmpty()? "" : " ") + next); - } - } - } - return new Command(cmdString, arguments, extraArguments); - } } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java index 19d591fdb..200740237 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java @@ -17,7 +17,7 @@ public CommandHandler(XiaoAiBot bot) { } public void handleCommand(Command cmd) { - bot.sendSplit(); + bot.getUi().sendSplit(); switch (cmd.getCmd()) { case "bye": { bot.setShouldQuit(true); @@ -28,17 +28,17 @@ public void handleCommand(Command cmd) { try { todo = Todo.parseTodo(cmd); } catch (Exception e) { - bot.sendMessage(String.format("Error parsing task: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error parsing task: %s", e.getMessage())); break; } - bot.getTasks().add(todo); - bot.sendMessageWithoutSplit("Got it. I've added this task:"); - bot.sendMessageWithoutSplit(todo.toStringWithIsDone()); - bot.sendMessage("Now you have " + bot.getTasks().size() + " tasks in the list."); + bot.getTaskList().tasks.add(todo); + bot.getUi().sendMessageWithoutSplit("Got it. I've added this task:"); + bot.getUi().sendMessageWithoutSplit(todo.toStringWithIsDone()); + bot.getUi().sendMessage("Now you have " + bot.getTaskList().tasks.size() + " tasks in the list."); try { - bot.saveData(); + bot.getStorage().saveData(bot.getTaskList()); } catch (Exception e) { - bot.sendMessage(String.format("Error saving data: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error saving data: %s", e.getMessage())); break; } break; @@ -48,17 +48,17 @@ public void handleCommand(Command cmd) { try { deadline = Deadline.parseDeadline(cmd); } catch (Exception e) { - bot.sendMessage(String.format("Error parsing task: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error parsing task: %s", e.getMessage())); break; } - bot.getTasks().add(deadline); - bot.sendMessageWithoutSplit("Got it. I've added this task:"); - bot.sendMessageWithoutSplit(deadline.toStringWithIsDone()); - bot.sendMessage("Now you have " + bot.getTasks().size() + " tasks in the list."); + bot.getTaskList().tasks.add(deadline); + bot.getUi().sendMessageWithoutSplit("Got it. I've added this task:"); + bot.getUi().sendMessageWithoutSplit(deadline.toStringWithIsDone()); + bot.getUi().sendMessage("Now you have " + bot.getTaskList().tasks.size() + " tasks in the list."); try { - bot.saveData(); + bot.getStorage().saveData(bot.getTaskList()); } catch (Exception e) { - bot.sendMessage(String.format("Error saving data: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error saving data: %s", e.getMessage())); break; } break; @@ -68,112 +68,118 @@ public void handleCommand(Command cmd) { try { event = Event.parseEvent(cmd); } catch (Exception e) { - bot.sendMessage(String.format("Error parsing task: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error parsing task: %s", e.getMessage())); break; } - bot.getTasks().add(event); - bot.sendMessageWithoutSplit("Got it. I've added this task:"); - bot.sendMessageWithoutSplit(event.toStringWithIsDone()); - bot.sendMessage("Now you have " + bot.getTasks().size() + " tasks in the list."); + bot.getTaskList().tasks.add(event); + bot.getUi().sendMessageWithoutSplit("Got it. I've added this task:"); + bot.getUi().sendMessageWithoutSplit(event.toStringWithIsDone()); + bot.getUi().sendMessage("Now you have " + bot.getTaskList().tasks.size() + " tasks in the list."); try { - bot.saveData(); + bot.getStorage().saveData(bot.getTaskList()); } catch (Exception e) { - bot.sendMessage(String.format("Error saving data: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error saving data: %s", e.getMessage())); break; } break; } case "mark": { if (cmd.args.size() != 1) { - bot.sendMessage("Incorrect arguments"); + bot.getUi().sendMessage("Incorrect arguments"); break; } int index; try { index = Integer.parseInt(cmd.args.get(0)); } catch (NumberFormatException e) { - bot.sendMessage(String.format("Error parsing int: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error parsing int: %s", e.getMessage())); break; } try { - bot.getTasks().get(index - 1).setDone(true); + bot.getTaskList().tasks.get(index - 1).setDone(true); } catch (IndexOutOfBoundsException e) { - bot.sendMessage(String.format("Error getting task: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error getting task: %s", e.getMessage())); break; } - bot.sendMessageWithoutSplit("Nice! I've marked this task as done:"); - bot.sendMessage(bot.getTasks().get(index - 1).toStringWithIsDone()); + bot.getUi().sendMessageWithoutSplit("Nice! I've marked this task as done:"); + bot.getUi().sendMessage(bot.getTaskList().tasks.get(index - 1).toStringWithIsDone()); try { - bot.saveData(); + bot.getStorage().saveData(bot.getTaskList()); } catch (Exception e) { - bot.sendMessage(String.format("Error saving data: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error saving data: %s", e.getMessage())); break; } break; } case "unmark": { if (cmd.args.size() != 1) { - bot.sendMessage("Incorrect arguments"); + bot.getUi().sendMessage("Incorrect arguments"); break; } int index; try { index = Integer.parseInt(cmd.args.get(0)); } catch (Exception e) { - bot.sendMessage(String.format("Error parsing int: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error parsing int: %s", e.getMessage())); break; } try { - bot.getTasks().get(index - 1).setDone(false); + bot.getTaskList().tasks.get(index - 1).setDone(false); } catch (Exception e) { - bot.sendMessage(String.format("Error getting task: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error getting task: %s", e.getMessage())); break; } - bot.sendMessageWithoutSplit("OK, I've marked this task as not done yet:"); - bot.sendMessage(bot.getTasks().get(index - 1).toStringWithIsDone()); + bot.getUi().sendMessageWithoutSplit("OK, I've marked this task as not done yet:"); + bot.getUi().sendMessage(bot.getTaskList().tasks.get(index - 1).toStringWithIsDone()); try { - bot.saveData(); + bot.getStorage().saveData(bot.getTaskList()); } catch (Exception e) { - bot.sendMessage(String.format("Error saving data: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error saving data: %s", e.getMessage())); break; } break; } case "delete": { if (cmd.args.size() != 1) { - bot.sendMessage("Incorrect arguments"); + bot.getUi().sendMessage("Incorrect arguments"); break; } int index; try { index = Integer.parseInt(cmd.args.get(0)); } catch (Exception e) { - bot.sendMessage(String.format("Error parsing int: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error parsing int: %s", e.getMessage())); break; } Task task; try { - task = bot.getTasks().get(index - 1); + task = bot.getTaskList().tasks.get(index - 1); } catch (Exception e) { - bot.sendMessage(String.format("Error getting task: %s", e.getMessage())); + bot.getUi().sendMessage(String.format("Error getting task: %s", e.getMessage())); + break; + } + bot.getUi().sendMessageWithoutSplit("Noted. I've removed this task:"); + bot.getUi().sendMessage(task.toStringWithIsDone()); + bot.getTaskList().tasks.remove(index - 1); + try { + bot.getStorage().saveData(bot.getTaskList()); + } catch (Exception e) { + bot.getUi().sendMessage(String.format("Error saving data: %s", e.getMessage())); break; } - bot.sendMessageWithoutSplit("Noted. I've removed this task:"); - bot.sendMessage(task.toStringWithIsDone()); - bot.getTasks().remove(index - 1); break; } case "list": { - List tasks = bot.getTasks(); + List tasks = bot.getTaskList().tasks; for (int i = 0; i < tasks.size(); i += 1) { Task task = tasks.get(i); - bot.sendMessageWithoutSplit((i + 1) + "." + task.toStringWithIsDone()); + bot.getUi().sendMessageWithoutSplit((i + 1) + "." + task.toStringWithIsDone()); } - bot.sendSplit(); + bot.getUi().sendSplit(); break; } default: { - bot.sendMessage("Unknown command"); + bot.getUi().sendMessage("Unknown command"); } } } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/storage/Storage.java b/src/main/java/cn/yfshadaow/cs2113/ip/storage/Storage.java new file mode 100644 index 000000000..fe8f99fd8 --- /dev/null +++ b/src/main/java/cn/yfshadaow/cs2113/ip/storage/Storage.java @@ -0,0 +1,66 @@ +package cn.yfshadaow.cs2113.ip.storage; + +import cn.yfshadaow.cs2113.ip.XiaoAiBot; +import cn.yfshadaow.cs2113.ip.task.Deadline; +import cn.yfshadaow.cs2113.ip.task.Event; +import cn.yfshadaow.cs2113.ip.task.Task; +import cn.yfshadaow.cs2113.ip.task.Todo; +import cn.yfshadaow.cs2113.ip.utils.TaskList; +import com.google.gson.*; +import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class Storage { + public void initialize() throws IOException { + Files.createDirectories(Paths.get(DATA_DIRECTORY_PATH)); + taskDataFile.createNewFile(); + } + + private static final String DATA_DIRECTORY_PATH = "./data/"; + + private static final String TASK_DATA_FILE_PATH = DATA_DIRECTORY_PATH + "taskData.json"; + + private final File taskDataFile = new File(TASK_DATA_FILE_PATH); + + private static final RuntimeTypeAdapterFactory taskAdapterFactory = RuntimeTypeAdapterFactory.of(Task.class, "type") + .registerSubtype(Deadline.class, "Deadline") + .registerSubtype(Event.class, "Event") + .registerSubtype(Todo.class, "Todo"); + public static final Gson gson = new GsonBuilder() + .registerTypeAdapterFactory(taskAdapterFactory) + .setPrettyPrinting() + .create(); + + public void saveData(TaskList list) throws IOException { + JsonObject dataObject = new JsonObject(); + JsonArray tasksArray = new JsonArray(); + for (Task t: list.tasks) { + if (t instanceof Todo) { + tasksArray.add(gson.toJsonTree(t, Task.class)); + } else if (t instanceof Deadline) { + tasksArray.add(gson.toJsonTree(t, Task.class)); + } else if (t instanceof Event) { + tasksArray.add(gson.toJsonTree(t, Task.class)); + } + } + dataObject.add("tasks", tasksArray); + FileUtils.write(taskDataFile, gson.toJson(dataObject), StandardCharsets.UTF_8); + } + + public TaskList loadData() throws IOException, IllegalStateException, JsonParseException { + TaskList list = new TaskList(); + String dataString = FileUtils.readFileToString(taskDataFile, StandardCharsets.UTF_8); + JsonObject dataObject = JsonParser.parseString(dataString).getAsJsonObject(); + JsonArray tasksArray = dataObject.getAsJsonArray("tasks"); + for (JsonElement e : tasksArray.asList()) { + list.tasks.add(gson.fromJson(e, Task.class)); + } + return list; + } +} diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/ui/Ui.java b/src/main/java/cn/yfshadaow/cs2113/ip/ui/Ui.java new file mode 100644 index 000000000..c11629d4d --- /dev/null +++ b/src/main/java/cn/yfshadaow/cs2113/ip/ui/Ui.java @@ -0,0 +1,48 @@ +package cn.yfshadaow.cs2113.ip.ui; + +import cn.yfshadaow.cs2113.ip.XiaoAiBot; + +import java.util.Scanner; + +public class Ui { + private static final String BOT_NAME = "XiaoAi"; + private static final String SPLIT = "____________________________________________"; + private static final String GREET_MESSAGE = "Welcome back, master!\n" + + BOT_NAME + " here. What can I do for you?"; + + private static final String QUIT_MESSAGE = "See you next time, master!"; + private final XiaoAiBot bot; + private final Scanner scanner = new Scanner(System.in); + public Ui(XiaoAiBot bot) { + this.bot = bot; + } + public void sendMessage(String message) { + sendMessageWithoutSplit(message); + sendSplit(); + } + + public void greet() { + sendMessage(GREET_MESSAGE); + } + + public void farewell() { + sendMessage(QUIT_MESSAGE); + } + + public void sendMessageWithoutSplit(String message) { + System.out.println(message); + } + + public void sendSplit() { + System.out.println(SPLIT); + } + + public String readLine() { + try { + return scanner.nextLine(); + } catch (Exception e) { + sendMessage(String.format("Error reading line: %s", e.getMessage())); + return null; + } + } +} diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/utils/Parser.java b/src/main/java/cn/yfshadaow/cs2113/ip/utils/Parser.java new file mode 100644 index 000000000..d7c13efa5 --- /dev/null +++ b/src/main/java/cn/yfshadaow/cs2113/ip/utils/Parser.java @@ -0,0 +1,44 @@ +package cn.yfshadaow.cs2113.ip.utils; + +import cn.yfshadaow.cs2113.ip.command.Command; + +import java.util.*; + +public class Parser { + + public static Command parseCommand(String s) throws IllegalArgumentException{ + Iterator iterator = Arrays.stream(s.split(" ")).iterator(); + if (!iterator.hasNext()) { + throw new IllegalArgumentException("Command cannot be empty!"); + } + String cmdString = iterator.next(); + List arguments = new ArrayList<>(); + Map extraArguments = new HashMap<>(); + + // currentPart stores which part of argument the parser is reading + // For example, if currentPart is null, the parser is reading main arguments + // If it is not null, the parser is reading extra arguments associated with that string + String currentPart = null; + while (iterator.hasNext()) { + String next = iterator.next(); + if (next.startsWith("-")) { + if (next.length() == 1) { + throw new IllegalArgumentException("Extra argument identifier cannot be empty!"); + } + currentPart = next.substring(1); + if (extraArguments.containsKey(currentPart)) { + throw new IllegalArgumentException("Duplicate argument identifier!"); + } + extraArguments.put(currentPart, ""); + } else { + if (currentPart == null) { + arguments.add(next); + } else { + String storedString = extraArguments.get(currentPart); + extraArguments.put(currentPart, storedString + (storedString.isEmpty()? "" : " ") + next); + } + } + } + return new Command(cmdString, arguments, extraArguments); + } +} diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/utils/TaskList.java b/src/main/java/cn/yfshadaow/cs2113/ip/utils/TaskList.java new file mode 100644 index 000000000..35653cbbe --- /dev/null +++ b/src/main/java/cn/yfshadaow/cs2113/ip/utils/TaskList.java @@ -0,0 +1,12 @@ +package cn.yfshadaow.cs2113.ip.utils; + +import cn.yfshadaow.cs2113.ip.XiaoAiBot; +import cn.yfshadaow.cs2113.ip.task.Task; + +import java.util.LinkedList; +import java.util.List; + +public class TaskList { + + public final List tasks = new LinkedList<>(); +} From 291a236c56ef0a6b164dd0621cb006eb1b630a2c Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Thu, 5 Oct 2023 00:19:39 +0800 Subject: [PATCH 19/22] Add find command: Allow user search for tasks using keywords --- .../cs2113/ip/command/CommandHandler.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java index 200740237..fbf22816d 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java @@ -6,6 +6,7 @@ import cn.yfshadaow.cs2113.ip.task.Task; import cn.yfshadaow.cs2113.ip.task.Todo; +import java.util.Arrays; import java.util.List; public class CommandHandler { @@ -178,6 +179,27 @@ public void handleCommand(Command cmd) { bot.getUi().sendSplit(); break; } + case "find" : { + if (cmd.args.isEmpty()) { + bot.getUi().sendMessage("Please enter keyword for searching"); + break; + } + String searchKeyWord = String.join(" ", cmd.args); + if (searchKeyWord.isEmpty()) { + bot.getUi().sendMessage("Search keyword cannot be empty"); + break; + } + bot.getUi().sendMessageWithoutSplit("Here are the matching tasks in your list:"); + List tasks = bot.getTaskList().tasks; + for (int i = 0; i < tasks.size(); i += 1) { + Task task = tasks.get(i); + if (task.getName().toLowerCase().contains(searchKeyWord.toLowerCase())) { + bot.getUi().sendMessageWithoutSplit((i + 1) + "." + task.toStringWithIsDone()); + } + } + bot.getUi().sendSplit(); + break; + } default: { bot.getUi().sendMessage("Unknown command"); } From 3f78a7967de49a108c1b87d3cc8d4442b0af83da Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Thu, 5 Oct 2023 00:58:27 +0800 Subject: [PATCH 20/22] Add javadoc --- .../java/cn/yfshadaow/cs2113/ip/Duke.java | 8 ++++ .../cn/yfshadaow/cs2113/ip/XiaoAiBot.java | 32 ++++++++++++++- .../yfshadaow/cs2113/ip/command/Command.java | 40 +++++++++++++++---- .../cs2113/ip/command/CommandHandler.java | 15 ++++++- .../yfshadaow/cs2113/ip/storage/Storage.java | 25 ++++++++++++ .../cn/yfshadaow/cs2113/ip/task/Deadline.java | 27 +++++++++++++ .../cn/yfshadaow/cs2113/ip/task/Event.java | 37 +++++++++++++++++ .../cn/yfshadaow/cs2113/ip/task/Task.java | 34 ++++++++++++++++ .../cn/yfshadaow/cs2113/ip/task/Todo.java | 15 +++++++ .../java/cn/yfshadaow/cs2113/ip/ui/Ui.java | 34 ++++++++++++++++ .../cn/yfshadaow/cs2113/ip/utils/Parser.java | 27 ++++++++----- .../yfshadaow/cs2113/ip/utils/TaskList.java | 6 +++ 12 files changed, 281 insertions(+), 19 deletions(-) diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/Duke.java b/src/main/java/cn/yfshadaow/cs2113/ip/Duke.java index da1e3d41e..6b0b80acd 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/Duke.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/Duke.java @@ -1,7 +1,15 @@ package cn.yfshadaow.cs2113.ip; +/** + * The main class for the application + */ public class Duke { + /** + * The entry point of application. + * + * @param args the input arguments + */ public static void main(String[] args) { XiaoAiBot bot = new XiaoAiBot(); bot.start(); diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java index 46a92626f..f94b41d6e 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/XiaoAiBot.java @@ -9,12 +9,19 @@ import com.google.gson.JsonParseException; import java.io.IOException; -import java.util.Scanner; +/** + * Represents a bot. + */ public class XiaoAiBot { private final Storage storage = new Storage(); + /** + * Gets storage. + * + * @return the storage + */ @SuppressWarnings("unused") public Storage getStorage() { return storage; @@ -22,6 +29,11 @@ public Storage getStorage() { private final Ui ui = new Ui(this); + /** + * Gets ui. + * + * @return the ui + */ @SuppressWarnings("unused") public Ui getUi() { return ui; @@ -29,12 +41,22 @@ public Ui getUi() { private final TaskList taskList = new TaskList(); + /** + * Gets task list. + * + * @return the task list + */ @SuppressWarnings("unused") public TaskList getTaskList() { return taskList; } + /** + * Gets command handler. + * + * @return the command handler + */ @SuppressWarnings("unused") public CommandHandler getCommandHandler() { return commandHandler; @@ -42,6 +64,11 @@ public CommandHandler getCommandHandler() { private final CommandHandler commandHandler = new CommandHandler(this); + /** + * Sets whether the bot should quit. + * + * @param shouldQuit whether the bot should quit + */ public void setShouldQuit(boolean shouldQuit) { this.shouldQuit = shouldQuit; } @@ -61,6 +88,9 @@ private void initialize() throws IOException{ } + /** + * Start the bot. + */ public void start() { try { initialize(); diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java index 0ee44ad8a..d70cfebd4 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/Command.java @@ -2,24 +2,50 @@ import java.util.*; +/** + * Represents a command. A command must have a name, but might have empty arguments and extra arguments. + */ public class Command { - private String cmd; + private String name; + /** + * Gets name. + * + * @return the name + */ @SuppressWarnings("unused") - public String getCmd() { - return cmd; + public String getName() { + return name; } + /** + * Sets name. + * + * @param name the name + */ @SuppressWarnings("unused") - public void setCmd(String cmd) { - this.cmd = cmd; + public void setName(String name) { + this.name = name; } + /** + * The Args. + */ public final List args = new ArrayList<>(); + /** + * The Extra args. + */ public final Map extraArgs = new HashMap<>(); - public Command(String cmd, List args, Map extraArgs) { - this.cmd = cmd; + /** + * Instantiates a new Command. + * + * @param name the name + * @param args the args + * @param extraArgs the extra args + */ + public Command(String name, List args, Map extraArgs) { + this.name = name; this.args.addAll(args); this.extraArgs.putAll(extraArgs); } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java index 200740237..c426d324b 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/command/CommandHandler.java @@ -8,17 +8,30 @@ import java.util.List; +/** + * Represents a command handler. + */ public class CommandHandler { private final XiaoAiBot bot; + /** + * Instantiates a new Command handler. + * + * @param bot the bot + */ public CommandHandler(XiaoAiBot bot) { this.bot = bot; } + /** + * Handle command. + * + * @param cmd the command + */ public void handleCommand(Command cmd) { bot.getUi().sendSplit(); - switch (cmd.getCmd()) { + switch (cmd.getName()) { case "bye": { bot.setShouldQuit(true); break; diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/storage/Storage.java b/src/main/java/cn/yfshadaow/cs2113/ip/storage/Storage.java index fe8f99fd8..9bb47df5f 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/storage/Storage.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/storage/Storage.java @@ -16,7 +16,15 @@ import java.nio.file.Files; import java.nio.file.Paths; +/** + * Represents a storage class used for saving and loading data. + */ public class Storage { + /** + * Initialize the storage instance by creating necessary files and directories. + * + * @throws IOException the io exception + */ public void initialize() throws IOException { Files.createDirectories(Paths.get(DATA_DIRECTORY_PATH)); taskDataFile.createNewFile(); @@ -32,11 +40,20 @@ public void initialize() throws IOException { .registerSubtype(Deadline.class, "Deadline") .registerSubtype(Event.class, "Event") .registerSubtype(Todo.class, "Todo"); + /** + * The constant gson. + */ public static final Gson gson = new GsonBuilder() .registerTypeAdapterFactory(taskAdapterFactory) .setPrettyPrinting() .create(); + /** + * Save data. + * + * @param list the list + * @throws IOException the io exception + */ public void saveData(TaskList list) throws IOException { JsonObject dataObject = new JsonObject(); JsonArray tasksArray = new JsonArray(); @@ -53,6 +70,14 @@ public void saveData(TaskList list) throws IOException { FileUtils.write(taskDataFile, gson.toJson(dataObject), StandardCharsets.UTF_8); } + /** + * Load data task list. + * + * @return the task list + * @throws IOException the io exception + * @throws IllegalStateException the illegal state exception + * @throws JsonParseException the json parse exception + */ public TaskList loadData() throws IOException, IllegalStateException, JsonParseException { TaskList list = new TaskList(); String dataString = FileUtils.readFileToString(taskDataFile, StandardCharsets.UTF_8); diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java index d14b99120..db27207f5 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Deadline.java @@ -2,19 +2,39 @@ import cn.yfshadaow.cs2113.ip.command.Command; +/** + * Represents a deadline task. + */ public class Deadline extends Task { private String by; + + /** + * Gets by. + * + * @return the by + */ @SuppressWarnings("unused") public String getBy() { return by; } + /** + * Sets by. + * + * @param by the by + */ @SuppressWarnings("unused") public void setBy(String by) { this.by = by; } + /** + * Instantiates a new Deadline. + * + * @param name task name + * @param by the by attribute + */ public Deadline(String name, String by) { this.name = name; this.by = by; @@ -25,6 +45,13 @@ public String toStringWithIsDone() { return "[D][" + (isDone ? "X" : " ") + "] " + name + " (by: " + by + ")"; } + /** + * Parse deadline from command. + * + * @param cmd the cmd + * @return the deadline + * @throws IllegalArgumentException the illegal argument exception + */ public static Deadline parseDeadline(Command cmd) throws IllegalArgumentException{ if (cmd.args.isEmpty()) { throw new IllegalArgumentException("Deadline name cannot be empty!"); diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java index 4e373964a..090c65564 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Event.java @@ -2,31 +2,61 @@ import cn.yfshadaow.cs2113.ip.command.Command; +/** + * Represents an event task. + */ public class Event extends Task { private String from; private String to; + /** + * Gets from. + * + * @return the from + */ @SuppressWarnings("unused") public String getFrom() { return from; } + /** + * Sets from. + * + * @param from the from + */ @SuppressWarnings("unused") public void setFrom(String from) { this.from = from; } + /** + * Gets to. + * + * @return the to + */ @SuppressWarnings("unused") public String getTo() { return to; } + /** + * Sets to. + * + * @param to the to + */ @SuppressWarnings("unused") public void setTo(String to) { this.to = to; } + /** + * Instantiates a new Event. + * + * @param name the name of task + * @param from the from attribute + * @param to the to attribute + */ public Event(String name, String from, String to) { this.name = name; this.from = from; @@ -38,6 +68,13 @@ public String toStringWithIsDone() { return "[E][" + (isDone ? "X" : " ") + "] " + name + " (from: " + from + " to: " + to + ")"; } + /** + * Parse event from command. + * + * @param cmd the cmd + * @return the event + * @throws IllegalArgumentException the illegal argument exception + */ public static Event parseEvent(Command cmd) throws IllegalArgumentException{ if (cmd.args.isEmpty()) { throw new IllegalArgumentException("Event name cannot be empty!"); diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java index b69b10521..a9f57065e 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Task.java @@ -2,31 +2,65 @@ import com.google.gson.annotations.Expose; +/** + * Represents an abstract task. + */ public abstract class Task { + /** + * Whether this task is done. + */ @Expose protected boolean isDone = false; + /** + * The task name. + */ @Expose protected String name; + /** + * Gets whether the task is done. + * + * @return whether task is done + */ @SuppressWarnings("unused") public boolean isDone() { return isDone; } + /** + * Sets whether the task is done + * + * @param done the boolean value to be set + */ public void setDone(boolean done) { isDone = done; } + /** + * Gets name. + * + * @return the name of task + */ @SuppressWarnings("unused") public String getName() { return name; } + /** + * Sets name. + * + * @param name the name of task + */ @SuppressWarnings("unused") public void setName(String name) { this.name = name; } + /** + * Gets a string used for printing by UI + * + * @return the formatted string + */ public String toStringWithIsDone() { return "[" + (isDone ? "X" : " ") + "] " + name; } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/task/Todo.java b/src/main/java/cn/yfshadaow/cs2113/ip/task/Todo.java index 5ab7f36aa..c50ca68b1 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/task/Todo.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/task/Todo.java @@ -2,11 +2,26 @@ import cn.yfshadaow.cs2113.ip.command.Command; +/** + * Represents a todo task. + */ public class Todo extends Task { + /** + * Instantiates a new Todo. + * + * @param name the name of task + */ public Todo(String name) { this.name = name; } + /** + * Parse todo from command + * + * @param cmd the command + * @return the todo task + * @throws IllegalArgumentException the illegal argument exception + */ public static Todo parseTodo(Command cmd) throws IllegalArgumentException{ if (cmd.args.isEmpty()) { throw new IllegalArgumentException("Todo name cannot be empty!"); diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/ui/Ui.java b/src/main/java/cn/yfshadaow/cs2113/ip/ui/Ui.java index c11629d4d..59a390e5a 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/ui/Ui.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/ui/Ui.java @@ -4,6 +4,9 @@ import java.util.Scanner; +/** + * The UI class used for receiving user's input and printing output + */ public class Ui { private static final String BOT_NAME = "XiaoAi"; private static final String SPLIT = "____________________________________________"; @@ -13,30 +16,61 @@ public class Ui { private static final String QUIT_MESSAGE = "See you next time, master!"; private final XiaoAiBot bot; private final Scanner scanner = new Scanner(System.in); + + /** + * Instantiates a new Ui instance. + * + * @param bot the bot + */ public Ui(XiaoAiBot bot) { this.bot = bot; } + + /** + * Send message to user. + * + * @param message the message to be sent + */ public void sendMessage(String message) { sendMessageWithoutSplit(message); sendSplit(); } + /** + * Greets the user. + */ public void greet() { sendMessage(GREET_MESSAGE); } + /** + * Farewell to the user. + */ public void farewell() { sendMessage(QUIT_MESSAGE); } + /** + * Send message without split. + * + * @param message the message to be sent + */ public void sendMessageWithoutSplit(String message) { System.out.println(message); } + /** + * Send split line to user + */ public void sendSplit() { System.out.println(SPLIT); } + /** + * Read line from scanner + * + * @return the string read from the scanner + */ public String readLine() { try { return scanner.nextLine(); diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/utils/Parser.java b/src/main/java/cn/yfshadaow/cs2113/ip/utils/Parser.java index d7c13efa5..e45f49113 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/utils/Parser.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/utils/Parser.java @@ -4,8 +4,18 @@ import java.util.*; +/** + * Represents a parser to parse commands. + */ public class Parser { + /** + * Parse command from string. + * + * @param s the string to be parsed + * @return the command parsed + * @throws IllegalArgumentException the illegal argument exception + */ public static Command parseCommand(String s) throws IllegalArgumentException{ Iterator iterator = Arrays.stream(s.split(" ")).iterator(); if (!iterator.hasNext()) { @@ -15,27 +25,24 @@ public static Command parseCommand(String s) throws IllegalArgumentException{ List arguments = new ArrayList<>(); Map extraArguments = new HashMap<>(); - // currentPart stores which part of argument the parser is reading - // For example, if currentPart is null, the parser is reading main arguments - // If it is not null, the parser is reading extra arguments associated with that string - String currentPart = null; + String currentTargetKey = null; while (iterator.hasNext()) { String next = iterator.next(); if (next.startsWith("-")) { if (next.length() == 1) { throw new IllegalArgumentException("Extra argument identifier cannot be empty!"); } - currentPart = next.substring(1); - if (extraArguments.containsKey(currentPart)) { + currentTargetKey = next.substring(1); + if (extraArguments.containsKey(currentTargetKey)) { throw new IllegalArgumentException("Duplicate argument identifier!"); } - extraArguments.put(currentPart, ""); + extraArguments.put(currentTargetKey, ""); } else { - if (currentPart == null) { + if (currentTargetKey == null) { arguments.add(next); } else { - String storedString = extraArguments.get(currentPart); - extraArguments.put(currentPart, storedString + (storedString.isEmpty()? "" : " ") + next); + String storedString = extraArguments.get(currentTargetKey); + extraArguments.put(currentTargetKey, storedString + (storedString.isEmpty()? "" : " ") + next); } } } diff --git a/src/main/java/cn/yfshadaow/cs2113/ip/utils/TaskList.java b/src/main/java/cn/yfshadaow/cs2113/ip/utils/TaskList.java index 35653cbbe..1949bf8b4 100644 --- a/src/main/java/cn/yfshadaow/cs2113/ip/utils/TaskList.java +++ b/src/main/java/cn/yfshadaow/cs2113/ip/utils/TaskList.java @@ -6,7 +6,13 @@ import java.util.LinkedList; import java.util.List; +/** + * Represents a task list. + */ public class TaskList { + /** + * The Tasks. + */ public final List tasks = new LinkedList<>(); } From 5bac8fa293c6b1097c5a2858d143a1733dcd614a Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Thu, 5 Oct 2023 01:30:54 +0800 Subject: [PATCH 21/22] Update README.md --- docs/README.md | 68 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/docs/README.md b/docs/README.md index 8077118eb..98ee971c1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,69 @@ -# User Guide +# XiaoAi User Guide ## Features -### Feature-ABC +### Feature: Add tasks -Description of the feature. +You can add your tasks to the task list. -### Feature-XYZ +### Feature: Delete tasks -Description of the feature. +You can delete an existing task from the task list. + +### Feature: List tasks + +You can list all existing tasks. + +### Feature: Mark task as done/not done + +You can mark an existing task as done/not done. + +### Feature: Find tasks + +You can search for tasks using keywords. ## Usage -### `Keyword` - Describe action +### `todo` - adds a todo task + +usage: `todo ` +example usage: `todo drink some coffee` + +### `deadline` - adds a deadline task + +usage: `deadline -by ` +example usage: `deadline submit assignment -by today 7pm` + +### `event` - adds an event task + +usage: `event -from -to ` +example usage: `event go to concert -from 11/7 8pm -to 11/7 10pm` + +### `list` - list all existing tasks + +usage: `list` + +### `delete` - delete a task with index + +usage: `delete ` +example usage: `delete 3` + +### `mark` - mark a task as done + +usage: `mark ` +example usage: `mark 3` + +### `unmark` - mark a task as not done -Describe the action and its outcome. +usage: `unmark ` +example usage: `unmark 3` -Example of usage: +### `find` - find all tasks with name containing the keywords -`keyword (optional arguments)` +usage: `find ` +example usage: `find read books` -Expected outcome: +### `bye` - stop the bot and quit -Description of the outcome. +usage: `bye` -``` -expected output -``` From af73b7fac6bad08c05ada9fdfe2096d183afa65f Mon Sep 17 00:00:00 2001 From: YFshadaow Date: Thu, 5 Oct 2023 09:30:30 +0800 Subject: [PATCH 22/22] Update artifact name in pom.xml --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 462d9a3bf..0c5e16ab1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,11 +5,11 @@ 4.0.0 cn.yfshadaow.cs2113.ip - Duke + XiaoAiBot 1.0.0 jar - Duke + XiaoAiBot 11