diff --git a/README.md b/README.md index af0309a9e..7ef723f5e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# WallE project template This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. @@ -13,7 +13,7 @@ Prerequisites: JDK 17, update Intellij to the most recent version. 1. If there are any further prompts, accept the defaults. 1. Configure the project to use **JDK 17** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
In the same dialog, set the **Project language level** field to the `SDK default` option. -1. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: +1. After that, locate the `src/main/java/WallE.java` file, right-click it, and choose `Run WallE.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: ``` Hello from ____ _ diff --git a/data.txt b/data.txt new file mode 100644 index 000000000..87bd9db86 --- /dev/null +++ b/data.txt @@ -0,0 +1,6 @@ +1,T,some stuff +0,E,coldplay concert,evening,night +1,D,submit stuff,2025-05-05,18:00 +1,D,assignment submission,2025-03-03,23:59 +0,E,CCA,evening,night +0,D,submit JAR release,2023-03-15,23:59 diff --git a/docs/README.md b/docs/README.md index 47b9f984f..d78c425e6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,153 @@ -# Duke User Guide -// Update the title above to match the actual product name +# Wall-E User Guide -// Product screenshot goes here +![Screenshot of sample Wall-E Usage](assets/WallE-Screenshot.png) -// Product intro goes here +**Product Overview** +Wall-E is a task management application designed to help you organize and keep track of your tasks with ease. +You can add todo items, set deadlines, events, and mark tasks as done or not done. The app will save your tasks in a file and retrieve them when you restart it. -## Adding deadlines +--- +## Adding Todo Tasks -// Describe the action and its outcome. +To add a todo task, simply use the `todo` command followed by the task description. -// Give examples of usage +### Example: `todo mop the floor` -Example: `keyword (optional arguments)` +**Expected Outcome:** +This will add a task called "mop the floor" and initialize it as incomplete. It has a "T" type to indicate it is a todo task. -// A description of the expected outcome goes here +``` +added: [T][ ] mop the floor +``` + +--- +## Adding Deadlines + +To add a task with a deadline, simply use the `deadline` command followed by the task description and the due date and time. +The task command *expects* a valid date and time of the format yyyy-MM-dd HHmm in 24 hour format. + +### Example: `deadline finish project /by 2025-03-10 1600` + +**Expected Outcome:** +This will add a task named "finish project" with the deadline of March 10, 2025, 4.00pm. It will be displayed in your task list with the type "D" for Deadline. + +``` +added: [D][ ] finish project (by: 2025-03-10 4.00 pm) +``` + +--- +## Adding Events + +You can add an event using the `event` command, providing the task description and the start and end dates. + +### Example: `event team meeting /from 9am /to around 12pm` + +**Expected Outcome:** +This adds an event for "team meeting" that spans from "9am" to "around 12pm". Event does not differentiate between +real dates and text input. + +``` +added: [E][ ] team meeting (from: 2025-03-01, to: 2025-03-02) +``` + +--- + +## Marking Tasks as Done + +You can mark any task as completed by using the `mark` command followed by the task number. + +### Example: `mark 1` + +**Expected Outcome:** +The task with the specified number will be marked as done. + +``` +Nice! I've marked this task as done: + [D][X] finish project (by: 2025-03-10) +``` + +--- + +## Unmarking Tasks + +If you wish to unmark a task that was previously marked as done, use the `unmark` command followed by the task number. + +### Example: `unmark 1` + +**Expected Outcome:** +The task will be unmarked as done. ``` -expected output +OK, I've marked this task as not done yet: + [D][ ] finish project (by: 2025-03-10) ``` -## Feature ABC +--- -// Feature details +## Deleting Tasks +If you no longer need a task, you can delete it using the `delete` command followed by the task number. + +### Example: `delete 2` + +**Expected Outcome:** +The task with the specified index will be removed from the task list. + +``` +deleted task: [T][ ] Buy groceries +``` + +--- + +## Listing All Tasks + +To view all the tasks currently in your list, use the `list` command. + +### Example: `list` + +**Expected Outcome:** +This will display all the tasks currently stored in your list. + +``` +Here are the tasks in your list: +1. [D][ ] finish project (by: 2025-03-10) +2. [E][ ] team meeting (from: 2025-03-01, to: 2025-03-02) +``` + +--- + +## Find By Keyword or Phrase + +To find tasks that contain some keyword or phrase, use the `find` command. + +### Example: `find homework` + +**Expected Outcome:** +This will display tasks that contain the provided search term(s). + +``` +Results of find command: +1. [D][ ] do homework (by: 2025-03-10) +2. [E][ ] submit cs2113 homework (from: 2025-03-01, to: 2025-03-02) +``` + +--- + +## Exit the Application + +To close the Wall-E application and save your tasks, use the `bye` command. + +### Example: `bye` + +**Expected Outcome:** +Wall-E will save the tasks to the file and print an exit message. + +``` +Saved data to data.txt +Bye. Hope to see you again soon! +``` -## Feature XYZ +--- -// Feature details \ No newline at end of file +This is your guide to using the Wall-E application. Enjoy organizing your tasks with ease! diff --git a/docs/assets/WallE-Screenshot.png b/docs/assets/WallE-Screenshot.png new file mode 100644 index 000000000..885a40474 Binary files /dev/null and b/docs/assets/WallE-Screenshot.png differ diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334c..000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..7d7f196bb --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: walle.WallE + diff --git a/src/main/java/walle/Storage.java b/src/main/java/walle/Storage.java new file mode 100644 index 000000000..dec807274 --- /dev/null +++ b/src/main/java/walle/Storage.java @@ -0,0 +1,119 @@ +package walle; + +import walle.task.Deadline; +import walle.task.Event; +import walle.task.Task; +import walle.task.Todo; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Handles reading from and writing to a storage file for tasks. + * Provides methods to load tasks from a file and save tasks to a file. + */ +public class Storage { + private File file; + + /** + * Reads the contents of the specified file and loads tasks into the provided list. + * + * @param filePath The path to the file to read from. + * @param tasks The list to store the loaded tasks. + * @return The number of tasks read from the file. + * @throws FileNotFoundException If the file cannot be found. + */ + public int readFileContents(String filePath, ArrayList tasks) throws FileNotFoundException { + Scanner scanner = new Scanner(file); + int listSize = 0; + while (scanner.hasNext()) { + String[] taskString = scanner.nextLine().split(","); + boolean isDone = taskString[0].equals("1") ? true : false; + String taskType = taskString[1]; + String taskDescription = taskString[2]; + + if (taskType.equals("T")) { + tasks.add(new Todo(taskDescription)); + } else if (taskType.equals("D")) { + String dueDate = taskString[3]; + String dueTime = taskString[4]; + tasks.add(new Deadline(taskDescription, dueDate, dueTime)); + } else if (taskType.equals("E")) { + String fromDate = taskString[3]; + String toDate = taskString[4]; + tasks.add(new Event(taskDescription, fromDate, toDate)); + } + + if (isDone) { + tasks.get(listSize).markAsDone(); + } + + listSize++; + } + scanner.close(); + return listSize; + } + + private void createFile(String filePath) { + try { + File tempFile = new File(filePath); + if (tempFile.createNewFile()) { + System.out.println("File created: " + tempFile.getName()); + FileWriter writer = new FileWriter(tempFile); + writer.close(); + } else { + System.out.println("File already exists."); + } + } catch (IOException e) { + System.out.println("An error occurred while creating the file."); + } + } + + /** + * Instantiate storage functionality by providing relative file path to stored data. + * Creates a new file if file does not exist. + * + * @param filePath Relative path to the data file to read from. + */ + public Storage(String filePath) { + file = new File(filePath); + if (!file.exists()) { + createFile(filePath); + } + } + + /** + * Saves the tasks to the file at the specified path. + * + * @param filePath The path to the file to save to. + * @param tasks The list of tasks to save. + * @param listSize The number of tasks to save. + */ + public void saveToFile(String filePath, ArrayList tasks, int listSize) { + try { + FileWriter writer = new FileWriter(file); + for (int i = 0; i < listSize; i++) { + Task t = tasks.get(i); + String status = t.isDone() ? "1" : "0"; + writer.write(status + "," + t.getTypeIcon() + "," + t.getDescription()); + if (t.getTypeIcon() == "T") { + writer.write("\n"); + } else if (t.getTypeIcon() == "D") { + Deadline d = (Deadline) t; + writer.write("," + d.getDueDate() + "," + d.getDueTime() + "\n"); + } else if (t.getTypeIcon() == "E") { + Event e = (Event) t; + writer.write("," + e.getStartDate() + "," + e.getEndDate() + "\n"); + } + } + writer.close(); + } catch (Exception e) { + System.out.println("An error occurred while saving the file."); + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/walle/TaskList.java b/src/main/java/walle/TaskList.java new file mode 100644 index 000000000..94078d455 --- /dev/null +++ b/src/main/java/walle/TaskList.java @@ -0,0 +1,136 @@ +package walle; + +import walle.task.Deadline; +import walle.task.Event; +import walle.task.Task; +import walle.task.Todo; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; + +/** + * Represents a list of tasks, with methods for managing tasks such as adding, removing, + * marking/unmarking, and displaying them. + */ +public class TaskList { + private int listSize; + private ArrayList tasks; + private UserInterface ui; + + /** + * Prints the list of tasks to the user interface. + */ + public void printTaskList() { + ui.printLineBreak(); + ui.printWithoutLineBreak("Here are the tasks in your list: "); + for (int i = 0; i < listSize; i++) { + ui.printWithoutLineBreak(Integer.toString(i + 1) + ". " + tasks.get(i).toString()); + } + ui.printLineBreak(); + } + + /** + * Deletes a task from the list by its index. + * + * @param taskIndex The index of the task to delete. + */ + public void deleteTask(int taskIndex) { + Task deletedTask = tasks.remove(taskIndex - 1); + listSize = getListSize() - 1; + ui.printWithLineBreak("Deleted task: " + deletedTask.toString()); + } + + /** + * Adds an event task to the task list. + * + * @param eventName The name/description of the event. + * @param startDate The start date of the event. + * @param endDate The end date of the event. + */ + public void addEvent(String eventName, String startDate, String endDate) { + tasks.add(new Event(eventName, startDate, endDate)); + listSize = getListSize() + 1; + ui.printWithLineBreak("Added: " + tasks.get(getListSize() - 1).toString()); + } + + /** + * Adds a deadline task to the task list. + * + * @param taskName The name/description of the task. + * @param dueDate The due date of the task. + */ + public void addDeadline(String taskName, LocalDate dueDate, LocalTime deadlineTime) { + tasks.add(new Deadline(taskName, dueDate, deadlineTime)); + listSize = getListSize() + 1; + ui.printWithLineBreak("Added: " + tasks.get(getListSize() - 1).toString()); + } + + /** + * Adds a to-do task to the task list. + * + * @param todoTask The name/description of the to-do task. + */ + public void addTodo(String todoTask) { + tasks.add(new Todo(todoTask)); + listSize = getListSize() + 1; + ui.printWithLineBreak("Added: " + tasks.get(getListSize() - 1).toString()); + } + + /** + * Unmarks a task as done. + * + * @param taskIndex The index of the task to unmark. + */ + public void unmarkTask(int taskIndex) { + tasks.get(taskIndex - 1).unmarkAsDone(); + ui.printWithLineBreak("OK, I've marked this task as not done yet:\n" + + "\t" + tasks.get(taskIndex - 1).toString()); + } + + /** + * Marks a task as done. + * + * @param taskIndex The index of the task to mark. + */ + public void markTask(int taskIndex) { + tasks.get(taskIndex - 1).markAsDone(); + ui.printWithLineBreak("Nice! I've marked this task as done:\n" + "\t" + tasks.get(taskIndex - 1)); + } + + /** + * Constructs an empty TaskList with no {@link Task} stored. + */ + public TaskList() { + listSize = 0; + tasks = new ArrayList<>(); + ui = new UserInterface(); + } + + /** + * Returns the current size of the task list. + * + * @return The number of tasks in the list. + */ + public int getListSize() { + return listSize; + } + + /** + * Returns the list of tasks. + * + * @return An ArrayList of tasks. + */ + public ArrayList getTasks() { + return tasks; + } + + /** + * Sets the size of the task list. + * + * @param listSize The new size of the task list. + */ + public void setListSize(int listSize) { + this.listSize = listSize; + } +} diff --git a/src/main/java/walle/UserInterface.java b/src/main/java/walle/UserInterface.java new file mode 100644 index 000000000..6ed0ea66f --- /dev/null +++ b/src/main/java/walle/UserInterface.java @@ -0,0 +1,77 @@ +package walle; + +import java.util.Scanner; + +/** + * Represents the user interface of the Wall-E application. Provides methods to print messages + * with specific formatting like line breaks and greetings. + */ +public class UserInterface { + private static final String EXIT_MESSAGE = "Bye. Hope to see you again soon!"; + private static final String GREETING = "Hello! I'm Wall-E!\n" + "\tWhat can I do for you?\n\n"; + private Scanner scanner; + + /** + * Prints a line break for formatting purposes. + */ + public void printLineBreak() { + String lineBreak = "\t_________________________________\n"; + System.out.print(lineBreak); + } + + /** + * Prints a message with line breaks before and after it. + * + * @param response The message to be printed. + */ + public void printWithLineBreak(String response) { + printLineBreak(); + System.out.println("\t" + response); + printLineBreak(); + } + + /** + * Prints a message without a line break after it. + * + * @param response The message to be printed. + */ + public void printWithoutLineBreak(String response) { + System.out.println("\t" + response); + } + + /** + * Prints a greeting message to the user. + */ + public void printGreeting() { + printWithLineBreak(GREETING); + } + + /** + * Prints the exit message when the user decides to exit the program. + */ + public void printExitMessage() { + printWithLineBreak(EXIT_MESSAGE); + } + + /** + * Gets next line of input from user. + * @return Input from user in {@link String} format + */ + public String getInput() { + return scanner.nextLine(); + } + + /** + * Closes standard input to prevent memory leakage. + */ + public void closeInput() { + scanner.close(); + } + + /** + * Constructs a new UserInterface object and instantiates standard input. + */ + public UserInterface() { + scanner = new Scanner(System.in); + } +} diff --git a/src/main/java/walle/WallE.java b/src/main/java/walle/WallE.java new file mode 100644 index 000000000..8b857b0e9 --- /dev/null +++ b/src/main/java/walle/WallE.java @@ -0,0 +1,88 @@ +package walle; + +import walle.command.CommandParser; +import walle.command.CommandType; + +import walle.exception.InvalidCommandException; +import walle.exception.InvalidCommandParameterException; +import walle.exception.WallEException; + +import java.io.FileNotFoundException; +import java.util.Scanner; + +/** + * Represents the Wall-E application, which handles user input, task management, and file storage. + */ +public class WallE { + private static final String FILE_NAME = "data.txt"; + private UserInterface ui; + private TaskList tasks; + private Storage storage; + private CommandParser parser; + + private void handleUserInput(String[] params) { + CommandType commandType = null; + try { + commandType = parser.getCommandType(params); + parser.handleCommand(commandType, params, tasks, ui); + } catch (InvalidCommandException e) { + ui.printWithLineBreak(e.getMessage()); + } catch (InvalidCommandParameterException e) { + ui.printWithLineBreak(e.getMessage()); + } + } + + private void initialiseWallE() throws WallEException { + ui.printGreeting(); + storage = new Storage(FILE_NAME); + try { + tasks.setListSize(storage.readFileContents(FILE_NAME, tasks.getTasks())); + } catch (FileNotFoundException e) { + throw new WallEException("Error: Unable to create storage file."); + } + } + + private void exitWallE() { + storage.saveToFile("data.txt", tasks.getTasks(), tasks.getListSize()); + ui.printWithoutLineBreak("Saved data to " + FILE_NAME); + ui.printExitMessage(); + } + + private void run() throws WallEException { + initialiseWallE(); + + ui.printWithLineBreak("Stored list size: " + tasks.getListSize()); + String userInput = ui.getInput(); + while (!userInput.equals("bye")) { + String[] params = userInput.split(" "); + handleUserInput(params); + userInput = ui.getInput(); + } + + exitWallE(); + ui.closeInput(); + } + + /** + * Constructs a new WallE object and initializes the user interface, task list, and command parser. + */ + public WallE() { + ui = new UserInterface(); + tasks = new TaskList(); + parser = new CommandParser(); + } + + /** + * The main method that starts the Wall-E application. + * + * @param args Command-line arguments (not used). + */ + public static void main(String[] args) { + WallE wallE = new WallE(); + try { + wallE.run(); + } catch (WallEException e) { + System.out.println("\t" + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/walle/command/CommandParser.java b/src/main/java/walle/command/CommandParser.java new file mode 100644 index 000000000..e5497df06 --- /dev/null +++ b/src/main/java/walle/command/CommandParser.java @@ -0,0 +1,290 @@ +package walle.command; + +import walle.TaskList; +import walle.UserInterface; +import walle.exception.InvalidCommandException; +import walle.exception.InvalidCommandParameterException; +import walle.task.Task; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * The CommandParser class is responsible for parsing user input commands, + * determining their type, and delegating them to appropriate handlers. + */ + public class CommandParser { + private static final int LIST_COMMAND_LENGTH = 1; + private static final int INDEX_COMMAND_LENGTH = 2; + private static final int MIN_TODO_LENGTH = 2; + private static final int MIN_DEADLINE_LENGTH = 4; + private static final int MIN_EVENT_LENGTH = 6; + + /** + * Parses the command array to determine the command type. + * + * @param params A string array where the first element specifies the command type. + * @return The corresponding {@link CommandType}. + * @throws InvalidCommandException If the command is invalid. + */ + public CommandType getCommandType(String[] params) throws InvalidCommandException { + CommandType commandType; + switch (params[0]) { + case "list": + commandType = CommandType.LIST; + break; + + case "mark": + commandType = CommandType.MARK; + break; + + case "unmark": + commandType = CommandType.UNMARK; + break; + + case "todo": + commandType = CommandType.TODO; + break; + + case "deadline": + commandType = CommandType.DEADLINE; + break; + + case "event": + commandType = CommandType.EVENT; + break; + + case "delete": + commandType = CommandType.DELETE; + break; + + case "find": + commandType = CommandType.FIND; + break; + + default: + commandType = null; + } + + if (commandType == null) { + throw new InvalidCommandException("Invalid command type"); + } + return commandType; + } + + /** + * Calls the appropriate handler based on the commandType. + * + * @param commandType The type of the command to execute. + * @param params The parameters associated with the command. + * @param tasks The list of tasks to modify based on the command. + * @throws InvalidCommandParameterException If the command parameters are invalid. + * @throws InvalidCommandException If the command type is unrecognized. + */ + public void handleCommand(CommandType commandType, String[] params, TaskList tasks, UserInterface ui) throws InvalidCommandParameterException, InvalidCommandException { + switch (commandType) { + case LIST: + handleListCommand(params, tasks); + break; + case MARK: + handleMarkCommand(params, tasks); + break; + case UNMARK: + handleUnmarkCommand(params, tasks); + break; + case TODO: + handleTodoCommand(params, tasks); + break; + case DELETE: + handleDeleteCommand(params, tasks); + break; + case DEADLINE: + handleDeadlineCommand(params, tasks); + break; + case EVENT: + handleEventCommand(params, tasks); + break; + case FIND: + handleFindCommand(params, tasks, ui); + break; + default: + throw new InvalidCommandException("Unknown command type"); + } + } + + private void handleListCommand(String[] params, TaskList tasks) throws InvalidCommandParameterException { + if (params.length != LIST_COMMAND_LENGTH) { + throw new InvalidCommandParameterException("Invalid list command. List does not take any arguments."); + } + tasks.printTaskList(); + } + + private void handleMarkCommand(String[] params, TaskList tasks) throws InvalidCommandParameterException { + if (params.length != INDEX_COMMAND_LENGTH) { + throw new InvalidCommandParameterException("Invalid mark command. Mark takes index as argument."); + } + if (!isValidInteger(params[1])) { + throw new InvalidCommandParameterException("Mark command expects an integer."); + } + if (!isValidIndex(params[1], tasks)) { + throw new InvalidCommandParameterException("Mark command expects a valid index."); + } + + int index = Integer.parseInt(params[1]); + tasks.markTask(index); + } + + private void handleUnmarkCommand(String[] params, TaskList tasks) throws InvalidCommandParameterException { + if (params.length != INDEX_COMMAND_LENGTH) { + throw new InvalidCommandParameterException("Invalid unmark command. Unmark takes index as argument."); + } + if (!isValidInteger(params[1])) { + throw new InvalidCommandParameterException("Unmark command expects a number."); + } + if (!isValidIndex(params[1], tasks)) { + throw new InvalidCommandParameterException("Unmark command expects a valid index."); + } + + int index = Integer.parseInt(params[1]); + tasks.unmarkTask(index); + } + + private void handleTodoCommand(String[] params, TaskList tasks) throws InvalidCommandParameterException { + if (params.length < MIN_TODO_LENGTH) { + throw new InvalidCommandParameterException("Enter a todo task."); + } + + String todoTask = String.join(" ", Arrays.copyOfRange(params, 1, params.length)); + tasks.addTodo(todoTask); + } + + private void handleDeleteCommand(String[] params, TaskList tasks) throws InvalidCommandParameterException { + if (params.length != INDEX_COMMAND_LENGTH) { + throw new InvalidCommandParameterException("Delete takes index as argument."); + } + if (!isValidInteger(params[1])) { + throw new InvalidCommandParameterException("Delete command expects a number."); + } + if (tasks.getListSize() == 0) { + throw new InvalidCommandParameterException("Nothing to delete in task list."); + } + if (!isValidIndex(params[1], tasks)) { + throw new InvalidCommandParameterException("Delete command expects a valid index."); + } + + int taskIndex = Integer.parseInt(params[1]); + tasks.deleteTask(taskIndex); + } + + private void handleDeadlineCommand(String[] params, TaskList tasks) throws InvalidCommandParameterException { + if (params.length < MIN_DEADLINE_LENGTH) { + throw new InvalidCommandParameterException("Invalid deadline command. Deadline command: deadline /by "); + } + + int byIndex = Arrays.asList(params).indexOf("/by"); + if (byIndex == -1 || byIndex >= params.length - 2) { + throw new InvalidCommandParameterException("Deadline command expects a valid due date and time of the format yyyy-MM-dd HHMM."); + } + if (!isValidDate(params[byIndex + 1])) { + throw new InvalidCommandParameterException("Deadline command expects a valid due date of the format yyyy-MM-dd HHMM."); + } + if (!isValidTime(params[byIndex + 2])) { + throw new InvalidCommandParameterException("Deadline command expects a valid time of the format HHMM (no separators between hour and minutes)."); + } + + String taskName = String.join(" ", Arrays.copyOfRange(params, 1, byIndex)); + String dueDateStr = params[byIndex + 1]; + String dueTimeStr = params[byIndex + 2]; + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HHmm"); + + tasks.addDeadline(taskName, LocalDate.parse(dueDateStr, dateFormatter), LocalTime.parse(dueTimeStr, timeFormatter)); + } + + private void handleEventCommand(String[] params, TaskList tasks) throws InvalidCommandParameterException { + if (params.length < MIN_EVENT_LENGTH) { + throw new InvalidCommandParameterException("Invalid event format. Event command: event /from /to "); + } + + int fromIndex = Arrays.asList(params).indexOf("/from"); + int toIndex = Arrays.asList(params).indexOf("/to"); + + if (fromIndex == -1 || toIndex == -1 || toIndex == params.length - 1 || toIndex == params.length - 1) { + throw new InvalidCommandParameterException("Event command expects a valid start and end date time."); + } + if (toIndex < fromIndex || fromIndex + 1 == toIndex) { + throw new InvalidCommandParameterException("Invalid event format. Event command: event /from /to "); + } + if (fromIndex == 1) { + throw new InvalidCommandParameterException("Event name expected."); + } + + String eventName = String.join(" ", Arrays.copyOfRange(params, 1, fromIndex)); + String fromDate = String.join(" ", Arrays.copyOfRange(params, fromIndex + 1, toIndex)); + String toDate = String.join(" ", Arrays.copyOfRange(params, toIndex + 1, params.length)); + tasks.addEvent(eventName, fromDate, toDate); + } + + private void handleFindCommand(String[] params, TaskList tasks, UserInterface ui) throws InvalidCommandParameterException { + if (params.length <= LIST_COMMAND_LENGTH) { + throw new InvalidCommandParameterException("Invalid find command. Find takes at least one keyword as argument."); + } + + String searchStr = String.join(" ", Arrays.copyOfRange(params, 1, params.length)); + ui.printLineBreak(); + ui.printWithoutLineBreak("Results of find command:"); + ArrayList taskIterable = tasks.getTasks(); + int index = 1; + + for (Task task : taskIterable) { + if (!task.getDescription().toLowerCase().contains(searchStr.toLowerCase())) { + continue; + } + ui.printWithoutLineBreak(String.valueOf(index) + ". " + task.toString()); + index++; + } + if (index == 1) { + ui.printWithoutLineBreak("No tasks found."); + } + + ui.printLineBreak(); + } + + + private boolean isValidInteger(String input) { + try { + Integer.parseInt(input); + } catch (NumberFormatException e) { + return false; + } + return true; + } + + private boolean isValidIndex(String input, TaskList tasks) { + int index = Integer.parseInt(input); + return index >= 1 && index <= tasks.getListSize(); + } + + private boolean isValidDate(String dateStr) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + try { + LocalDate.parse(dateStr, formatter); + } catch (Exception e) { + return false; + } + return true; + } + + private boolean isValidTime(String timeStr) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmm"); + try { + LocalTime.parse(timeStr, formatter); + } catch (Exception e) { + return false; + } + return true; + } +} diff --git a/src/main/java/walle/command/CommandType.java b/src/main/java/walle/command/CommandType.java new file mode 100644 index 000000000..dd41d18d8 --- /dev/null +++ b/src/main/java/walle/command/CommandType.java @@ -0,0 +1,16 @@ +package walle.command; + +/** + * Enum representing the different types of commands that can be executed. + * This enum defines the available commands that the system can handle. + */ +public enum CommandType { + LIST, + MARK, + UNMARK, + TODO, + DEADLINE, + EVENT, + DELETE, + FIND; +} diff --git a/src/main/java/walle/exception/InvalidCommandException.java b/src/main/java/walle/exception/InvalidCommandException.java new file mode 100644 index 000000000..88f5de659 --- /dev/null +++ b/src/main/java/walle/exception/InvalidCommandException.java @@ -0,0 +1,10 @@ +package walle.exception; + +/** + * Exception thrown when an invalid command is encountered. + */ +public class InvalidCommandException extends Exception { + public InvalidCommandException(String message) { + super(message); + } +} diff --git a/src/main/java/walle/exception/InvalidCommandParameterException.java b/src/main/java/walle/exception/InvalidCommandParameterException.java new file mode 100644 index 000000000..93c6007b8 --- /dev/null +++ b/src/main/java/walle/exception/InvalidCommandParameterException.java @@ -0,0 +1,10 @@ +package walle.exception; + +/** + * Exception thrown when the parameters of a command are invalid. + */ +public class InvalidCommandParameterException extends Exception { + public InvalidCommandParameterException(String message) { + super(message); + } +} diff --git a/src/main/java/walle/exception/WallEException.java b/src/main/java/walle/exception/WallEException.java new file mode 100644 index 000000000..2a521a92d --- /dev/null +++ b/src/main/java/walle/exception/WallEException.java @@ -0,0 +1,10 @@ +package walle.exception; + +/** + * A custom exception class for handling general exceptions in the WallE application. + */ +public class WallEException extends Exception { + public WallEException(String errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/walle/task/Deadline.java b/src/main/java/walle/task/Deadline.java new file mode 100644 index 000000000..45c3af3a9 --- /dev/null +++ b/src/main/java/walle/task/Deadline.java @@ -0,0 +1,66 @@ +package walle.task; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +/** + * Represents a task with a deadline. + * Inherits from the {@link Task} class and adds a due date. + */ +public class Deadline extends Task { + protected LocalDate dueDate; + protected LocalTime dueTime; + + public Deadline(String description, LocalDate dueDate, LocalTime dueTime) { + super(description); + this.dueDate = dueDate; + this.dueTime = dueTime; + } + + public Deadline(String description, String dueDate, String dueTime) { + super(description); + this.dueDate = LocalDate.parse(dueDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + this.dueTime = LocalTime.parse(dueTime, DateTimeFormatter.ofPattern("HH:mm")); + } + + /** + * Returns the type of the task as a string. + * + * @return A string representing the task type ("[D]"). + */ + @Override + public String getType() { + return "[D]"; + } + + /** + * Returns the type icon for the task. + * + * @return A string representing the type icon ("D"). + */ + public String getTypeIcon() { + return "D"; + } + + /** + * Returns the due date of the task. + * + * @return The due date as a string. + */ + public String getDueDate() { + return dueDate.toString(); + } + + public String getDueTime() { + return dueTime.toString(); + } + + @Override + public String toString() { + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a"); + + return super.toString() + " (by: " + dueDate.format(dateFormatter) + " " + dueTime.format(timeFormatter) + ")"; + } +} diff --git a/src/main/java/walle/task/Event.java b/src/main/java/walle/task/Event.java new file mode 100644 index 000000000..ad3ccedda --- /dev/null +++ b/src/main/java/walle/task/Event.java @@ -0,0 +1,65 @@ +package walle.task; + +/** + * Represents an event task with a start and end date. + * Inherits from the {@link Task} class and adds start and end dates. + */ +public class Event extends Task { + protected String startDate; + protected String endDate; + + /** + * Constructs an Event task with a description, start date, and end date. + * + * @param description The description of the event. + * @param startDate The start date of the event. + * @param endDate The end date of the event. + */ + public Event(String description, String startDate, String endDate) { + super(description); + this.startDate = startDate; + this.endDate = endDate; + } + + /** + * Returns the type of the event task as a string. + * + * @return A string representing the task type ("[E]"). + */ + @Override + public String getType() { + return "[E]"; + } + + /** + * Returns the type icon for the event task. + * + * @return A string representing the type icon ("E"). + */ + public String getTypeIcon() { + return "E"; + } + + /** + * Returns the start date of the event. + * + * @return The start date as a string. + */ + public String getStartDate() { + return startDate; + } + + /** + * Returns the end date of the event. + * + * @return The end date as a string. + */ + public String getEndDate() { + return endDate; + } + + @Override + public String toString() { + return super.toString() + " (from: " + startDate + ", to: " + endDate + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/walle/task/Task.java b/src/main/java/walle/task/Task.java new file mode 100644 index 000000000..a6d89b4c7 --- /dev/null +++ b/src/main/java/walle/task/Task.java @@ -0,0 +1,79 @@ +package walle.task; + +/** + * Represents a general task with a description and completion status. + * This is the base class for other specific types of tasks. + */ +public class Task { + protected String description; + protected boolean isDone; + + /** + * Constructs a task with the specified description and initializes its status as incomplete. + * + * @param description The description of the task. + */ + public Task(String description) { + this.description = description; + this.isDone = false; + } + + /** + * Returns the status icon representing the completion status of the task. + * + * @return A string representing the task's completion status ("[X]" for done, "[ ]" for not done). + */ + public String getStatusIcon() { + return (isDone ? "[X]" : "[ ]"); + } + + /** + * Marks the task as completed. + */ + public void markAsDone() { + isDone = true; + } + + /** + * Marks the task as incomplete. + */ + public void unmarkAsDone() { + isDone = false; + } + + /** + * Returns the type of the task as a string. + * + * @return A string representing the task type ("[ ]"). + */ + public String getType() { + return "[ ]"; + } + + /** + * Returns the type icon for the task. + * + * @return A string representing the type icon (" "). + */ + public String getTypeIcon() { + return " "; + } + + /** + * Returns the description of the task. + * + * @return The description as a string. + */ + public boolean isDone() { + return isDone; + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + return getType() + getStatusIcon() + " " + description; + } +} diff --git a/src/main/java/walle/task/Todo.java b/src/main/java/walle/task/Todo.java new file mode 100644 index 000000000..dcb85d984 --- /dev/null +++ b/src/main/java/walle/task/Todo.java @@ -0,0 +1,35 @@ +package walle.task; + +/** + * Represents a to-do task. + * Inherits from the {@link Task} class and specifies task type as "to-do". + */ +public class Todo extends Task { + /** + * Constructs a Todo task with the specified description. + * + * @param description The description of the to-do task. + */ + public Todo(String description) { + super(description); + } + + /** + * Returns the type of the to-do task as a string. + * + * @return A string representing the task type ("[T]"). + */ + @Override + public String getType() { + return "[T]"; + } + + /** + * Returns the type icon for the to-do task. + * + * @return A string representing the type icon ("T"). + */ + public String getTypeIcon() { + return "T"; + } +} diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 087374464..aee335182 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -15,7 +15,7 @@ IF ERRORLEVEL 1 ( REM no error here, errorlevel == 0 REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ..\bin Duke < input.txt > ACTUAL.TXT +java -classpath ..\bin WallE < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT