diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..156ebfca20 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: seedu.duke.Main + diff --git a/README.md b/README.md index f82e2494b7..6582893042 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# Timetable Comparer 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. @@ -20,7 +20,7 @@ Prerequisites: JDK 11 (use the exact version), update Intellij to the most recen | _ \ _ _| | _____ | | | | | | | |/ / _ \ | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| + |____/ \__,_|_|\_\___ What is your name? ``` diff --git a/build.gradle b/build.gradle index ea82051fab..2be7bf5439 100644 --- a/build.gradle +++ b/build.gradle @@ -26,10 +26,11 @@ test { showStackTraces true showStandardStreams = false } + } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("seedu.duke.Main") } shadowJar { @@ -43,4 +44,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..385ee36ce4 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,8 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +Display | Name | Github Profile | Portfolio +--------|:--------------:|:--------------:|:---------: +![](https://via.placeholder.com/100.png?text=Photo) | Leong Zhe Ming | [Github](https://github.com/Leong-ZM) | [Portfolio](docs/team/johndoe.md) +![](https://via.placeholder.com/100.png?text=Photo) | Zhang Wenqing | [Github](https://github.com/z-wenqing) | [Portfolio](docs/team/johndoe.md) +![](https://via.placeholder.com/100.png?text=Photo) | Zhu Sijia | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +![](https://via.placeholder.com/100.png?text=Photo) | John Nguyen | [Github](https://github.com/john-nng) | [Portfolio](docs/team/johndoe.md) diff --git a/docs/AddUserTaskSequenceDiagram.puml b/docs/AddUserTaskSequenceDiagram.puml new file mode 100644 index 0000000000..414e78de32 --- /dev/null +++ b/docs/AddUserTaskSequenceDiagram.puml @@ -0,0 +1,38 @@ +@startuml +title changeTaskTiming Sequence Diagram + +participant ":Parser" as Caller +participant ":changeTaskTiming" as Method +participant ":changeFlexibleTaskTiming" as FlexibleMethod +participant ":InputValidator" as InputValidator +participant ":UserList" as UserList +participant ":User" as User +participant ":Timetable" as Timetable + +Caller -> Method: changeTaskTiming(command, userList) +activate Method +Method -> InputValidator: validateChangeTaskTiming(command) +activate InputValidator +Method -> InputValidator: command.split("\\s+") +Method <-- InputValidator: parts[] +Method -> InputValidator: Arrays.asList(parts) +Method <-- InputValidator: wordList[] +Method -> InputValidator: parts[2] +Method -> InputValidator: parts[wordList.indexOf("/index") + 1] +Method -> InputValidator: parts[wordList.indexOf("/from") + 1] +Method -> InputValidator: parts[wordList.indexOf("/to") + 1] +Method -> InputValidator: validateDay(day) +deactivate InputValidator +Method -> UserList: userList.getActiveUser() +Method <-- UserList: activeUser +Method -> User: activeUser.getTimetable() +activate User +Method <-- User: timetable +Method -> FlexibleMethod: timetable.changeFlexibleTaskTiming(day, index - 1, newStartTime, newEndTime) +activate FlexibleMethod +FlexibleMethod <-- Timetable: successful change +deactivate FlexibleMethod +deactivate User +deactivate Method + +@enduml \ No newline at end of file diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..b889dfe6d2 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,151 @@ # Developer Guide -## Acknowledgements +## Design & implementation -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +### Save timetable function +The saving timetable mechanism is facilitated by the Storage class. It implements the following operations: +* `Storage#createFolder()`: Creates a folder in the directory where all the users' timetable will be stored in the local computer. +* `Storage#addExistingUsers()`: Loops through the folder to add all previous saved users upon opening the app, as well as their corresponding tasks. +* `Storage#addUserInFolder(User user)`: When a new user is added, a file with the name of the user will be created in the folder. +* `Storage#writeTaskInFile(User user)`: when a new task of a specific user is added, it will be written into the correct file. -## Design & implementation +The following sequence diagram shows how the operations in the Storage class goes through when the app is opened and it loads data from history: +![Storage UML](diagram%2FStorageUML.png) + + +## Parser Component + +Here is a general guide of how Parse Class looks like in sequence diagram. + +![Parse.png](diagram%2FParse.png) + +Here is a class diagram for the method changeTaskTiming method in Parser Class. + +![changeTaskTimingClassDiagram.png](diagram%2FchangeTaskTimingClassDiagram.png) + +The following sequence diagram of changeTaskTiming shows the interactions with other objects. + +![changeTaskTiming_Sequence_Diagram.png](diagram%2FchangeTaskTiming_Sequence_Diagram.png) + +Here is how this method works: + +1. The Parser calls the changeTaskTiming method with a command and the UserList. It calls InputValidator. + +2. The InputValidator validates the command, ensuring that it meets the required format in days and index. + +3. If validation is successful, the User calls the timetable and calls its changeFlexibleTaskTiming method with the specified parameters. + +4. The Timetable updates the timing of the flexible task. + +5. If successful, the InputValidator sends a success message back to the Parser. Otherwise, it throws a RuntimeException. + +## Timetable Component +Here is a sequence diagram of changeTaskType method in Timetable class. It shows the interactions with other objects. +![changeTaskType.png](diagram%2FchangeTaskType.png) + +Here is how this method works: +1. The sequence starts with the Parser sending a request to the Timetable to change the type of a task. + +2. The Timetable activates and processes the changeTaskType method. + +3. Within the Timetable, the dayOfWeek parameter is capitalized to ensure consistency. + +4. The Timetable calls the list of tasks associated with the specified day (dayOfWeek). It activates the Task object to fetch the task at the specified index (index). -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +5. The Task object modifies the type of the task to the new type specified by the Parser. +6. Finally, the modified task or any relevant status information is returned to the Parser. +## Next Command + +Proposed implementation + +The next command displays the next task of the current user based on the current time. Given below is how it works. + +Step 1: The user launches the application, creates a user, and creates several tasks for the user. + +Step 2: The user types the command 'next' to see their next task. + +Step 3: The next command is triggered. It first determines the current day and takes the arraylist of tasks of the user's timetable on that day. + +Step 4: It then loops through the tasks on that day, starting from the back (the later tasks), until the task's start time is before the current time. + +Step 5: It displays the last task it looped though, or a special message if there is no task that day. + +![NextCommand UML](diagram%2FNextCommand.png) + +# Appendix: Project Requirements ## Product scope ### Target user profile -{Describe the target user profile} +People who +* have busy timetables +* wish to be able to compare theirs with their friends' easily +* prefer desktop apps over other types +* can type fast +* prefer typing to mouse interactions +* are reasonably comfortable using CLI apps ### Value proposition -{Describe the value proposition: what problem does it solve?} +The app can store multiple timetables and easily display and compare them automatically, saving the time and effort of doing so by hand. ## User Stories -|Version| As a ... | I want to ... | So that I can ...| -|--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +| Version | As a ... | I want to ... | So that I can ... | +|---------|----------|---------------------------|------------------------------------------------------------------| +| v1.0 | new user | see usage instructions | refer to them when I forget how to use the application | +| | user | add a new user | store my timetable | +| | user | switch users | interact with my own timetable | +| | user | add a task | | +| | user | compare two timetables | see the common time between them | +| v2.0 | user | find a to-do item by name | locate a to-do without having to go through the entire list | +| | user | specify task importance | remember if a task is important or not | +| | user | compare all timetables | view a summarized timetable comprising of all current timetables | + ## Non-Functional Requirements -{Give non-functional requirements} +1. Should work on any mainstream OS (Windows, Linux, Unix, MacOS) as long as it has Java 11 or above installed. +2. Should be able to hold up to 10 persons without a noticeable sluggishness in performance for typical usage. +3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. ## Glossary -* *glossary item* - Definition +* **User** - A person using this app. +* **Timetable** - A number of tasks belonging to a specific user. +* **Task** - An event in a timetable, with a description, start time, end time, and type. ## Instructions for manual testing -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +Given below are instructions to test the app manually. + +### Launch and Shutdown + +1. Initial launch + +Download the jar file and copy into an empty folder. Double-click the jar file. Expected: Shows the GUI with a list of commands. The window size may not be optimum; adjust it as necessary. + +2. Shutdown + +Enter 'bye'. The app should close and all current data should be saved in a data folder. + +### Add user + +Prerequisites: None + +* Valid command: `adduser User1`. Expected: Confirmation that user was added. +* Invalid command: `adduser`. Expected: Error message shown. +* Invalid command: `adduser User1`, where User1 already exists. Expected: Error message shown. + +### Add task + +Prerequisites: At least one user has been added + +* Valid command: `addtask /on Monday /task test1 /from 9:00 /to 11:00 /type f`. Expected: Confirmation that task was added. +* Invalid commands. Expected: Error message shown detailing expected format. + * `addtask /on Monday` + * `addtask /on Monday /task description` + * `addtask /on Monday /task description /from 09:00 /to 11:00` + * `addtask Monday description 09:00 11:00` + * `addtask /on MONDAY /task description /from 09:00 /to 11:00` diff --git a/docs/NextCommand.puml b/docs/NextCommand.puml new file mode 100644 index 0000000000..0ed15eb02d --- /dev/null +++ b/docs/NextCommand.puml @@ -0,0 +1,43 @@ +@startuml +autonumber + +participant ":Main" as Main +participant ":Parser" as Parser +participant ":DayOfWeek" as DayOfWeek +participant ":Timetable" as Timetable +participant ":UI" as UI + + +Main -> Parser: (parseCommand("next", userList)) +activate Parser +DayOfWeek -> Parser: dayOfWeek +Parser -> Parser: capitalizeDay(dayOfWeek) +activate Parser +Parser --> Parser: result +deactivate Parser + +Parser -> Timetable: getWeeklyTasks(dayOfWeek) +activate Timetable +Timetable --> Parser: tasksOfDay +deactivate Timetable +Parser -> Parser: tasksOfDay.size() +activate Parser +Parser --> Parser: result +deactivate Parser + +alt numOfTasks == 0 + Parser -> UI: printNoTasks() + activate UI + UI --> Main: display no text + deactivate UI +else + loop numOfTasks + alt currentTime < task.startTime + Parser -> UI: nextTask() + activate UI + + deactivate UI + end + end +end +@enduml \ No newline at end of file diff --git a/docs/Parse.puml b/docs/Parse.puml new file mode 100644 index 0000000000..5961d48e23 --- /dev/null +++ b/docs/Parse.puml @@ -0,0 +1,43 @@ +@startuml +participant ":UI" as UI +participant ":Parser" as Parser +participant ":UserList" as UserList +participant ":Timetable" as Timetable +participant ":InputValidator" as InputValidator + +UI -> Parser: parseCommand(command, userList) +activate Parser + +Parser -> UserList: setActiveUser(user) +activate UserList +UserList --> Parser: Confirmation + +alt command = "changeTaskType" + Parser -> InputValidator: validateChangeTaskType(command) + activate InputValidator + InputValidator -> Timetable: changeTaskType(dayOfWeek, index, newType) + activate Timetable + Timetable --> Parser: Success message + deactivate Timetable + InputValidator --> Parser: No exceptions thrown + deactivate InputValidator + Parser --> UI: "Task type changed successfully." +else command = "deleteTask" + Parser -> InputValidator: validateDeleteTaskInput(command) + activate InputValidator + InputValidator -> Timetable: deleteUserTask(day, index) + activate Timetable + Timetable --> Parser: Success message + deactivate Timetable + InputValidator --> Parser: No exceptions thrown + deactivate InputValidator + Parser --> UI: Success message +else + note right of Parser + Other command cases handled similarly + end note + Parser --> UI: Output message +end + +deactivate Parser +@enduml diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..35f1e5bcdb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,3 @@ -# Duke +# TimetableComparer -{Give product intro here} - -Useful links: -* [User Guide](UserGuide.md) -* [Developer Guide](DeveloperGuide.md) -* [About Us](AboutUs.md) +Refer to UserGuide: [User Guide](https://ay2324s2-cs2113-t13-2.github.io/tp/UserGuide.html) diff --git a/docs/Storage.puml b/docs/Storage.puml new file mode 100644 index 0000000000..420ecd4134 --- /dev/null +++ b/docs/Storage.puml @@ -0,0 +1,63 @@ +@startuml +scale 1.5 +participant ":Duke" as duke +participant ":Storage" as storage +participant ":File" as file +participant ":UserList" as userList +participant ":UI" as UI +participant ":Timetable" as timetable +participant ":User" as user + + +duke -> storage: createFolder() +activate storage +storage -->> duke: +deactivate storage + +duke -> storage: addExistingUsers() +activate storage +storage -> file: listFiles() +activate file +file -->> storage: File +deactivate file + +alt directory is not empty + loop file + opt file is not null + storage -> userList: addUser(user: User) + activate userList + userList -> userList: add(user: User) + alt allUsers.size() == 1 + userList -> UI: printActiveUser() + activate UI + UI -->> userList + deactivate UI + end + userList -->> storage + deactivate userList + + storage -> storage: loadData(user: User) + storage -> storage: extractTaskInfo(String line, String day) + storage --> storage: Task + storage -> user: getTimetable() + activate user + user -> timetable: addUserTask(day: String, task: Task) + activate timetable + timetable --> user + deactivate timetable + user -->> storage + deactivate user + end + end + +else + storage -> UI: printEmptyDirectory(); + activate UI + UI -->> storage + deactivate UI +end + +storage --> duke +deactivate storage + +@enduml \ No newline at end of file diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..e3ddf4c349 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,375 @@ # User Guide -## Introduction +### Introduction -{Give a product intro} +Welcome to **TimeTableComparer**! This application is meant for creating and storing task within a weekly calendar and seeing shared time between user's timetables. A convient way to determine shared free time! -## Quick Start +## Table of Contents +- [Quick Start](#quick-start) +- [Features](#features) + - [Adding a user: `adduser`](#adduser) + - [Show current user: `current`](#current) + - [Listing all users: `list`](#list) + - [Switch users: `switch`](#switch) + - [Adding a task: `task`](#addtask) + - [Adding a task (duplication check): `addtwdc`](#addtwdc) + - [Deleting a task: `deletetask`](#deletetask) + - [Comparing two timetables: `compare`](#compare) + - [Comparing all timetables: `compareall`](#compareall) + - [Add a task for all users: `addforall`](#addforall) + - [Changing a task's times: `changetasktiming`](#changetasktiming) + - [Changing a task's type: `changetasktype`](#changetasktype) + - [View today's tasks: `todaytask`](#todaytask) + - [Add a recurring task: `addrepeattask`](#addrepeattask) + - [View urgent tasks in next few hours: `urgent`](#urgent) + - [Add a task for certain users: `addfor`](#addfor) + - [Help: `help`](#help) + - [Exit program: `bye`](#bye) +- [FAQ](#faq) +- [Command Summary](#commandsummary) -{Give steps to get started quickly} +## Quick Start +Downloading TimeTableComparer -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +1. Ensure that you have [Java 11](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html) or above installed. +2. Down the latest version of `tP.jar` from [here](http://link.to/duke). +3. Copy the file to the folder you want to use as the home folder for the application. For example,`C:\Users\setupuser\Documents\StockPal\stockpal.jar`. +4. Open your terminal or gitBash and `cd` into the folder you placed the .jar file. +5. Run `java -jar tP.jar` -## Features +## Features +> Note: `CAPITAL_LETTERS` Indicate the section for user's input. -{Give detailed description of each feature} +## Adding a user: `adduser` -### Adding a todo: `todo` -Adds a new item to the list of todo items. +Add a new user into the user list. -Format: `todo n/TODO_NAME d/DEADLINE` +Format: `adduser NAME` -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +Example: `adduser mike` -Example of usage: +Expected Output: +``` +New user added: mike +The active user is: mike +File created: mike.txt +``` +> *If this is the first user created then the active user will be set to the new user* -`todo n/Write the rest of the User Guide d/next week` +## Show current user: `current` -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +Displays the active user. -## FAQ +Format:`current` -**Q**: How do I transfer my data to another computer? +Example: `current` +``` +adduser john +adduser jill +adduser jack +current +``` +Expected Output: +``` +The active user is: john +``` -**A**: {your answer here} +## Listing all existing users: `list` -## Command Summary +View the list of existing usernames. -{Give a 'cheat sheet' of commands here} +Format: `list` -* Add todo `todo n/TODO_NAME d/DEADLINE` +Example: `list` + +Expected Output: +``` +The current users are: +jane +john +jill +``` + +## Switch to another user: `switch` + +Switch the active user to the inputted name. + +Format: `switch USER_NAME` + +Example: `switch jane` + +Expected Output: +``` +The active user is: jane +``` + +## Adding a task: `addtask` + +Add a new task into the active user's list. + +Format: `addtask /on DAY /task TASK_NAME /from TIME /to TIME /type TASK_TYPE` +> * Both starting time and end time must be in the format of HH:MM or H:MM. +> * TASK_TYPE is either `f` (for flexible tasks) or `c` (for compulsory tasks). + +Example: `addtask /on monday /task lecture /from 11:00 /to 12:00 /type f` + +Expected Output: +``` +The following task is added: lecture (monday from 11:00 to 12:00) type: f +Timetable has been written to data/mike.txt +``` + +## Adding a task with duplication check: `addtwdc` + +Adding a new task with a duplication check, ensuring that task of this type does not get duplicated. + +Format: `addtwdc /on DATE /task TASK_NAME /from TIME /to TIME /type TASK_TYPE` +>* Both starting time and end time must be in the format of HH:MM or H:MM. +>* TASK_TYPE is either `f` (for flexible tasks) or `c` (for compulsory tasks). + +Example: `addtwdc /on monday /task lecture /from 11:00 /to 12:00 /type f` + +Expected Output (Duplicate Found): +``` +Invalid command. +Task already exists. Cannot add duplicate task. +``` + +## Delete task from one user's timetable: `deletetask` + +Delete a task given a specific day and task number. + +Format: `deletetask /on DAY /index TASK_INDEX` + +Example: `deletetask /on monday /index 1` + +Expected Output: +``` +Task lecture is deleted from monday +New task list for Monday: +No task for monday! +Timetable has been written to data/jane.txt +``` +> This output is given that we deleted the only task on monday. + +## Compare two timetables: `compare` + +Compare timetables between two specified users. + +Format: `compare NAME_1 NAME_2` + +Example: `compare john jane` + +Expected Output: +``` +_________________________________________ +Shared free time on Monday: + 00:00 - 09:00 + 11:00 - 12:00 + 13:00 - 23:59 +_________________________________________ +Shared free time on Tuesday: + 00:00 - 09:00 + 11:00 - 23:59 +_________________________________________ +Shared free time on Wednesday: + ** Whole day is free on Wednesday +_________________________________________ +Shared free time on Thursday: + ** Whole day is free on Thursday +_________________________________________ +Shared free time on Friday: + 00:00 - 13:00 + 14:00 - 23:59 +_________________________________________ +Shared free time on Saturday: + ** Whole day is free on Saturday +_________________________________________ +Shared free time on Sunday: + ** Whole day is free on Sunday +``` + +## Compare all timetables: `compareall` + +Compare timetables between all existing users. + +Format: `compareall` + +Example: `compareall` + +Expected Output: +``` +Comparing all timetables: +_________________________________________ +Shared free time on Monday: + 00:00 - 09:00 + 11:00 - 12:00 + 13:00 - 23:59 +_________________________________________ +Shared free time on Tuesday: + 00:00 - 09:00 + 11:00 - 23:59 +_________________________________________ +Shared free time on Wednesday: + ** Whole day is free on Wednesday +_________________________________________ +Shared free time on Thursday: + ** Whole day is free on Thursday +_________________________________________ +Shared free time on Friday: + 00:00 - 13:00 + 14:00 - 23:59 +_________________________________________ +Shared free time on Saturday: + 00:00 - 02:00 + 04:00 - 23:59 +_________________________________________ +Shared free time on Sunday: + ** Whole day is free on Sunday + +``` + +## Add a task for all existing users: `addforall` + +Add a task for all existing users. + +Format: `addforall /on DAY /task TASK_NAME /from TIME /to TIME` + +Example: `addforall /on sunday /task laundry /from 7:00 /to 9:00` +* Note: there is no need to specify type for common events as tasks added for all users are assumed to be of type "common", + +Expected Output: +``` +Timetable has been written to data/jane.txt +Timetable has been written to data/john.txt +Timetable has been written to data/jill.txt +The following task is added for all users: laundry (sunday from 07:00 to 09:00) +``` + +## Change the start time or end time of a task: `changetasktiming` + +Change the starting/ending time of a task given a day and task number. + +Format:`changetasktiming /on DAY /index TASK_INDEX /from TIME /to TIME` + +Example: `changetasktiming /on monday /index 1 /from 12:00 /to 13:00` + +Expected Output: +``` +Flexible task timing changed successfully. +Timetable has been written to data/jane.txt +``` + +## Change the task type: `changetasktype` + +Format:`changetasktype /on DAY /index TASK_INDEX /type F_OR_C` + +Example: `changetasktype /on monday /index 1 /type c` + +Expected Output: +``` +Task type changed successfully. +Timetable has been written to data/jane.txt +``` + +## View today's tasks: `todaytask` + +Format: `todaytask` + +Example: `todaytask` + +Expected Output: +``` +_________________________________________ +Today : + 1. walk dog (friday from 13:00 to 14:00) type: c +``` +>Given that tasks exist the day this command is ran. + +## Add a task which occurs on multiple days: `addrepeattask` + +Add a task which occurs on multiple days given a set of days. + +Format: `addrepeattask /task TASK_NAME /on DAYS /from START_TIME /to END_TIME /type F_OR_C` + +Example: `addrepeattask /task lecture /on monday tuesday /from 9:00 /to 11:00 /type c` + +Expected Output: +``` +Timetable has been written to data/user.txt +Repeated task added successfully! + +``` + +## Find urgent tasks which happen in a day within next few hours:`urgent` + +Format: `urgent /in HOURS` + +Example: `urgent /in 3` + +Expected output: +``` +Urgent tasks within the next 3 hours: +lec (Monday from 12:00 to 13:00) type: c +``` + +## Add a task for multiple users: `addfor` + +Format: `addfor /user USER1, USER2, ... /on DAYS /task DESCRIPTION /from START_TIME /to END_TIME /type F_OR_C` + +Example: `addfor /user simon, helen, tim /on monday /task project meeting /from 9:00 /to 11:00 /type f` + +* Note: The usernames have to be separated by ",". + +Expected Output: +``` +Timetable has been written to data/Simon.txt +Timetable has been written to data/Helen.txt +Timetable has been written to data/Tim.txt +``` + +## List name of commands: `help` + +Format: `help` + +Example: `help` + +## Exit the program: `bye` + +Format: `bye` + +Example: `bye` + +Expected Output: `Bye.` + +## FAQ + +**Q**: Where are the timetables stored? + +**A**: The timetables are stored in the folder named "data" in the same directory. +The name of the file indicates the owner of the timetable. + +**Q**: Can I delete a user from the userlist? + +**A**: No. + +## Command Summary + +- Add User `adduser NAME` +- Show Active User `current` +- List All Users `list` +- Switch Users `switch` +- Adding a Task `addtask /on DAY /task DESCRIPTION /from START /to END /type [f/c]` +- Adding a Task (Duplication Check) `addtwdc /on DAY /task DESCRIPTION /from START /to END /type [f/c]` +- Delete a Task `deletetask /on DAY /index TASK_NUMBER` +- Compare Two Timetables `compare NAME_1 NAME_2` +- Compare All Timetables `compareall` +- Add a Task For All Users `addforall /on DAY /task DESCRIPTION /from START /to END` +- Changing a Task's Time `changetasktiming /on DAY /index TASK_INDEX /from TIME /to TIME` +- Changing a Task's Type `changetasktype /on DAY /index TASK_INDEX /type F_OR_C` +- List Today's Tasks `todaytask` +- Add a Recurring task `addrepeattask /task TASK_NAME /on DAYS /from START_TIME /to END_TIME /type F_OR_C` +- Find urgent tasks in next few hours `urgent /in HOURS` +- Add Task For Certain Users `addfor /user USER1, USER2, ... /on DAYS /task DESCRIPTION /from START_TIME /to END_TIME /type F_OR_C` +- Help `help` +- Exit Program `bye` diff --git a/docs/changeTaskTimingClassDiagram.puml b/docs/changeTaskTimingClassDiagram.puml new file mode 100644 index 0000000000..4c7c546cb0 --- /dev/null +++ b/docs/changeTaskTimingClassDiagram.puml @@ -0,0 +1,51 @@ +@startuml +class InputValidator { + +validateChangeTaskTiming(command: String): void + +validateDay(day: String): void +} + +class UserList { + +getActiveUser(): User +} + +class User { + -timetable: Timetable + +getTimetable(): Timetable +} + +class Timetable { + +changeFlexibleTaskTiming(dayOfWeek: String, index: int, newStartTime: LocalTime, newEndTime: LocalTime): void +} + +class IndexOutOfBoundsException +class IllegalArgumentException + +class System { + +println(message: String): void +} + +class LocalTime +class InvalidDayException +class InvalidFormatException + +InputValidator "1" --> "0..1" InvalidFormatException +UserList "1" --> "1" User +User "1" --> "1" Timetable + +Caller "1" --> "1" InputValidator: use +Caller "1" --> "1" UserList: use +Caller "1" --> "1" System: use +UserList "1" --> "1" System: use +InputValidator "1" --> "1" InvalidDayException: use +InputValidator "1" --> "1" IndexOutOfBoundsException: use +InputValidator "1" --> "1" IllegalArgumentException: use +InputValidator "1" --> "1" LocalTime: use +Method "1" --> "1" InputValidator: use +Method "1" --> "1" UserList: use +Method "1" --> "1" User: use +Method "1" --> "1" Timetable: use +Method "1" --> "1" System: use +Timetable "1" --> "0..1" LocalTime: use +Timetable "1" --> "0..1" IndexOutOfBoundsException: use +Timetable "1" --> "0..1" IllegalArgumentException: use +@enduml \ No newline at end of file diff --git a/docs/changeTaskTimingObjectDiagram.puml b/docs/changeTaskTimingObjectDiagram.puml new file mode 100644 index 0000000000..551088e2a4 --- /dev/null +++ b/docs/changeTaskTimingObjectDiagram.puml @@ -0,0 +1,19 @@ +@startuml +object parser +object inputValidator +object userList +object user +object timetable +object indexOutOfBoundsException +object illegalArgumentException +object localTime +object task + +parser --> inputValidator +parser --> userList +userList --> user +user --> timetable +timetable --> task : weeklyTasks +@enduml + + diff --git a/docs/changeTaskType.puml b/docs/changeTaskType.puml new file mode 100644 index 0000000000..a4d66c246b --- /dev/null +++ b/docs/changeTaskType.puml @@ -0,0 +1,29 @@ +@startuml +participant ":Parser" as Parser +participant ":Timetable" as Timetable +participant ":Task" as Task + +Parser -> Timetable: changeTaskType(dayOfWeek, index, newType) +activate Timetable +Timetable -> Timetable: capitalizeDay(dayOfWeek) +activate Timetable +Timetable --> Timetable: result +deactivate Timetable +Timetable -> Timetable: getTasks(dayOfWeek) +activate Timetable +Timetable --> Timetable: result +deactivate Timetable + +activate Task +Timetable -> Task: getTask(index) +Task -> Task: setType(newType) +activate Task +Task --> Task: result +deactivate Task + + +Task -->Timetable +Timetable --> Parser: +deactivate Task +deactivate Timetable +@enduml diff --git a/docs/diagram/NextCommand.png b/docs/diagram/NextCommand.png new file mode 100644 index 0000000000..ea24d77fd9 Binary files /dev/null and b/docs/diagram/NextCommand.png differ diff --git a/docs/diagram/Parse.png b/docs/diagram/Parse.png new file mode 100644 index 0000000000..9658e89108 Binary files /dev/null and b/docs/diagram/Parse.png differ diff --git a/docs/diagram/StorageUML.png b/docs/diagram/StorageUML.png new file mode 100644 index 0000000000..2e76f759ed Binary files /dev/null and b/docs/diagram/StorageUML.png differ diff --git a/docs/diagram/changeTaskTimingClassDiagram.png b/docs/diagram/changeTaskTimingClassDiagram.png new file mode 100644 index 0000000000..347dec8ea8 Binary files /dev/null and b/docs/diagram/changeTaskTimingClassDiagram.png differ diff --git a/docs/diagram/changeTaskTiming_Sequence_Diagram.png b/docs/diagram/changeTaskTiming_Sequence_Diagram.png new file mode 100644 index 0000000000..a3513db3a6 Binary files /dev/null and b/docs/diagram/changeTaskTiming_Sequence_Diagram.png differ diff --git a/docs/diagram/changeTaskType.png b/docs/diagram/changeTaskType.png new file mode 100644 index 0000000000..fd50261ce0 Binary files /dev/null and b/docs/diagram/changeTaskType.png differ diff --git a/docs/team/john-nng.md b/docs/team/john-nng.md new file mode 100644 index 0000000000..fd0f8b465d --- /dev/null +++ b/docs/team/john-nng.md @@ -0,0 +1,32 @@ + +# John Nguyen Project Portfolio Page + +## Project: Timetable Comparer +### Team: AY2324S2-CS2113-T13-2 +[Code Contribution](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=john-nng&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +This is a task scheduler and timetable comparison command line app that allows users to create timetables consisting of task throughout a given week. Multiple users have their own timetable compare timetables to see shared time between users, seeing upcoming task and viewing their timetables for the week. This makes it easy for people to schedule events in relation to each other based on their shared free time. + +### Contributions + +#### Features +- **Structured the Timetable Class datastructure.** I adjusted the parameters of the Timetable Class to conventiently do calculations with other tables, for example, the data structure of the timetable is a HashMap of days associated with list of tasks which are the values for the dictionary keys. +- **Implemented InputValidator Class and set up the framework for this class.** This class is used to validate the correct syntax for every command in this app. This uses regex to make sure the String pattern of a command correct matches how the comand should be called. I setup the logic for this InputValidator class and my functions I created could be referenced for future additions of new commands. +- **Implemented Compare function between 2 users.** I added the essential feature of comparing two timetables of two users and displaying their shared free time bassed on each day. This is done by merging the two timetables then finding the free time within the merged timetables then displaying the results. +- **Abstracted the Main Class.** I abstracted the Parsing logic that was originally in the Main class, this significantly decluttered the main Class and abstracted the logic into the Parser Class. The while loop simply takes in Input and then Parsing will take care of the bulk of the work. +- **Implemented the Parser Class.** I set up the framework for the Parser class which takes in a string command and will identify that command it is. Based on that It will perform the command logic, abstracting other parts of the code if neccesary. +- example of InputValidator within Parser Class: +- image + + +#### Testing +- Wrote test for InputValidator class. I wrote J-Unit test for InputValidator class to ensure that the functions were correctly validating various input commands. Such as Adduser, addtask, deletetask, compareall, compare. PR [here](https://github.com/AY2324S2-CS2113-T13-2/tp/commit/e9c5d00429e60a5d1a5d2938cabfeb0007e9c21d) +- Wrote some test in the TimetableTest Class for addtask function and compare functions to see if various task would be added properly and compared properly. + +#### Enhancements to Current Features +- Enchanced the UI ouput for timetables and timetable comparison commands. Making these outputs more readable. +- Updated the help message UI output. To reflect the complete set of commands and their usages in a clean format. [PR Here](https://github.com/AY2324S2-CS2113-T13-2/tp/commit/b5974cdf35554456984cd02415fbd7d87facab7a) + +#### Documentation +- Updated Documentation for all commands in User Guide and formatted them in a easy to read manner. Bulk of work done during these PR's ([1](https://github.com/AY2324S2-CS2113-T13-2/tp/blob/9dbe139124adfecf78f4c94d8f662b283788a8fa/docs/UserGuide.md), [2](https://github.com/AY2324S2-CS2113-T13-2/tp/blob/65c01a9d7d4875eedc494afa923108eec9e48055/docs/UserGuide.md), [3](https://github.com/AY2324S2-CS2113-T13-2/tp/blob/47db8ea97ed3da106a83d75a7ae90152a5e80200/docs/UserGuide.md)) +- Updated Documentation for DG, Fixed Class Diagram and Sequence Diagram format errors [PR here](https://github.com/AY2324S2-CS2113-T13-2/tp/commit/afc1f429c45bda99b36cf8cae4ac14941c0ccd2a) diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/leong-zm.md b/docs/team/leong-zm.md new file mode 100644 index 0000000000..57e137c748 --- /dev/null +++ b/docs/team/leong-zm.md @@ -0,0 +1,29 @@ +# Leong Zhe Ming's Project Portfolio Page + +## Project: Time Comparer + +### Team: AY2324S2-CS2113-T13-2 + +Time Comparer is a desktop application used for time management between users. + +This is a task scheduler and timetable comparison command line app that allows users to create timetables consisting of +task throughout a week. Each user has their own timetable, which can be compared to see shared time between +users. This makes it easy for people to schedule events based on their shared free time. + +### Summary of Contributions +[Code Contribution](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=Leong-ZM&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +#### Features +- **Added the base User and UserList classes:** Created the basic class that track the current active users, allowing user creation and switching between users, as well as setting a framework for future functionality. +- **Added the help command:** To show currently available commands and their formatting. +- **Added compareAll command:** Improved existing code of comparing two timetables to allow it to iteratively compare all currently existing timetables. +- **Added the next command:** Allows users to easily view the next upcoming task based on the current system time. + +#### Testing +- **Parser Tests:** Wrote the majority of tests on the Parser to ensure that proper input formatting can be read properly, and that incorrect inputs are caught as exceptions and produce relevant error messages. + +#### Documentation +- **Developer Guide - next command:** Added documentation and UML diagram for the implementation of the next command. + +#### Community +- **Bug Reports:** Reported bugs and suggestions for other teams in the class. [Issues. ](https://github.com/Leong-ZM/ped/issues) diff --git a/docs/team/z-wenqing.md b/docs/team/z-wenqing.md new file mode 100644 index 0000000000..b9641dddb9 --- /dev/null +++ b/docs/team/z-wenqing.md @@ -0,0 +1,49 @@ +# Zhang Wenqing's Project Portfolio Page + +## Project: Timetable Comparer + +### Team: AY2324S2-CS2113-T13-2 + +Timetable Comparer is a desktop application used for timetable management among a group of users. It operates through a Command Line Interface (CLI), enabling users to add their weekly tasks and utilize the tool to find common free time slots for group activities. + +Given below are my contributions to the project. + +* **Code Contributed**: https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=z-wenqing&breakdown=true + + +* **New feature**: Added the ability to store user data such as username and timetable of each user. + * When the user launches the application for the first time, a designated folder is created in the application's directory to store all data. + * When a new user is added, a file named after the user's name will be created within the created data folder. + * Tasks added by users are recorded in their respective files. + * On subsequent launches, all the existing users and their corresponding timetables will be retrieved and loaded, allowing users to check their existing tasks. + + +* **New feature**: Added the feature to check whether the task added clashes with the existing tasks. + + +* **New feature**: Added the feature to add tasks for multiple users. + * The users are able to add a task for multiple specified users simultaneously. This feature makes it more convenient for users to add common activities for multiple users, such as attending lectures, and they do not have to add tasks one by one. + * The usernames inputted by the user will be checked against the existing users and their timetables to make sure that the users specified are valid users and the task added does not clash with the existing tasks. The usernames that are invalid, as well as users that have conflict tasks will be outputted for users to clearly see the issue. + + +* **New feature**: Added the feature to add tasks for all users. + * The user can add a confirmed group activity for all users and they can also check events that are common for all users. + + +* **Contributions to the UG** + * Add examples of command in version 1.0. + + +* **Contributions to the DG** + * Add sequence diagram for the interaction between Storage class and other classes, illustrating how the storing and retrieving data work. + + +* **Contributions to team-based tasks** + * Set up the GitHub team org/repo. + * General code enhancements to fix testing errors and style errors to pass gradle checks. + + +* **Testing** + * Added extensive testing for storage class. A new folder named "test_data" is created for testing. During testing, the directory will be directed to "test_data" instead of "data" folder where all the actual user data is stored. + * Helped teammates to fix test case errors where storage is involved. + diff --git a/docs/team/zhusijia0711.md b/docs/team/zhusijia0711.md new file mode 100644 index 0000000000..0556783f14 --- /dev/null +++ b/docs/team/zhusijia0711.md @@ -0,0 +1,59 @@ +# Zhu Sijia's Project Portfolio Page + +## Project: Time Comparer + +Time Comparer is a desktop application used for time management between users. + +The user can add and delete tasks using the application and can compare their + +timetable with other users using the same desktop. + +Given below are my contributions to the project. + +- **New Feature:** Add a command that allows an user to add one task to his/her timetable. + - What it does: Allows the user to add a task with a task description, start time, end time and task type one at a time. + - Justification: This is the basic functionality for a time management application. + +- **New Feature:** Add a command that allows an user to delete the task he/she previously added. + - What it does: Allow the user to delete a task using the index of the task in a day. + - Justification: This is the basic functionality for a time management application. + +- **New Feature:** Add a command that allows an user to add a same task to different days. + - What it does: Allow the user to add a task which can happen in multiple days to the timetable. + - Justification: This feature reduces troublesome of users as they would not need to add repeated task one by one. + +- **New Feature:** Add a command that allows an user to change their task timing + - What is does: Allow the user to change the start time and end time for the previously added task. + - Justification: This feature reduces troublesome of users as they do not need to first delete the task and add the + task one more time in order to change the time. + +- **New Feature:** Add a command that allows an user to change their task type. + - What it does: In our application logic, if the task type is assigned to be flexible(f), it means the timings of the + task can be changed. If a task is marked as compulsory(c), it means its timing cannot be changed. This feature helps + users to change the task type whenever there is a change in their plan. + - Justification: This feature reduces troublesome of users as they do not need to first delete the task and add the + task one more time in order to change the task type. + +- - **New Feature:** Add a command that allows an user to see today's tasks(if any). + - What it does: Allow the users to see the tasks that happens today. + +- - **New Feature:** Add a command that allows an user to see urgent tasks in the next few hours. + - What it does: Allow the users to see the tasks that happens in the next few hours in today. + - If the stated urgent hours exceeds the 23:59, the method will print the tasks until the end of today. + +- Code contributed: + - https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=ZhuSijia0711&tabRepo=AY2324S2-CS2113-T13-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false + +- Project management: + - Managed releases v1.release, v2.0 release jar file and user guide pdf + +- Enhancement to existing features: + - Wrote tests for timetable class and input validator class (Pull Request #57, #59) + +- Documentation: + - User guide: + - Added documentation for the features addtask, delete task and so on. #73 + - Added developer guide. #64 + +- Community: + - Reported 9 bugs and suggestions for other teams in the class (above average). \ No newline at end of file diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/main/java/seedu/duke/InputValidator.java b/src/main/java/seedu/duke/InputValidator.java new file mode 100644 index 0000000000..0a763f0863 --- /dev/null +++ b/src/main/java/seedu/duke/InputValidator.java @@ -0,0 +1,232 @@ +package seedu.duke; + +import seedu.duke.exceptions.InvalidDayException; +import seedu.duke.exceptions.InvalidFormatException; +import seedu.duke.exceptions.InvalidUserException; + +public class InputValidator { + /** + * Validates correctly formatted compare command. The expected format is + * "compare user1 user2" + * + * @param input String Input. + * @throws InvalidFormatException If the input does not match the expected format: "compare (user1) (user2)". + */ + public static void validateCompareInput(String input) throws InvalidFormatException { + String regex = "(?i)^compare\\s+\\w+\\s+\\w+(\\s+)?$"; + + if (!input.matches(regex)) { + throw new InvalidFormatException("[ERROR] Invalid compare format. " + + "Expected format: compare "); + } + } + + public static void validateCompareAllInput(String input) throws InvalidFormatException { + String regex = "(?i)^compareall(\\s+)?$"; + + if (!input.matches(regex)) { + throw new InvalidFormatException("[ERROR] Invalid compareall format. " + + "Expected format: compareall"); + } + } + + /** + * Validates correctly formatted addTask command. The expected format is + * "addtask /on [date] /task [description] /from [start time] /to [start time] /type [type]" + * Note: Start and End times should be formatted as such: HH:mm + * Regex Guide: + * (?i)^ = case insensitive for command name + * \s+ = any amount of white space + * (\w+) = any non-digit word + * ((.+)?) = any combination of non numerical and numerical characters + * (\d{1,2}:\d{2}) = the format DD:DD (D = digit) + * (/type\s+[fc](\s+)?)$ = regex must end in "/type [f/c]" + * + * @param input String Input. + * @throws InvalidFormatException If the input does not match the expected format. + */ + public static void validateAddTaskInput(String input) throws InvalidFormatException { + String regex = "(?i)^addtask\\s+/on\\s+(\\w+)\\s+/task\\s+((.+)?)\\s" + + "/from\\s+(\\d{1,2}:\\d{2})\\s+/to\\s+(\\d{1,2}:\\d{2})\\s+(/type\\s+[fc](\\s+)?)$"; + + if (!input.matches(regex)) { + throw new InvalidFormatException("[ERROR] Invalid addtask format. " + + "Expected format: addtask /on [day] /task [description] /from [start time] /to [end time] " + + "/type [f/c]"); + } + } + public static void validateAddTaskWDCInput(String input) throws InvalidFormatException { + String regex = "(?i)^addtwdc\\s+/on\\s+(\\w+)\\s+/task\\s+(.+?)\\s" + + "+/from\\s+(\\d{1,2}:\\d{2})\\s+/to\\s+(\\d{1,2}:\\d{2})(\\s+/type\\s+[fc])$"; + + if (!input.matches(regex)) { + throw new InvalidFormatException("[ERROR] Invalid addTaskWithDuplicationCheck format. " + + "Expected format: addtwdc /on [day] /task [description] /from [start time] /to [end time] " + + "/type [f/c]"); + } + } + + /** + * Validates correctly formatted deleteTask command. The expected format is + * "deletetask /on [day] /index [index]" format + * Note: index is the index of the task in the task list for the given day + * + * @param input String Input. + * @throws InvalidFormatException If the input does not match the expected format. + */ + public static void validateDeleteTaskInput(String input) throws InvalidFormatException { + String regex = "(?i)^deletetask\\s+/on\\s+(\\w+)\\s+/index\\s+(\\d+)$"; + + if (!input.matches(regex)) { + throw new InvalidFormatException("[ERROR] Invalid deleteTask format. " + + "Expected format: deleteTask /on [day] /index [index]"); + } + } + + /** + * Validates correctly formatted addUser command. The expected format is "adduser user" + * + * @param input String Input. + * @throws InvalidFormatException If the input does not match the expected format: "adduser (user)". + */ + public static void validateAddUserInput(String input) throws InvalidFormatException { + String regex = "(?i)^adduser\\s+\\w+(\\s+)?$"; + + if (!input.matches(regex)) { + throw new InvalidFormatException("[ERROR] Invalid addUser format. " + + "Expected format: adduser "); + } + } + + /** + * Validates correctly formatted switch command. The expected format is "switch user" + * + * @param input String Input. + * @throws InvalidFormatException If the input does not match the expected format: "switch (user)". + */ + public static void validateSwitchInput(String input) throws InvalidFormatException { + String regex = "(?i)^switch\\s+\\w+$"; + + if (!input.matches(regex)) { + throw new InvalidFormatException("[ERROR] Invalid switch format. " + + "Expected format: switch "); + } + } + + /** + * Validates correctly spelled usernames that currently exist in the user database. + * + * @param input String Input. + * @throws InvalidUserException If the input does not match any current user's name in the user database. + */ + public static void validateUserInput(String input, UserList userList) throws InvalidUserException { + if (userList.getUsers().isEmpty()) { + throw new InvalidUserException("[ERROR] Current User List is empty. Please add users."); + } + for (User u : userList.getUsers()) { + if (u.getName().equals(input)) { + return; + } + } + throw new InvalidUserException("[ERROR] Invalid User: " + input + ". Please input a existing user name"); + } + + /** + * Validates if the inputted string is an actual day. + * + * @param input String Input. + * @throws InvalidDayException If the input is not an actual day. + */ + public static void validateDay(String input) throws InvalidDayException { + String[] validDays = new String[]{"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"}; + + for (String day : validDays) { + if (day.equalsIgnoreCase(input)) { + return; + } + } + throw new InvalidDayException("[ERROR] Invalid day. Please enter a day from Monday - Sunday."); + } + + /** + * Validates whether a Timetable exist or not. + * + * @param table inputted Timetable. + * @throws NullPointerException If the input Timetable does not exist. + */ + public static void validateTableExistence(Timetable table) throws NullPointerException { + if (table == null) { + throw new NullPointerException("Timetable object is null."); + } + } + + public static void validateChangeTaskTiming(String input) throws InvalidFormatException { + String prefix = "(?i)^changeTaskTiming\\s+/on\\s+"; + String dayPattern = "(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)"; + String indexPattern = "\\d+"; + String startPattern = "\\d{1,2}:\\d{2}"; + String endPattern = "\\d{1,2}:\\d{2}"; + String suffix = "$"; + String regex = prefix + dayPattern + "\\s+/index\\s+" + indexPattern + "\\s+/from\\s+" + + startPattern + "\\s+/to\\s+" + endPattern + suffix; + if (!input.matches(regex)) { + throw new InvalidFormatException("[ERROR] Invalid changeTaskTiming format. " + + "Expected format: changeTaskTiming /on [day] /index [index] /from [new start time] " + + "/to [new end time]"); + } + } + + public static void validateAddTaskForAll(String input) throws InvalidFormatException { + String regex = "(?i)^addforall\\s+/on\\s+(\\w+)\\s+/task\\s+((.+)?)\\s" + + "/from\\s+(\\d{1,2}:\\d{2})\\s+/to\\s+(\\d{1,2}:\\d{2})\\s?"; + + if (!input.matches(regex)) { + throw new InvalidFormatException("[ERROR] Invalid addforall format. " + + "Expected format: addforall /on [day] /task [description] /from [start time] /to [end time]"); + } + } + + public static void validateChangeTaskType(String input) throws InvalidFormatException { + String prefix = "(?i)^changeTaskType\\s+/on\\s+"; + String dayPattern = "(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)"; + String indexPattern = "\\d+"; + String typePattern = "[fc]"; // Assuming task types can be 'f' for flexible and 'c' for compulsory + String suffix = "$"; + String regex = prefix + dayPattern + "\\s+/index\\s+" + indexPattern + "\\s+/type\\s+" + typePattern + suffix; + if (!input.matches(regex)) { + throw new InvalidFormatException("[ERROR] Invalid changeTaskType format. " + + "Expected format: changeTaskType /on [day] /index [index] /type [f/c]"); + } + } + + public static void validateAddRepeatTask(String input) throws InvalidFormatException { + String regex = "(?i)^addrepeattask\\s+/task\\s+(.+?)\\s+/on\\s+(\\w+(\\s+\\w+)*)\\s+" + + "/from\\s+(\\d{1,2}:\\d{2})\\s+/to\\s+(\\d{1,2}:\\d{2})\\s+/type\\s+([fc])$"; + + if (!input.matches(regex)) { + throw new InvalidFormatException("[ERROR] Invalid addRepeatTask format. " + + "Expected format: addRepeatTask /task [description] /on [day(s)] /from [start time] " + + "/to [end time] /type [f/c]"); + } + } + + public static void validAddFor(String input) throws InvalidFormatException { + String commandPattern = "(?i)addfor\\s+"; + String usersPattern = "/user\\s+([\\w\\s,]+)\\s+"; + String dayPattern = "/on\\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)\\s+"; + String taskDescriptionPattern = "(?i)/task\\s+([\\w\\s]+)\\s+"; + String startPattern = "/from\\s+(\\d{1,2}:\\d{2})\\s+"; + String endPattern = "/to\\s+(\\d{1,2}:\\d{2})\\s+"; + String typePattern = "/type\\s+([cfCF])"; + String suffix = "$"; + String pattern = commandPattern + usersPattern + dayPattern + taskDescriptionPattern + + startPattern + endPattern + typePattern + suffix; + + if (!input.matches(pattern)) { + throw new InvalidFormatException("[ERROR] Invalid addfor format. " + + "Expected format: addfor /user [user1], [user2], ... /on [day] /task [description] " + + "/from [start time] /to [end time] /type [f/c]"); + } + } +} + diff --git a/src/main/java/seedu/duke/Main.java b/src/main/java/seedu/duke/Main.java new file mode 100644 index 0000000000..d8151d6240 --- /dev/null +++ b/src/main/java/seedu/duke/Main.java @@ -0,0 +1,40 @@ +package seedu.duke; + +import seedu.duke.exceptions.NoUserException; +import seedu.duke.ui.UI; + +import java.io.FileNotFoundException; +import java.util.Scanner; + +public class Main { + static final Scanner IN = new Scanner(System.in); + static boolean isFinished = false; + + public static void setIsFinished(boolean b) { + isFinished = b; + } + + public static void main(String[] args) throws FileNotFoundException { + UI.printGreeting(); + UserList userList = new UserList(); + Storage.createFolder(); + Storage.addExistingUsers(userList); + + if (userList.getListLength() == 0) { + UI.printHelp(); + } + + while (!isFinished) { + try { + String input = IN.nextLine(); + Parser.parseCommand(input, userList); + + } catch (NoUserException e) { + UI.printNoUsers(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + + } + } +} diff --git a/src/main/java/seedu/duke/Parser.java b/src/main/java/seedu/duke/Parser.java new file mode 100644 index 0000000000..cb91e41fbc --- /dev/null +++ b/src/main/java/seedu/duke/Parser.java @@ -0,0 +1,535 @@ +package seedu.duke; + +import seedu.duke.exceptions.InvalidDayException; +import seedu.duke.exceptions.InvalidFormatException; +import seedu.duke.exceptions.InvalidUserException; +import seedu.duke.exceptions.NoUserException; +import seedu.duke.ui.UI; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.LocalDate; +import java.time.DayOfWeek; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +public class Parser { + public static final String[] DAYS = new String[] + {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}; + + private static final int COMMAND_INDEX_DAY = 2; + private static final int USERS_INDEX = 5; + private static final int DAY_INDEX = 3; + private static final int DESCRIPTION_INDEX = 5; + private static final int START_INDEX = 5; + private static final int END_INDEX = 3; + private static final int TYPE_INDEX = 5; + private static final int DAY_INDEX_IN_ADD_FOR_ALL = 2; + private static final int USER_PART = 1; + private static final int DAY_PART = 2; + private static final int DESCRIPTION_PART = 3; + private static final int START_PART = 4; + private static final int END_PART = 5; + private static final int TYPE_PART = 6; + + public static String capitalizeFirstLetter(String input) { + String lowerCase = input.toLowerCase(); + return lowerCase.substring(0, 1).toUpperCase() + lowerCase.substring(1); + } + + /** + * Parses User Input and Identifies the command used. + * + * @param command The users text input. + */ + public static void parseCommand(String command, UserList userList) throws + InvalidFormatException, InvalidDayException, InvalidUserException, NoUserException, IOException { + if (command.equalsIgnoreCase("list")) { + UI.printListingUsers(); + userList.listAll(); + } else if (command.equalsIgnoreCase("help")) { + UI.printHelp(); + } else if (command.equalsIgnoreCase("bye")) { + UI.printBye(); + Main.setIsFinished(true); + } else if (command.equalsIgnoreCase("current")) { + UI.printActiveUser(userList.getActiveUser().getName()); + } else if (command.equalsIgnoreCase("view")) { + userList.getActiveUser().viewTimetable(); + } else if (command.equalsIgnoreCase("next")) { + printNextTask(userList.getActiveUser()); + } else if (command.toLowerCase().startsWith("adduser")) { + InputValidator.validateAddUserInput(command); + String[] parts = command.split("\\s+"); + String userName = Parser.capitalizeFirstLetter(parts[1]); + if (userList.containsUser(userName)) { + throw new InvalidUserException("User already exists. Use a different name. "); + } else { + User newUser = new User(userName); + UI.printNewUser(newUser.getName()); + userList.addUser(newUser); + newUser.getStorage().addUserInFolder(); + } + } else if (command.toLowerCase().startsWith("switch")) { + InputValidator.validateSwitchInput(command); + String[] parts = command.split("\\s+"); + String userName = parts[1]; + userList.setActiveUser(userList.findUser(userName)); + UI.printActiveUser(userList.getActiveUser().getName()); + } else if (command.toLowerCase().startsWith("addtask")) { + if (userList.getUsers().isEmpty()) { + throw new NoUserException(); + } + Task task = parseTask(command); + User currentUser = userList.getActiveUser(); + if (checkClash(task, currentUser)) { + UI.printClashTasks(); + return; + } + addTask(command, userList); + currentUser.getStorage().writeTaskInFile(currentUser); + } else if (command.toLowerCase().startsWith("addtwdc")) { + addTaskWithDuplicationCheck(command, userList); + } else if (command.toLowerCase().startsWith("deletetask")) { + deleteTask(command, userList); + } else if (command.toLowerCase().startsWith("changetasktiming")) { + changeTaskTiming(command, userList); + } else if (command.toLowerCase().startsWith("addrepeattask")) { + if (userList.getUsers().isEmpty()) { + throw new NoUserException(); + } + addRepeatTask(command, userList); + } else if (command.toLowerCase().startsWith("changetasktype")) { + if (userList.getUsers().isEmpty()) { + throw new NoUserException(); + } + changeTaskType(command, userList); + } else if (command.toLowerCase().startsWith("todaytask")) { + if (userList.getUsers().isEmpty()) { + throw new NoUserException(); + } + todayTask(command,userList); + } else if (command.toLowerCase().startsWith("compareall")) { + InputValidator.validateCompareAllInput(command); + UI.printComparingAll(); + UI.printSharedTime(Timetable.compareAllTimetables(userList)); + } else if (command.toLowerCase().startsWith("compare")) { + InputValidator.validateCompareInput(command); + String[] parts = command.split("\\s+"); + String user1 = capitalizeFirstLetter(parts[1]); + String user2 = capitalizeFirstLetter(parts[2]); + InputValidator.validateUserInput(user1, userList); + InputValidator.validateUserInput(user2, userList); + UI.printCompareUsers(user1, user2); + UI.printSharedTime(Timetable.compareTimetable(userList.findUser(user1).getTimetable(), + userList.findUser(user2).getTimetable())); + } else if (command.toLowerCase().startsWith("addforall")) { + InputValidator.validateAddTaskForAll(command); + addTaskForAll(command, userList); + } else if (command.toLowerCase().startsWith("addfor")) { + InputValidator.validAddFor(command); + addFor(command, userList); + } else if (command.toLowerCase().startsWith("viewcommonevents")) { + printConfirmedEvent(userList); + } else if (command.toLowerCase().startsWith("urgent /in")) { + if (userList.getUsers().isEmpty()) { + throw new NoUserException(); + } + printUrgentTasks(command, userList); + } else { + UI.printInvalidCommand(); + } + } + + private static void printUrgentTasks(String command, UserList userList) { + try { + String[] parts = command.split("\\s+"); + int hours = Integer.parseInt(parts[2]); + + LocalDateTime now = LocalDateTime.now(); + int currentHour = now.getHour(); + if(currentHour + hours >= 24){ + System.out.println("Hour input exceeds current day."); + } + LocalDateTime deadline = now.plusHours(hours); + List urgentTasks = findUrgentTasks(userList.getActiveUser(), now, deadline); + if (urgentTasks.isEmpty()) { + System.out.println("No urgent tasks within the specified timeframe."); + } else { + if(currentHour + hours >= 24){ + System.out.println("Urgent tasks until end of today: "); + for (Task task : urgentTasks) { + System.out.println(task); + } + } else { + System.out.println("Urgent tasks within the next " + hours + " hours:"); + for (Task task : urgentTasks) { + System.out.println(task); + } + } + } + } catch (NumberFormatException | IndexOutOfBoundsException e) { + System.out.println("Invalid command format. Please use: urgent /in [hours]"); + } + } + + private static List findUrgentTasks(User user, LocalDateTime now, LocalDateTime deadline) { + List urgentTasks = new ArrayList<>(); + Timetable userTimetable = user.getTimetable(); + String currentDayOfWeek = now.getDayOfWeek().toString().toLowerCase(); // Get current day of the week + String capitalisedDay = Parser.capitalizeFirstLetter(currentDayOfWeek); + ArrayList tasks = userTimetable.getWeeklyTasks().get(capitalisedDay); + if (tasks != null) { + for (Task task : tasks) { + LocalDateTime taskStartTime = LocalDateTime.of(now.toLocalDate(), task.getStartTime()); + if (now.isBefore(taskStartTime) && taskStartTime.isBefore(deadline)) { + urgentTasks.add(task); + } + } + } + return urgentTasks; + } + + + + private static void changeTaskType(String command, UserList userList) throws InvalidFormatException { + try { + InputValidator.validateChangeTaskType(command); + String[] parts = command.split("\\s+"); + List wordList = Arrays.asList(parts); + String day = wordList.get(COMMAND_INDEX_DAY); + int index = Integer.parseInt(wordList.get(wordList.indexOf("/index") + 1)); + String newType = wordList.get(wordList.indexOf("/type") + 1); + InputValidator.validateDay(day); + + User currentUser = userList.getActiveUser(); + currentUser.getTimetable().changeTaskType(day, index - 1, newType); + System.out.println("Task type changed successfully."); + currentUser.getStorage().writeTaskInFile(currentUser); + } catch (NumberFormatException | IOException e) { + throw new RuntimeException(e); + } catch (IndexOutOfBoundsException e) { + throw new InvalidFormatException("The selected task does not exist. "); + } catch (InvalidDayException e) { + throw new InvalidFormatException("[ERROR] Invalid day. Please enter a day from Monday - Sunday. "); + } + } + + private static void deleteTask(String command, UserList userList) { + try { + InputValidator.validateDeleteTaskInput(command); + String[] parts = command.split("\\s+"); + String day = parts[2]; + int index = Integer.parseInt(parts[4]) - 1; + InputValidator.validateDay(day); + User currentUser = userList.getActiveUser(); + currentUser.getTimetable().deleteUserTask(day, index); + currentUser.getStorage().writeTaskInFile(currentUser); + } catch (InvalidFormatException | InvalidDayException e) { + System.out.println(e.getMessage()); + } catch (IOException e) { + System.out.println("Something went wrong: " + e.getMessage()); + } + } + + /** + * Adds a task to the timetable with task duplication detection. + * + * @param command The user input command. + * @param userList The list of users. + */ + private static void addTaskWithDuplicationCheck(String command, UserList userList) { + try { + InputValidator.validateAddTaskWDCInput(command); + Task task = parseTask(command); + boolean addedSuccessfully = userList.getActiveUser().getTimetable() + .addUserTaskWithDuplicationCheck(task.day, task); + + if (addedSuccessfully) { + UI.printAddTask(task); + } else { + System.out.println("Task already exists. Cannot add duplicate task."); + } + } catch (InvalidFormatException | InvalidDayException e) { + System.out.println(e.getMessage()); + } + } + + private static void addTask(String command, UserList userList) { + try { + InputValidator.validateAddTaskInput(command); + Task task = parseTask(command); + User currentuser = userList.getActiveUser(); + currentuser.getTimetable().addUserTask(task.day, task); + UI.printAddTask(task); + + } catch (InvalidFormatException | InvalidDayException e) { + System.out.println(e.getMessage()); + } + } + + public static boolean checkClash(Task taskToBeAdded, User user) { + Timetable timetable = user.getTimetable(); + String day = taskToBeAdded.day; + String capitalisedDay = Parser.capitalizeFirstLetter(day); + timetable.getWeeklyTasks().get(capitalisedDay).sort(Comparator.comparing(Task::getStartTime)); + for (Task task : timetable.getWeeklyTasks().get(capitalisedDay)) { + if (taskToBeAdded.startTime.isAfter(task.getStartTime()) && + taskToBeAdded.startTime.isBefore(task.getEndTime()) + || (taskToBeAdded.endTime.isAfter(task.getStartTime()) && + taskToBeAdded.endTime.isBefore(task.getEndTime())) + || (taskToBeAdded.startTime.equals(task.getStartTime())) + || (taskToBeAdded.endTime.equals(task.getEndTime())) + || (taskToBeAdded.startTime.isBefore(task.getStartTime()) && + taskToBeAdded.endTime.isAfter(task.getEndTime()))) { + return true; + } + } + return false; + } + + public static Task parseTask(String command) throws InvalidDayException, InvalidFormatException { + InputValidator.validateAddTaskInput(command); + String[] parts = command.split("\\s+"); + List wordList = Arrays.asList(parts); + String day = Parser.capitalizeFirstLetter(parts[2]); + String description = parseDescription(wordList); + String startTime = parts[wordList.indexOf("/from") + 1]; + String endTime = parts[wordList.indexOf("/to") + 1]; + String type = parts[wordList.indexOf("/type") + 1]; + InputValidator.validateDay(day); + return new Task(description, day, startTime, endTime, type); + } + + public static Task parseAddForAllTask(String command) throws InvalidDayException { + String[] parts = command.split("\\s+"); + List wordList = Arrays.asList(parts); + String day = Parser.capitalizeFirstLetter(parts[DAY_INDEX_IN_ADD_FOR_ALL]); + String description = parseDescription(wordList); + String startTime = parts[wordList.indexOf("/from") + 1]; + String endTime = parts[wordList.indexOf("/to") + 1]; + + String type = "common"; + InputValidator.validateDay(day); + return new Task(description, day, startTime, endTime, type); + } + + /** + * Prints tasks for today. + * + * @param userList The list of users. + */ + public static void todayTask(String command, UserList userList) { + String[] parts = command.trim().split("\\s+"); + if(parts.length > 1){ + System.out.println("Please simply use 'todaytask' to view today's task" + + " without additional text!"); + return; + } + String dayOfWeek = DayOfWeek.from(LocalDate.now()).toString(); + String capitalizedDay = Parser.capitalizeFirstLetter(dayOfWeek); + ArrayList tasksForToday = userList.getActiveUser().getTimetable().getWeeklyTasks().get(capitalizedDay); + if (tasksForToday.isEmpty()) { + UI.printNoTask("today"); + return; + } + UI.printLine(); + UI.printDayHeader("Today"); + int count = 1; + for (Task task : tasksForToday) { + UI.printTaskInList(count, task.toString()); + count++; + } + } + + private static void changeTaskTiming(String command, UserList userList) throws + InvalidFormatException, InvalidDayException { + try { + InputValidator.validateChangeTaskTiming(command); + String[] parts = command.split("\\s+"); + List wordList = Arrays.asList(parts); + String day = parts[2]; + int index = Integer.parseInt(parts[wordList.indexOf("/index") + 1]); + LocalTime newStartTime = LocalTime.parse(parts[wordList.indexOf("/from") + 1]); + LocalTime newEndTime = LocalTime.parse(parts[wordList.indexOf("/to") + 1]); + InputValidator.validateDay(day); + + User currentUser = userList.getActiveUser(); + currentUser.getTimetable().changeFlexibleTaskTiming(day, + index - 1, newStartTime, newEndTime); + currentUser.getStorage().writeTaskInFile(currentUser); + System.out.println("Flexible task timing changed successfully."); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (InvalidDayException e) { + throw new InvalidDayException("[ERROR] Invalid day. Please enter a day from Monday - Sunday."); + } + } + + private static void addRepeatTask(String command, UserList userList) { + try { + InputValidator.validateAddRepeatTask(command); + String[] parts = command.split("\\s+"); + List wordlist = Arrays.asList(parts); + int taskIndex = wordlist.indexOf("/task"); + if (taskIndex == -1 || taskIndex + 1 >= wordlist.size()) { + throw new InvalidFormatException(("Please enter a task name!")); + } + String description = command.substring(command.indexOf("/task") + "/task".length(), + command.indexOf("/on")).trim(); + int daysIndex = wordlist.indexOf("/on") + 1; + int endDaysIndex = wordlist.indexOf("/from"); + String[] days = Arrays.copyOfRange(parts, daysIndex, endDaysIndex); + if (days.length < 2) { + throw new InvalidDayException("Please enter at least 2 days, or you want to use addtask command!"); + } + String startTime = parts[wordlist.indexOf("/from") + 1]; + String endTime = parts[wordlist.indexOf("/to") + 1]; + String type = parts[wordlist.indexOf("/type") + 1]; + User currentUser = userList.getActiveUser(); + for (String day : days) { + String capitalizedDay = Parser.capitalizeFirstLetter(day); + Task task = new Task(description, capitalizedDay, startTime, endTime, type); + currentUser.getTimetable().addUserTask(capitalizedDay, task); + } + currentUser.getStorage().writeTaskInFile(currentUser); + System.out.println("Repeated task added successfully!"); + } catch (InvalidDayException e) { + System.out.println("Invalid day input: " + e.getMessage()); + } catch (InvalidFormatException e) { + System.out.println("[ERROR] Invalid addRepeatTask format.\n" + + "Expected format: addRepeatTask /task [description] /on [day(s)] /from [start time]" + + "/to [end time] /type [f/c]"); + } catch (IOException e) { + System.out.println("Error: " + e.getMessage()); + } + } + + + private static String parseDescription(List words) { + int startIndex = words.indexOf("/task") + 1; + int endIndex = words.indexOf("/from") - 1; + StringBuilder description = new StringBuilder(); + for (int i = startIndex; i <= endIndex; i++) { + description.append(words.get(i)); + if (i < endIndex) { + description.append(" "); + } + } + return description.toString(); + } + + + private static void addTaskForAll(String command, UserList userList) + throws InvalidDayException, IOException { + //InputValidator.validateAddTaskForAll(command); + Task task = parseAddForAllTask(command); + assert !userList.getUsers().isEmpty() : "There is no user added."; + for (User user : userList.getUsers()) { + user.getTimetable().addUserTask(task.day, task); + user.getStorage().writeTaskInFile(user); + } + UI.printAddForAll(task); + } + + private static void printConfirmedEvent(UserList userList) { + assert !userList.getUsers().isEmpty() : "There is no user added."; + int taskCount = 1; + for (String day : DAYS) { + for (Task task : userList.getActiveUser().getTimetable().getWeeklyTasks().get(day)) { + if (task.type.equals("common")) { + System.out.println(taskCount + ". " + task); + taskCount++; + } + } + } + if (taskCount == 1) { + System.out.println("There is no common events."); + } + } + + private static void printNextTask(User currentUser) { + LocalTime currentTime = LocalTime.now(); + String dayOfWeek = DayOfWeek.from(LocalDate.now()).toString(); + String capitalizedDay = Parser.capitalizeFirstLetter(dayOfWeek); + Task current = new Task("temp", dayOfWeek, currentTime.toString(), currentTime.toString(), "f"); + + ArrayList tasksOfDay = currentUser.getTimetable().getWeeklyTasks().get(capitalizedDay); + int numOfTasks = tasksOfDay.size(); + + if (numOfTasks == 0) { + UI.printNoTasks(); + return; + } + Task nextTask = null; + for (int i = numOfTasks - 1; i >= 0; i -= 1) { + if (current.getStartTime().isBefore(tasksOfDay.get(i).getStartTime())) { + nextTask = tasksOfDay.get(i); + } else { + if (nextTask == null) { + UI.printNoTasks(); + } else { + UI.printNext(); + System.out.println(nextTask); + } + return; + } + } + } + + private static void addFor(String command, UserList userList) throws IOException, InvalidUserException { + String[] words = command.split("/"); + String users = words[USER_PART].substring(USERS_INDEX).trim(); // to exclude words "/user" + String day = words[DAY_PART].substring(DAY_INDEX).trim(); // to exclude words "/on" + String capitalisedDay = Parser.capitalizeFirstLetter(day); + String description = words[DESCRIPTION_PART].substring(DESCRIPTION_INDEX).trim(); // to exclude words "/task" + String start = words[START_PART].substring(START_INDEX).trim(); // to exclude words "/from" + String end = words[END_PART].substring(END_INDEX).trim(); // to exclude words "/to" + String type = words[TYPE_PART].substring(TYPE_INDEX).trim(); // to exclude words "/type" + String[] usernames = users.split(","); + + Task task = new Task(description, capitalisedDay, start, end, type); + ArrayList clashedUsers = new ArrayList<>(); + ArrayList invalidUsers = new ArrayList<>(); + for (String username : usernames) { + User user = userList.findUser(username.trim()); + if (user == null) { + if (!invalidUsers.contains(username.trim())) { + invalidUsers.add(username.trim()); + } + continue; + } + if (checkClash(task, user)) { + if (!clashedUsers.contains(username.trim())) { + clashedUsers.add(username.trim()); + } + } + } + + if (!invalidUsers.isEmpty() || !clashedUsers.isEmpty()) { + StringBuilder errorMessage = new StringBuilder(); + if (!invalidUsers.isEmpty()) { + errorMessage.append("Invalid users: ").append(String.join(", ", invalidUsers)).append(". "); + } + if (!clashedUsers.isEmpty()) { + errorMessage.append("Users with task clashes: ").append(String.join(", ", clashedUsers)).append(". "); + } + throw new InvalidUserException(errorMessage.toString()); + } + + + for (String username : usernames) { + User user = userList.findUser(username.trim()); + if (!checkClash(task, user)) { + user.getTimetable().addUserTask(capitalisedDay, task); + user.getStorage().writeTaskInFile(user); + } else { + System.out.println("For " + user.getName() + + ", the task " + task + " clashes with existing tasks OR already exist!"); + } + } + } +} diff --git a/src/main/java/seedu/duke/Storage.java b/src/main/java/seedu/duke/Storage.java new file mode 100644 index 0000000000..2ea38cea66 --- /dev/null +++ b/src/main/java/seedu/duke/Storage.java @@ -0,0 +1,224 @@ +package seedu.duke; + +import seedu.duke.ui.UI; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Scanner; + +import static seedu.duke.Parser.DAYS; + +/** + * Represents a storage manager that deals with loading tasks from the file and saving tasks in the file. + */ + + +public class Storage { + public static final int START_TIME_INDEX = 3; + public static final int END_TIME_INDEX_INCREMENT = 2; + public static final int END_TIME_END_INDEX = 16; + public static final int DESCRIPTION_INDEX = 18; + public static final int TYPE_INDEX_INCREMENT = 7; + + public static String folderPath = "data"; + public static final String BOX_OUTLINE = "+---------+\n"; + public static final String BOX_OUTLINE_FOR_WEDNESDAY = "+-------------+\n"; + public static final String BOX_OUTLINE_FOR_FRIDAY = "+------+\n"; + public static final String LINE_SEPARATOR = + ".................................................................................................\n"; + public String filePath; + + public Storage(String filePath) { + this.filePath = filePath; + } + + /** + * Gets the file path of the timetable of the user. + * + * @return the string that represents the filepath of the file storing the timetable of the user. + */ + public String getFilePath() { + return filePath; + } + + /** + * Gets the folder path of the folder where all data is stored. + * + * @return the string that represents the path of the folder where all data is stored. + */ + public static String getFolderPath() { + return folderPath; + } + + /** + * Sets the path of the data folder to a new path. + * + * @param newPath the string that represents the new path, which should be a relative path. + */ + public static void setFolderPath(String newPath) { + folderPath = newPath; + } + + /** + * Creates a new folder in the same directory when the application is first launched. + * Otherwise, print that the folder has already been created. + */ + public static void createFolder() { + File folder = new File(folderPath); + + if (!folder.exists()) { + boolean folderCreated = folder.mkdirs(); + if (folderCreated) { + System.out.println("Folder created successfully."); + } else { + System.out.println("Failed to create folder."); + } + } else { + System.out.println("Folder already exists."); + } + } + + /** + * Add users that are stored in the data folder to the user list, for a returning user. + * + * @param userList the list of users added. + * @throws FileNotFoundException if the data file is not found. + */ + public static void addExistingUsers(UserList userList) throws FileNotFoundException { + File directory = new File(folderPath); + + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file != null) { + String fileName = file.getName(); + int indexOfDot = fileName.indexOf("."); + String userName = fileName.substring(0, indexOfDot); + User user = new User(userName); + userList.addUser(user); + user.getStorage().loadData(user); + } + } + } else { + UI.printEmptyDirectory(); + } + + } + + /** + * Loads data from the data file. + * + * @param user the user that we want to load data. + * @throws FileNotFoundException if the data file is not found. + */ + public void loadData(User user) throws FileNotFoundException { + File f = new File(filePath); + String day = ""; + Scanner s = new Scanner(f); + while (s.hasNext()) { + String line = s.nextLine(); + // ignore lines for formatting and weeks that have no tasks + if (line.startsWith("Username") || line.startsWith("+") || line.startsWith(".") + || line.isEmpty() || line.equals("No task :)")) { + continue; + } + if (line.startsWith("|")) { + day = line.substring(1, line.length() - 1).trim(); + continue; + } + user.getTimetable().addUserTask(day, extractTaskInfo(line, day)); + } + } + + /** + * Creates a data file for a new user when a new user is added. + */ + public void addUserInFolder() { + File f = new File(filePath); + try { + if (f.createNewFile()) { + System.out.println("File created: " + f.getName()); + } + } catch (IOException e) { + System.out.println("Something went wrong: " + e.getMessage()); + } + } + + /** + * Extracts task information from the local text file. + * + * @param line the current line in the file. + * @param day the day of the week. + * @return a Task object represented by this line. + */ + public static Task extractTaskInfo(String line, String day) { + int indexOfDash = line.indexOf("-"); + String startTime = line.substring(START_TIME_INDEX, indexOfDash).trim(); + String endTime = line.substring(indexOfDash + END_TIME_INDEX_INCREMENT, END_TIME_END_INDEX).trim(); + int indexOfType = line.indexOf("(type:"); + String description = line.substring(DESCRIPTION_INDEX, indexOfType).trim(); + int indexOfRightParenthesis = line.indexOf(")"); + String type = line.substring(indexOfType + TYPE_INDEX_INCREMENT, indexOfRightParenthesis); + return new Task(description, day, startTime, endTime, type); + } + + /** + * Writes the text to data file + * + * @param filePath a relative path giving the location of the data file, relative to the current working directory. + * @param textToAdd text to write to the file. + * @param isAppend whether to append the text or overwrite the whole file. + * @throws IOException If there is something wrong. + */ + public static void writeToFile(String filePath, String textToAdd, boolean isAppend) throws IOException { + FileWriter fw = new FileWriter(filePath, isAppend); + fw.write(textToAdd); + fw.close(); + } + + /** + * Adds task in file. + * + * @param user the user that the timetable belongs to. + */ + + public void writeTaskInFile(User user) throws IOException { + Timetable timetable = user.getTimetable(); + writeToFile(filePath, "Username: " + user.getName() + "\n", false); + for (String day : DAYS) { + String outline; + switch (day) { + case ("Wednesday"): + outline = BOX_OUTLINE_FOR_WEDNESDAY; + break; + case ("Friday"): + outline = BOX_OUTLINE_FOR_FRIDAY; + break; + default: + outline = BOX_OUTLINE; + break; + } + writeToFile(filePath, outline, true); + writeToFile(filePath, "| " + day + " |" + "\n", true); + writeToFile(filePath, outline, true); + + if (timetable.getWeeklyTasks().get(day).isEmpty()) { + writeToFile(filePath, "No task :)\n", true); + } else { + int taskCount = 1; + for (Task task : timetable.getWeeklyTasks().get(day)) { + writeToFile(filePath, taskCount + ". " + task.getStartTime() + " - " + task.getEndTime() + + ": " + task.getDescription() + " (type: " + task.getType() + ")" + "\n", true); + taskCount += 1; + } + } + writeToFile(filePath, LINE_SEPARATOR, true); + writeToFile(filePath, "\n", true); + } + + System.out.println("Timetable has been written to " + filePath); + + } +} diff --git a/src/main/java/seedu/duke/Task.java b/src/main/java/seedu/duke/Task.java new file mode 100644 index 0000000000..084868de99 --- /dev/null +++ b/src/main/java/seedu/duke/Task.java @@ -0,0 +1,70 @@ +package seedu.duke; + +import java.time.LocalTime; + +public class Task { + protected String description; + protected String day; + protected LocalTime startTime; + protected LocalTime endTime; + protected String type; + + /** + * Represents the constructor for Task class that takes in parameters including the description of the task, + * the day of the task, the starting time and the ending time of the task. + * + * @param description description of the task. + * @param day day of the task. + * @param from starting time of the task. + * @param to ending time of the task. + */ + public Task(String description, String day, String from, String to, String type) { + this.description = description; + this.day = day; + String fromHour = from.split(":")[0]; + String fromMinute = from.split(":")[1]; + String toHour = to.split(":")[0]; + String toMinute = to.split(":")[1]; + String formattedFrom = formatDates(fromHour) + ":" + formatDates(fromMinute); + String formattedTo = formatDates(toHour) + ":" + formatDates(toMinute); + this.startTime = LocalTime.parse(formattedFrom); + this.endTime = LocalTime.parse(formattedTo); + this.type = type; + } + + public LocalTime getStartTime() { + return startTime; + } + + public LocalTime getEndTime() { + return endTime; + } + + public String getType() { + return type; + } + + public void setEndTime(LocalTime endTime) { + this.endTime = endTime; + } + + public void setStartTime(LocalTime startTime) { + this.startTime = startTime; + } + + public void setType(String type) { + this.type = type; + } + + private String formatDates(String time) { + return time.length() == 1 ? "0" + time : time; + } + public String getDescription(){ + return description; + } + + @Override + public String toString() { + return description + " (" + day + " from " + startTime + " to " + endTime + ")" + " type: " + type; + } +} diff --git a/src/main/java/seedu/duke/Timetable.java b/src/main/java/seedu/duke/Timetable.java new file mode 100644 index 0000000000..835086c28f --- /dev/null +++ b/src/main/java/seedu/duke/Timetable.java @@ -0,0 +1,180 @@ +package seedu.duke; + +import seedu.duke.ui.UI; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +import static seedu.duke.ui.UI.printTasksOfTheDay; + +/** + * This class represents the Timetable object consisting of Arraylist of Tasks for each day of the week. + */ +public class Timetable { + public static final String[] DAYS = new String[] + {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}; + private Map> weeklyTasks; // Map to store tasks for each day + + public Timetable() { + weeklyTasks = new HashMap<>(); + //Initialize the map with empty lists for each day + for (String day : DAYS) { + weeklyTasks.put(day, new ArrayList<>()); + } + } + + public Map> getWeeklyTasks() { + return weeklyTasks; + } + + /** + * Adds task on dayOfWeek at an index + * @param dayOfWeek first Timetable. + * @param task task to add. + */ + public void addUserTask(String dayOfWeek, Task task) { + String capitalizedDay = dayOfWeek.substring(0, 1).toUpperCase() + dayOfWeek.substring(1); + weeklyTasks.get(capitalizedDay).add(task); + } + + /** + * Adds a task to the timetable with duplication check. + * @param dayOfWeek The day of the week. + * @param task The task to add. + * @return True if the task was added successfully, false if a duplicate was found. + */ + public boolean addUserTaskWithDuplicationCheck(String dayOfWeek, Task task) { + String capitalizedDay = dayOfWeek.substring(0, 1).toUpperCase() + dayOfWeek.substring(1); + ArrayList tasksOfDay = weeklyTasks.get(capitalizedDay); + for (Task existingTask : tasksOfDay) { + if (existingTask.getDescription().equalsIgnoreCase(task.getDescription()) + && existingTask.getStartTime().equals(task.getStartTime()) + && existingTask.getEndTime().equals(task.getEndTime())) { + return false; + } + } + tasksOfDay.add(task); + return true; + } + + + /** + * Deletes task on dayOfWeek at an index + * @param dayOfWeek first Timetable. + * @param index index of task within task list + */ + public void deleteUserTask(String dayOfWeek, int index) { + String capitalizedDay = dayOfWeek.substring(0, 1).toUpperCase() + dayOfWeek.substring(1); + //check if index is a valid number within a day's tasklist + if (index >= 0 && index < weeklyTasks.get(capitalizedDay).size()) { + Task taskDeleted = weeklyTasks.get(capitalizedDay).get(index); + weeklyTasks.get(capitalizedDay).remove(index); + System.out.println("Task " + taskDeleted.description + " is deleted from " + dayOfWeek); + System.out.println("New task list for " + capitalizedDay + ":"); + printTasksOfTheDay(dayOfWeek, getWeeklyTasks()); + } else { + System.out.println("Invalid task index. Please try again."); + } + } + + public void changeFlexibleTaskTiming(String dayOfWeek, int index, LocalTime newStartTime, LocalTime newEndTime) { + assert dayOfWeek != null : "Day of week cannot be null"; + assert newStartTime != null : "New start time cannot be null"; + assert newEndTime != null : "New end time cannot be null"; + Task task = readDay(dayOfWeek, index); + if (!task.getType().equals("f")) { + throw new IllegalArgumentException("Task on " + dayOfWeek + " at index " + + (index + 1) + " is not flexible, timings cannot be changed."); + } + task.setStartTime(newStartTime); + task.setEndTime(newEndTime); + } + + public void changeTaskType(String dayOfWeek, int index, String newType) { + Task task = readDay(dayOfWeek, index); + task.setType(newType); + } + + private Task readDay(String dayOfWeek, int index) { + String capitalizedDay = dayOfWeek.substring(0, 1).toUpperCase() + dayOfWeek.substring(1); + ArrayList tasks = weeklyTasks.get(capitalizedDay); + if (index < 0 || index >= tasks.size()) { + throw new IndexOutOfBoundsException("Invalid index"); + } + return tasks.get(index); + } + + /** + * Compares and prints overlapping free time between two Timetables. + * + * @param table1 First Timetable. + * @param table2 Second Timetable + * @return Returns a merged timetable comprising both input timetables. + */ + public static Timetable compareTimetable(Timetable table1, Timetable table2) { + Timetable mergedTimetable = new Timetable(); + if (table1.equals(table2)) { + return table1; + } + try { + InputValidator.validateTableExistence(table1); + InputValidator.validateTableExistence(table2); + for (String day : Timetable.DAYS) { + mergedTimetable.weeklyTasks.replace(day, mergeAndSortTasks(table1.getWeeklyTasks().get(day), + table2.getWeeklyTasks().get(day))); + } + } catch (NullPointerException e) { + System.out.println(e.getMessage()); + } + return mergedTimetable; + } + + /** + * Compares and prints overlapping free time between all Timetables. + * + * @param userList List of all users. + * @return Returns a merged timetable comprising all timetables. + */ + public static Timetable compareAllTimetables(UserList userList) { + Timetable mergedTimetable = new Timetable(); + for (User user : userList.getUsers()) { + mergedTimetable = compareTimetable(mergedTimetable, user.getTimetable()); + } + return mergedTimetable; + } + + private static ArrayList mergeAndSortTasks(ArrayList taskList1, ArrayList taskList2) { + ArrayList mergedTasks = new ArrayList<>(taskList1); + mergedTasks.addAll(taskList2); + mergedTasks.sort(Comparator.comparing(Task::getStartTime)); + return mergedTasks; + } + + public static void findOverlappingFreeTime(ArrayList tasks, String day) { + if (!tasks.isEmpty()) { + LocalTime previousEndTime = LocalTime.MIN; + for (Task task : tasks) { + if (task.getStartTime().isAfter(previousEndTime)) { + //System.out.println(" "+previousEndTime + " - " + task.getStartTime()); + UI.printTimeFrame(previousEndTime, task.getStartTime()); + } + previousEndTime = task.getEndTime(); + } + if (previousEndTime.isBefore(LocalTime.MAX)) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); + if (previousEndTime.format(formatter).equals("23:59")) { + UI.printFullDay(); + } else { + //System.out.println(" "+previousEndTime + " - " + LocalTime.MAX.format(formatter)); + UI.printTimeFrame(previousEndTime, LocalTime.MAX.format(formatter)); + } + } + } else { + UI.printFreeDay(day); + } + } +} diff --git a/src/main/java/seedu/duke/User.java b/src/main/java/seedu/duke/User.java new file mode 100644 index 0000000000..a890bfbec7 --- /dev/null +++ b/src/main/java/seedu/duke/User.java @@ -0,0 +1,40 @@ +package seedu.duke; + +import seedu.duke.ui.UI; + +public class User { + private static String folderPath = "data"; + private Timetable timetable; + private final String name; + private Storage storage; + + public User(String name) { + this.name = name; + this.timetable = new Timetable(); + this.storage = new Storage(folderPath + "/" + name + ".txt"); + } + + public static void setFolderPath(String newPath) { + User.folderPath = newPath; + } + + public Storage getStorage() { + return storage; + } + + public String getName() { + return name; + } + + public void viewTimetable() { + for (String day : Timetable.DAYS) { + UI.printTasksOfTheDay(day, timetable.getWeeklyTasks()); + } + } + + public Timetable getTimetable() { + return timetable; + } + + +} diff --git a/src/main/java/seedu/duke/UserList.java b/src/main/java/seedu/duke/UserList.java new file mode 100644 index 0000000000..be6b0f235e --- /dev/null +++ b/src/main/java/seedu/duke/UserList.java @@ -0,0 +1,67 @@ +package seedu.duke; + +import seedu.duke.exceptions.InvalidUserException; +import seedu.duke.ui.UI; + +import java.util.ArrayList; + +public class UserList { + private ArrayList allUsers; + private User activeUser; + + public UserList() { + allUsers = new ArrayList<>(); + } + + public User getActiveUser() { + return activeUser; + } + + public void setActiveUser(User user) throws InvalidUserException { + if (user != null) { + activeUser = user; + } else { + throw new InvalidUserException("User does not exist!"); + } + } + + public void addUser(User user) { + allUsers.add(user); + if (allUsers.size() == 1) { + activeUser = allUsers.get(0); //If this was the first user added, set them as the current user + UI.printActiveUser(user.getName()); + } + } + + public void listAll() { + for (User user : allUsers) { + System.out.println(user.getName()); + } + } + + public ArrayList getUsers() { + return allUsers; + } + + public int getListLength() { + return allUsers.size(); + } + + public User findUser(String name) { + for (User u : allUsers) { + if (name.equalsIgnoreCase(u.getName())) { + return u; + } + } + return null; + } + + public boolean containsUser(String userName) { + for (User user : allUsers) { + if (userName.equalsIgnoreCase(user.getName())) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/seedu/duke/exceptions/InvalidDayException.java b/src/main/java/seedu/duke/exceptions/InvalidDayException.java new file mode 100644 index 0000000000..b5e41a24dc --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidDayException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions; + +public class InvalidDayException extends Exception { + public InvalidDayException(String message) { + super(message); + } + +} diff --git a/src/main/java/seedu/duke/exceptions/InvalidFormatException.java b/src/main/java/seedu/duke/exceptions/InvalidFormatException.java new file mode 100644 index 0000000000..c01762e054 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidFormatException.java @@ -0,0 +1,10 @@ +package seedu.duke.exceptions; + +/** + * This class represents Expections expected from the InputValidator class. + */ +public class InvalidFormatException extends Exception{ + public InvalidFormatException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/duke/exceptions/InvalidUserException.java b/src/main/java/seedu/duke/exceptions/InvalidUserException.java new file mode 100644 index 0000000000..7539be9c61 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidUserException.java @@ -0,0 +1,10 @@ +package seedu.duke.exceptions; + +/** + * This class represents user Expections expected from the InputValidator class. + */ +public class InvalidUserException extends Exception{ + public InvalidUserException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/duke/exceptions/NoUserException.java b/src/main/java/seedu/duke/exceptions/NoUserException.java new file mode 100644 index 0000000000..c94c1fd6e8 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/NoUserException.java @@ -0,0 +1,4 @@ +package seedu.duke.exceptions; + +public class NoUserException extends Exception { +} diff --git a/src/main/java/seedu/duke/ui/UI.java b/src/main/java/seedu/duke/ui/UI.java new file mode 100644 index 0000000000..18edf63f88 --- /dev/null +++ b/src/main/java/seedu/duke/ui/UI.java @@ -0,0 +1,178 @@ +package seedu.duke.ui; + +import seedu.duke.Task; +import seedu.duke.Timetable; + +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Map; + +import static seedu.duke.Timetable.findOverlappingFreeTime; + +public class UI { + public static void printGreeting() { + System.out.println("Timetable comparison app opened. "); + } + + public static void printBye() { + System.out.println("Bye. "); + } + + public static void printListingUsers() { + System.out.println("The current users are: "); + } + + public static void printNewUser(String name) { + System.out.println("New user added: " + name); + } + + public static void printActiveUser(String description) { + System.out.println("The active user is: " + description); + } + + public static void printInvalidCommand() { + System.out.println("Invalid command. "); + } + + public static void printAddTask(Task task) { + System.out.println("The following task is added: " + task); + } + + public static void printHelp() { + System.out.println( + "Note: use hh:mm 24hr time format (ex. 13:00) \n" + + "List of available commands: " + UI.line() + + "View list of commands (what you are looking at): \n" + "help" + UI.line() + + "List all users: \n" + "list" + UI.line() + + "Exit the app: \n" + "bye" + UI.line() + + "View current user: \n" + "current" + UI.line() + + "View timetable of current user: \n" + "view" + UI.line() + + "View your next task: \n" + "next" + UI.line() + + "Add new user: \n" + "adduser " + UI.line() + + "Switch to user: \n" + "switch " + UI.line() + + "Add task for current user:\n" + + "addtask /on /task /from /to /type " + + UI.line() + + "Add task for current user (check duplicates):\n" + + "addtwdc /on /task /from /to /type " + + UI.line() + + "Add a task for all users: \n" + + "addforall /on /task /from /to " + + UI.line() + + "Add a task that repeats over certain days: \n" + + "addrepeattask /on /task /from /to /type " + + UI.line() + + "Add a task for certain users: \n" + + "addfor /user /on /task /from /to /type " + + UI.line() + + "Delete task: \n" + "deletetask" + UI.line() + + "Change a task's timing: \n" + + "changetasktiming /on /index /from /to " + UI.line() + + "Change a task's type: \n" + + "changetasktype /on /index /type " + UI.line() + + "Compare timetables of all users: \n" + "compareall" + UI.line() + + "Compare timetables between two users: \n" + "compare " + UI.line() + + "List today's tasks: \n" + "todaytasks" + UI.line() + + "List urgent tasks within certain timeframe: \n" + "urgent /in " + UI.line() + + "View common events: \n" + "viewcommonevents"); + printLine(); + } + + public static void printAddForAll(Task task) { + System.out.println("The following task is added for all users: " + task.toString()); + } + + public static void printEmptyDirectory() { + System.out.println("Directory is empty."); + } + + public static void printLine() { + System.out.println("____________________________________________________________"); + } + + public static String line() { + return ("\n_________________________________________\n"); + } + + /** + *

+ * Prints the overlapping free time for each day in the format: + * ---------------------- + * Shared Free Time on [day] + * HH:mm - HH:mm: Overlapping Free Time + */ + public static void printSharedTime(Timetable merged) { + for (String day : Timetable.DAYS) { + UI.printLine(); + System.out.println("Shared free time on " + day + ":"); + findOverlappingFreeTime(merged.getWeeklyTasks().get(day), day); + + } + } + + public static void printCompareUsers(String user1, String user2) { + System.out.println("Comparing timetables of " + user1 + " and " + user2 + ": "); + } + + public static void printComparingAll() { + System.out.println("Comparing all timetables: "); + } + + /** + * Prints tasks of the day specified. + * + * @param day day of the week the task is on. + */ + public static void printTasksOfTheDay(String day, Map> weeklyTask) { + String capitalizedDay = day.substring(0, 1).toUpperCase() + day.substring(1); + if (weeklyTask.get(capitalizedDay).isEmpty()) { + UI.printNoTask(day); + return; + } + System.out.println("_________________________________________"); + UI.printDayHeader(capitalizedDay); + int count = 1; + for (Task task : weeklyTask.get(capitalizedDay)) { + UI.printTaskInList(count, task.toString()); + count++; + } + } + + public static void printTimeFrame(LocalTime time1, String time2) { + System.out.println(" "+time1 + " - " + time2); + } + public static void printTimeFrame(LocalTime time1, LocalTime time2) { + System.out.println(" "+time1 + " - " + time2); + } + public static void printFullDay() { + System.out.println(" None"); + } + public static void printFreeDay(String day) { + System.out.println(" ** Whole day is free on " + day); + } + public static void printNoTask(String day) { + System.out.println("No task for " + day + "!"); + } + public static void printDayHeader(String day) { + System.out.println(day + " :"); + } + public static void printTaskInList(int count, String task) { + System.out.println(" "+count + ". " + task); + } + + public static void printNext() { + System.out.println("Your next task is: "); + } + + public static void printNoTasks() { + System.out.println("You have no tasks today. "); + } + + public static void printNoUsers() { + System.out.println("Please add a user before adding tasks."); + } + + public static void printClashTasks() { + System.out.println("The task you want to add clashes with existing tasks. Please check again."); + } +} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/duke/DukeTest.java index 2dda5fd651..6ca414e690 100644 --- a/src/test/java/seedu/duke/DukeTest.java +++ b/src/test/java/seedu/duke/DukeTest.java @@ -1,12 +1,44 @@ package seedu.duke; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; +import java.time.LocalTime; class DukeTest { @Test public void sampleTest() { assertTrue(true); } + + @Test + public void testAddTask() { + UserList userlist = new UserList(); + User user = new User("Test User"); + userlist.addUser(user); + Task task = new Task("Lecture", "Monday", "1:00", "2:00", "f"); + user.getTimetable().addUserTask("Monday", task); + assertEquals("Lecture", task.description); + assertEquals(LocalTime.parse("01:00"), task.startTime); + } + @Test + public void testAddUser() { + UserList userList = new UserList(); + User user = new User("Test User"); + userList.addUser(user); + + // Verify that the user is added to the list + assertEquals(1, userList.getListLength()); + assertEquals(user, userList.getActiveUser()); + assertTrue(userList.getUsers().contains(user)); + + User user2 = new User("Test User 2"); + userList.addUser(user2); + + assertEquals(2, userList.getListLength()); + assertEquals(user, userList.getActiveUser()); + assertTrue(userList.getUsers().contains(user)); + assertTrue(userList.getUsers().contains(user2)); + } } diff --git a/src/test/java/seedu/duke/InputValidatorTest.java b/src/test/java/seedu/duke/InputValidatorTest.java new file mode 100644 index 0000000000..afc8bd4544 --- /dev/null +++ b/src/test/java/seedu/duke/InputValidatorTest.java @@ -0,0 +1,253 @@ +package seedu.duke; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.fail; + +import seedu.duke.exceptions.InvalidDayException; +import seedu.duke.exceptions.InvalidFormatException; +class InputValidatorTest { + @Test + public void testAddUser() { + String[] inputList = { + "adduser john", + "adduser john100 ", + "ADDUSEr j", + "ADDUSER xjohnx", + "addUser 20 " + }; + for (String input : inputList) { + try { + InputValidator.validateAddUserInput(input); + } catch (InvalidFormatException e){ + fail(); + } + } + } + + @Test + public void testValidChangeTaskTimingFormat() { + String validInput = "changeTaskTiming /on monday /index 1 /from 09:00 /to 10:00"; + try { + InputValidator.validateChangeTaskTiming(validInput); + } catch (InvalidFormatException e) { + fail("Unexpected exception: " + e.getMessage()); + } + } + + @Test + public void testInvalidChangeTaskTimingFormat() { + String invalidInput = "changeTaskTiming /on monday /index 1 /from 09:00"; + try { + InputValidator.validateChangeTaskTiming(invalidInput); + fail("Expected InvalidFormatException"); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid changeTaskTiming format. Expected format: changeTaskTiming " + + "/on [day] /index [index] /from [new start time] /to [new end time]", e.getMessage()); + } + } + @Test + public void testValidChangeTaskTypeFormat() { + String validInput = "changetasktype /on Monday /index 1 /type f"; + try { + InputValidator.validateChangeTaskType(validInput); + } catch (InvalidFormatException e) { + fail("Unexpected exception: " + e.getMessage()); + } + } + + @Test + public void testInvalidChangeTaskTypeFormat() { + String invalidInput = "changetasktype /on Monday /index 1 /type a"; + try { + InputValidator.validateChangeTaskType(invalidInput); + fail("Expected InvalidFormatException"); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid changeTaskType format. Expected format: " + + "changeTaskType /on [day] /index [index] /type [f/c]", e.getMessage()); + } + } + @Test + public void testValidAddRepeatTaskFormat() { + String validInput = "addRepeatTask /task lec /on monday tuesday /from 08:00 /to 10:00 /type f"; + try { + InputValidator.validateAddRepeatTask(validInput); + } catch (InvalidFormatException e) { + fail("Unexpected exception: " + e.getMessage()); + } + } + + @Test + public void testInvalidAddRepeatTaskFormat() { + String invalidInput = "addRepeatTask /task lec /from 08:00 /to 10:00 /type f"; + try { + InputValidator.validateAddRepeatTask(invalidInput); + fail("Expected InvalidFormatException"); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid addRepeatTask format. Expected format: addRepeatTask /task " + + "[description] /on [day(s)] /from [start time] /to [end time] /type [f/c]", e.getMessage()); + } + } + + @Test + public void testValidateDay() { + String[] inputList = {"monday", "tuesday", "wednesday", "thursday", "friday", "Monday", "MONDAY"}; + for (String input : inputList) { + try { + InputValidator.validateDay(input); + } catch (InvalidDayException e) { + fail(); + } + } + } + + @Test + public void testAddTask() { + String[] inputList = { + "addtask /on MONDAY /task walk dogs /from 10:00 /to 16:00 /type c", + "ADDTASK /on tuesday /task walk dogs /from 00:00 /to 23:59 /type f", + "addtask /on MONDAY /task walk dogs100 /from 10:00 /to 16:00 /type c", + "Addtask /on sunday /task walk dogssSS /from 10:00 /to 16:00 /type f", + "addtask /on Friday /task walk dogs /from 10:00 /to 16:00 /type c" + }; + for (String input : inputList) { + try { + InputValidator.validateAddTaskInput(input); + } catch (InvalidFormatException e){ + fail(); + } + } + } + + @Test + public void testInvalidAddTask() { + //addtask mispelled + String invalidInput1 = "addtas /on MONDAY /task walk dogs /from 10:00 /to 16:00 /type c"; + try { + InputValidator.validateAddTaskInput(invalidInput1); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid addtask format. " + + "Expected format: addtask /on [day] /task [description] /from [start time] /to [end time] " + + "/type [f/c]", e.getMessage()); + } + //No space between from time and "/to" + String invalidInput2 = "addtask /on MONDAY /task walk dogs /from 10:00/to 16:00 /type c"; + try { + InputValidator.validateAddTaskInput(invalidInput2); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid addtask format. " + + "Expected format: addtask /on [day] /task [description] /from [start time] /to [end time] " + + "/type [f/c]", e.getMessage()); + } + //Monday extra y + String invalidInput3 = "addtask /on MONDAYy /task walk dogs /from 10:00 /to 16:00 /type c"; + try { + InputValidator.validateAddTaskInput(invalidInput3); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid addtask format. " + + "Expected format: addtask /on [day] /task [description] /from [start time] /to [end time] " + + "/type [f/c]", e.getMessage()); + } + //different type v + String invalidInput4 = "addtask /on MONDAY /task walk dogs /from 10:00 /to 16:00 /type v"; + try { + InputValidator.validateAddTaskInput(invalidInput4); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid addtask format. " + + "Expected format: addtask /on [day] /task [description] /from [start time] /to [end time] " + + "/type [f/c]", e.getMessage()); + } + } + + @Test + public void testDeleteTask() { + String[] inputList = { + "deletetask /on MONDAY /index 1", + "deletetask /on saturday /index 100", + "Deletetask /on MONDAY /index 12", + "DELETETASK /on tuesdAY /index 1", + "deleteTASK /on saturday /index 4000" + }; + for (String input : inputList) { + try { + InputValidator.validateDeleteTaskInput(input); + } catch (InvalidFormatException e){ + fail(); + } + } + } + + + @Test + public void testCompare() { + String[] inputList = {"compare john jill", "COMPARE john jill ", "COMpare john bill", "comPArE jane yu"}; + for (String input : inputList) { + try { + InputValidator.validateCompareInput(input); + } catch (InvalidFormatException e) { + fail(); + } + } + } + + @Test + public void testInvalidCompare() { + String invalidInput1 = "compare john jack jill"; + try { + InputValidator.validateCompareInput(invalidInput1); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid compare format. " + + "Expected format: compare ", e.getMessage()); + } + String invalidInput2 = "compar john jack "; + try { + InputValidator.validateCompareInput(invalidInput2); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid compare format. " + + "Expected format: compare ", e.getMessage()); + } + String invalidInput3 = "compare jo"; + try { + InputValidator.validateCompareInput(invalidInput3); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid compare format. " + + "Expected format: compare ", e.getMessage()); + } + } + + @Test + public void testCompareAll() { + String[] inputList = {"compareall ", "COMPAREALL ", "COMpareAll", "comPArEall"}; + for (String input : inputList) { + try { + InputValidator.validateCompareAllInput(input); + } catch (InvalidFormatException e) { + fail(); + } + } + } + + @Test + public void testInvalidCompareAll() { + String invalidInput1 = "compareall l"; + try { + InputValidator.validateCompareAllInput(invalidInput1); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid compareall format. " + + "Expected format: compareall", e.getMessage()); + } + String invalidInput2 = "comparall "; + try { + InputValidator.validateCompareAllInput(invalidInput2); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid compareall format. " + + "Expected format: compareall", e.getMessage()); + } + String invalidInput3 = "compareallllll"; + try { + InputValidator.validateCompareAllInput(invalidInput3); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid compareall format. " + + "Expected format: compareall", e.getMessage()); + } + } +} diff --git a/src/test/java/seedu/duke/ParserTest.java b/src/test/java/seedu/duke/ParserTest.java new file mode 100644 index 0000000000..0ba0caea35 --- /dev/null +++ b/src/test/java/seedu/duke/ParserTest.java @@ -0,0 +1,246 @@ +package seedu.duke; + +import org.junit.jupiter.api.Test; +import seedu.duke.exceptions.InvalidDayException; +import seedu.duke.exceptions.InvalidFormatException; +import seedu.duke.exceptions.InvalidUserException; +import seedu.duke.exceptions.NoUserException; + +import java.io.IOException; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +class ParserTest { + UserList userlist = new UserList(); + + @Test + public void sampleTest() { + assertTrue(true); + } + + @Test + public void adduserCommandTest() throws InvalidDayException, InvalidFormatException, InvalidUserException, + NoUserException, IOException { + Parser.parseCommand("addUser User1", userlist); + + assertEquals(1, userlist.getListLength()); + assertEquals("User1", userlist.getActiveUser().getName()); + + Parser.parseCommand("addUser User2", userlist); + Parser.parseCommand("addUser User3", userlist); + Parser.parseCommand("addUser User4", userlist); + + assertEquals(4, userlist.getListLength()); + } + + @Test + public void invalidAdduserCommandTest() { + try { + Parser.parseCommand("addUser", userlist); + fail(); + } catch (Exception e) { + assertEquals("[ERROR] Invalid addUser format. Expected format: adduser ", + e.getMessage()); + } + + try { + Parser.parseCommand("addUser User1", userlist); + Parser.parseCommand("addUser User1", userlist); + fail(); + } catch (Exception e) { + assertEquals("User already exists. Use a different name. ", e.getMessage()); + } + } + + @Test + public void compareCommandTest() { + User user1 = new User("User1"); + User user2 = new User("User2"); + userlist.addUser(user1); + userlist.addUser(user2); + + try { + Parser.parseCommand("compare User1 User1", userlist); + Parser.parseCommand("compare user1 user1", userlist); + Parser.parseCommand("compare User1 User2", userlist); + } catch (Exception e) { + fail(); + } + } + + @Test + public void switchCommandTest() throws InvalidDayException, InvalidFormatException, InvalidUserException, + NoUserException, IOException { + User user1 = new User("User1"); + User user2 = new User("User2"); + userlist.addUser(user1); + userlist.addUser(user2); + Parser.parseCommand("switch User2", userlist); + + assertEquals(2, userlist.getListLength()); + assertEquals("User2", userlist.getActiveUser().getName()); + } + + @Test + public void invalidSwitchCommandTest() { + User user1 = new User("User1"); + User user2 = new User("User2"); + userlist.addUser(user1); + userlist.addUser(user2); + + try { + Parser.parseCommand("switch", userlist); + fail(); + } catch (InvalidFormatException e) { + assertEquals("[ERROR] Invalid switch format. Expected format: switch ", + e.getMessage()); + } catch (Exception e) { + fail(); + } + + try { + Parser.parseCommand("switch noUser", userlist); + fail(); + } catch (InvalidUserException e) { + assertEquals("User does not exist!", e.getMessage()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void addTaskCommandTest() throws InvalidDayException, InvalidUserException, InvalidFormatException, + NoUserException, IOException { + + // + Storage.setFolderPath("test_data"); + User.setFolderPath("test_data"); + + User user1 = new User("User1"); + userlist.addUser(user1); + + Parser.parseCommand("addtask /on Monday /task test1 /from 9:00 /to 11:00 /type f", userlist); + Parser.parseCommand("addtask /on Monday /task test2 /from 13:00 /to 15:00 /type f", userlist); + ArrayList testTasksMon = user1.getTimetable().getWeeklyTasks().get("Monday"); + ArrayList testTasksTue = user1.getTimetable().getWeeklyTasks().get("Tuesday"); + + assertEquals("test1", testTasksMon.get(0).getDescription()); + assertEquals("09:00", testTasksMon.get(0).getStartTime().toString()); + assertEquals("11:00", testTasksMon.get(0).getEndTime().toString()); + + assertEquals("test2", testTasksMon.get(1).getDescription()); + assertEquals("13:00", testTasksMon.get(1).getStartTime().toString()); + assertEquals("15:00", testTasksMon.get(1).getEndTime().toString()); + + try { + String testDescription = testTasksTue.get(1).getDescription(); + fail(); + } catch (IndexOutOfBoundsException e) { + assertEquals("Index 1 out of bounds for length 0", e.getMessage()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void invalidAddTaskCommandTest() { + User user1 = new User("User1"); + userlist.addUser(user1); + + String expectedErrorMessage = "[ERROR] Invalid addtask format. Expected format: addtask /on [day]" + + " /task [description] /from [start time] /to [end time] /type [f/c]"; + String[] testMessages = {"addtask", + "addtask /on Monday", + "addtask /on Monday /task description", + "addtask /on Monday /task description /from 09:00 /to 11:00", + "addtask Monday description 09:00 11:00", + "addtask /on MONDAY /task description /from 09:00 /to 11:00"}; + for (String message : testMessages) { + try { + Parser.parseCommand(message, userlist); + fail(); + } catch (Exception e) { + assertEquals(expectedErrorMessage, e.getMessage()); + } + } + + try { + Parser.parseCommand("addtask /on Oneday /task lecture /from 9:00 /to 11:00 /type f", userlist); + } catch (Exception e) { + assertEquals("[ERROR] Invalid day. Please enter a day from Monday - Sunday.", e.getMessage()); + } + + try { + Parser.parseCommand("addtask /on Monday /task lecture /from 9:00 /to 25:00 /type f", userlist); + } catch (Exception e) { + assertEquals("Text '25:00' could not be parsed: Invalid value for HourOfDay" + + " (valid values 0 - 23): 25", e.getMessage()); + } + } + + @Test + public void addForAllTest() throws InvalidDayException, InvalidUserException, InvalidFormatException, + NoUserException, IOException { + Storage.setFolderPath("test_data"); + User.setFolderPath("test_data"); + + User user1 = new User("User1"); + User user2 = new User("User2"); + userlist.addUser(user1); + userlist.addUser(user2); + + Parser.parseCommand("addforall /on Monday /task lecture /from 9:00 /to 11:00", userlist); + + Task addedTaskUser1 = user1.getTimetable().getWeeklyTasks().get("Monday").get(0); + Task addedTaskUser2 = user1.getTimetable().getWeeklyTasks().get("Monday").get(0); + + assertEquals("lecture", addedTaskUser1.getDescription()); + assertEquals("lecture", addedTaskUser2.getDescription()); + assertEquals("09:00", addedTaskUser2.getStartTime().toString()); + assertEquals("11:00", addedTaskUser2.getEndTime().toString()); + } + + @Test + public void changeTaskTypeTest() throws InvalidDayException, NoUserException, IOException, + InvalidUserException, InvalidFormatException { + Storage.setFolderPath("test_data"); + User.setFolderPath("test_data"); + + User user1 = new User("User1"); + userlist.addUser(user1); + Parser.parseCommand("addtask /on Monday /task test1 /from 9:00 /to 11:00 /type f", userlist); + Task task = user1.getTimetable().getWeeklyTasks().get("Monday").get(0); + assertEquals("f", task.getType()); + + Parser.parseCommand("changetasktype /on Monday /index 1 /type c", userlist); + assertEquals("c", task.getType()); + + Parser.parseCommand("changetasktype /on Monday /index 1 /type c", userlist); + assertEquals("c", task.getType()); + } + + @Test + public void invalidChangeTaskTypeTest() throws InvalidDayException, NoUserException, IOException, + InvalidUserException, InvalidFormatException { + Storage.setFolderPath("test_data"); + User.setFolderPath("test_data"); + + User user1 = new User("User1"); + userlist.addUser(user1); + Parser.parseCommand("addtask /on Monday /task test1 /from 9:00 /to 11:00 /type f", userlist); + Task task = user1.getTimetable().getWeeklyTasks().get("Monday").get(0); + assertEquals("f", task.getType()); + + try { + Parser.parseCommand("changetasktype /on Monday /index 2 /type c", userlist); + fail(); + } catch (InvalidFormatException e) { + assertEquals("The selected task does not exist. ", e.getMessage()); + } catch (IOException e) { + fail(); + } + } +} diff --git a/src/test/java/seedu/duke/StorageTest.java b/src/test/java/seedu/duke/StorageTest.java new file mode 100644 index 0000000000..fec75e3aac --- /dev/null +++ b/src/test/java/seedu/duke/StorageTest.java @@ -0,0 +1,142 @@ +package seedu.duke; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Scanner; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.duke.Parser.DAYS; +import static seedu.duke.Storage.folderPath; +import static seedu.duke.Storage.setFolderPath; +import static seedu.duke.Storage.writeToFile; +import static seedu.duke.Storage.extractTaskInfo; +import static seedu.duke.Storage.BOX_OUTLINE; +import static seedu.duke.Storage.BOX_OUTLINE_FOR_WEDNESDAY; +import static seedu.duke.Storage.BOX_OUTLINE_FOR_FRIDAY; +import static seedu.duke.Storage.LINE_SEPARATOR; + + +public class StorageTest { + + private static String filePath; + + @BeforeEach + void setUp() { + String userName = "testUser"; + filePath = "test_data/" + userName + ".txt"; + setFolderPath("test_data"); + User.setFolderPath("test_data"); + File folder = new File(folderPath); + boolean folderCreated; + if (!folder.exists()) { + folderCreated = folder.mkdirs(); + if (folderCreated) { + System.out.println("Folder created successfully."); + } else { + System.out.println("Failed to create folder."); + } + } else { + System.out.println("Folder already exists."); + } + } + + + @AfterAll + static void resetFolderPath() { + Storage.setFolderPath("data"); + User.setFolderPath("data"); + } + + @Test + void testStorageInit() { + User testUser = new User("testUser"); + assertEquals("test_data/testUser.txt", testUser.getStorage().getFilePath()); + } + + @Test + void testSetFolderPath() { + Storage.setFolderPath("testPath"); + assertEquals("testPath", Storage.getFolderPath()); + } + + @Test + void testAddExistingUsers() throws IOException { + setFolderPath("test_data"); + String userName = "testUser"; + + writeToFile(filePath, "Username: " + userName, false); + UserList userList = new UserList(); + Storage.addExistingUsers(userList); + assertTrue(userList.containsUser(userName), "UserList should contain the added user"); + } + + @Test + public void testAddNewUserToFolder() { + UserList userList = new UserList(); + User newUser = new User("testAddUser"); + userList.addUser(newUser); + newUser.getStorage().addUserInFolder(); + + File directory = new File(folderPath); + File file = new File(directory, "testAddUser.txt"); + System.out.println(directory.getAbsolutePath()); + assertTrue(file.exists()); + } + + @Test + public void testWriteToFile() throws Exception { + User testUser = new User("testUser"); + testUser.getTimetable().addUserTask("Monday", new Task("lecture", "Monday", "10:00", "11:00", "c")); + + testUser.getStorage().writeTaskInFile(testUser); + + Scanner scanner = new Scanner(new File(filePath)); + assertTrue(scanner.hasNextLine(), "Empty file."); + assertEquals("Username: testUser", scanner.nextLine().trim(), "First line should contain the username"); + } + + @Test + public void testExtractTaskInfo() { + String line = "1. 01:00 - 02:00: lecture (type: c)"; + Task task = extractTaskInfo(line, "Monday"); + assertEquals("lecture", task.getDescription()); + assertEquals("01:00", task.getStartTime().toString()); + assertEquals("02:00", task.getEndTime().toString()); + assertEquals("c", task.getType()); + } + + @Test + public void testLoadData() throws IOException { + writeToFile(filePath, "Username: " + "testUser" + "\n", false); + for (String day : DAYS) { + String outline; + switch (day) { + case ("Wednesday"): + outline = BOX_OUTLINE_FOR_WEDNESDAY; + break; + case ("Friday"): + outline = BOX_OUTLINE_FOR_FRIDAY; + break; + default: + outline = BOX_OUTLINE; + break; + } + writeToFile(filePath, outline, true); + writeToFile(filePath, "| " + day + " |" + "\n", true); + writeToFile(filePath, outline, true); + + if (day.equals("Monday")) { + writeToFile(filePath, "1. 01:00 - 02:00: lecture (type: c)", true); + } else if (day.equals("Thursday")) { + writeToFile(filePath, "2. 02:00 - 03:00: lecture (type: f)", true); + } + writeToFile(filePath, LINE_SEPARATOR, true); + writeToFile(filePath, "\n", true); + } + } +} diff --git a/src/test/java/seedu/duke/TimetableTest.java b/src/test/java/seedu/duke/TimetableTest.java new file mode 100644 index 0000000000..b7af23eb0e --- /dev/null +++ b/src/test/java/seedu/duke/TimetableTest.java @@ -0,0 +1,90 @@ +package seedu.duke; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; + +import java.time.LocalTime; + +class TimetableTest { + @Test + public void testAddUserTask() { + Timetable timetable = new Timetable(); + Task task = new Task("Study","2024-03-18", "09:00", "11:00","f"); + + timetable.addUserTask("Tuesday", task); + + //timetable.printTasksOfTheDay("Tuesday"); + assertEquals(1, timetable.getWeeklyTasks().get("Tuesday").size()); + assertEquals(task, timetable.getWeeklyTasks().get("Tuesday").get(0)); + } + @Test + public void testCompareTimetables() { + Timetable timetable1 = new Timetable(); + Timetable timetable2 = new Timetable(); + Task task1 = new Task("Study","2024-03-18", "09:00", "11:00","f"); + Task task2 = new Task("Study","2024-03-18", "07:00", "10:00","f"); + timetable1.addUserTask("Monday", task1); + timetable2.addUserTask("Monday", task2); + + Timetable.compareTimetable(timetable1, timetable2); + } + @Test + public void testChangeFlexibleTaskTiming_correctInput() { + Timetable timetable = new Timetable(); + Task flexibleTask = new Task("lec", "monday", "09:00", "11:00", "f"); + timetable.addUserTask("monday", flexibleTask); + try { + timetable.changeFlexibleTaskTiming("monday", 0, LocalTime.of(11, 0), + LocalTime.of(12, 0)); + } catch (Exception e) { + fail("Unexpected exception: " + e.getMessage()); + } + } + @Test + public void testChangeFlexibleTaskTiming_invalidIndex(){ + Timetable timetable = new Timetable(); + try{ + timetable.changeFlexibleTaskTiming("monday", 0, LocalTime.of(11, 0), + LocalTime.of(12, 0)); + fail("Expected IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException e){ + assertEquals("Invalid index", e.getMessage()); + } + } + @Test + public void testChangeFlexibleTaskTiming_nonFlexibleTask() { + Timetable timetable = new Timetable(); + Task nonFlexibleTask = new Task("lec", "monday", "09:00", "11:00", "c"); + timetable.addUserTask("monday", nonFlexibleTask); + try { + timetable.changeFlexibleTaskTiming("monday", 0, LocalTime.of(11, 0), LocalTime.of(12, 0)); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Task on monday at index 1 is not flexible, " + + "timings cannot be changed.", e.getMessage()); + } + } + @Test + public void testChangeTaskType_validInput() { + Timetable timetable = new Timetable(); + Task task = new Task("lec", "monday", "09:00", "11:00", "f"); + timetable.addUserTask("monday", task); + try { + timetable.changeTaskType("monday", 0, "c"); + } catch (Exception e) { + fail("Unexpected exception: " + e.getMessage()); + } + } + @Test + public void testChangeTaskType_invalidIndex() { + Timetable timetable = new Timetable(); + try { + timetable.changeTaskType("monday", 0, "c"); + fail("Expected IndexOutOfBoundsException"); + } catch (IndexOutOfBoundsException e) { + assertEquals("Invalid index", e.getMessage()); + } + } +} diff --git a/test_data/User1.txt b/test_data/User1.txt new file mode 100644 index 0000000000..c5f331da6d --- /dev/null +++ b/test_data/User1.txt @@ -0,0 +1,43 @@ +Username: User1 ++---------+ +| Monday | ++---------+ +1. 09:00 - 11:00: test1 (type: c) +................................................................................................. + ++---------+ +| Tuesday | ++---------+ +No task :) +................................................................................................. + ++-------------+ +| Wednesday | ++-------------+ +No task :) +................................................................................................. + ++---------+ +| Thursday | ++---------+ +No task :) +................................................................................................. + ++------+ +| Friday | ++------+ +No task :) +................................................................................................. + ++---------+ +| Saturday | ++---------+ +No task :) +................................................................................................. + ++---------+ +| Sunday | ++---------+ +No task :) +................................................................................................. + diff --git a/test_data/User2.txt b/test_data/User2.txt new file mode 100644 index 0000000000..4a28a75079 --- /dev/null +++ b/test_data/User2.txt @@ -0,0 +1,43 @@ +Username: User2 ++---------+ +| Monday | ++---------+ +1. 09:00 - 11:00: lecture (type: common) +................................................................................................. + ++---------+ +| Tuesday | ++---------+ +No task :) +................................................................................................. + ++-------------+ +| Wednesday | ++-------------+ +No task :) +................................................................................................. + ++---------+ +| Thursday | ++---------+ +No task :) +................................................................................................. + ++------+ +| Friday | ++------+ +No task :) +................................................................................................. + ++---------+ +| Saturday | ++---------+ +No task :) +................................................................................................. + ++---------+ +| Sunday | ++---------+ +No task :) +................................................................................................. + diff --git a/test_data/User3.txt b/test_data/User3.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test_data/User4.txt b/test_data/User4.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test_data/testAddUser.txt b/test_data/testAddUser.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test_data/testUser.txt b/test_data/testUser.txt new file mode 100644 index 0000000000..f7c6c3dddf --- /dev/null +++ b/test_data/testUser.txt @@ -0,0 +1 @@ +Username: testUser \ No newline at end of file diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..f658c87e1c 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,112 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling +Timetable comparison app opened. +Folder created successfully. +Note: use hh:mm 24hr time format (ex. 13:00) +List of available commands: +_________________________________________ +View list of commands (what you are looking at): +help +_________________________________________ +List all users: +list +_________________________________________ +Exit the app: +bye +_________________________________________ +View current user: +current +_________________________________________ +View timetable of current user: +view +_________________________________________ +View your next task: +next +_________________________________________ +Add new user: +adduser +_________________________________________ +Switch to user: +switch +_________________________________________ +Add task for current user: +addtask /on /task /from /to /type +_________________________________________ +Add task for current user (check duplicates): +addtwdc /on /task /from /to /type +_________________________________________ +Add a task for all users: +addforall /on /task /from /to +_________________________________________ +Add a task that repeats over certain days: +addrepeattask /on /task /from /to /type +_________________________________________ +Add a task for certain users: +addfor /user /on /task /from /to /type +_________________________________________ +Delete task: +deletetask +_________________________________________ +Change a task's timing: +changetasktiming /on /index /from /to +_________________________________________ +Change a task's type: +changetasktype /on /index /type +_________________________________________ +Compare timetables of all users: +compareall +_________________________________________ +Compare timetables between two users: +compare +_________________________________________ +List today's tasks: +todaytasks +_________________________________________ +List urgent tasks within certain timeframe: +urgent /in +_________________________________________ +View common events: +viewcommonevents +____________________________________________________________ +New user added: Simon +The active user is: Simon +File created: Simon.txt +The following task is added: lecture (Monday from 09:00 to 11:00) type: f +Timetable has been written to data/Simon.txt +New user added: Tim +File created: Tim.txt +The active user is: Tim +The following task is added: lecture (Monday from 09:00 to 11:00) type: f +Timetable has been written to data/Tim.txt +_________________________________________ +Monday : + 1. lecture (Monday from 09:00 to 11:00) type: f +No task for Tuesday! +No task for Wednesday! +No task for Thursday! +No task for Friday! +No task for Saturday! +No task for Sunday! +Comparing timetables of Simon and Tim: +____________________________________________________________ +Shared free time on Monday: + 00:00 - 09:00 + 11:00 - 23:59 +____________________________________________________________ +Shared free time on Tuesday: + ** Whole day is free on Tuesday +____________________________________________________________ +Shared free time on Wednesday: + ** Whole day is free on Wednesday +____________________________________________________________ +Shared free time on Thursday: + ** Whole day is free on Thursday +____________________________________________________________ +Shared free time on Friday: + ** Whole day is free on Friday +____________________________________________________________ +Shared free time on Saturday: + ** Whole day is free on Saturday +____________________________________________________________ +Shared free time on Sunday: + ** Whole day is free on Sunday +[ERROR] Invalid day. Please enter a day from Monday - Sunday. +Bye. diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..e38cfc6f9f 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1,9 @@ -James Gosling \ No newline at end of file +adduser simon +addtask /on Monday /task lecture /from 9:00 /to 11:00 /type f +adduser tim +switch tim +addtask /on Monday /task lecture /from 9:00 /to 11:00 /type f +view +compare simon tim +addtask /on Eightday /task lecture /from 9:00 /to 11:00 /type f +bye diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 25ac7a2989..181ca9d40c 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -16,4 +16,8 @@ java -jar %jarloc% < ..\..\text-ui-test\input.txt > ..\..\text-ui-test\ACTUAL.TX cd ..\..\text-ui-test +:: PowerShell commands to trim trailing spaces +powershell -Command "(Get-Content -Path EXPECTED.TXT) | ForEach-Object { $_.TrimEnd() } | Set-Content -Path EXPECTED.TXT" +powershell -Command "(Get-Content -Path ACTUAL.TXT) | ForEach-Object { $_.TrimEnd() } | Set-Content -Path ACTUAL.TXT" + FC ACTUAL.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed! diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 1dcbd12021..fade424594 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -12,6 +12,10 @@ java -jar $(find ../build/libs/ -mindepth 1 -print -quit) < input.txt > ACTUAL. cp EXPECTED.TXT EXPECTED-UNIX.TXT dos2unix EXPECTED-UNIX.TXT ACTUAL.TXT + +sed -i'' -e 's/[[:space:]]*$//' EXPECTED-UNIX.TXT +sed -i'' -e 's/[[:space:]]*$//' ACTUAL.TXT + diff EXPECTED-UNIX.TXT ACTUAL.TXT if [ $? -eq 0 ] then