diff --git a/src/main/java/corgi/Corgi.java b/src/main/java/corgi/Corgi.java index c4562eab84..79a4ca852b 100644 --- a/src/main/java/corgi/Corgi.java +++ b/src/main/java/corgi/Corgi.java @@ -1,5 +1,7 @@ package corgi; +import java.util.Stack; + import corgi.commands.Command; import corgi.commands.CommandExecutionException; import corgi.parsers.CommandParser; @@ -10,6 +12,7 @@ import corgi.tasks.Task; import corgi.tasks.TaskList; import corgi.ui.TextRenderer; +import javafx.util.Pair; /** @@ -19,25 +22,26 @@ * This class initializes the chatbot and handles user input and commands. */ public class Corgi { - private TaskList tasks; - private Storage storage; - private TextRenderer renderer; + private State state; + private Stack> history; /** - * Constructs new Corgi chatbot with an empty task list. + * Constructs new Corgi chatbot with an empty task list, + * a text renderer, a storage and a history stack. */ public Corgi() { - this.renderer = new TextRenderer(); - this.storage = new Storage<>(new TaskParser(), "./data/tasks.txt"); - this.tasks = new TaskList(storage.load()); - + TextRenderer newRenderer = new TextRenderer(); + Storage newStorage = new Storage<>(new TaskParser(), "./data/tasks.txt"); + TaskList newList = new TaskList(newStorage.load()); + this.state = new State(newList, newStorage, newRenderer); + this.history = new Stack<>(); // if (tasks.size() > 0) { // this.renderer.showTasksLoaded(tasks.size()); // } } public String getIntro() { - return renderer.showIntro(); + return this.state.getTextRenderer().showIntro(); } /** @@ -51,14 +55,16 @@ public String getResponse(String input) { try { cmd = new CommandParser().parse(input); assert cmd != null : "Command returned from parser cannot be null"; - return cmd.execute(this.tasks, this.renderer, this.storage); + Pair result = cmd.execute(this.state, this.history); + this.state = result.getKey(); + return result.getValue(); } catch (InvalidCommandFormatException e) { - return this.renderer.showError(e.getClass().getSimpleName(), e.getMessage()); + return this.state.getTextRenderer().showError(e.getClass().getSimpleName(), e.getMessage()); } catch (InvalidCommandTypeException e) { // Todo: Print all valid commands - return this.renderer.showError(e.getClass().getSimpleName(), e.getMessage()); + return this.state.getTextRenderer().showError(e.getClass().getSimpleName(), e.getMessage()); } catch (CommandExecutionException e) { - return this.renderer.showError(e.getClass().getSimpleName(), e.getMessage()); + return this.state.getTextRenderer().showError(e.getClass().getSimpleName(), e.getMessage()); } } } diff --git a/src/main/java/corgi/State.java b/src/main/java/corgi/State.java new file mode 100644 index 0000000000..cbc0f72250 --- /dev/null +++ b/src/main/java/corgi/State.java @@ -0,0 +1,112 @@ +package corgi; + +import corgi.storage.Storage; +import corgi.tasks.Task; +import corgi.tasks.TaskList; +import corgi.tasks.TaskListIndexOutOfBoundsException; +import corgi.tasks.TaskStatusException; +import corgi.ui.TextRenderer; + +/** + * An immutable State class to store the state of the chatbot. + */ +public final class State { + private final TaskList tasks; + private final Storage storage; + private final TextRenderer renderer; + + /** + * Construct new state object with given task list, storage and text renderer. + * + * @param tasks The given task list. + * @param storage The given storage. + * @param renderer The given text renderer. + */ + public State( + TaskList tasks, Storage storage, TextRenderer renderer) { + this.tasks = tasks; + this.storage = storage; + this.renderer = renderer; + } + + /** + * Getter for task list. + * + * @return The task list. + */ + public TaskList getTaskList() { + return this.tasks; + } + + /** + * Getter for storage. + * + * @return The storage. + */ + public Storage getStorage() { + return this.storage; + } + + /** + * Getter for text renderer. + * + * @return The text renderer. + */ + public TextRenderer getTextRenderer() { + return this.renderer; + } + + /** + * Add target task to the task list. + * + * @param task The target task. + * @return New state with the updated task list. + */ + public State addTask(Task task) { + TaskList newTaskList = this.tasks.add(task); + + this.storage.save(newTaskList); + + return new State(newTaskList, storage, renderer); + } + + /** + * Remove task at the target index in the task list. + * + * @param index The target index. + * @return New state with the updated task list. + * @throws TaskListIndexOutOfBoundsException + */ + public State removeTask(int index) throws TaskListIndexOutOfBoundsException { + TaskList newTaskList = this.tasks.remove(index); + + this.storage.save(newTaskList); + + return new State(newTaskList, storage, renderer); + } + + /** + * Mark task at the target index to a given status. + * + * @param index The target index. + * @param status The expected status. + * @return New state with the updated task list. + * @throws TaskListIndexOutOfBoundsException + * @throws TaskStatusException + */ + public State markTask(int index, boolean status) + throws TaskListIndexOutOfBoundsException, TaskStatusException { + TaskList newTaskList = this.tasks.mark(index, status); + + this.storage.save(newTaskList); + + return new State(newTaskList, storage, renderer); + } + + /** + * Store the current task list to local. + */ + public void save() { + this.storage.save(this.tasks); + } +} diff --git a/src/main/java/corgi/commands/AddTaskCommand.java b/src/main/java/corgi/commands/AddTaskCommand.java index 9ad0694cfa..5c9e856eb7 100644 --- a/src/main/java/corgi/commands/AddTaskCommand.java +++ b/src/main/java/corgi/commands/AddTaskCommand.java @@ -1,9 +1,12 @@ package corgi.commands; -import corgi.storage.Storage; +import java.util.Stack; + +import corgi.State; import corgi.tasks.Task; import corgi.tasks.TaskList; import corgi.ui.TextRenderer; +import javafx.util.Pair; /** * Represents a command to add a task to the task list. @@ -25,7 +28,7 @@ public class AddTaskCommand extends Command { * Initializes a new AddTaskCommand instance with the specified task and command type. * * @param target The task to be added. - * @param type The type of command (CommandType.TODO, CommandType.DEADLINE, or CommandType.EVENT). + * @param type The type of command. */ public AddTaskCommand(Task target) { super(false); @@ -34,16 +37,28 @@ public AddTaskCommand(Task target) { /** * Executes the command by adding the specified task to the task list, saving the updated list to storage, - * and return formatted message indicating that the task has been added. + * and storing the state to the history stack. * - * @param list The task list to which the task should be added. - * @param renderer The text renderer to return formatted message. - * @param storage The storage for saving and loading tasks (if applicable). + * @param currState The current state of the application. + * @param history The history stack to store the states. + * @return A pair containing the new state and a string message indicating the result of the command execution. */ @Override - public String execute(TaskList list, TextRenderer renderer, Storage storage) { - list.add(this.target); - storage.save(list); - return renderer.showTaskAdded(this.taskType, target.toString(), list.size()); + public Pair execute(State currState, Stack> history) { + history.push(new Pair<>(currState, this)); + + State newState = currState.addTask(this.target); + + TextRenderer renderer = newState.getTextRenderer(); + TaskList list = newState.getTaskList(); + + String returnMsg = renderer.showTaskAdded(this.taskType, target.toString(), list.size()); + + return new Pair<>(newState, returnMsg); + } + + @Override + public String toString() { + return "Add task " + this.target; } } diff --git a/src/main/java/corgi/commands/Command.java b/src/main/java/corgi/commands/Command.java index 909ce8f4d4..2a71c7f193 100644 --- a/src/main/java/corgi/commands/Command.java +++ b/src/main/java/corgi/commands/Command.java @@ -1,9 +1,9 @@ package corgi.commands; -import corgi.storage.Storage; -import corgi.tasks.Task; -import corgi.tasks.TaskList; -import corgi.ui.TextRenderer; +import java.util.Stack; + +import corgi.State; +import javafx.util.Pair; /** * An abstract class to represent command. @@ -26,14 +26,14 @@ public Command(boolean isExit) { /** * Executes the command, performing its intended action on the provided task list, - * text renderer, and storage. + * text renderer, and storage. Returns new state and string message * - * @param list The task list to perform the command action on. - * @param renderer The text renderer to return formatted message. - * @param storage The storage for saving and loading tasks (if applicable). + * @param currState The current state of the application. + * @param history The history stack to store the states. + * @return A pair containing the new state and a string message indicating the result of the command execution. * @throws CommandExecutionException If an error occurs during command execution. */ - public abstract String execute(TaskList list, TextRenderer renderer, Storage storage) + public abstract Pair execute(State currState, Stack> history) throws CommandExecutionException; /** diff --git a/src/main/java/corgi/commands/CommandType.java b/src/main/java/corgi/commands/CommandType.java index 9318b0115e..704d9f5e73 100644 --- a/src/main/java/corgi/commands/CommandType.java +++ b/src/main/java/corgi/commands/CommandType.java @@ -13,7 +13,8 @@ public enum CommandType { LIST("list"), DELETE("delete [task no.]"), DATE("date [yyyy-mm-dd]"), - FIND("find [keyword]"); + FIND("find [keyword]"), + UNDO("undo"); private final String commandFormat; @@ -55,6 +56,8 @@ public static CommandType getCommandType(String commandStr) throws InvalidComman return DATE; case "find": return FIND; + case "undo": + return UNDO; default: throw new InvalidCommandException(); } diff --git a/src/main/java/corgi/commands/DeleteTaskCommand.java b/src/main/java/corgi/commands/DeleteTaskCommand.java index c0061c84d7..84c7c77e79 100644 --- a/src/main/java/corgi/commands/DeleteTaskCommand.java +++ b/src/main/java/corgi/commands/DeleteTaskCommand.java @@ -1,10 +1,12 @@ package corgi.commands; -import corgi.storage.Storage; -import corgi.tasks.Task; +import java.util.Stack; + +import corgi.State; import corgi.tasks.TaskList; import corgi.tasks.TaskListIndexOutOfBoundsException; import corgi.ui.TextRenderer; +import javafx.util.Pair; /** * Represents a command to delete a task from the task list. @@ -28,24 +30,37 @@ public DeleteTaskCommand(int targetIdx) { /** * Executes the command by deleting the task at the specified index from the task list, - * saving the updated list to storage, and return message indicating - * that the task has been deleted. + * saving the updated list to storage, store the state to history stack. * - * @param list The task list from which the task should be deleted. - * @param renderer The text renderer to return formatted message. - * @param storage The storage for saving and loading tasks (if applicable). + * @param currState The current state of the application. + * @param history The history stack to store the states. + * @return A pair containing the new state and a string message indicating the result of the command execution. * @throws CommandExecutionException If an error occurs during command execution. */ @Override - public String execute(TaskList list, TextRenderer renderer, Storage storage) + public Pair execute(State currState, Stack> history) throws CommandExecutionException { try { - String targetTaskInfo = list.getTaskInfo(targetIdx); - list.remove(targetIdx); - storage.save(list); - return renderer.showTaskDeleted(targetTaskInfo, list.size()); + history.push(new Pair<>(currState, this)); + + TaskList currList = currState.getTaskList(); + String targetTaskInfo = currList.getTaskInfo(targetIdx); + + State newState = currState.removeTask(targetIdx); + + TextRenderer renderer = newState.getTextRenderer(); + TaskList list = newState.getTaskList(); + + String returnMsg = renderer.showTaskDeleted(targetTaskInfo, list.size()); + + return new Pair<>(newState, returnMsg); } catch (TaskListIndexOutOfBoundsException e) { throw new CommandExecutionException("Invalid index provided!"); } } + + @Override + public String toString() { + return "Delete task " + (this.targetIdx + 1); + } } diff --git a/src/main/java/corgi/commands/ExitCommand.java b/src/main/java/corgi/commands/ExitCommand.java index 4f8e605795..a205de7499 100644 --- a/src/main/java/corgi/commands/ExitCommand.java +++ b/src/main/java/corgi/commands/ExitCommand.java @@ -1,9 +1,10 @@ package corgi.commands; -import corgi.storage.Storage; -import corgi.tasks.Task; -import corgi.tasks.TaskList; +import java.util.Stack; + +import corgi.State; import corgi.ui.TextRenderer; +import javafx.util.Pair; /** * Represents a command to exit the application. @@ -19,14 +20,18 @@ public ExitCommand() { } /** - * Executes the command by return an exit message to the user, indicating that the application is terminating. + * Executes the command by return an exit message, indicating that the application is terminating. * - * @param list The task list (not used in this command). - * @param renderer The text renderer to return formatted message. - * @param storage The storage for saving and loading tasks (not used in this command). + * @param currState The current state of the application. + * @param history The history stack to store the states. + * @return A pair containing the new state and a string message indicating the result of the command execution. */ @Override - public String execute(TaskList list, TextRenderer renderer, Storage storage) { - return renderer.showExitMsg(); + public Pair execute(State currState, Stack> history) { + TextRenderer renderer = currState.getTextRenderer(); + + String returnMsg = renderer.showExitMsg(); + + return new Pair<>(currState, returnMsg); } } diff --git a/src/main/java/corgi/commands/FindTasksContainKeywordCommand.java b/src/main/java/corgi/commands/FindTasksContainKeywordCommand.java index 27e240f605..01558d4d60 100644 --- a/src/main/java/corgi/commands/FindTasksContainKeywordCommand.java +++ b/src/main/java/corgi/commands/FindTasksContainKeywordCommand.java @@ -1,11 +1,13 @@ package corgi.commands; +import java.util.Stack; import java.util.function.Predicate; -import corgi.storage.Storage; +import corgi.State; import corgi.tasks.Task; import corgi.tasks.TaskList; import corgi.ui.TextRenderer; +import javafx.util.Pair; /** * Represents a command to find tasks containing a specific keyword in the task list. @@ -40,18 +42,21 @@ public FindTasksContainKeywordCommand(String target) { * It then return the filtered tasks to the user or a message indicating * that no matching tasks were found. * - * @param list The task list to filter. - * @param renderer The text renderer to return formatted message. - * @param storage The storage for saving and loading tasks (not used in this command). + * @param currState The current state of the application. + * @param history The history stack to store the states. + * @return A pair containing the new state and a string message indicating the result of the command execution. */ @Override - public String execute(TaskList list, TextRenderer renderer, Storage storage) { - TaskList tasksContainKeyword = list.filter(predicate); - - if (tasksContainKeyword.isEmpty()) { - return renderer.showKeywordNotFound(this.target); - } else { - return renderer.showTasksWithKeyword(this.target, tasksContainKeyword.toString()); - } + public Pair execute(State currState, Stack> history) { + TaskList currList = currState.getTaskList(); + TextRenderer currTextRenderer = currState.getTextRenderer(); + + TaskList tasksContainKeyword = currList.filter(predicate); + + String returnMsg = tasksContainKeyword.isEmpty() + ? currTextRenderer.showKeywordNotFound(this.target) + : currTextRenderer.showTasksWithKeyword(this.target, tasksContainKeyword.toString()); + + return new Pair<>(currState, returnMsg); } } diff --git a/src/main/java/corgi/commands/FindTasksOnDateCommand.java b/src/main/java/corgi/commands/FindTasksOnDateCommand.java index b9b8ba7978..61aa3ea076 100644 --- a/src/main/java/corgi/commands/FindTasksOnDateCommand.java +++ b/src/main/java/corgi/commands/FindTasksOnDateCommand.java @@ -1,14 +1,16 @@ package corgi.commands; import java.time.LocalDate; +import java.util.Stack; import java.util.function.Predicate; -import corgi.storage.Storage; +import corgi.State; import corgi.tasks.Deadline; import corgi.tasks.Event; import corgi.tasks.Task; import corgi.tasks.TaskList; import corgi.ui.TextRenderer; +import javafx.util.Pair; /** * Represents a command to find tasks on a specific date in the task list. @@ -51,20 +53,23 @@ public FindTasksOnDateCommand(LocalDate target) { * Executes the command by filtering the task list based on the given predicate to find tasks on the specified date. * It then returns the filtered tasks to the user or a message indicating that no tasks were found on the date. * - * @param list The task list to filter. - * @param renderer The text renderer to return formatted message. - * @param storage The storage for saving and loading tasks (not used in this command). + * @param currState The current state of the application. + * @param history The history stack to store the states. + * @return A pair containing the new state and a string message indicating the result of the command execution. */ @Override - public String execute(TaskList list, TextRenderer renderer, Storage storage) { - TaskList tasksOnDate = list.filter(predicate); + public Pair execute(State currState, Stack> history) { + TaskList currList = currState.getTaskList(); + TextRenderer currTextRenderer = currState.getTextRenderer(); + + TaskList tasksOnDate = currList.filter(predicate); String outputDate = this.target.format(Task.DATE_OUTPUT_FORMATTER); - if (tasksOnDate.isEmpty()) { - return renderer.showNoTaskOnDate(outputDate); - } else { - return renderer.showTasksOnDate(outputDate, tasksOnDate.toString()); - } + String returnMsg = tasksOnDate.isEmpty() + ? currTextRenderer.showNoTaskOnDate(outputDate) + : currTextRenderer.showTasksOnDate(outputDate, tasksOnDate.toString()); + + return new Pair<>(currState, returnMsg); } } diff --git a/src/main/java/corgi/commands/ListTasksCommand.java b/src/main/java/corgi/commands/ListTasksCommand.java index 6c2c16fc56..3dabf7a7dc 100644 --- a/src/main/java/corgi/commands/ListTasksCommand.java +++ b/src/main/java/corgi/commands/ListTasksCommand.java @@ -1,9 +1,11 @@ package corgi.commands; -import corgi.storage.Storage; -import corgi.tasks.Task; +import java.util.Stack; + +import corgi.State; import corgi.tasks.TaskList; import corgi.ui.TextRenderer; +import javafx.util.Pair; /** * Represents a command to list tasks in the task list. @@ -21,17 +23,19 @@ public ListTasksCommand() { * Executes the command by retrieving and displaying the list of tasks to the user. * It returns either the list of tasks or a message indicating that no tasks are in the list. * - * @param list The task list to be displayed. - * @param renderer The text renderer to return formatted message. - * @param storage The storage for saving and loading tasks (not used in this command). + * @param currState The current state of the application. + * @param history The history stack to store the states. + * @return A pair containing the new state and a string message indicating the result of the command execution. */ @Override - public String execute(TaskList list, TextRenderer renderer, Storage storage) { - if (list.isEmpty()) { - return renderer.showNoTaskFound(); - } else { - return renderer.showTaskList(list.toString()); - } + public Pair execute(State currState, Stack> history) { + TaskList currList = currState.getTaskList(); + TextRenderer currRenderer = currState.getTextRenderer(); + + String returnMsg = currList.isEmpty() + ? currRenderer.showNoTaskFound() + : currRenderer.showTaskList(currList.toString()); + return new Pair<>(currState, returnMsg); } } diff --git a/src/main/java/corgi/commands/MarkTaskCommand.java b/src/main/java/corgi/commands/MarkTaskCommand.java index 93555810ce..4620355422 100644 --- a/src/main/java/corgi/commands/MarkTaskCommand.java +++ b/src/main/java/corgi/commands/MarkTaskCommand.java @@ -1,11 +1,13 @@ package corgi.commands; -import corgi.storage.Storage; -import corgi.tasks.Task; +import java.util.Stack; + +import corgi.State; import corgi.tasks.TaskList; import corgi.tasks.TaskListIndexOutOfBoundsException; import corgi.tasks.TaskStatusException; import corgi.ui.TextRenderer; +import javafx.util.Pair; /** * Represents a command to mark a task as done or undone in the task list. @@ -37,28 +39,39 @@ public MarkTaskCommand(int index, boolean status) { /** * Executes the command by marking the task at the specified index with the new status, - * saving the updated list to storage, and return a message indicating the task's status change. + * saving the updated list to storage. * - * @param list The task list to be updated. - * @param renderer The text renderer to return formatted message. - * @param storage The storage for saving and loading tasks (if applicable). + * @param currState The current state of the application. + * @param history The history stack to store the states. + * @return A pair containing the new state and a string message indicating the result of the command execution. * @throws CommandExecutionException If an error occurs during command execution. */ @Override - public String execute(TaskList list, TextRenderer renderer, Storage storage) + public Pair execute(State currState, Stack> history) throws CommandExecutionException { try { - list.mark(this.index, this.status); - storage.save(list); - if (status) { - return renderer.showTaskDone(list.getTaskInfo(this.index)); - } else { - return renderer.showTaskUndone(list.getTaskInfo(this.index)); - } + history.push(new Pair<>(currState, this)); + + State newState = currState.markTask(this.index, this.status); + + TextRenderer renderer = newState.getTextRenderer(); + TaskList list = newState.getTaskList(); + + String returnMsg = (status) + ? renderer.showTaskDone(list.getTaskInfo(this.index)) + : renderer.showTaskUndone(list.getTaskInfo(this.index)); + + return new Pair<>(newState, returnMsg); } catch (TaskListIndexOutOfBoundsException e) { throw new CommandExecutionException("Invalid index provided!"); } catch (TaskStatusException e) { throw new CommandExecutionException("The task is already in that status!"); } } + + @Override + public String toString() { + String action = this.status ? "Mark" : "Unmark"; + return action + " task " + (this.index + 1); + } } diff --git a/src/main/java/corgi/commands/UndoCommand.java b/src/main/java/corgi/commands/UndoCommand.java new file mode 100644 index 0000000000..0d53abf6d8 --- /dev/null +++ b/src/main/java/corgi/commands/UndoCommand.java @@ -0,0 +1,47 @@ +package corgi.commands; + +import java.util.Stack; + +import corgi.State; +import corgi.ui.TextRenderer; +import javafx.util.Pair; + +/** + * Represents a command to undo previous command action. + */ +public class UndoCommand extends Command { + /** + * Initializes a new UndoCommand instance. + */ + public UndoCommand() { + super(false); + } + + /** + * Executes the command by reverting the previous action based on the provided + * history stack and updating the task list accordingly. + * + * @param currState The current state of the application. + * @param history The history stack to store the states. + * @return A pair containing the new state and a string message indicating the result of the command execution. + */ + @Override + public Pair execute(State currState, Stack> history) + throws CommandExecutionException { + if (history.isEmpty()) { + throw new CommandExecutionException("Nothing to undo!"); + } + + Pair previous = history.pop(); + State prevState = previous.getKey(); + Command prevCommand = previous.getValue(); + + prevState.save(); + + TextRenderer renderer = prevState.getTextRenderer(); + + String returnMsg = renderer.showUndoSucceed(prevCommand.toString()); + + return new Pair<>(prevState, returnMsg); + } +} diff --git a/src/main/java/corgi/parsers/CommandParser.java b/src/main/java/corgi/parsers/CommandParser.java index 3f4a799316..2fda5820ff 100644 --- a/src/main/java/corgi/parsers/CommandParser.java +++ b/src/main/java/corgi/parsers/CommandParser.java @@ -13,6 +13,7 @@ import corgi.commands.InvalidCommandException; import corgi.commands.ListTasksCommand; import corgi.commands.MarkTaskCommand; +import corgi.commands.UndoCommand; import corgi.tasks.Deadline; import corgi.tasks.Event; import corgi.tasks.Task; @@ -47,6 +48,9 @@ public Command parse(String fullCommand) throws InvalidCommandFormatException, I Command command = null; switch (cmd) { + case UNDO: + command = newUndoCommand(inputs); + break; case BYE: command = newExitCommand(inputs); break; @@ -84,6 +88,14 @@ public Command parse(String fullCommand) throws InvalidCommandFormatException, I return command; } + private Command newUndoCommand(String[] inputs) throws InvalidCommandFormatException { + if (inputs.length > 1) { + throw new InvalidCommandFormatException("No argument is needed!" + "\nFormat: " + + CommandType.UNDO.getCommandFormat()); + } + return new UndoCommand(); + } + private Command newExitCommand(String[] inputs) throws InvalidCommandFormatException { if (inputs.length > 1) { throw new InvalidCommandFormatException("No argument is needed!" + "\nFormat: " diff --git a/src/main/java/corgi/storage/Storage.java b/src/main/java/corgi/storage/Storage.java index 0de082efdd..28bd188640 100644 --- a/src/main/java/corgi/storage/Storage.java +++ b/src/main/java/corgi/storage/Storage.java @@ -17,9 +17,9 @@ * * @param The type of object being stored and loaded. */ -public class Storage> { - private Parser parser; - private String filePath; +public final class Storage> { + private final Parser parser; + private final String filePath; /** * Constructs a Storage instance with the given parser and file path. diff --git a/src/main/java/corgi/tasks/Deadline.java b/src/main/java/corgi/tasks/Deadline.java index 04e06671a5..9704292c93 100644 --- a/src/main/java/corgi/tasks/Deadline.java +++ b/src/main/java/corgi/tasks/Deadline.java @@ -7,8 +7,8 @@ /** * Deadline task, a type of task that need to be done before a specific date/time. */ -public class Deadline extends Task { - private LocalDate by; +public final class Deadline extends Task { + private final LocalDate by; /** * Initializes a new deadline task with the given description and deadline. @@ -33,6 +33,22 @@ public Deadline(boolean status, String desc, LocalDate by) { this.by = by; } + @Override + public Deadline markAsDone() throws TaskStatusException { + if (status) { + throw new TaskStatusException("The task is already marked as done."); + } + return new Deadline(true, desc, by); + } + + @Override + public Deadline markAsNotDone() throws TaskStatusException { + if (!status) { + throw new TaskStatusException("The task is already marked as not done."); + } + return new Deadline(false, desc, by); + } + /** * Checks if the deadline task is happening on the specified target date. * diff --git a/src/main/java/corgi/tasks/Event.java b/src/main/java/corgi/tasks/Event.java index 6a63d8b5a4..28b44eaae0 100644 --- a/src/main/java/corgi/tasks/Event.java +++ b/src/main/java/corgi/tasks/Event.java @@ -7,9 +7,9 @@ /** * Event task, a type of task that start at a specific date/time and ends at a specific date/time. */ -public class Event extends Task { - private LocalDate from; - private LocalDate to; +public final class Event extends Task { + private final LocalDate from; + private final LocalDate to; /** * Initializes a new event with the given description and duration. @@ -24,6 +24,7 @@ public Event(String desc, LocalDate from, LocalDate to) { this.to = to; } + /** * Initializes a new event task with the given status, description, start date, and end date. * @@ -38,6 +39,22 @@ public Event(boolean status, String desc, LocalDate from, LocalDate to) { this.to = to; } + @Override + public Event markAsDone() throws TaskStatusException { + if (status) { + throw new TaskStatusException("The task is already marked as done."); + } + return new Event(true, desc, from, to); + } + + @Override + public Event markAsNotDone() throws TaskStatusException { + if (!status) { + throw new TaskStatusException("The task is already marked as not done."); + } + return new Event(false, desc, from, to); + } + /** * Checks if the event task is happening on the specified target date. * diff --git a/src/main/java/corgi/tasks/Task.java b/src/main/java/corgi/tasks/Task.java index 45ca365c21..57c15d214d 100644 --- a/src/main/java/corgi/tasks/Task.java +++ b/src/main/java/corgi/tasks/Task.java @@ -9,8 +9,8 @@ public abstract class Task implements Storable { public static final DateTimeFormatter DATE_INPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); public static final DateTimeFormatter DATE_OUTPUT_FORMATTER = DateTimeFormatter.ofPattern("MMM dd yyyy"); - protected String desc; - protected boolean status; + protected final String desc; + protected final boolean status; /** * Initializes a new task with its description. The task's initial status is set to not done. @@ -24,24 +24,20 @@ public Task(boolean status, String desc) { } /** - * Mark task as done. + * Marks the task as done. + * + * @return A new Task instance with the "done" status set to true, while keeping the original task unchanged. + * @throws TaskStatusException If an error occurs while marking the task as done. */ - public void markAsDone() throws TaskStatusException { - if (this.status == true) { - throw new TaskStatusException("The task is already marked as done."); - } - this.status = true; - } + public abstract Task markAsDone() throws TaskStatusException; /** - * Mark task as not done. + * Marks the task as not done. + * + * @return A new Task instance with the "done" status set to false, while keeping the original task unchanged. + * @throws TaskStatusException If an error occurs while marking the task as not done. */ - public void markAsNotDone() throws TaskStatusException { - if (this.status == false) { - throw new TaskStatusException("The task is already marked as not done."); - } - this.status = false; - } + public abstract Task markAsNotDone() throws TaskStatusException; /** * Returns an icon representing the status of the task. diff --git a/src/main/java/corgi/tasks/TaskList.java b/src/main/java/corgi/tasks/TaskList.java index 0c897d959e..802c278eec 100644 --- a/src/main/java/corgi/tasks/TaskList.java +++ b/src/main/java/corgi/tasks/TaskList.java @@ -13,7 +13,7 @@ * This class implements the StorableList interface to provide methods for tasks storing. */ public class TaskList implements StorableList { - private List tasks; + private final List tasks; /** * Constructs an empty TaskList. @@ -28,51 +28,61 @@ public TaskList() { * @param tasks The list of tasks to initialize the TaskList. */ public TaskList(List tasks) { - this.tasks = new ArrayList<>(tasks); + this.tasks = tasks; } /** - * Adds a task to the TaskList. + * Adds a new task to the TaskList and returns a new immutable TaskList with the added task. * - * @param t The task to be added. + * @param t The task to add to the TaskList. + * @return A new TaskList containing all previous tasks and the added task. */ - public void add(Task t) { - this.tasks.add(t); + public TaskList add(Task t) { + List newList = new ArrayList<>(this.tasks); + newList.add(t); + return new TaskList(newList); } /** - * Removes a task at the specified index from the TaskList. + * Removes a task at the specified index from the TaskList and returns a new immutable TaskList + * without the removed task. * * @param index The index of the task to be removed. + * @return A new TaskList with the specified task removed. * @throws TaskListIndexOutOfBoundsException If the index is invalid. */ - public void remove(int index) throws TaskListIndexOutOfBoundsException { + public TaskList remove(int index) throws TaskListIndexOutOfBoundsException { if (!isValidIndex(index)) { throw new TaskListIndexOutOfBoundsException(index); } - this.tasks.remove(index); + List newList = new ArrayList<>(this.tasks); + newList.remove(index); + return new TaskList(newList); } /** - * Marks a task's status as done or not done. + * Marks a task's status as done or not done and returns a new immutable TaskList with the updated task. * * @param index The index of the task to be marked. * @param status The new status of the task. + * @return A new TaskList with the specified task's status updated. * @throws TaskListIndexOutOfBoundsException If the index is invalid. - * @throws TaskStatusException If task was already marked as the given status. + * @throws TaskStatusException If the task was already marked with the given status. */ - public void mark(int index, boolean status) throws TaskListIndexOutOfBoundsException, TaskStatusException { + public TaskList mark(int index, boolean status) throws TaskListIndexOutOfBoundsException, TaskStatusException { if (!isValidIndex(index)) { throw new TaskListIndexOutOfBoundsException(index); } - Task task = this.tasks.get(index); + List updatedTasks = new ArrayList<>(this.tasks); - if (status) { - task.markAsDone(); - } else { - task.markAsNotDone(); - } + Task targetTask = updatedTasks.get(index); + + Task modifiedTask = (status) ? targetTask.markAsDone() : targetTask.markAsNotDone(); + + updatedTasks.set(index, modifiedTask); + + return new TaskList(updatedTasks); } /** diff --git a/src/main/java/corgi/tasks/ToDo.java b/src/main/java/corgi/tasks/ToDo.java index 95572ac334..80c23ed367 100644 --- a/src/main/java/corgi/tasks/ToDo.java +++ b/src/main/java/corgi/tasks/ToDo.java @@ -4,7 +4,7 @@ /** * Todo task, a type of task without any date/time attached to it. */ -public class ToDo extends Task { +public final class ToDo extends Task { /** * Initializes a new todo task with the given description. The task's initial status is set to not done. * @@ -24,6 +24,22 @@ public ToDo(boolean status, String desc) { super(status, desc); } + @Override + public ToDo markAsDone() throws TaskStatusException { + if (status) { + throw new TaskStatusException("The task is already marked as done."); + } + return new ToDo(true, desc); + } + + @Override + public ToDo markAsNotDone() throws TaskStatusException { + if (!status) { + throw new TaskStatusException("The task is already marked as not done."); + } + return new ToDo(false, desc); + } + /** * Converts the todo task to a storable string representation. * diff --git a/src/main/java/corgi/ui/TextRenderer.java b/src/main/java/corgi/ui/TextRenderer.java index 1b4d256340..9840906555 100644 --- a/src/main/java/corgi/ui/TextRenderer.java +++ b/src/main/java/corgi/ui/TextRenderer.java @@ -7,7 +7,7 @@ /** * The TextRenderer class is responsible to return message after each command. */ -public class TextRenderer { +public final class TextRenderer { private static final String LOGO = " ____ ___ ____ ____ ___\n" + " / ___/ _ \\| _ \\ / ___|_ _|\n" + "| | | | | | |_) | | _ | |\n" @@ -202,4 +202,8 @@ public String showNoTaskFound() { public String showTaskList(String taskList) { return taskList; } + + public String showUndoSucceed(String commandDesc) { + return returnMessage("Undo successful: " + commandDesc); + } }