:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`.
+
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitDatastore()`, so the datastore state will not be saved into the `datastoreVersions`.
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
+Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command.
+The `undo` command will call `Model#undoChanges()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous datastore state, and restores the datastore to that state.
![UndoRedoState3](images/UndoRedoState3.png)
-
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather
+
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial Datastore state, then there are no previous Datastore states to restore. The `undo` command uses `Model#canUndoDatastore()` to check if this is the case. If so, it will return an error to the user rather
than attempting to perform the undo.
+[//]: # (Page Break:)
+
+
The following sequence diagram shows how an undo operation goes through the `Logic` component:
![UndoSequenceDiagram](images/UndoSequenceDiagram-Logic.png)
@@ -206,20 +564,26 @@ Similarly, how an undo operation goes through the `Model` component is shown bel
![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png)
-The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state.
+[//]: # (Page Break:)
+
+
+The `redo` command does the opposite — it calls `Model#redoChanges()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the datastore to that state.
-
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
+
:information_source: **Note:** If the `currentStatePointer` is at index `datastoreVersions.size() - 1`, pointing to the latest datastore state, then there are no undone Datastore states to restore. The `redo` command uses `Model#canRedoDatastore()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
+Step 5. The user then decides to execute the command `list`. Commands that do not modify the datastore, such as `list`, will usually not call `Model#commitDatastore()`, `Model#undoChanges()` or `Model#redoChanges()`. Thus, the `datastoreVersions` remains unchanged.
![UndoRedoState4](images/UndoRedoState4.png)
-Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern desktop applications follow.
+Step 6. The user executes `clear`, which calls `Model#commitDatastore()`. Since the `currentStatePointer` is not pointing at the end of the `datastoreVersions`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern desktop applications follow.
![UndoRedoState5](images/UndoRedoState5.png)
+[//]: # (Page Break:)
+
+
The following activity diagram summarizes what happens when a user executes a new command:
@@ -237,12 +601,6 @@ The following activity diagram summarizes what happens when a user executes a ne
* Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
* Cons: We must ensure that the implementation of each individual command are correct.
-_{more aspects and alternatives to be added}_
-
-### \[Proposed\] Data archiving
-
-_{Explain here how the data archiving feature will be implemented}_
-
--------------------------------------------------------------------------------------------------------------------
@@ -256,81 +614,295 @@ _{Explain here how the data archiving feature will be implemented}_
--------------------------------------------------------------------------------------------------------------------
-## **Appendix: Requirements**
+## **Appendix A: Requirements**
### Product scope
**Target user profile**:
-* has a need to manage a significant number of contacts
+* is a manager of a befriending volunteer organisation
+* has a need to keep track of many befriendees/elderly of the volunteer programme
+* has a need to keep track of volunteers in the organisation
+* has a need to keep track of logs of activities/visits made by volunteers to befriendees
* prefer desktop apps over other types
* can type fast
* prefers typing to mouse interactions
* is reasonably comfortable using CLI apps
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Value proposition**: manage the volunteer organisation faster than a typical mouse/GUI driven app. Our application aims to provide fast access to all contacts with intuitive search and filtering, enable pairing of volunteers to elderly befriendees, show multiple lists to enable multitasking and support efficient adding of logs between pairs of volunteers and befriendees.
### User stories
Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
-| Priority | As a … | I want to … | So that I can… |
-| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
-
-*{More to be added}*
+| Priority | As a … | I want to … | So that I can… |
+|----------|-----------------|--------------------------------------------------------------------------------|---------------------------------------------------------------|
+| `* * *` | manager | add volunteers’ and elderly befriendees’ contacts | |
+| `* * *` | manager | edit volunteers’ and elderly befriendees’ details | |
+| `* * *` | manager | delete volunteer and elderly befriendee contacts | |
+| `* * *` | manager | view list of volunteers and elderly befriendees | keep track of the roster |
+| `* * *` | manager | tag elderly befriendees with relevant details | accommodate for any special circumstances |
+| `* * *` | manager | pair and unpair volunteers with befriendee contacts | assign the pairings |
+| `* * *` | manager | add log entries for each visit made by a volunteer to an elderly befriendee | keep track of the visits |
+| `* * *` | manager | search for specific elderly befriendee or volunter befriender | find and view the details of befriendee or volunteer |
+| `* * *` | manager | search log entries based on a befriendee or volunteer | find and view the details of relevant log entries |
+| `* *` | manager | have a one-stop view of all important information regarding elderly and volunteers | contact each person easily |
+| `* *` | manager | undo and redo changes made to the contacts, pairings, and logs | revert changes made in error |
+| `* *` | first-time user | access a help page | refer to the features that come along with the application |
+| `* *` | first-time user | see sample befriendee and volunteer profiles | try out the features with pre-loaded data |
+| `*` | first-time user | follow a guided tour | be aware of how to use and access features in the application |
+
+[//]: # (Page Break:)
+
### Use cases
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+(For all use cases below, the **System** is the `Elder Scrolls` and the **Actor** is the
+`volunteer organisation manager`, unless specified otherwise)
-**Use case: Delete a person**
+**Use case: UC01 - Add a contact**
**MSS**
-1. User requests to list persons
-2. AddressBook shows a list of persons
-3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+1. User requests to add contact, entering contact information
+2. Elder Scrolls adds the contact
+3. Elder Scrolls displays the details of the contact added
Use case ends.
**Extensions**
-* 2a. The list is empty.
+* 2a. The given contact details are invalid.
- Use case ends.
+ * 2a1. Elder Scrolls shows an error message.
-* 3a. The given index is invalid.
+ Use case ends.
+
+* 3a. The given contact details are already in the contact book
+
+ * 3a1. Elder scrolls shows an error message.
- * 3a1. AddressBook shows an error message.
+ Use case ends.
+
+**Use case: UC02 - Delete a contact**
+
+**MSS**
+
+1. User requests to list all contacts
+2. Elder Scrolls shows a list of all contacts
+3. User requests to delete a specific contact.
+4. Elder Scrolls deletes the specified contact.
+
+ Use case ends.
+
+**Extensions**
+
+* 3a. The specific contact given by the user is invalid.
+
+ * 3a1. Elder Scrolls shows an error message.
Use case resumes at step 2.
-*{More to be added}*
+* 4a. The contact requested to be deleted is still paired.
-### Non-Functional Requirements
+ * 4a1. Elder Scrolls shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: UC03 - List all contacts and logs**
+
+**MSS**
+
+1. User requests to list all contacts and logs
+2. Elder Scrolls shows a list of all contacts and logs
+
+ Use case ends.
+
+**Use case: UC04 - Pair a volunteer and befriendee**
-1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
-2. Should be able to hold up to 1000 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.
+**MSS**
+
+1. User
requests to list all contacts and logs (UC03).
+2. Elder Scrolls shows a list of contacts and logs.
+3. User requests to pair a specific volunteer and befriendee in the list.
+4. Elder Scrolls pairs the specified volunteer and befriendee.
+
+ Use case ends.
+
+**Extensions**
+
+* 3a. One or both of the specified persons are invalid.
+
+ * 3a1. Elder Scrolls shows an error message indicating that the specified person(s) given are invalid.
+
+ Use case resumes at step 2.
+
+* 3b. One or both of the specified persons are already paired.
+
+ * 3b1. Elder Scrolls shows an error message indicating that the specified person(s) are already paired.
+
+ Use case resumes at step 2.
+
+**Use case: UC05 - Edit field for a specific contact**
+
+**MSS**
+
+1. User
requests to list all contacts and logs (UC03).
+2. Elder Scrolls shows a list of contacts and logs.
+3. User requests to edit a field for a specific contact, with a new specified value.
+4. Elder Scrolls edits the field for the specified person in the list with the new specified value.
+
+ Use case ends.
+
+**Extensions**
+
+* 3a. The specific contact given by the user is invalid.
+ * 3a1. Elder Scrolls shows an error message.
+ Use case resumes at step 2.
+
+* 3b. The field that the user wants to edit is not specified
+ * 3b1. Elder Scrolls shows an error message to prompt the user to specify a field to edit.
+ Use case resumes at step 2.
+
+* 3c. The new specified value is not provided by the user
+ * 3c1. Elder Scrolls shows an error message to prompt the user to specify a new value for the field the user wishes to edit.
+ Use case resumes at step 2.
+
+
+**Use case: UC06 - Add a log entry for volunteer-befriendee pairs**
+
+**MSS**
+
+1. User
requests to list all contacts and logs (UC03).
+2. Elder Scrolls shows a list of contacts and logs.
+3. User requests to add a log entry for two paired individuals, and enters details of the activity log, including date, time, and remarks.
+4. Elder Scrolls records the log entry for the selected pair.
+
+ Use case ends.
+
+**Extensions**
+
+* 3a. The selected pair is not currently paired.
+ * 3a1. Elder Scrolls displays an error message indicating that the selected pair is not currently paired.
+ Use case resumes at step 2.
+
+* 3b. The entered details for the log are incomplete or invalid.
+ * 3b1. Elder Scrolls shows an error message to prompt user to correct the incomplete or invalid details.
+ Use case resumes at step 2.
+
+**Use case: UC07 - Find a person based on name keyword**
+
+**MSS**
+
+1. User requests to find a person based on a name keyword.
+2. Elder Scrolls displays a list of contacts matching the name keyword.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. User wants to find person specifically amongst the volunteer.
+ * 1a1. User specifies the role as volunteer.
+ * 1a2. Elder Scrolls displays a list of volunteers matching the name keyword.
+ Use case ends.
+* 1b. User wants to find persons that are paired.
+ * 1b1. User specifies the pairing status as paired.
+ * 1b2. Elder Scrolls displays a list of paired persons matching the name keyword.
+ Use case ends.
+* 1c. User wants to find persons that are tagged with a specific tag.
+ * 1c1. User specifies the tag as a property.
+ * 1c2. Elder Scrolls displays a list of persons tagged with the specified tag.
+ Use case ends.
+
+**Use case: UC08 - Find associated logs to a specific person**
+
+**MSS**
+
+1. User requests to find logs associated with a specific person.
+2. Elder Scrolls displays a list of logs associated with the specific person.
+
+ Use case ends.
+
+**Use case: UC09 - Edit field for a specific log**
+
+**MSS**
+
+1. User
requests to list all contacts and logs (UC03).
+2. Elder Scrolls shows a list of contacts and logs.
+3. User requests to edit a field in a specific log, with a new specified value.
+4. Elder Scrolls edits that field for that specific log in the list with the new specified value.
+
+ Use case ends.
-*{More to be added}*
+**Extensions**
+
+* 3a. The given specific log is invalid.
+ * 3a1. Elder Scrolls shows an error message.
+ Use case resumes at step 2.
+
+* 3b. The field that the user wants to edit is not specified
+ * 3b1. Elder Scrolls shows an error message to prompt the user to specify a field to edit.
+ Use case resumes at step 2.
+
+**Use case: UC10 - Undo a command**
+
+**MSS**
+
+1. User requests to execute a command.
+2. Elder Scrolls executes the command.
+3. User requests to undo the command.
+4. Elder Scrolls undoes the command.
+
+ Use case ends
+
+[//]: # (Page Break:)
+
+
+**Extensions**
+
+* 3a. The command that was previously executed does not mutate data in the application
+ * 1a1. Elder Scrolls shows an error message to tell the user there is no previous operation to be undone.
+ Use case resumes at step 2.
+
+**Use case: UC11 - Redo a command**
+
+**MSS**
+
+1. User
requests to undo a command (UC10).
+2. Elder Scrolls undoes the command.
+3. User requests to revert the effects of the undo command.
+4. Elder Scrolls reverts the effect of the undo command.
+
+ Use case ends.
+
+### Non-Functional Requirements
+
+1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
+2. Should be able to hold up to 1000 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.
+4. Should have a simple menu structure with clear labels, guiding users to key actions without extensive tutorials.
+5. Should have graceful error handling with clear human-readable messages to the user to guide them in fixing their command.
+6. Should have a robust data storage mechanism that can handle data corruption and large data sets.
### Glossary
-* **Mainstream OS**: Windows, Linux, Unix, MacOS
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+* **Volunteer**: An individual who offers their time and services to social service agencies or causes without financial compensation, in this context they carry out befriending activities with the beneficiaries.
+* **Befriendee**: An individual who receives support, companionship, or assistance from volunteers, in this context they are the beneficiaries of the befriending activities.
+* **Elder Scrolls**: The Volunteer Management System (VMS) developed by our team for efficient management and bookkeeping of volunteers, befriendees, and their interactions.
+* **Tagging**: Adding an arbitrary detail(s) to a volunteer or befriendee profile to aid in identifying special conditions
+* **Index**: The position or number assigned to each item in a list, used for reference when performing actions such as editing or deleting entries in Elder Scrolls.
+* **Pairing**: The process of associating a volunteer with a befriendee in Elder Scrolls, allowing them to work together on activities or support services.
+* **Logs**: Records of interactions, activities, or events between volunteers and befriendees in Elder Scrolls, used for tracking service hours, progress, and communication.
+* **Command Line Interface (CLI)**: A text-based interface used for interacting with Elder Scrolls through commands typed into a terminal or command prompt.
+* **Graphical User Interface (GUI)**: A visual interface used for interacting with Elder Scrolls, providing intuitive controls and displays for managing volunteers, befriendees and logs.
+
--------------------------------------------------------------------------------------------------------------------
+[//]: # (Page Break:)
+
-## **Appendix: Instructions for manual testing**
+## **Appendix B: Instructions for manual testing**
Given below are instructions to test the app manually.
@@ -340,43 +912,357 @@ testers are expected to do more *exploratory* testing.
### Launch and shutdown
-
1. Initial launch
-
1. Download the jar file and copy into an empty folder
-
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+ 1. Launch Elder Scrolls: Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar elderscrolls.jar` command to run the application. Expected: Shows the GUI with a set of sample contacts and logs. The window size may not be optimum.
1. Saving window preferences
-
1. Resize the window to an optimum size. Move the window to a different location. Close the window.
-
1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-1. _{ more test cases … }_
+### View sample Volunteer and Befriendee profiles and sample Logs
+1. Close Elder Scrolls.
+2. Delete the file `./data/datastore.json` if it exists.
+3. Launch Elder Scrolls.
+ Expected: Lists of sample volunteers, befriendees and logs are shown.
+
+### Find a specific person based on name keywords, pairing status, or tags
+1. Find a volunteer by name keywords
+ 1. Prerequisites: Starting with sample data. Refer to the previous test case to load sample data. Use `list` to reset the view before each testcase.
+ 1. Test case: `find`
+ Expected: No change in list. Error displayed show an invalid command format, where at least one of the optional parameters must be entered.
+ 1. Test case: `find David`
+ Expected: Shows the list of volunteers and befriendees with the name "David".
+ 1. Test case: `find david alex`
+ Expected: Shows the list of volunteers and befriendees with either the name "David" or "Alex".
+
+2. Find a person by pairing status
+ 1. Prerequisites: Starting with sample data. Refer to the previous test case to load sample data. Use `list` to reset the view before each testcase.
+ 1. Test case: `find --paired`
+ Expected: Shows a list of all paired volunteers and befriendees. The list may contain multiple paired persons.
+ 1. Test case: `find --unpaired`
+ Expected: Shows a list of all unpaired volunteers and befriendees. The list may contain multiple unpaired persons.
+
+3. Find a person by tags
+ 1. Prerequisites: Starting with sample data. Refer to the previous test case to load sample data. Use `list` to reset the view before each testcase.
+ 1. Test case: `find t/student`
+ Expected: Shows a list of all volunteers and befriendees with the tag "student".
+
+4. Restrict find operation to volunteers or befriendees
+ 1. Prerequisites: Starting with sample data. Refer to the previous test case to load sample data. Use `list` to reset the view before each testcase.
+ 1. Test case: `find r/volunteer --paired`
+ Expected: Shows list of all volunteers that are paired. Befriendee list is untouched.
+ 1. Test case: `find r/befriendee t/handicapped`
+ Expected: Shows list of all befriendees that have tag `handicapped`. Volunteer list is untouched.
+
+### Adding a person
+1. Adding a person while all befriendees and volunteers are being shown
+
+ 1. Prerequisites: List all persons using the `list` command. Multiple persons in both the befriendees and volunteers list.
+
+ 1. Test case: `add n/Michelle r/volunteer p/98909221 e/mich@gmail.com a/Wow street, blk 123, t/smart`
+ Expected: Contact with the above details is added to the volunteer list. Details of the added contact shown in the status message.
+
+ 1. Test case: `add n/Giggs p/88221143 e/gig@hotmail.com a/Hello street, block 4, #04-01`
+ Expected: No person is added. Error details indicating "Invalid command format!" shown in the status message. Status bar remains the same.
+
+ 1. Other incorrect add commands to try: `add`, `add n/`
+ Expected: Similar to previous.
+
+2. Adding a person where the name already exists in the volunteer list
+
+ 1. Prerequisites: List all persons using the `list` command. There exists a volunteer contact where the name is "Michelle".
+
+ 1. Test case: `add n/Michelle r/volunteer p/98909221 e/mich@gmail.com a/Wow street, blk 123, t/smart`
+ Expected: No person is added. Error details shown in the status message. Status bar remains the same.
+
+### Editing a person
+1. Editing a person while all befriendees and volunteers are being shown
+
+ 1. Prerequisites: List all persons using the `list` command. The person indicated to be edited should exist in the volunteer or befriendee list.
+
+ 1. Test case: `edit 1 r/befriendee p/91225454 e/zhuoran@example.com`
+ Expected: Details of the first befriendee on the befriendee list are edited. Updated details of the edited contact shown in the status message.
+
+ 1. Test case: `edit 1 n/Josh`
+ Expected: No contact is edited. Error details indicating "Role must be specified..." shown in the status message. Status bar remains the same.
+
+ 1. Test case: `edit 0`
+ Expected: No contact is edited. Error details indicating "Invalid command format!" shown in the status message. Status bar remains the same.
+
+ 1. Other incorrect edit command to try: `edit x r/volunteer n/James` (where x is larger than the volunteer list size)
+ Expected: No contact is edited. Error details indicating "The person index provided is invalid" shown in the status message. Status bar remains the same.
+
### Deleting a person
+1. Deleting a person while all befriendees and volunteers are being shown
-1. Deleting a person while all persons are being shown
+ 1. Prerequisites: List all persons using the `list` command. Multiple persons in both the befriendees and volunteers list. The index indicated exists, the contact at that index is not paired with anyone and does not have any logs.
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+ 1. Test case: `delete 3 r/volunteer`
+ Expected: Third contact is deleted from the volunteer list. Details of the deleted contact shown in the status message.
1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+ Expected: No person is deleted. Error details indicating "Role must be specified..." shown in the status message. Status bar remains the same.
+
+ 1. Test case: `delete 0 r/volunteer`
+ Expected: No person is deleted. Error details indicating "Index is not a non-zero" shown in the status message. Status bar remains the same.
+
+2. Deleting a person where the contact is paired
+
+ 1. Prerequisites: List all persons using the `list` command. Multiple persons in both the befriendees and volunteers list. The befriendee at index 1 of the befriendee list is paired with the volunteer at index 1 of the volunteer list.
+
+ 1. Test case: `delete 1 r/volunteer`
+ Expected: No person is deleted. Error details indicating "Unable to delete contact: Contact is paired..." shown in the status message. Status bar remains the same.
+
+3. Deleting a person where the contact has a log
+
+ 1. Prerequisites: List all persons using the `list` command. Multiple persons in both the befriendees and volunteers list. The befriendee at index 2 of the befriendee list exists, is not paired but has a log in the log list.
+
+ 1. Test case: `delete 2 r/befriendee`
+ Expected: No person is deleted. Error details indicating "Unable to delete contact: Contact has a log in Elder Scrolls..." shown in the status message. Status bar remains the same.
+
+
+### Pairing two persons
+1. Pairing two persons while all befriendees and volunteers are being shown
+
+ 1. Prerequisites: List all persons using the `list` command. Multiple persons in both the befriendees and volunteers list. The contacts at the indicated indices exist and are not paired.
+
+ 1. Test case: `pair 1 1`
+ Expected: The befriendee at index 1 of the befriendee list and the volunteer at index 1 of the volunteer list are paired. Details of the two persons shown in the status message.
+
+ 1. Test case: `pair`
+ Expected: No person is paired. Error details indicating "Invalid command format!" shown in the status message. Status bar remains the same.
+
+ 1. Other incorrect pair command to try: `pair 1 x` (where x is larger than the volunteer list size)
+ Expected: No person is paired. Error details indicating "The person index provided is invalid" shown in the status message. Status bar remains the same.
+
+2. Pairing two persons where one or both of the persons are already paired
+
+ 1. Prerequisites: List all persons using the `list` command. Multiple persons in both the befriendees and volunteers list. The contacts at the indicated indices exist and one or both are already paired.
+
+ 1. Test case: `pair 1 2`
+ Expected: No person is paired. Error details indicating "One or both of the persons are already paired..." shown in the status message. Status bar remains the same.
+
+
+### Unpairing two persons
+1. Unpairing two persons while all befriendees and volunteers are being shown
+
+ 1. Prerequisites: List all persons using the `list` command. Multiple persons in both the befriendees and volunteers list. The contacts at the indicated indices exist and are paired with each other.
+
+ 1. Test case: `unpair 1 1`
+ Expected: The befriendee at index 1 of the befriendee list and the volunteer at index 1 of the volunteer list are unpaired. Details of the two persons shown in the status message.
+
+ 1. Test case: `unpair 3`
+ Expected: No person is unpaired. Error details indicating "Invalid command format!" shown in the status message. Status bar remains the same.
+
+ 1. Other incorrect unpair command to try: `unpair 1 x` (where x is larger than the volunteer list size)
+ Expected: No person is unpaired. Error details indicating "The person index provided is invalid" shown in the status message. Status bar remains the same.
+
+2. Unpairing two persons where the persons are not paired with each other
+
+ 1. Prerequisites: List all persons using the `list` command. Multiple persons in both the befriendees and volunteers list. The contacts at the indicated indices exist and are not paired with each other.
+
+ 1. Test case: `unpair 2 3`
+ Expected: No person is unpaired. Error details indicating "The two persons are not paired..." shown in the status message. Status bar remains the same.
+
+### Adding a log
+1. Adding a log while all befriendees and volunteers are being shown
+
+ 1. Prerequisites: List all persons using the `list` command. Multiple persons in both the befriendees and volunteers list. The first index refers to the befriendee and the second index refers to the volunteer. The contacts at the indicated indices exist and are paired with each other.
+
+ 1. Test case: `logadd 1 1 t/Icebreaker session s/2022-03-05 d/2 r/got to know more about befriendee`
+ Expected: A log is added for the paired befriendee at index 1 of the befriendee list and the volunteer at index 1 of the volunteer list.
+
+ 1. Test case: `logadd 1 1 t/Icebreaker session d/2 `
+ Expected: No log is added. Error details indicating "Invalid command format!" shown in the status message. Status bar remains the same.
+
+ 1. Other incorrect logadd command to try: `logadd 1 x t/Icebreaker session s/2022-03-05 d/2 r/got to know more about befriendee` (where x is larger than the volunteer list size)
+ Expected: No log is added. Error details indicating "The person index provided is invalid." shown in the status message. Status bar remains the same.
+
+
+### Editing a log
+1. Editing a log while all logs are being shown
+
+ 1. Prerequisites: List all logs using the `list` command. Multiple logs in logs list. The log at the indicated index should exist in the logs list.
+
+ 1. Test case: `logedit 1 t/Cinema visit`
+ Expected: The log at index 1 of the log list is edited. Updated details of the edited log shown in the status message.
+
+ 1. Test case: `logedit 1`
+ Expected: No log is edited. Error details indicating "At least one field to edit must be provided." shown in the status message. Status bar remains the same.
+
+ 1. Other incorrect logedit command to try: `logedit x t/Cinema visit` (where x is larger than the log list size)
+ Expected: No log is edited. Error details indicating "The log index provided is invalid." shown in the status message. Status bar remains the same.
+
+### Deleting a log
+1. Deleting a log while all logs are being shown
+
+ 1. Prerequisites: List all logs using the `list` command. Multiple logs in logs list. The log at the indicated index should exist in the logs list.
+
+ 1. Test case: `logdelete 3`
+ Expected: The log at index 3 of the log list is deleted. Details of the deleted log shown in the status message.
+
+ 1. Test case: `logdelete`
+ Expected: No log is deleted. Error details indicating "Invalid command format!" shown in the status message. Status bar remains the same.
+
+ 1. Other incorrect logdelete command to try: `logdelete x` (where x is larger than the log list size)
+ Expected: No log is deleted. Error details indicating "Unable to delete log: The log index provided is invalid." shown in the status message. Status bar remains the same.
+
+
+### Finding Logs associated with a person
+1. Find Logs for a person while all volunteers and befriendees are shown
+ 1. Prerequisites: Starting with sample data. Refer to the previous test case to load sample data. Use `list` to reset the view before each testcase. The contact at the indicated index should exist in the volunteer or befriendee list.
+ 2. Test case: `findlog`
+ Expected: No change in list. Error displayed show an invalid command format, where at least one of the optional parameters must be entered.
+ 3. Test case: `findlog 1 r/volunteer`
+ Expected: Shows the list of logs associated with the volunteer at the index 1.
+ 4. Test case: `findlog 2 r/befriendee`
+ Expected: Shows the list of logs associated with the befriendee at the index 2.
+
+[//]: # (Page Break:)
+
+
+### Undo previously executed commands
+1. Undo a command that mutates the data in Elder Scrolls (`add`, `edit`, `delete`, `pair`, `unpair`, `logadd`, `logedit`, `logdelete`, `clear`)
+ 1. **Prerequisites:** Starting with sample data. Refer to the previous test case to load sample data. Use `list` to reset the view before each testcase.
+ 2. **Test case:** `delete 3 r/volunteer` followed by `undo`
+ **Expected:** After the first command, the third contact in the volunteer list is deleted.
+ Subsequently after the second command is executed, the deletion of the third contact in the volunteer list is undone.
+ The status bar displays that the previous operation has been undone.
+ 3. **Test case:** `edit 1 r/volunteer n/alex` followed by `undo`
+ Expected: After the first command is executed, the first person in the volunteer list has their name edited to "alex", the logs previously associated with "Alex Yeoh" now display "alex" as the volunteer associated with them.
+ Subsequently, after the second command is executed, the edit made on the name of the first volunteer in the volunteer list is undone, and their name is back to being "Alex Yeoh".
+ The status bar displays that the previous operation has been undone.
+
+
+2. Executing an undo command when the application is newly launched
+ 1. **Prerequisites:** Exit the application and launch it again, making sure to not execute any commands before the testcase.
+ 2. **Test case:** `undo`
+ **Expected:** No operation is undone. Error details indicating "No previous operation to be undone" shown in the status message. Status bar remains the same.
+
+
+3. Executing an undo command after an operation that does not modify data in Elder scrolls is executed
+ 1. **Prerequisites:** Exit the application and launch it again, making sure to not execute any commands before the testcase. Start with sample data. Refer to the previous test cases on how to load sample data.
+ 2. **Test case:** `list` followed by `undo`
+ **Expected:** All persons should be listed after the first command, with a success message displayed in the status message.
+ Subsequently, after executing the second command, no operation is undone, the user's view of the volunteer, befriendee and log lists remains the same.
+ Error details indicating "No previous operation to be undone" shown in the status message. Status bar remains the same.
+ 3. **Test case:** `find --paired` followed by `undo`
+ **Expected:** After the first command, lists of all paired befriendees and volunteers should be shown.
+ Subsequently, after executing the second command, no operation is undone, the user's view of the volunteer, befriendee and log lists remains the same.
+ Error details indicating "No previous operation to be undone" shown in the status message. Status bar remains the same.
+ 4. **Test case:** `findlog 1 r/volunteer` followed by `undo`
+ **Expected:** After the first command, the list of logs associated with the volunteer at index 1 should be shown.
+ Subsequently, after executing the second command, no operation is undone, the user's view of the volunteer, befriendee and log lists remains the same.
+ Error details indicating "No previous operation to be undone" shown in the status message. Status bar remains the same.
+
+### Redo changes made by previous Undo command
+1. Redo a command that has been undone
+ 1. **Prerequisites:** Start with the sample data. Refer to the previous test case to load sample data. Use `list` to reset the view before each testcase.
+ 2. **Test case:** `delete 3 r/volunteer` followed by `undo` followed by `redo`
+ **Expected:** After the first command, the third volunteer in the volunteer list is deleted. After the second command, the deletion of the third volunteer is undone.
+ Subsequently, after the `redo` command is executed, the third volunteer in the volunteer is once again deleted, reverting the effects of the undo command that was executed.
+ The status bar displays that the previous undo operation has been reversed.
+ 3. **Test case:** `edit 1 r/volunteer n/alex` followed by `undo` followed by `redo`
+ **Expected:** After the first command, the first volunteer in the volunteer list will have their name edited to "alex" from "Alex Yeoh". After the second command, the edit made on the first volunteer's name will be undone, and their name will be reverted back to "Alex Yeoh".
+ Subsequently, after the `redo` command is executed, the first volunteer in the volunteer list will once again have their name edited to "alex", reverting the effects of the undo command that was executed.
+ The status bar displays that the previous undo operation has been reversed.
+
+
+2. Executing a redo command when the application is newly launched
+ 1. **Prerequisites:** Exit the application and launch it again, making sure to not execute any commands before the testcase.
+ 2. **Test case:** `redo`
+ **Expected:** No undo operation is reversed. Error details indicating "No previous undo operation to be reversed" shown in the status message. Status bar remains the same.
+
+
+3. Executing a redo command after executing an undo command followed by a command mutating the data in Elder Scrolls.
+ 1. **Prerequisites:** Start with the sample data. Refer to the previous test cases to load sample data. Execute `delete 3 r/volunteer` followed by `undo`
+ as done in the test case for the `undo` command
+ 2. **Test case:** `edit 1 r/volunteer n/alex` followed by `redo`
+ **Expected:** After the first command, the first volunteer in the volunteer list will have their name edited to "alex" from "Alex Yeoh".
+ Subsequently, when the `redo` command is executed, the `undo` operation of the `delete 3 r/volunteer` command will not be reversed.
+ Error details indicating "No previous undo operation to be reversed" shown in the status message. Status bar remains the same.
+
+
+### Clearing Elder Scrolls data
+1. Clearing all data
+ 1. **Prerequisites:** At least one volunteer or one befriendee is present in the lists.
+ 1. **Test case:** `clear`
+ **Expected:** The lists of befriendees, volunteers and logs are cleared. The lists are empty.
+
+### Exiting Elder Scrolls
+1. Exiting the application
+ 1. **Test case:** `exit`
+ **Expected:** The application window closes.
+
+### Opening the Help Window
+1. Accessing the help window
+ 1. **Test case:** `help`
+ **Expected:** The help window opens.
+
+--------------------------------------------------------------------------------------------------------------------
+
+[//]: # (Page Break:)
+
+
+## **Appendix C: Effort**
+
+### Difficulty Estimation
+
+Overall, the team believes that the features we have chosen to undertake are fairly complex, as they involve fundamental changes to the application state, as is the case with `undo` and `redo`. Additionally, the addition of `log` features and separation of person types meant that the existing relationships between entities inherited from AB3 had to be restructured significantly.
+
+The team also made significant changes to the UI, in an attempt to modernise the look and feel of the application. With meticulous reference to modern design systems, the team made significant changes to the styling of the application. These changes were challenging as they required a good understanding of JavaFX and the ability to work within the confines of the existing codebase.
+
+### Challenges Faced
+
+1. **Global ID vs Indexed ID**: We initially decided on using a global ID for each person, but later realised upon further exploration of the codebase that refactoring all instances of list indexing would be an outsized task. Thus, the team compromised by using global IDs for identifications, but still using list indices for client-facing commands.
+2. **Adding Logs**: The team faced challenges in adding logs to the application, as it required a significant restructuring of the existing codebase. Given that logs were an entirely new set of entities to be stored in the application, where AB3 had only a single entity, we quickly realised that the existing structure of the `model` was not sufficient to handle the new requirements. This led to a significant amount of refactoring and restructuring of the `Model` and `Storage` classes.
+3. **Relationships between entities**: Given that the new features included two different classes of `Person` as well as a `Log` class associated with both, the team spent a significant amount of time planning and testing the correctness of the relations between these entities. Through defensive programming and good test coverage, we were able to ensure that all user operations modifying these relationships were correctly reflected in the application state.
+
+[//]: # (Page Break:)
+
+
+### Effort Required
+
+Overall, over the thousands of lines of code changed, the team estimates that a few dozen man-hours were spent on the project. The team met regularly to discuss the project, and each member contributed significantly to the project through an even distribution of work over the main areas: adding commands, restructuring logic and backend, UI. The team also spent a significant amount of time on testing, as the new features added to the application were complex and required a significant amount of additional test code to ensure that they worked as expected.
+
+As a result, we were able to deliver on planned features while increasing our code coverage over the inherited project.
+
+### Achievements
+
+A robust and extensible application was delivered, with a significant amount of new features added to the application. The team was able to deliver on planned features, and the performance of new features, such as `log` operations, were significantly improved over old features of the inherited project.
+
+--------------------------------------------------------------------------------------------------------------------
+[//]: # (Page Break:)
+
+
+## **Appendix D: Planned Enhancements**
+
+Our current version of Elder Scrolls enables users to efficiently manage volunteers, befriendees, and their interactions. However, we have identified planned enhancements that will further improve the user experience and functionality of the application.
+
+* Team size: 5. (Max 10 planned enhancements)
+
+1. **Even more flexible Log Entry Editing**: The current `logedit` command does not allow users to reassign the volunteer or befriendee involved in a log, with the only way being to delete and re-add a new log. This could be inconvenient if someone accidentally inputs the wrong pair of indices resulting in a wrong pair being referenced. Hence, we plan to make `logedit` support reassignment of logs to other, valid pairs.
+
+2. **Enhanced find feature**: The current `find` command returns valid entries based on full matching of user inputs. We plan to enhance our `find` command with partial name keyword/tag search functionality, enabling users to find contacts based on partial name/tag matches. Improves search efficiency and usability in case a user may not fully remember a contact's name or tag.
+
+3. **Better find command format**: While the current `find` command supports a more concise and naturalised search, users may feel that the parameter format is not standardised across all commands, leading to inconvenience for those with bad memory. We plan to implement and support standardised `find` command parameters (such as `n/NAME_KEYWORD`, `p/PAIR_STATUS` etc.) that would help streamline the formatting across all our commands. This would also naturally include appropriate exception handling for invalid input parameters as well.
+
+4. **Add customizability to constraints of logs added**: Currently, logs added can have dates in the future. While this may be desirable (for e.g. to log a planned visit with todos, and subsequently update remarks after the visit), it may also be undesirable (for e.g. to prevent accidental future date inputs). We plan to add a configuration option to allow users to set constraints on the dates of logs added, such that users can customize the behavior to suit their needs.
+
+5. **Flexible Grouping of Volunteer and Befriendee**: Currently, our system only supports 1-1 pairing of volunteer and befriendee. We recognise that expanding beyond will bring stronger flexibility and generalizability, and hence plan to expand the current 1-to-1 pairing system to allow for more flexible grouping of volunteers and befriendees. Specifically, this would allow multiple volunteers to be paired with a single befriendee, vice versa, or allow a group of volunteers to be paired with a group of befriendees. This would be consistent with logadd, logedit, and logdelete commands.
+
+6. **More Informative Pairing and Unpairing Outputs**: Currently, the `pair` and `unpair` commands do not provide exact and detailed feedback during failure of the operation (e.g. person already paired, or persons are not paired together). While the user can thereafter search to find these details using the `find` command, we plan to enhance these commands to provide stronger feedback, including which person is paired, whom each person is currently paired with, and provide shortcuts for a user to easily unpair that person (e.g. by providing copy-paste formatted commands in the output).
+
+7. **More Customizable UI for output**: Currently, while the entire window is adjustable with each list scaling individually, the output message window is not height adjustable. To improve user experience, we plan to make the output message window height adjustable, allowing users to adjust the height of the output message window to their preference.
- 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+8. **More Specific Error Messages showing specific parameter absence/invalidity**: While the current error messages are informative, they do not provide specific details on which parameter is missing or invalid. We plan to enhance the error messages to provide more specific details on which parameter is missing or invalid, allowing users to quickly identify and rectify the issue. (E.g. for `add` command, the error message will specify which parameter is missing or invalid).
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous.
+9. **Better duplicate person detection**: Currently, our application disallows duplicate people without case sensitivity, meaning 2 contacts with names `John Doe` and `john doe` respectively are considered duplicate people. However, extra white spaces in the name will cause contacts to be considered different people, even if everything else is equal. Thus, we plan to enhance the duplicate detection in our application to warn the user when inputting such near match cases. This will give users the ability to confirm if they intend to add a new contact resembling an existing one, with extra white spaces.
-1. _{ more test cases … }_
-### Saving data
-1. Dealing with missing/corrupted data files
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
-1. _{ more test cases … }_
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
index 275445bd551..690e54b1311 100644
--- a/docs/SettingUp.md
+++ b/docs/SettingUp.md
@@ -23,7 +23,7 @@ If you plan to use Intellij IDEA (highly recommended):
1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
:exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project.
1. **Verify the setup**:
- 1. Run the `seedu.address.Main` and try a few commands.
+ 1. Run the `scrolls.elder.Main` and try a few commands.
1. [Run the tests](Testing.md) to ensure they all pass.
--------------------------------------------------------------------------------------------------------------------
diff --git a/docs/Testing.md b/docs/Testing.md
index 8a99e82438a..e6e759783b6 100644
--- a/docs/Testing.md
+++ b/docs/Testing.md
@@ -31,6 +31,6 @@ This project has three types of tests:
1. *Unit tests* targeting the lowest level methods/classes.
e.g. `seedu.address.commons.StringUtilTest`
1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
- e.g. `seedu.address.storage.StorageManagerTest`
+ e.g. `storage.scrolls.elder.StorageManagerTest`
1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
- e.g. `seedu.address.logic.LogicManagerTest`
+ e.g. `logic.scrolls.elder.LogicManagerTest`
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 7abd1984218..8abc9d6d67c 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,49 +3,275 @@ layout: page
title: User Guide
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+## **Introduction - What is Elder Scrolls?**
+
+{: style="text-align: justify" }
+**Elder Scrolls is a** ___free Volunteer Management System (VMS)___ that streamlines the coordination of volunteers and befriendees, with a particular focus on elderly befriending programs. Elder Scrolls combines the _speed of Command Line Interface (CLI) interaction_ with the _benefits of a Graphical User Interface (GUI)_. Whether you prefer the agility of typing or the convenience of visual interaction, Elder Scrolls ensures that your volunteer management tasks are completed swiftly and seamlessly.
+
+{: style="text-align: justify" }
+**Elder Scrolls is crafted with _volunteer managers_, and _organizers_ involved in elderly befriending initiatives in mind**. Whether you're managing volunteers for elderly care, community outreach, or other social services, Elder Scrolls offers a comprehensive solution for you to simplify and enhance your volunteer management efforts.
+
+{: style="text-align: justify" }
+**No more cumbersome bookkeeping**: manage volunteers and befriendees seamlessly in one intuitive platform. Say goodbye to endless spreadsheets – Elder Scrolls centralizes tasks, making them faster and more effective, with **two main features**: ___Volunteer/Befriendee Management___, and ___Log Management___. These features allow you to effortlessly add and update your volunteers' and befriendees' information, and easily keep track of all the interactions between them.
+
+{: style="text-align: justify" }
+Developed for efficiency by our team, ___Elder Scrolls lets you focus on what matters most – making a difference in the lives of others___.
+
+
+
+## **About this User Guide**
+
+{: style="text-align: justify" }
+**Welcome to the user guide for Elder Scrolls!** Thank you for choosing our application! Whether you're new or experienced, this guide has everything you need to quickly get acquainted. Find answers, get step-by-step instructions, and learn about all the features Elder Scrolls has to offer:
+
+* [**Quick Start**](#1-quick-start): Get started with Elder Scrolls quickly and easily.
+* [**User Interface**](#2-user-interface): Understand the intuitively simple layout of Elder Scrolls.
+* [**Features**](#3-features): Explore all the functionalities of Elder Scrolls.
+* [**Command Summary**](#8-command-summary): Find all the essential commands at a glance.
+* [**FAQs**](#6-faq-frequently-asked-questions): Get answers to common questions about Elder Scrolls.
+* [**Glossary**](#9-glossary): Familiarise with key terms and concepts used in Elder Scrolls.
+* Spot the **:bulb: symbol** for tips provided to enhance your experience!
+
+Let's dive in and maximize your Elder Scrolls experience!
+
+--------------------------------------------------------------------------------------------------------------------
+
+## **Table of Contents**
* Table of Contents
{:toc}
--------------------------------------------------------------------------------------------------------------------
-## Quick start
+## **Setting Up**
+
+Before getting started, let's ensure everything is set up for Elder Scrolls to run correctly:
+
+1. Ensure you have `Java 11` installed on your computer. This is crucial for Elder Scrolls to function properly.
+ * If you're unsure whether Java 11 is installed, follow this short [guide](https://www.baeldung.com/java-check-is-installed) to check.
+ * Install Java 11 (if needed): If Java 11 is not installed, follow the provided installation instructions [here](https://docs.oracle.com/en/java/javase/11/install/overview-jdk-installation.html#GUID-8677A77F-231A-40F7-98B9-1FD0B48C346A). If you're using a Mac computer with Apple Silicon, you can download Java 11 from [here](https://www.azul.com/downloads/?version=java-11-lts&os=macos&architecture=arm-64-bit&package=jdk-fx#zulu) instead.
+2. Next, download our latest `elderscrolls.jar` release [here](https://github.com/AY2324S2-CS2103T-T09-3/tp/releases).
+3. Next, copy the downloaded `elderscrolls.jar` to the desired home folder for Elder Scrolls. Elder Scrolls will store all application files and data in this folder, so it is best to create an empty folder for this purpose.
+
+--------------------------------------------------------------------------------------------------------------------
+
+## **1. Quick Start**
+[Back to About & Table of Contents ←](#about-this-user-guide)
+
+Once you've completed the setup, you're ready to launch Elder Scrolls! Let's get started:
+
+1. Open a command terminal, and navigate by using `cd` into the folder you put the jar file in.
+2. Use the `java -jar elderscrolls.jar` command to run the application.
+
+A GUI similar to the one below should appear in a few seconds. If this is your first time launching Elder Scrolls, the application should contain some sample data to get you started!
+
+
+
+
The starting user interface with sample data
+
+
+After this, you're all set to begin using Elder Scrolls! Let's make managing volunteers and befriendees a breeze.
+
+**Let's walk through some important commands to get you started:**
+
+1. The most integral function of Elder Scrolls is to help you to keep track of the people in your volunteering organisation. Naturally, there is a way to add new volunteers and befriendees to the system. Let's try adding a new befriendee, John Doe, to the system using the **`add` command**.
+
+ ```
+ add n/John Doe p/98765432 e/johnd@example.com a/Blk 123 #01-01 r/befriendee
+ ```
+
+
+
+
Scroll down the left Befriendees
list to see John's card at the bottom of the list.
+
-1. Ensure you have Java `11` or above installed in your Computer.
+
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+2. John now needs a volunteer friend! Let's have volunteer Bernice take care of him. From the list, you can see that Bernice has been hard at work befriending Irfan, but sadly, we'll need to reassign her. Let's unpair Bernice and Irfan using the **`unpair` command**, since Irfan sits at the 2nd index in the befriendee list.
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+ ```
+ unpair 2 2
+ ```
-1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png)
+
+
+
Both lists should reset to their original state, and looking at Bernice and Irfan's cards, they are no longer paired.
+
+
-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try:
+3. Now we are ready to pair Bernice with John. But John's card is far down the list, and it can be inconvenient to scroll down to find his index. No problem, one of the most powerful features of Elder Scrolls is the **`find` command**.
- * `list` : Lists all contacts.
+ This command allows you to search for contacts based on various criteria, such as name, role, pairing status, and tags. Now, let's try to bring John to the top of our list using the **`find` command**.
- * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+ ```
+ find r/befriendee John
+ ```
+
+
+
Here, we've specified the r/befriendee
filter, which ensures we only search in the Befriendees
list, leaving the Volunteers
list untouched.
+
- * `delete 3` : Deletes the 3rd contact shown in the current list.
+4. Now that we've found John, let's pair him with Bernice. Bernice sits at the 2nd index in the volunteer list, so we can pair them using the **`pair` command**.
- * `clear` : Deletes all contacts.
+ ```
+ pair 1 2
+ ```
+
+
+
You should see the Paired with:
field in Bernice and John's cards reflect the new pairing.
+
- * `exit` : Exits the app.
-1. Refer to the [Features](#features) below for details of each command.
+5. Finally, let's add an entry in `Logs` to record the first meeting between Bernice and John. We can `find` John again, then use the **`logadd` command** to do this.
+
+ ```
+ find r/befriendee John
+ ```
+ ```
+ logadd 1 2 t/First Meeting s/2024-04-10 d/1 r/Introduction
+ ```
+
+
+
+
You should see the log card appear in the Logs
list on the right, with the details supplied.
+
+
+6. To double-check that our log has been added, let's use the **`logfind` command**, which can help us to find all logs associated with John. First, let's search for our pair John and Bernice, then use the `logfind` command to find the log we just added.
+
+ ```
+ find john bernice
+ ```
+ ```
+ logfind 1 r/befriendee
+ ```
+
+
+
+
You should see a single log card in the Logs
list, which we have just added.
+
+
+
+7. This summarizes the basic workflow of Elder Scrolls. To reset your view of each list and clear all filters, use the **`list` command**. Feel free to remove the sample data and start fresh by using the **`clear` command** and get **started with your own data!**
+
+ ```
+ list
+ ```
+ ```
+ clear
+ ```
+
+ **The lists should now be empty, ready for you to start managing your own volunteers and befriendees!**
+
+7. You can refer to the [**Features**](#3-features) below for detailed descriptions of all the commands available in Elder Scrolls.
--------------------------------------------------------------------------------------------------------------------
-## Features
+[//]: # (Page Break:)
+
+
+
+## **2. User Interface**
+[Back to About & Table of Contents ←](#about-this-user-guide)
+
+
+
+
+
User interface Layout Breakdown of Elder Scrolls
+
+
+### 2.1 Toolbar
+
+The toolbar at the top provides functionality for **accessing help and exiting** Elder Scrolls. The application window contains the following buttons:
+* `File`: Hovering over opens a dropdown menu, click on Exit to close the application!
+* `Help`: Hovering over opens a dropdown menu, whereby clicking on Help F1 opens a help pop-up!
+
+### 2.2 Command Input Box
+
+The command input box at the top of the application window is where you can **type commands to interact** with Elder Scrolls. **Press `Enter` to execute** the command.
+
+### 2.3 Command Output Box
+
+The command output box displays the **results of the commands** you have executed. It also **displays error messages** when an invalid command is entered. Paying close attention to the feedback provided here helps enhance your experience with Elder Scrolls!
+
+### 2.4 Befriendee List Panel
+
+This section **displays all your registered befriendees**, and you may filter the display using our supported find commands. The list is updated in real time as you execute commands.
+
+
Befriendee List Card
+
+Each befriendee list card displays the following information:
+
+
+
+
+
+1. **Index**: The index of the befriendee in the list.
+2. **Name**: The name of the befriendee.
+3. **Tags**: Tags associated with the befriendee.
+4. **Phone Number**: The phone number of the befriendee.
+5. **Address**: The address of the befriendee.
+6. **Email**: The email address of the befriendee.
+7. **Paired with**: The name of the volunteer the befriendee is paired with, if any.
+8. **Most Recent Log**: The most recent log associated with the befriendee, if any.
+
+
+
+### 2.5 Volunteer List Panel
+
+Similar to the Befriendee List Panel, this section **displays all your registered volunteers**. The volunteer list cards also support an added aggregate statistic `Time Served`, which helps you better track volunteer hours!
+
+
Volunteer List Card
+
+
+
+
+
+1. **Index**: The index of the volunteer in the list.
+2. **Name**: The name of the volunteer.
+3. **Tags**: Tags associated with the volunteer.
+4. **Phone Number**: The phone number of the volunteer.
+5. **Address**: The address of the volunteer.
+6. **Email**: The email address of the volunteer.
+7. **Paired with**: The name of the befriendee the volunteer is paired with, if any.
+8. **Time Served**: The total time volunteered by the volunteer, in hours.
+9. **Most Recent Log**: The most recent log associated with the volunteer, if any.
+
+
+
+### 2.6 Log List Panel
+
+Your Log List Panel **displays all logs associated with your befriendees and volunteers**.
+
+
Log List Card
+
+
+
+
+
+1. **Index**: The index of the log in the list.
+2. **Title**: The title of the log.
+3. **Start Date**: The date recorded in the log of the activity.
+4. **Volunteer Name**: The name of the volunteer associated with the log.
+5. **Befriendee Name**: The name of the befriendee associated with the log.
+6. **Duration**: The duration of the activity in hours.
+7. **Remarks**: Any additional remarks or notes about the activity.
+
+--------------------------------------------------------------------------------------------------------------------
+
+
+
+## **3. Features**
+[Back to About & Table of Contents ←](#about-this-user-guide)
**:information_source: Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
+* For all commands which require an `INDEX` (e.g., `VOLUNTEER_INDEX`), the index refers to the index number shown in the displayed person list.
+ The indexes provided **must be indexes of people currently displayed in the person lists**.
+ * As the indexes are one-based, index values must be positive integers greater than 0. Any other values are automatically invalidated and hence constitute an invalid command format.
+
+* Words in `UPPER_CASE` are placeholders to represent parameters that should be supplied by the user.
e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
* Items in square brackets are optional.
@@ -60,139 +286,457 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`.
-* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
+* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line breaks may be omitted when copied over to the application.
-### Viewing help : `help`
+
-Shows a message explaning how to access the help page.
+### **3.1 Volunteer / Befriendee Management**
-![help message](images/helpMessage.png)
+#### 3.1.1 Adding a Volunteer or Befriendee: `add`
-Format: `help`
+Adds a volunteer/befriendee to the address book.
+Format: `add n/NAME r/ROLE p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
-### Adding a person: `add`
+Constraints:
+* `n/NAME` must be alphanumeric and cannot be empty.
+* `p/PHONE_NUMBER` must be at least 3 digits long.
+* `r/ROLE` must **only** be either `volunteer` or `befriendee`, which will add either a volunteer or befriendee respectively.
+* `e/EMAIL` must be a valid email address of the format `local-part@domain`.
+* `a/ADDRESS` has no limitations but cannot have line breaks, i.e., it should be the single-line format for addresses.
+* `t/TAG` must be alphanumeric.
+* `n/NAME`, `r/ROLE`, `p/PHONE_NUMBER`, `e/EMAIL` and `a/ADDRESS` must be provided and cannot be empty.
-Adds a person to the address book.
+
:bulb: **Tip:**
+A person can have any number of tags (including 0).
+
+
+Examples:
+* `add n/John Doe r/volunteer p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
+* `add n/Betsy Crowe r/befriendee t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal t/homicide`
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+#### 3.1.2 Editing a person : `edit`
+
+Edits an existing person in Elder Scrolls.
+
+Format: `edit INDEX r/ROLE [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+
+Constraints:
+* `n/NAME` must be alphanumeric and cannot be empty.
+* `p/PHONE_NUMBER` must be at least 3 digits long.
+* `r/ROLE` must **only** be either `volunteer` or `befriendee`, which will edit either a volunteer or befriendee respectively.
+* `e/EMAIL` must be a valid email address.
+* `a/ADDRESS` has no limitations but cannot have line breaks, i.e., it should be the single-line format for addresses.
+* `t/TAG` must be alphanumeric.
+
+Additional Information:
+* Edits the person at the specified `INDEX` for the list corresponding to the provided role. For example, if `r/volunteer` is provided, the person at the `INDEX` in the volunteer list will be edited.
+* At least one of the optional fields must be provided.
+* Existing values will be updated to the input values.
+* When editing tags, the existing tags of the person will be removed i.e. adding of tags is not cumulative.
+* You can remove all the person’s tags by typing `t/` without specifying any tags after it.
+
+Examples:
+* `edit 1 r/volunteer p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
+* `edit 2 r/befriendee n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
+
+#### 3.1.3 Pairing a befriendee and volunteer : `pair`
+
+Pairs an existing befriendee and volunteer in Elder Scrolls.
+
+Format: `pair BEFRIENDEE_INDEX VOLUNTEER_INDEX`
+
+* The person at `BEFRIENDEE_INDEX` must be a befriendee and the person at `VOLUNTEER_INDEX` must be a volunteer.
+* Neither of the two people should be paired, if they are, they must be unpaired before pairing again.
+
+Examples:
+* `pair 1 2` Pairs the befriendee at Index 1 of the befriendee list and the volunteer at Index 2 of the volunteer list.
+* `pair 3 3` Pairs the befriendee at Index 3 of the befriendee list and the volunteer at Index 3 of the volunteer list.
+
+[//]: # (Page Break:)
+
+
+#### 3.1.4 Unpairing a befriendee and volunteer : `unpair`
+
+Unpairs an existing befriendee and volunteer in Elder Scrolls.
+
+Format: `unpair BEFRIENDEE_INDEX VOLUNTEER_INDEX`
+
+* The person at `BEFRIENDEE_INDEX` must be a befriendee and the person at `VOLUNTEER_INDEX` must be a volunteer.
+* The befriendee and volunteer must be paired with each other before they can be unpaired.
+
+
+
+:bulb: **Tip:**
+
+Unpairing can be greatly simplified with the help of the `find` command.
+For example, let's say you would like to unpair `David Li`.
+1. Find the name of David's partner by using `find David`, and looking at the `Paired with:` field.
+2. Let's say his partner's name is `Alex`, now use `find --paired David Alex` to list only David and his partner.
+3. Use `unpair 1 1` to unpair David and his partner, as they will likely be the only people listed.
+
+The same strategy can be applied any time you wish to find who a person is paired with specifically.
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
Examples:
-* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
-* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal`
+* `unpair 1 2` Unpairs the befriendee at Index 1 of the befriendee list and the volunteer at Index 2 of the volunteer list.
+* `unpair 3 3` Unpairs the befriendee at Index 3 of the befriendee list and the volunteer at Index 3 of the volunteer list.
-### Listing all persons : `list`
+#### 3.1.5 Listing all persons : `list`
-Shows a list of all persons in the address book.
+Shows a list of all persons and logs in Elder Scrolls.
Format: `list`
-### Editing a person : `edit`
+* Persons and Logs are listed separately, and in the order they were added, from earliest to latest.
+* The list command is commonly used with the `find` command to reset the view after a search.
+
+[//]: # (Page Break:)
+
+
+#### 3.1.6 Locating persons: `find`
+
+Find persons through a variety of filters.
-Edits an existing person in the address book.
+Format: `find [FILTERS]`
+Alternative format: `search [FILTERS]`
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+**At least 1** filter must be provided.
+The following filters are supported:
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
+* By name: `NAME`
+* By pairing status: `--paired` or `--unpaired`
+* By tag: `t/TAG`
+* By role: `r/ROLE`
+
+##### By name: `NAME`
+
+In its most basic form, the command requires a single _case-insensitive_ `NAME` filter, which will filter both lists for all persons whose **names** contain the given `NAME`. Only full words will be matched.
+
+Example: `find david` (finds all persons with `David`, `david`, etc. in their names, but will not find `Davidson` or `Davidsonson`)
+
+`NAME` is also the **only** filter which can be provided multiple times. Providing multiple `NAME` filters will return all persons whose names contain any of the given `NAME`s.
+
+Example: `find David Yeoh` (finds all persons with `David` _or_ `Yeoh` in their names, like `David Li`, `Alex Yeoh`, and even `Yeoh David`)
+
+##### By pairing status: `--paired` or `--unpaired`
+
+If the `--paired` or `--unpaired` filter is provided, the search will be limited to either paired or non-paired persons.
+
+Examples:
+* `find --unpaired` (finds all unpaired persons)
+* `find --paired David` (finds all paired persons with `David` in their names)
+
+##### By tag: `t/TAG`
+
+If the _case-sensitive_ `t/TAG` filter is provided, the search will be limited to persons with the specified tag(s).
+
+Example: `find t/friend` (finds all persons who have the tag `friend`)
+
+[//]: # (Page Break:)
+
+
+##### By role: `r/ROLE`
+
+The `r/ROLE` filter is provided, the search will be limited to the specified list. The other list remains unaffected.
+Only two versions of this filter are allowed: `r/volunteer` and `r/befriendee`.
+
+
:bulb: **Tip:**
+The `r/ROLE` filter can only be applied together with another filter. It cannot be used on its own.
+
+
+Example: `find r/volunteer David` (finds all volunteers with `David` in their names, the befriendee list remains unfiltered)
+
+##### Combining filters
+
+* Different types of filters can be combined to narrow down the search results further.
+* When multiple filters are provided, each filter should be separated by a space.
+* The order in which filters are provided does not matter.
+* Invalid filters (e.g. `r/XXX, -paired`) will be ignored, and the search will proceed with the valid filters to enhance efficiency.
+
+Examples:
+* `find alex david`
+ ![result for 'find alex david'](images/findAlexDavidResult.png)
+
+[//]: # (Page Break:)
+
+
+* `find r/volunteer t/student --paired Bernice` (finds all paired volunteers with the tag `student` and name containing `Bernice`)
+ ![result for 'find Bernice'](images/findBerniceResult.png)
+
+
:bulb: **Tip:**
+Use the `list` command to reset your view after using the `find` command.
+
+
+#### 3.1.7 Deleting a person : `delete`
+
+Deletes the specified person from Elder Scrolls.
+
+Supported aliases for `delete`: `del`, `remove`, `rm`
+
+Format: `delete INDEX r/ROLE`
+
+* Deletes the person at the specified `INDEX`, for the list corresponding to the provided role.
+* The deleted person must not be paired with any other person.
+* Deletion is not allowed if there exists associated logs to the person.
+* As such, a paired person must be unpaired, with all associated logs deleted before the person may be removed from Elder Scrolls, to ensure consistency of data.
+
+Examples:
+* `list` followed by `delete 2 r/volunteer` Deletes the 2nd volunteer in the application.
+* `find Betsy` followed by `del 1 r/befriendee` Deletes the 1st befriendee found with `Betsy` in their name.
+
+--------------------------------------------------------------------------------------------------------------------
+[//]: # (Page Break:)
+
+
+### **3.2 Log Management**
+
+#### 3.2.1 Adding a log : `logadd`
+
+Adds a log for an existing paired befriendee and volunteer. If the added log is their most recent log, the respective persons' `latest log` card details updates accordingly.
+
+Format: `logadd BEFRIENDEE_INDEX VOLUNTEER_INDEX t/TITLE s/START_DATE d/DURATION r/REMARKS`
+
+Constraints:
+* The person at `BEFRIENDEE_INDEX` must be a befriendee and the person at `VOLUNTEER_INDEX` must be a volunteer.
+* The two persons must be paired before a log can be added.
+* The `START_DATE` must be in the format `YYYY-MM-DD`. Valid dates range from `0001-01-01` to `9999-12-31`. Only dates that exist in the standard Gregorian calendar are accepted.
+* The `DURATION` **must be a positive integer** ranging from 1 to 999. We cannot guarantee correct behaviour for durations exceeding 999 hours.
+* `t/TITLE`, `s/START_DATE`, `d/DURATION` and `r/REMARKS` must be provided.
+* `s/START_DATE`, `d/DURATION` cannot be empty.
+
+Examples:
+* `logadd 1 1 t/Movies s/2020-01-09 d/3 r/had popcorn` Adds a log between the befriendee at Index 1 and the volunteer at Index 1 with the title `Movies`, start date `2020-01-09`, duration `3` and remarks `had popcorn`.
+* `logadd 2 3 t/Shopping s/2020-09-09 d/2 r/bought groceries` Adds a log between the befriendee at Index 2 and the volunteer at Index 3 with the title `Shopping`, start date `2020-09-09`, duration `2` and remarks `bought groceries`.
+
+#### 3.2.2 Editing a log : `logedit`
+
+Edits an existing log in Elder Scrolls.
+
+Format: `logedit INDEX [t/TITLE] [s/START_DATE] [d/DURATION] [r/REMARKS]`
+
+Constraints:
+* Edits the log at the specified `INDEX`.
* At least one of the optional fields must be provided.
* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+* The `START_DATE` must be in the format `YYYY-MM-DD`. Valid dates range from `0001-01-01` to `9999-12-31`. Only dates that exist in the standard Gregorian calendar are accepted.
+* The `DURATION` **must be a positive integer** ranging from 1 to 999. We cannot guarantee correct behaviour for durations exceeding 999 hours.
Examples:
-* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
-* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
+* `logedit 1 t/Cinema Visit s/2020-01-10 d/3 r/had popcorn` Edits the title, start date, duration and remarks of the 1st log to be `Movies`, `2020-01-09`, `3` and `had popcorn` respectively.
+
+#### 3.2.3 Deleting a log : `logdelete`
-### Locating persons by name: `find`
+Deletes the specified log from the address book.
-Finds persons whose names contain any of the given keywords.
+Supported aliases for `logdelete`: `logdel`, `logremove`, `logrm`
-Format: `find KEYWORD [MORE_KEYWORDS]`
+Format: `logdelete INDEX`
-* The search is case-insensitive. e.g `hans` will match `Hans`
-* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
-* Only the name is searched.
-* Only full words will be matched e.g. `Han` will not match `Hans`
-* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+* Deletes the log at the specified `INDEX`.
Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png)
+* `list` followed by `logdelete 1` deletes the 1st log in the address book.
+* `logfind 1 r/befriendee` followed by `logdelete 1` deletes the 1st log in the results of the `logfind` command.
-### Deleting a person : `delete`
+#### 3.2.4 Finding a log associated with a person: `logfind`
-Deletes the specified person from the address book.
+Find all logs associated with a person.
-Format: `delete INDEX`
+Format: `logfind INDEX r/ROLE`
-* Deletes the person at the specified `INDEX`.
-* The index refers to the index number shown in the displayed person list.
-* The index **must be a positive integer** 1, 2, 3, …
+* Find all logs associated with the person at the specified `INDEX`, for the list corresponding to the provided role.
Examples:
-* `list` followed by `delete 2` deletes the 2nd person in the address book.
-* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command.
+* `logfind 1 r/befriendee` returns all logs associated with the befriendee at Index 1.
+* `logfind 2 r/volunteer` returns all logs associated with the volunteer at Index 2.
+
+--------------------------------------------------------------------------------------------------------------------
+[//]: # (Page Break:)
+
-### Clearing all entries : `clear`
+### **3.3 Undo and Redo Functionality**
+#### 3.3.1 Undo the most recent command : `undo`
-Clears all entries from the address book.
+Undo the latest command that made a change to the data stored in Elder Scrolls.
+This excludes commands like `list`,`find` and `logfind` which do not change any data stored in Elder Scrolls.
+Format: `undo`
+
+* The persons list and logs list will be refreshed to show all entries.
+* There must have been a previous command executed that modified data in Elder Scrolls.
+* At every launch of the application, there will be no commands to be undone.
+* Undo history will be erased when you exit the application.
+
+Examples:
+* You mistakenly executed a `clear` command and cleared every entry in Elder Scrolls. You can simply execute the command `undo` to revert the changes and all your entries in Elder Scrolls will be restored to their previous state, before the `clear` command was executed.
+* You might have performed multiple `delete` commands in sequence, and realised you've deleted the wrong entries. Not to worry, you can execute multiple `undo` commands to revert the changes made by the wrongful delete commands.
+
+#### 3.3.2 Revert the most recent undo command : `redo`
+
+Reverts data stored in Elder Scrolls back to its state before execution of latest undo command.
+
+Format: `redo`
+
+* The persons list and logs list will be refreshed to show all entries.
+* When a command that modifies data in Elder Scrolls is executed after an undo command is executed, the redo command will no longer be available.
+* There must have been a previous undo command executed in Elder Scrolls.
+* Redo history will be erased when you exit the application.
+
+Examples:
+* After executing a successful `undo` command, if you were to call any command that modifies the data in Elder Scrolls, such as `delete`, the `redo` command will not longer be available.
+* Let's say you've just executed an `add`, and mistakenly executed a `undo` command right after. You can then call `redo` to once again execute the `add` that was previously undone.
+
+--------------------------------------------------------------------------------------------------------------------
+
+### **3.4 Other Commands: Help, Clear and Exiting**
+
+#### 3.4.1 Viewing help : `help`
+
+Shows a message explaining how to access the help page/user guide.
+Format: `help`
+
+* Any extraneous inputs/parameters (e.g. `help me`) will be ignored.
+
+
+
+
+
+
+#### 3.4.2 Clearing all entries : `clear`
+
+Clears all entries from Elder Scrolls.
Format: `clear`
-### Exiting the program : `exit`
+
:bulb: **Tip:**
+Be careful when using the `clear` command as it will remove all entries from Elder Scrolls! However, if done accidentally, remember that you can always use the `undo` command to revert the changes made by the `clear` command!
+
-Exits the program.
+#### 3.4.3 Exiting the program : `exit`
+Exits the program.
Format: `exit`
-### Saving the data
+## **4. Saving the data**
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+Elder Scrolls data is saved in the hard disk automatically after any command that changes the data. There is no need to save manually!
+
+
:bulb: **Tip:**
+While Elder Scrolls saves automatically, you can also easily make backups and snapshots of your data! To do so, simply copy the `datastore.json` file located in the `data` folder of the Elder Scrolls home directory as a backup file! Making a backup copy is never a bad idea!
+
-### Editing the data file
+## **5. Editing the data file**
-AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
+All application data is saved automatically as a JSON file `[JAR file location]/data/datastore.json`. Advanced users are welcome to update data directly by editing that data file.
:exclamation: **Caution:**
-If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
-Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
+If your changes to the data file makes its format invalid, Elder Scrolls will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
+Furthermore, certain edits can cause the Elder Scrolls to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
-### Archiving data files `[coming in v2.0]`
+## **6. FAQ: Frequently Asked Questions**
+[Back to About & Table of Contents ←](#about-this-user-guide)
-_Details coming soon ..._
+**Q**: ___Can I track volunteer hours and activities with Elder Scrolls?___
+**A**: Absolutely! Elder Scrolls includes features for logging volunteer activities, such as pairing volunteers with befriendees, recording service hours, and adding remarks. This functionality makes it easy to track volunteer contributions and monitor engagement.
---------------------------------------------------------------------------------------------------------------------
+**Q**: ___Is Elder Scrolls suitable for managing large volunteer teams?___
+**A**: Elder Scrolls lets you handle volunteer management tasks efficiently, whether you're managing a small team or a large group of volunteers. Its intuitive interface and robust features make it suitable for organizations of all sizes.
+
+**Q**: ___Is Elder Scrolls suitable for non-profit organizations?___
+**A**: Absolutely! Elder Scrolls is ideal for non-profit organizations looking to streamline their volunteer management processes. Whether you're coordinating volunteers for community events, outreach programs, or support services, Elder Scrolls can help simplify your workflow and enhance organizational efficiency.
+
+**Q**: ___Can I access Elder Scrolls from multiple devices?___
+**A**: Elder Scrolls is your trusty sidekick on one device at a time. So, while you can't have it on multiple gadgets simultaneously, it's always ready to lend a hand on your desktop or laptop whenever you need it.
+
+[//]: # (Page Break:)
+
+
+**Q**: ___Is there a way to back up my data?___
+**A**: Absolutely! Your Elder Scrolls data is like a prized possession, and just like any treasure, it's wise to keep it safe. After every tweak or change you make, your data gets automatically saved to a JSON file on your hard disk. But for that extra peace of mind, making a backup copy is never a bad idea!
+
+**Q**: ___How do I transfer my data to another computer?___
+**A**: Moving to a new computer? No problem! Simply download Elder Scrolls on the new machine and swap the sample data file it creates with your precious data from the previous Elder Scrolls home folder. Your volunteers and befriendees are ready to join you on the next adventure!
+
+**Q**: ___What should I do if I encounter issues / want to suggest improvements for Elder Scrolls?___
+**A**: If you encounter any glitches, have questions or have suggestions, our team is here to assist you. Simply reach out to us, and we'll provide the necessary assistance to address your concerns. Alternatively, file an issue [here](https://github.com/AY2324S2-CS2103T-T09-3/tp/issues) on our GitHub repository, and we'll get back to you as soon as possible.
-## FAQ
+**Q**: ___Is Elder Scrolls open source?___
+**A**: Yes, it is! Elder Scrolls is an open-source project, which means its source code is freely available for anyone to view, modify, and distribute. This open nature fosters collaboration and innovation within the community, allowing users to contribute to its development and customize it to suit their needs.
-**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
+**Q**: ___Is Elder Scrolls free to use?___
+**A**: Absolutely! Elder Scrolls is available free of charge, making it accessible to organizations and individuals of all sizes. Whether you're a non-profit organization, a community group, or an individual volunteer manager, you can download and use Elder Scrolls without any cost involved. Plus, being open source means you can also contribute to its ongoing development if you wish.
+
+**Q**: ___Where can I find more resources on Volunteer Management Systems?___
+**A**: If you're looking for more information on Volunteer Management Systems, you're in the right place! Our team has meticulously designed Elder Scrolls with existing VMS guidelines in mind, ensuring it aligns seamlessly with industry standards.
+
+For additional resources, you can explore online forums, community groups, and volunteer management guides to learn more about best practices and strategies for effective volunteer coordination. Here are some resources we found useful:
+* [NCSS SG: Roadmap for Selection & Implementation of Volunteer Management System, A Guide for Social Service Agencies, Singapore](https://www.ncss.gov.sg/docs/default-source/ncss-press-release-doc/volunteer-management-system-selection-and-implementation-roadmap-pdf.pdf)
+* [NCVO UK: Choosing and implementing a volunteer management system](https://www.ncvo.org.uk/help-and-guidance/involving-volunteers/planning-for-volunteers/choosing-and-implementing-a-volunteer-management-system-1/)
--------------------------------------------------------------------------------------------------------------------
+[//]: # (Page Break:)
+
-## Known issues
+## **7. Known issues**
1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again.
+2. The application is not currently optimised for **'extreme' inputs** (e.g., a person name with 1000 characters, an index that exceeds the range of int), support will be added in a future version.
--------------------------------------------------------------------------------------------------------------------
-## Command summary
-
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX`
e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+## **8. Command Summary**
+[Back to About & Table of Contents ←](#about-this-user-guide)
+
+| Action | Format, Examples |
+|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Add** | `add n/NAME r/ROLE p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
e.g., `add n/James Ho r/volunteer p/88889999 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` |
+| **Edit** | `edit INDEX r/ROLE [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
e.g.,`edit 2 r/volunteer n/James Lee e/jameslee@example.com` |
+| **Pair** | `pair BEFRIENDEE_INDEX VOLUNTEER_INDEX`
e.g., `pair 1 2` |
+| **Unpair** | `unpair BEFRIENDEE_INDEX VOLUNTEER_INDEX`
e.g., `unpair 1 2` |
+| **List** | `list` |
+| **Find** | `find NAME_KEYWORD [MORE_NAME_KEYWORDS]... [r/ROLE] [t/TAG] [--paired]/[--unpaired] `
e.g., `find James r/volunteer --paired ` |
+| **Delete** | `delete INDEX r/ROLE`
e.g., `delete 3 r/befriendee` |
+| **LogAdd** | `logadd BEFRIENDEE_INDEX VOLUNTEER_INDEX t/TITLE s/START_DATE d/DURATION r/REMARKS`
e.g., `logadd 1 2 t/Movies s/2020-01-09 d/3 r/had popcorn` |
+| **LogEdit** | `logedit INDEX [t/TITLE] [s/START_DATE] [d/DURATION] [r/REMARKS]`
e.g., `logedit 1 t/Movies s/2020-01-09 d/3 r/had popcorn` |
+| **LogDelete** | `logdelete INDEX`
e.g., `logdelete 1` |
+| **LogFind** | `logfind INDEX r/ROLE`
e.g., `logfind 1 r/befriendee` |
+| **Undo** | `undo` |
+| **Redo** | `redo` |
+| **Help** | `help` |
+| **Clear** | `clear` |
+| **Exit** | `exit` |
+
+[//]: # (Page Break:)
+
+
+## **9. Glossary**
+[Back to About & Table of Contents ←](#about-this-user-guide)
+
+**Volunteer**: An individual who offers their time and services to social service agencies or causes without financial compensation, in this context they carry out befriending activities with the beneficiaries.
+
+**Befriendee**: An individual who receives support, companionship, or assistance from volunteers, in this context they are the beneficiaries of the befriending activities.
+
+**Volunteer Management System (VMS)**: A digital volunteer management tool designed to aid an organisation in the management of volunteers to improve productivity and potentially enhance the volunteer experience.
+
+**Elder Scrolls**: The Volunteer Management System (VMS) developed by our team for efficient management and bookkeeping of volunteers, befriendees, and their interactions.
+
+**Command Line Interface (CLI)**: A text-based interface used for interacting with Elder Scrolls through commands typed into a terminal or command prompt.
+
+**Graphical User Interface (GUI)**: A visual interface used for interacting with Elder Scrolls, providing intuitive controls and displays for managing volunteers, befriendees and logs.
+
+**Java 11**: The minimum version of Java required for Elder Scrolls to function properly.
+
+**Jar file**: A Java archive file containing all necessary components of Elder Scrolls for running the application.
+
+**Index**: The position or number assigned to each item in a list, used for reference when performing actions such as editing or deleting entries in Elder Scrolls.
+
+**Pairing**: The process of associating a volunteer with a befriendee in Elder Scrolls, allowing them to work together on activities or support services.
+
+**Logs**: Records of interactions, activities, or events between volunteers and befriendees in Elder Scrolls, used for tracking service hours, progress, and communication.
+
+**Backup**: A copy of Elder Scrolls data stored separately from the main application, used to safeguard against data loss or corruption.
+
+**Open Source**: Software whose source code is freely available to the public, allowing users to view, modify, and distribute it according to open-source licenses.
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..06825f0414f 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "Elder Scrolls"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2324S2-CS2103T-T09-3"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..94895071f45 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -288,7 +288,7 @@ table {
text-align: center;
}
.site-header:before {
- content: "AB-3";
+ content: "Elder Scrolls";
font-size: 32px;
}
}
diff --git a/docs/diagrams/AddSequenceDiagram-Logic.puml b/docs/diagrams/AddSequenceDiagram-Logic.puml
new file mode 100644
index 00000000000..23b536e06cd
--- /dev/null
+++ b/docs/diagrams/AddSequenceDiagram-Logic.puml
@@ -0,0 +1,57 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":AddCommandParser" as AddCommandParser LOGIC_COLOR
+participant "p:AddCommand" as AddCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("add ...")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("add ...")
+activate AddressBookParser
+
+create AddCommandParser
+AddressBookParser -> AddCommandParser
+activate AddCommandParser
+AddCommandParser --> AddressBookParser
+deactivate AddCommandParser
+
+AddressBookParser -> AddCommandParser : parse("...")
+activate AddCommandParser
+
+create AddCommand
+AddCommandParser -> AddCommand
+activate AddCommand
+
+AddCommand --> AddCommandParser
+deactivate AddCommand
+
+AddCommandParser --> AddressBookParser : f
+deactivate AddCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.'
+AddCommandParser -[hidden]-> AddressBookParser
+destroy AddCommandParser
+
+AddressBookParser --> LogicManager : u
+deactivate AddressBookParser
+
+LogicManager -> AddCommand : execute()
+activate AddCommand
+
+ref over AddCommand : Execute AddCommand
+
+
+
+AddCommand --> LogicManager : result
+deactivate AddCommand
+AddCommand -[hidden]-> LogicManager : result
+destroy AddCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/AddSequenceDiagram2.puml b/docs/diagrams/AddSequenceDiagram2.puml
new file mode 100644
index 00000000000..855547c3eb5
--- /dev/null
+++ b/docs/diagrams/AddSequenceDiagram2.puml
@@ -0,0 +1,48 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+mainframe Execute AddCommand
+
+box Logic LOGIC_COLOR_T1
+participant ":AddCommand" as AddCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Datastore" as Datastore MODEL_COLOR
+end box
+
+activate AddCommand
+
+'get PersonStore'
+AddCommand -> Model : getMutableDatastore()
+activate Model
+Model --> AddCommand : Datastore
+deactivate Model
+
+AddCommand -> Datastore : getMutablePersonStore()
+activate Datastore
+Datastore --> AddCommand : PersonStore
+deactivate Datastore
+
+AddCommand -> Datastore : addPerson(Person)
+activate Datastore
+Datastore --> AddCommand :
+deactivate Datastore
+
+AddCommand -> Model : commitDatastore()
+activate Model
+Model --> AddCommand :
+deactivate Model
+
+create CommandResult
+AddCommand -> CommandResult
+activate CommandResult
+CommandResult --> AddCommand
+deactivate CommandResult
+
+<-- AddCommand : result
+
+@enduml
diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml
index 48b6cc4333c..67e4a547e32 100644
--- a/docs/diagrams/ArchitectureSequenceDiagram.puml
+++ b/docs/diagrams/ArchitectureSequenceDiagram.puml
@@ -8,10 +8,10 @@ Participant ":Logic" as logic LOGIC_COLOR
Participant ":Model" as model MODEL_COLOR
Participant ":Storage" as storage STORAGE_COLOR
-user -[USER_COLOR]> ui : "delete 1"
+user -[USER_COLOR]> ui : "delete 1 r/volunteer"
activate ui UI_COLOR
-ui -[UI_COLOR]> logic : execute("delete 1")
+ui -[UI_COLOR]> logic : execute("delete 1 r/volunteer")
activate logic LOGIC_COLOR
logic -[LOGIC_COLOR]> model : deletePerson(p)
@@ -20,17 +20,23 @@ activate model MODEL_COLOR
model -[MODEL_COLOR]-> logic
deactivate model
-logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook)
+logic -[LOGIC_COLOR]> model : commitDatastore()
+activate model MODEL_COLOR
+
+model -[MODEL_COLOR]> storage : commitDatastore()
activate storage STORAGE_COLOR
-storage -[STORAGE_COLOR]> storage : Save to file
+storage -[STORAGE_COLOR]> storage : add(datastore)
activate storage STORAGE_COLOR_T1
storage --[STORAGE_COLOR]> storage
deactivate storage
-storage --[STORAGE_COLOR]> logic
+storage --[STORAGE_COLOR]> model
deactivate storage
+model -[MODEL_COLOR]-> logic
+deactivate model
+
logic --[LOGIC_COLOR]> ui
deactivate logic
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
index 598474a5c82..6f49670d48e 100644
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ b/docs/diagrams/BetterModelClassDiagram.puml
@@ -12,10 +12,21 @@ UniqueTagList -[hidden]down- UniquePersonList
UniqueTagList -right-> "*" Tag
UniquePersonList -right-> Person
+Person *-up-> personId
Person -up-> "*" Tag
+personId -[hidden]left--> Tag
+
+Volunteer .left.|> Person
+Befriendee .left.|> Person
+
Person *--> Name
Person *--> Phone
Person *--> Email
Person *--> Address
+Person *--> "0..1" pairedWithId
+Person --> "0..1" pairedWithName
+Person *--> "0..1" latestLogId
+Person *--> Role
+Person *--> timeServed
@enduml
diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml
index 8c0892d6a70..16a809a250d 100644
--- a/docs/diagrams/CommitActivityDiagram.puml
+++ b/docs/diagrams/CommitActivityDiagram.puml
@@ -8,10 +8,10 @@ start
'Since the beta syntax does not support placing the condition outside the
'diamond we place it as the true branch instead.
-if () then ([command commits AddressBook])
+if () then ([command commits Datastore])
:Purge redundant states;
- :Save AddressBook to
- addressBookStateList;
+ :Save Datastore to
+ datastoreVersions;
else ([else])
endif
stop
diff --git a/docs/diagrams/DatastoreClassDiagram.puml b/docs/diagrams/DatastoreClassDiagram.puml
new file mode 100644
index 00000000000..3d0b96d4967
--- /dev/null
+++ b/docs/diagrams/DatastoreClassDiagram.puml
@@ -0,0 +1,76 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Package Datastore as DatastorePackage <
>{
+Class "<>\nReadOnlyDatastore" as ReadOnlyDatastore
+Class "<>\nReadOnlyPersonStore" as ReadOnlyPersonStore
+Class "<>\nReadOnlyLogStore" as ReadOnlyLogStore
+Class Datastore
+
+Class PersonStore
+Class LogStore
+
+Class UniquePersonList
+Class "{abstract}\nPerson" as Person
+Class Address
+Class Email
+Class Name
+Class Phone
+Class Tag
+Class Role
+
+Class Log
+
+Class Volunteer
+Class Befriendee
+
+Datastore .up.|> ReadOnlyDatastore
+PersonStore .up.|> ReadOnlyPersonStore
+LogStore .up.|> ReadOnlyLogStore
+
+ReadOnlyPersonStore .up.> ReadOnlyDatastore
+ReadOnlyLogStore .up.> ReadOnlyDatastore
+
+Datastore *-down-> "1" PersonStore
+Datastore *-down-> "1" LogStore
+
+LogStore *--> "*" Log
+
+PersonStore *--> "1" UniquePersonList
+UniquePersonList --> "~* all" Person
+Person *--> Name
+Person *--> Phone
+Person *--> Email
+Person *--> Address
+Person --> pairedWithName
+Person *--> pairedWithId
+Person *--> latestLogId
+Person -up-> "*" Tag
+Person *--> Role
+Person *-left-> timeServed
+Person *-left-> personId
+
+Volunteer .up.|> Person
+Befriendee .up.|> Person
+
+
+Volunteer -[hidden]right--> Befriendee
+
+Person -[hidden]right--> Volunteer
+Person -[hidden]right--> Befriendee
+
+ReadOnlyPersonStore -[hidden]right--> Datastore
+ReadOnlyLogStore -[hidden]right--> ReadOnlyPersonStore
+
+Name -[hidden]right-> Phone
+Phone -[hidden]right-> Address
+Address -[hidden]right-> Email
+
+Volunteer"0..1" - "0..1"Befriendee: pairedWith
+(Volunteer, Befriendee) .. Log
+Befriendee -[hidden]down Volunteer
+
+@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram-Logic.puml b/docs/diagrams/DeleteSequenceDiagram-Logic.puml
new file mode 100644
index 00000000000..1b32182c2e9
--- /dev/null
+++ b/docs/diagrams/DeleteSequenceDiagram-Logic.puml
@@ -0,0 +1,57 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR
+participant "p:DeleteCommand" as DeleteCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("delete 1 r/volunteer")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("delete 1 r/volunteer")
+activate AddressBookParser
+
+create DeleteCommandParser
+AddressBookParser -> DeleteCommandParser
+activate DeleteCommandParser
+DeleteCommandParser --> AddressBookParser
+deactivate DeleteCommandParser
+
+AddressBookParser -> DeleteCommandParser : parse("1 r/volunteer")
+activate DeleteCommandParser
+
+create DeleteCommand
+DeleteCommandParser -> DeleteCommand
+activate DeleteCommand
+
+DeleteCommand --> DeleteCommandParser
+deactivate DeleteCommand
+
+DeleteCommandParser --> AddressBookParser : f
+deactivate DeleteCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.'
+DeleteCommandParser -[hidden]-> AddressBookParser
+destroy DeleteCommandParser
+
+AddressBookParser --> LogicManager : u
+deactivate AddressBookParser
+
+LogicManager -> DeleteCommand : execute()
+activate DeleteCommand
+
+ref over DeleteCommand : Execute DeleteCommand
+
+
+
+DeleteCommand --> LogicManager : result
+deactivate DeleteCommand
+DeleteCommand -[hidden]-> LogicManager : result
+destroy DeleteCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml
index 5241e79d7da..138cf247e33 100644
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ b/docs/diagrams/DeleteSequenceDiagram.puml
@@ -12,12 +12,13 @@ end box
box Model MODEL_COLOR_T1
participant "m:Model" as Model MODEL_COLOR
+participant "p:PersonStore" as PersonStore MODEL_COLOR
end box
-[-> LogicManager : execute("delete 1")
+[-> LogicManager : execute("delete 1 r/volunteer")
activate LogicManager
-LogicManager -> AddressBookParser : parseCommand("delete 1")
+LogicManager -> AddressBookParser : parseCommand("delete 1 r/volunteer")
activate AddressBookParser
create DeleteCommandParser
@@ -27,7 +28,7 @@ activate DeleteCommandParser
DeleteCommandParser --> AddressBookParser
deactivate DeleteCommandParser
-AddressBookParser -> DeleteCommandParser : parse("1")
+AddressBookParser -> DeleteCommandParser : parse("1 r/volunteer")
activate DeleteCommandParser
create DeleteCommand
@@ -49,7 +50,19 @@ deactivate AddressBookParser
LogicManager -> DeleteCommand : execute(m)
activate DeleteCommand
-DeleteCommand -> Model : deletePerson(1)
+DeleteCommand -> Model : getMutablePersonStore()
+activate Model
+
+Model --> DeleteCommand : PersonStore
+deactivate Model
+
+DeleteCommand -> PersonStore : removePerson(personToDelete)
+activate PersonStore
+
+PersonStore --> DeleteCommand
+deactivate PersonStore
+
+DeleteCommand -> Model : commitDatastore()
activate Model
Model --> DeleteCommand
@@ -66,5 +79,6 @@ DeleteCommand --> LogicManager : r
deactivate DeleteCommand
[<--LogicManager
+destroy DeleteCommand
deactivate LogicManager
@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram2.puml b/docs/diagrams/DeleteSequenceDiagram2.puml
new file mode 100644
index 00000000000..4355654fb8a
--- /dev/null
+++ b/docs/diagrams/DeleteSequenceDiagram2.puml
@@ -0,0 +1,48 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+mainframe Execute DeleteCommand
+
+box Logic LOGIC_COLOR_T1
+participant ":DeleteCommand" as DeleteCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Datastore" as Datastore MODEL_COLOR
+end box
+
+activate DeleteCommand
+
+'get PersonStore'
+DeleteCommand -> Model : getMutableDatastore()
+activate Model
+Model --> DeleteCommand : Datastore
+deactivate Model
+
+DeleteCommand -> Datastore : getMutablePersonStore()
+activate Datastore
+Datastore --> DeleteCommand : PersonStore
+deactivate Datastore
+
+DeleteCommand -> Datastore : removePerson(Person)
+activate Datastore
+Datastore --> DeleteCommand :
+deactivate Datastore
+
+DeleteCommand -> Model : commitDatastore()
+activate Model
+Model --> DeleteCommand :
+deactivate Model
+
+create CommandResult
+DeleteCommand -> CommandResult
+activate CommandResult
+CommandResult --> DeleteCommand
+deactivate CommandResult
+
+<-- DeleteCommand : result
+
+@enduml
diff --git a/docs/diagrams/EditSequenceDiagram-Logic.puml b/docs/diagrams/EditSequenceDiagram-Logic.puml
new file mode 100644
index 00000000000..fcae33a5546
--- /dev/null
+++ b/docs/diagrams/EditSequenceDiagram-Logic.puml
@@ -0,0 +1,57 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":EditCommandParser" as EditCommandParser LOGIC_COLOR
+participant "p:EditCommand" as EditCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("edit ...")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseEditCommand("edit ...")
+activate AddressBookParser
+
+create EditCommandParser
+AddressBookParser -> EditCommandParser
+activate EditCommandParser
+EditCommandParser --> AddressBookParser
+deactivate EditCommandParser
+
+AddressBookParser -> EditCommandParser : parse("...")
+activate EditCommandParser
+
+create EditCommand
+EditCommandParser -> EditCommand
+activate EditCommand
+
+EditCommand --> EditCommandParser
+deactivate EditCommand
+
+EditCommandParser --> AddressBookParser : f
+deactivate EditCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.'
+EditCommandParser -[hidden]-> AddressBookParser
+destroy EditCommandParser
+
+AddressBookParser --> LogicManager : u
+deactivate AddressBookParser
+
+LogicManager -> EditCommand : execute()
+activate EditCommand
+
+ref over EditCommand : Execute EditCommand
+
+
+
+EditCommand --> LogicManager : result
+deactivate EditCommand
+EditCommand -[hidden]-> LogicManager : result
+destroy EditCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/EditSequenceDiagram2.puml b/docs/diagrams/EditSequenceDiagram2.puml
new file mode 100644
index 00000000000..58098ff7a10
--- /dev/null
+++ b/docs/diagrams/EditSequenceDiagram2.puml
@@ -0,0 +1,53 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+mainframe Execute EditCommand
+
+box Logic LOGIC_COLOR_T1
+participant ":EditCommand" as EditCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Datastore" as Datastore MODEL_COLOR
+end box
+
+activate EditCommand
+
+'get PersonStore'
+EditCommand -> Model : getMutableDatastore()
+activate Model
+Model --> EditCommand : Datastore
+deactivate Model
+
+EditCommand -> Datastore : getMutablePersonStore()
+activate Datastore
+Datastore --> EditCommand : PersonStore
+deactivate Datastore
+
+EditCommand -> Datastore : getMutableLogStore()
+activate Datastore
+Datastore --> EditCommand : LogStore
+deactivate Datastore
+
+EditCommand -> Datastore : setPerson(Person)
+activate Datastore
+Datastore --> EditCommand :
+deactivate Datastore
+
+EditCommand -> Model : commitDatastore()
+activate Model
+Model --> EditCommand :
+deactivate Model
+
+create CommandResult
+EditCommand -> CommandResult
+activate CommandResult
+CommandResult --> EditCommand
+deactivate CommandResult
+
+<-- EditCommand : result
+
+@enduml
diff --git a/docs/diagrams/FindSequenceDiagram.puml b/docs/diagrams/FindSequenceDiagram.puml
new file mode 100644
index 00000000000..5febff8f7ab
--- /dev/null
+++ b/docs/diagrams/FindSequenceDiagram.puml
@@ -0,0 +1,54 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR
+participant "f:FindCommand" as FindCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("find r/volunteer --unpaired")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("find r/volunteer --unpaired")
+activate AddressBookParser
+
+create FindCommandParser
+AddressBookParser -> FindCommandParser
+activate FindCommandParser
+FindCommandParser --> AddressBookParser
+deactivate FindCommandParser
+
+AddressBookParser -> FindCommandParser : parse("r/volunteer --unpaired")
+activate FindCommandParser
+
+create FindCommand
+FindCommandParser -> FindCommand
+activate FindCommand
+FindCommand --> FindCommandParser
+deactivate FindCommand
+
+FindCommandParser --> AddressBookParser : f
+deactivate FindCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.'
+FindCommandParser -[hidden]-> AddressBookParser
+destroy FindCommandParser
+
+AddressBookParser --> LogicManager : f
+deactivate AddressBookParser
+
+LogicManager -> FindCommand : execute()
+activate FindCommand
+
+ref over FindCommand : Execute FindCommand
+
+FindCommand --> LogicManager : result
+deactivate FindCommand
+FindCommand -[hidden]-> LogicManager : result
+destroy FindCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/FindSequenceDiagram2.puml b/docs/diagrams/FindSequenceDiagram2.puml
new file mode 100644
index 00000000000..689ff78596b
--- /dev/null
+++ b/docs/diagrams/FindSequenceDiagram2.puml
@@ -0,0 +1,62 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+mainframe Execute FindCommand
+
+box Logic LOGIC_COLOR_T1
+participant ":FindCommand" as FindCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Datastore" as Datastore MODEL_COLOR
+end box
+
+activate FindCommand
+
+'get PersonStore'
+FindCommand -> Model : getMutableDatastore()
+activate Model
+Model --> FindCommand : Datastore
+deactivate Model
+
+FindCommand -> Datastore : getPersonStore()
+activate Datastore
+Datastore --> FindCommand : PersonStore
+deactivate Datastore
+
+'getCombinedPredicate'
+FindCommand -> FindCommand : getCombinedPredicate()
+activate FindCommand
+deactivate FindCommand
+
+
+alt isSearchingVolunteer && isSearchingBefriendee
+ FindCommand -> FindCommand : searchAllPersons()
+ activate FindCommand #FFBBBB
+ deactivate FindCommand
+
+
+else isSearchingVolunteer
+ FindCommand -> FindCommand : searchVolunteerOnly()
+ activate FindCommand #b0e396
+ deactivate FindCommand
+
+else else
+ FindCommand -> FindCommand : searchBefriendeeOnly()
+ activate FindCommand #96ace3
+ deactivate FindCommand
+
+end
+
+create CommandResult
+FindCommand -> CommandResult
+activate CommandResult
+CommandResult --> FindCommand
+deactivate CommandResult
+
+<-- FindCommand : result
+
+@enduml
diff --git a/docs/diagrams/LogAddSequenceDiagram-Logic.puml b/docs/diagrams/LogAddSequenceDiagram-Logic.puml
new file mode 100644
index 00000000000..c6a867dc4e1
--- /dev/null
+++ b/docs/diagrams/LogAddSequenceDiagram-Logic.puml
@@ -0,0 +1,57 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":LogAddCommandParser" as LogAddCommandParser LOGIC_COLOR
+participant "p:LogAddCommand" as LogAddCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("logadd ...")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("logadd ...")
+activate AddressBookParser
+
+create LogAddCommandParser
+AddressBookParser -> LogAddCommandParser
+activate LogAddCommandParser
+LogAddCommandParser --> AddressBookParser
+deactivate LogAddCommandParser
+
+AddressBookParser -> LogAddCommandParser : parse("...")
+activate LogAddCommandParser
+
+create LogAddCommand
+LogAddCommandParser -> LogAddCommand
+activate LogAddCommand
+
+LogAddCommand --> LogAddCommandParser
+deactivate LogAddCommand
+
+LogAddCommandParser --> AddressBookParser : f
+deactivate LogAddCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.'
+LogAddCommandParser -[hidden]-> AddressBookParser
+destroy LogAddCommandParser
+
+AddressBookParser --> LogicManager : u
+deactivate AddressBookParser
+
+LogicManager -> LogAddCommand : execute()
+activate LogAddCommand
+
+ref over LogAddCommand : Execute LogAddCommand
+
+
+
+LogAddCommand --> LogicManager : result
+deactivate LogAddCommand
+LogAddCommand -[hidden]-> LogicManager : result
+destroy LogAddCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/LogAddSequenceDiagram2.puml b/docs/diagrams/LogAddSequenceDiagram2.puml
new file mode 100644
index 00000000000..258ee6a6244
--- /dev/null
+++ b/docs/diagrams/LogAddSequenceDiagram2.puml
@@ -0,0 +1,58 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+mainframe Execute LogAddCommand
+
+box Logic LOGIC_COLOR_T1
+participant ":LogAddCommand" as LogAddCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Datastore" as Datastore MODEL_COLOR
+end box
+
+activate LogAddCommand
+
+'get PersonStore'
+LogAddCommand -> Model : getMutableDatastore()
+activate Model
+Model --> LogAddCommand : Datastore
+deactivate Model
+
+LogAddCommand -> Datastore : getMutablePersonStore()
+activate Datastore
+Datastore --> LogAddCommand : PersonStore
+deactivate Datastore
+
+LogAddCommand -> Datastore : getMutableLogStore()
+activate Datastore
+Datastore --> LogAddCommand : LogStore
+deactivate Datastore
+
+LogAddCommand -> Datastore : setPerson(Person)
+activate Datastore
+Datastore --> LogAddCommand :
+deactivate Datastore
+
+LogAddCommand -> Datastore : addLog(Log)
+activate Datastore
+Datastore --> LogAddCommand :
+deactivate Datastore
+
+LogAddCommand -> Model : commitDatastore()
+activate Model
+Model --> LogAddCommand :
+deactivate Model
+
+create CommandResult
+LogAddCommand -> CommandResult
+activate CommandResult
+CommandResult --> LogAddCommand
+deactivate CommandResult
+
+<-- LogAddCommand : result
+
+@enduml
diff --git a/docs/diagrams/LogDeleteSequenceDiagram-Logic.puml b/docs/diagrams/LogDeleteSequenceDiagram-Logic.puml
new file mode 100644
index 00000000000..6a8110fac80
--- /dev/null
+++ b/docs/diagrams/LogDeleteSequenceDiagram-Logic.puml
@@ -0,0 +1,57 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":LogDeleteCommandParser" as LogDeleteCommandParser LOGIC_COLOR
+participant "p:LogDeleteCommand" as LogDeleteCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("logdelete ...")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("logdelete ...")
+activate AddressBookParser
+
+create LogDeleteCommandParser
+AddressBookParser -> LogDeleteCommandParser
+activate LogDeleteCommandParser
+LogDeleteCommandParser --> AddressBookParser
+deactivate LogDeleteCommandParser
+
+AddressBookParser -> LogDeleteCommandParser : parse("...")
+activate LogDeleteCommandParser
+
+create LogDeleteCommand
+LogDeleteCommandParser -> LogDeleteCommand
+activate LogDeleteCommand
+
+LogDeleteCommand --> LogDeleteCommandParser
+deactivate LogDeleteCommand
+
+LogDeleteCommandParser --> AddressBookParser : f
+deactivate LogDeleteCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.'
+LogDeleteCommandParser -[hidden]-> AddressBookParser
+destroy LogDeleteCommandParser
+
+AddressBookParser --> LogicManager : u
+deactivate AddressBookParser
+
+LogicManager -> LogDeleteCommand : execute()
+activate LogDeleteCommand
+
+ref over LogDeleteCommand : Execute LogDeleteCommand
+
+
+
+LogDeleteCommand --> LogicManager : result
+deactivate LogDeleteCommand
+LogDeleteCommand -[hidden]-> LogicManager : result
+destroy LogDeleteCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/LogDeleteSequenceDiagram2.puml b/docs/diagrams/LogDeleteSequenceDiagram2.puml
new file mode 100644
index 00000000000..41d8d256b95
--- /dev/null
+++ b/docs/diagrams/LogDeleteSequenceDiagram2.puml
@@ -0,0 +1,58 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+mainframe Execute LogDeleteCommand
+
+box Logic LOGIC_COLOR_T1
+participant ":LogDeleteCommand" as LogDeleteCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Datastore" as Datastore MODEL_COLOR
+end box
+
+activate LogDeleteCommand
+
+'get PersonStore'
+LogDeleteCommand -> Model : getMutableDatastore()
+activate Model
+Model --> LogDeleteCommand : Datastore
+deactivate Model
+
+LogDeleteCommand -> Datastore : getMutablePersonStore()
+activate Datastore
+Datastore --> LogDeleteCommand : PersonStore
+deactivate Datastore
+
+LogDeleteCommand -> Datastore : getMutableLogStore()
+activate Datastore
+Datastore --> LogDeleteCommand : LogStore
+deactivate Datastore
+
+LogDeleteCommand -> Datastore : setPerson(Person)
+activate Datastore
+Datastore --> LogDeleteCommand :
+deactivate Datastore
+
+LogDeleteCommand -> Datastore : removeLog(logIdToDelete)
+activate Datastore
+Datastore --> LogDeleteCommand :
+deactivate Datastore
+
+LogDeleteCommand -> Model : commitDatastore()
+activate Model
+Model --> LogDeleteCommand :
+deactivate Model
+
+create CommandResult
+LogDeleteCommand -> CommandResult
+activate CommandResult
+CommandResult --> LogDeleteCommand
+deactivate CommandResult
+
+<-- LogDeleteCommand : result
+
+@enduml
diff --git a/docs/diagrams/LogEditSequenceDiagram-Logic.puml b/docs/diagrams/LogEditSequenceDiagram-Logic.puml
new file mode 100644
index 00000000000..c56dcaf17e3
--- /dev/null
+++ b/docs/diagrams/LogEditSequenceDiagram-Logic.puml
@@ -0,0 +1,57 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":LogEditCommandParser" as LogEditCommandParser LOGIC_COLOR
+participant "p:LogEditCommand" as LogEditCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("logedit ...")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("logedit ...")
+activate AddressBookParser
+
+create LogEditCommandParser
+AddressBookParser -> LogEditCommandParser
+activate LogEditCommandParser
+LogEditCommandParser --> AddressBookParser
+deactivate LogEditCommandParser
+
+AddressBookParser -> LogEditCommandParser : parse("...")
+activate LogEditCommandParser
+
+create LogEditCommand
+LogEditCommandParser -> LogEditCommand
+activate LogEditCommand
+
+LogEditCommand --> LogEditCommandParser
+deactivate LogEditCommand
+
+LogEditCommandParser --> AddressBookParser : f
+deactivate LogEditCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.'
+LogEditCommandParser -[hidden]-> AddressBookParser
+destroy LogEditCommandParser
+
+AddressBookParser --> LogicManager : u
+deactivate AddressBookParser
+
+LogicManager -> LogEditCommand : execute()
+activate LogEditCommand
+
+ref over LogEditCommand : Execute LogEditCommand
+
+
+
+LogEditCommand --> LogicManager : result
+deactivate LogEditCommand
+LogEditCommand -[hidden]-> LogicManager : result
+destroy LogEditCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/LogEditSequenceDiagram2.puml b/docs/diagrams/LogEditSequenceDiagram2.puml
new file mode 100644
index 00000000000..531e95ec718
--- /dev/null
+++ b/docs/diagrams/LogEditSequenceDiagram2.puml
@@ -0,0 +1,58 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+mainframe Execute LogEditCommand
+
+box Logic LOGIC_COLOR_T1
+participant ":LogEditCommand" as LogEditCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Datastore" as Datastore MODEL_COLOR
+end box
+
+activate LogEditCommand
+
+'get PersonStore'
+LogEditCommand -> Model : getMutableDatastore()
+activate Model
+Model --> LogEditCommand : Datastore
+deactivate Model
+
+LogEditCommand -> Datastore : getMutablePersonStore()
+activate Datastore
+Datastore --> LogEditCommand : PersonStore
+deactivate Datastore
+
+LogEditCommand -> Datastore : getMutableLogStore()
+activate Datastore
+Datastore --> LogEditCommand : LogStore
+deactivate Datastore
+
+LogEditCommand -> Datastore : setPerson(Person)
+activate Datastore
+Datastore --> LogEditCommand :
+deactivate Datastore
+
+LogEditCommand -> Datastore : setLog(Log)
+activate Datastore
+Datastore --> LogEditCommand :
+deactivate Datastore
+
+LogEditCommand -> Model : commitDatastore()
+activate Model
+Model --> LogEditCommand :
+deactivate Model
+
+create CommandResult
+LogEditCommand -> CommandResult
+activate CommandResult
+CommandResult --> LogEditCommand
+deactivate CommandResult
+
+<-- LogEditCommand : result
+
+@enduml
diff --git a/docs/diagrams/LogFindSequenceDiagram-Logic.puml b/docs/diagrams/LogFindSequenceDiagram-Logic.puml
new file mode 100644
index 00000000000..7653095e4c6
--- /dev/null
+++ b/docs/diagrams/LogFindSequenceDiagram-Logic.puml
@@ -0,0 +1,57 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":LogFindCommandParser" as LogFindCommandParser LOGIC_COLOR
+participant "p:LogFindCommand" as LogFindCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("logfind ...")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("logfind ...")
+activate AddressBookParser
+
+create LogFindCommandParser
+AddressBookParser -> LogFindCommandParser
+activate LogFindCommandParser
+LogFindCommandParser --> AddressBookParser
+deactivate LogFindCommandParser
+
+AddressBookParser -> LogFindCommandParser : parse("...")
+activate LogFindCommandParser
+
+create LogFindCommand
+LogFindCommandParser -> LogFindCommand
+activate LogFindCommand
+
+LogFindCommand --> LogFindCommandParser
+deactivate LogFindCommand
+
+LogFindCommandParser --> AddressBookParser : f
+deactivate LogFindCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.'
+LogFindCommandParser -[hidden]-> AddressBookParser
+destroy LogFindCommandParser
+
+AddressBookParser --> LogicManager : u
+deactivate AddressBookParser
+
+LogicManager -> LogFindCommand : execute()
+activate LogFindCommand
+
+ref over LogFindCommand : Execute LogFindCommand
+
+
+
+LogFindCommand --> LogicManager : result
+deactivate LogFindCommand
+LogFindCommand -[hidden]-> LogicManager : result
+destroy LogFindCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/LogFindSequenceDiagram2.puml b/docs/diagrams/LogFindSequenceDiagram2.puml
new file mode 100644
index 00000000000..de516811e9b
--- /dev/null
+++ b/docs/diagrams/LogFindSequenceDiagram2.puml
@@ -0,0 +1,49 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+mainframe Execute LogFindCommand
+
+box Logic LOGIC_COLOR_T1
+participant ":LogFindCommand" as LogFindCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Datastore" as Datastore MODEL_COLOR
+participant ":LogStore" as LogStore MODEL_COLOR
+end box
+
+activate LogFindCommand
+
+'get PersonStore'
+LogFindCommand -> Model : getMutableDatastore()
+activate Model
+Model --> LogFindCommand : Datastore
+deactivate Model
+
+LogFindCommand -> Datastore : getMutablePersonStore()
+activate Datastore
+Datastore --> LogFindCommand : PersonStore
+deactivate Datastore
+
+LogFindCommand -> Datastore : getMutableLogStore()
+activate Datastore
+Datastore --> LogFindCommand : LogStore
+deactivate Datastore
+
+LogFindCommand -> LogStore : updateFilteredLogListByPersonId(personId)
+activate LogStore
+LogStore --> LogFindCommand :
+deactivate LogStore
+
+create CommandResult
+LogFindCommand -> CommandResult
+activate CommandResult
+CommandResult --> LogFindCommand
+deactivate CommandResult
+
+<-- LogFindCommand : result
+
+@enduml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 0de5673070d..2de404cdacf 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -5,50 +5,34 @@ skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
Package Model as ModelPackage <>{
-Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook
+Class "<>\nReadOnlyDatastore" as ReadOnlyDatastore
+Class "<>\nReadOnlyPersonStore" as ReadOnlyPersonStore
+Class "<>\nReadOnlyLogStore" as ReadOnlyLogStore
Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs
Class "<>\nModel" as Model
-Class AddressBook
+Class Datastore
Class ModelManager
Class UserPrefs
-Class UniquePersonList
-Class Person
-Class Address
-Class Email
-Class Name
-Class Phone
-Class Tag
-
Class I #FFFFFF
}
Class HiddenOutside #FFFFFF
HiddenOutside ..> Model
-AddressBook .up.|> ReadOnlyAddressBook
+Datastore .up.|> ReadOnlyDatastore
+ReadOnlyPersonStore .up.> ReadOnlyDatastore
+ReadOnlyLogStore .up.> ReadOnlyDatastore
ModelManager .up.|> Model
Model .right.> ReadOnlyUserPrefs
-Model .left.> ReadOnlyAddressBook
-ModelManager -left-> "1" AddressBook
+Model .left.> ReadOnlyDatastore
+ModelManager -left-> "1" Datastore
ModelManager -right-> "1" UserPrefs
UserPrefs .up.|> ReadOnlyUserPrefs
-AddressBook *--> "1" UniquePersonList
-UniquePersonList --> "~* all" Person
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
-Person *--> "*" Tag
-
-Person -[hidden]up--> I
-UniquePersonList -[hidden]right-> I
-
-Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
+ReadOnlyPersonStore -[hidden]right--> Datastore
+ReadOnlyLogStore -[hidden]right--> ReadOnlyPersonStore
-ModelManager --> "~* filtered" Person
+'ModelManager --> "~* filtered" Person
@enduml
diff --git a/docs/diagrams/PairSequenceDiagram-Logic.puml b/docs/diagrams/PairSequenceDiagram-Logic.puml
new file mode 100644
index 00000000000..e5456399666
--- /dev/null
+++ b/docs/diagrams/PairSequenceDiagram-Logic.puml
@@ -0,0 +1,57 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":PairCommandParser" as PairCommandParser LOGIC_COLOR
+participant "p:PairCommand" as PairCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("pair 1 1")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("pair 1 1")
+activate AddressBookParser
+
+create PairCommandParser
+AddressBookParser -> PairCommandParser
+activate PairCommandParser
+PairCommandParser --> AddressBookParser
+deactivate PairCommandParser
+
+AddressBookParser -> PairCommandParser : parse("1 1")
+activate PairCommandParser
+
+create PairCommand
+PairCommandParser -> PairCommand
+activate PairCommand
+
+PairCommand --> PairCommandParser
+deactivate PairCommand
+
+PairCommandParser --> AddressBookParser : f
+deactivate PairCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.'
+PairCommandParser -[hidden]-> AddressBookParser
+destroy PairCommandParser
+
+AddressBookParser --> LogicManager : u
+deactivate AddressBookParser
+
+LogicManager -> PairCommand : execute()
+activate PairCommand
+
+ref over PairCommand : Execute PairCommand
+
+
+
+PairCommand --> LogicManager : result
+deactivate PairCommand
+PairCommand -[hidden]-> LogicManager : result
+destroy PairCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/PairSequenceDiagram2.puml b/docs/diagrams/PairSequenceDiagram2.puml
new file mode 100644
index 00000000000..4dbfc2b162b
--- /dev/null
+++ b/docs/diagrams/PairSequenceDiagram2.puml
@@ -0,0 +1,48 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+mainframe Execute PairCommand
+
+box Logic LOGIC_COLOR_T1
+participant ":PairCommand" as PairCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Datastore" as Datastore MODEL_COLOR
+end box
+
+activate PairCommand
+
+'get PersonStore'
+PairCommand -> Model : getMutableDatastore()
+activate Model
+Model --> PairCommand : Datastore
+deactivate Model
+
+PairCommand -> Datastore : getMutablePersonStore()
+activate Datastore
+Datastore --> PairCommand : PersonStore
+deactivate Datastore
+
+PairCommand -> Datastore : setPerson(Person)
+activate Datastore
+Datastore --> PairCommand :
+deactivate Datastore
+
+PairCommand -> Model : commitDatastore()
+activate Model
+Model --> PairCommand :
+deactivate Model
+
+create CommandResult
+PairCommand -> CommandResult
+activate CommandResult
+CommandResult --> PairCommand
+deactivate CommandResult
+
+<-- PairCommand : result
+
+@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index a821e06458c..4f69fffe390 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -14,10 +14,10 @@ Class JsonUserPrefsStorage
Class "<>\nStorage" as Storage
Class StorageManager
-package "AddressBook Storage" #F4F6F6{
-Class "<>\nAddressBookStorage" as AddressBookStorage
-Class JsonAddressBookStorage
-Class JsonSerializableAddressBook
+package "Datastore Storage" #F4F6F6{
+Class "<>\nDatastoreStorage" as DatastoreStorage
+Class JsonDatastoreStorage
+Class JsonSerializableDatastore
Class JsonAdaptedPerson
Class JsonAdaptedTag
}
@@ -29,15 +29,15 @@ HiddenOutside ..> Storage
StorageManager .up.|> Storage
StorageManager -up-> "1" UserPrefsStorage
-StorageManager -up-> "1" AddressBookStorage
+StorageManager -up-> "1" DatastoreStorage
Storage -left-|> UserPrefsStorage
-Storage -right-|> AddressBookStorage
+Storage -right-|> DatastoreStorage
JsonUserPrefsStorage .up.|> UserPrefsStorage
-JsonAddressBookStorage .up.|> AddressBookStorage
-JsonAddressBookStorage ..> JsonSerializableAddressBook
-JsonSerializableAddressBook --> "*" JsonAdaptedPerson
+JsonDatastoreStorage .up.|> DatastoreStorage
+JsonDatastoreStorage ..> JsonSerializableDatastore
+JsonSerializableDatastore --> "*" JsonAdaptedPerson
JsonAdaptedPerson --> "*" JsonAdaptedTag
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..7c7a338d2e0 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -12,9 +12,12 @@ Class MainWindow
Class HelpWindow
Class ResultDisplay
Class PersonListPanel
-Class PersonCard
Class StatusBarFooter
Class CommandBox
+Class LogListPanel
+Class VolunteerCard
+Class BefriendeeCard
+Class LogCard
}
package Model <> {
@@ -32,22 +35,30 @@ UiManager .left.|> Ui
UiManager -down-> "1" MainWindow
MainWindow *-down-> "1" CommandBox
MainWindow *-down-> "1" ResultDisplay
-MainWindow *-down-> "1" PersonListPanel
+MainWindow *-down-> "2" PersonListPanel
+MainWindow *-down-> "1" LogListPanel
MainWindow *-down-> "1" StatusBarFooter
MainWindow --> "0..1" HelpWindow
-PersonListPanel -down-> "*" PersonCard
+PersonListPanel -down-> "*" VolunteerCard
+PersonListPanel -down-> "*" BefriendeeCard
+LogListPanel -down-> "*" LogCard
MainWindow -left-|> UiPart
ResultDisplay --|> UiPart
CommandBox --|> UiPart
PersonListPanel --|> UiPart
-PersonCard --|> UiPart
+LogListPanel --|> UiPart
+VolunteerCard --|> UiPart
+BefriendeeCard --|> UiPart
+LogCard --|> UiPart
StatusBarFooter --|> UiPart
HelpWindow --|> UiPart
-PersonCard ..> Model
+VolunteerCard -right..> Model
+BefriendeeCard -right..> Model
+LogCard -right..> Model
UiManager -right-> Logic
MainWindow -left-> Logic
diff --git a/docs/diagrams/UndoRedoState0-InitialState.puml b/docs/diagrams/UndoRedoState0-InitialState.puml
new file mode 100644
index 00000000000..ab483bfd200
--- /dev/null
+++ b/docs/diagrams/UndoRedoState0-InitialState.puml
@@ -0,0 +1,21 @@
+@startuml
+!include style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+skinparam ClassBackgroundColor #FFFFAA
+
+title Initial state
+
+package States {
+ class State1 as "ds0:Datastore"
+ class State2 as "ds1:Datastore"
+ class State3 as "ds2:Datastore"
+}
+State1 -[hidden]right-> State2
+State2 -[hidden]right-> State3
+hide State2
+hide State3
+
+class Pointer as "Current State" #FFFFFF
+Pointer -up-> State1
+@end
diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml
index 43a45903ac9..a527c9bdd28 100644
--- a/docs/diagrams/UndoRedoState0.puml
+++ b/docs/diagrams/UndoRedoState0.puml
@@ -7,9 +7,7 @@ skinparam ClassBackgroundColor #FFFFAA
title Initial state
package States {
- class State1 as "ab0:AddressBook"
- class State2 as "ab1:AddressBook"
- class State3 as "ab2:AddressBook"
+ class State1 as "ds0:Datastore"
}
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml
index 5a41e9e1651..90705cfb289 100644
--- a/docs/diagrams/UndoRedoState1.puml
+++ b/docs/diagrams/UndoRedoState1.puml
@@ -4,12 +4,11 @@ skinparam ClassFontColor #000000
skinparam ClassBorderColor #000000
skinparam ClassBackgroundColor #FFFFAA
-title After command "delete 5"
+title After command "delete 5 r/volunteer"
package States <> {
- class State1 as "ab0:AddressBook"
- class State2 as "ab1:AddressBook"
- class State3 as "ab2:AddressBook"
+ class State1 as "ds:Datastore"
+ class State2 as "ds1:Datastore"
}
State1 -[hidden]right-> State2
diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml
index ad32fce1b0b..dfaf5f0487c 100644
--- a/docs/diagrams/UndoRedoState2.puml
+++ b/docs/diagrams/UndoRedoState2.puml
@@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA
title After command "add n/David"
package States <> {
- class State1 as "ab0:AddressBook"
- class State2 as "ab1:AddressBook"
- class State3 as "ab2:AddressBook"
+ class State1 as "ds0:Datastore"
+ class State2 as "ds1:Datastore"
+ class State3 as "ds2:Datastore"
}
State1 -[hidden]right-> State2
diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml
index 9187a690036..dcb0d43b06b 100644
--- a/docs/diagrams/UndoRedoState3.puml
+++ b/docs/diagrams/UndoRedoState3.puml
@@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA
title After command "undo"
package States <> {
- class State1 as "ab0:AddressBook"
- class State2 as "ab1:AddressBook"
- class State3 as "ab2:AddressBook"
+ class State1 as "ds0:Datastore"
+ class State2 as "ds1:Datastore"
+ class State3 as "ds2:Datastore"
}
State1 -[hidden]right-> State2
diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml
index 2bc631ffcd0..28b3122211a 100644
--- a/docs/diagrams/UndoRedoState4.puml
+++ b/docs/diagrams/UndoRedoState4.puml
@@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA
title After command "list"
package States <> {
- class State1 as "ab0:AddressBook"
- class State2 as "ab1:AddressBook"
- class State3 as "ab2:AddressBook"
+ class State1 as "ds0:Datastore"
+ class State2 as "ds1:Datastore"
+ class State3 as "ds2:Datastore"
}
State1 -[hidden]right-> State2
diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml
index e77b04104aa..00114856073 100644
--- a/docs/diagrams/UndoRedoState5.puml
+++ b/docs/diagrams/UndoRedoState5.puml
@@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA
title After command "clear"
package States <> {
- class State1 as "ab0:AddressBook"
- class State2 as "ab1:AddressBook"
- class State3 as "ab3:AddressBook"
+ class State1 as "ds0:Datastore"
+ class State2 as "ds1:Datastore"
+ class State3 as "ds3:Datastore"
}
State1 -[hidden]right-> State2
@@ -18,5 +18,5 @@ State2 -[hidden]right-> State3
class Pointer as "Current State" #FFFFFF
Pointer -up-> State3
-note right on link: State ab2 deleted.
+note right on link: State ds2 deleted.
@end
diff --git a/docs/diagrams/UndoSequenceDiagram-Logic.puml b/docs/diagrams/UndoSequenceDiagram-Logic.puml
index e57368c5159..502be88b2d7 100644
--- a/docs/diagrams/UndoSequenceDiagram-Logic.puml
+++ b/docs/diagrams/UndoSequenceDiagram-Logic.puml
@@ -30,12 +30,17 @@ deactivate AddressBookParser
LogicManager -> UndoCommand : execute()
activate UndoCommand
-UndoCommand -> Model : undoAddressBook()
+UndoCommand -> Model : canUndoDatastore()
activate Model
Model --> UndoCommand
deactivate Model
+UndoCommand -> Model : undoChanges()
+activate Model
+Model --> UndoCommand
+deactivate Model
+
UndoCommand --> LogicManager : result
deactivate UndoCommand
UndoCommand -[hidden]-> LogicManager : result
diff --git a/docs/diagrams/UndoSequenceDiagram-Model.puml b/docs/diagrams/UndoSequenceDiagram-Model.puml
index 54d83208cb8..aba402a77ea 100644
--- a/docs/diagrams/UndoSequenceDiagram-Model.puml
+++ b/docs/diagrams/UndoSequenceDiagram-Model.puml
@@ -4,20 +4,28 @@ skinparam ArrowFontStyle plain
box Model MODEL_COLOR_T1
participant ":Model" as Model MODEL_COLOR
-participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR
+participant ":DatastoreVersionStorage" as DatastoreVersionStorage MODEL_COLOR
end box
-[-> Model : undoAddressBook()
+[-> Model : canUndoDatastore()
activate Model
-Model -> VersionedAddressBook : undo()
-activate VersionedAddressBook
+Model -> DatastoreVersionStorage : canUndo()
+activate DatastoreVersionStorage
+DatastoreVersionStorage --> Model
+deactivate DatastoreVersionStorage
+[<-- Model
+
+[-> Model : undoChanges()
+Model -> DatastoreVersionStorage : executeUndo()
+activate DatastoreVersionStorage
+DatastoreVersionStorage --> Model : prevDatastore
+deactivate DatastoreVersionStorage
-VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook)
-VersionedAddressBook --> Model :
-deactivate VersionedAddressBook
+Model -> Model : setDataStore(prevDatastore)
[<-- Model
+
deactivate Model
@enduml
diff --git a/docs/images/AddSequenceDiagram-Logic.png b/docs/images/AddSequenceDiagram-Logic.png
new file mode 100644
index 00000000000..913fcbcedd3
Binary files /dev/null and b/docs/images/AddSequenceDiagram-Logic.png differ
diff --git a/docs/images/AddSequenceDiagram2.png b/docs/images/AddSequenceDiagram2.png
new file mode 100644
index 00000000000..e04c1e255f1
Binary files /dev/null and b/docs/images/AddSequenceDiagram2.png differ
diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png
index 37ad06a2803..8ef1876102d 100644
Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ
diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png
index 02a42e35e76..8b008bb7c24 100644
Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ
diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/CommitActivityDiagram.png
index 5b464126b35..04904d9d081 100644
Binary files a/docs/images/CommitActivityDiagram.png and b/docs/images/CommitActivityDiagram.png differ
diff --git a/docs/images/DatastoreClassDiagram.png b/docs/images/DatastoreClassDiagram.png
new file mode 100644
index 00000000000..47838c4ba39
Binary files /dev/null and b/docs/images/DatastoreClassDiagram.png differ
diff --git a/docs/images/DeleteSequenceDiagram-Logic.png b/docs/images/DeleteSequenceDiagram-Logic.png
new file mode 100644
index 00000000000..49e44b09761
Binary files /dev/null and b/docs/images/DeleteSequenceDiagram-Logic.png differ
diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png
index ac2ae217c51..63d182f31d5 100644
Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ
diff --git a/docs/images/DeleteSequenceDiagram2.png b/docs/images/DeleteSequenceDiagram2.png
new file mode 100644
index 00000000000..d41ef2c1c56
Binary files /dev/null and b/docs/images/DeleteSequenceDiagram2.png differ
diff --git a/docs/images/EditSequenceDiagram-Logic.png b/docs/images/EditSequenceDiagram-Logic.png
new file mode 100644
index 00000000000..c0863f758a8
Binary files /dev/null and b/docs/images/EditSequenceDiagram-Logic.png differ
diff --git a/docs/images/EditSequenceDiagram2.png b/docs/images/EditSequenceDiagram2.png
new file mode 100644
index 00000000000..aecd74b5a80
Binary files /dev/null and b/docs/images/EditSequenceDiagram2.png differ
diff --git a/docs/images/FindSequenceDiagram.png b/docs/images/FindSequenceDiagram.png
new file mode 100644
index 00000000000..38cc93ce530
Binary files /dev/null and b/docs/images/FindSequenceDiagram.png differ
diff --git a/docs/images/FindSequenceDiagram2.png b/docs/images/FindSequenceDiagram2.png
new file mode 100644
index 00000000000..cf11b6c87e8
Binary files /dev/null and b/docs/images/FindSequenceDiagram2.png differ
diff --git a/docs/images/LogAddSequenceDiagram-Logic.png b/docs/images/LogAddSequenceDiagram-Logic.png
new file mode 100644
index 00000000000..204cb8976d3
Binary files /dev/null and b/docs/images/LogAddSequenceDiagram-Logic.png differ
diff --git a/docs/images/LogAddSequenceDiagram2.png b/docs/images/LogAddSequenceDiagram2.png
new file mode 100644
index 00000000000..5c4c2e30c1b
Binary files /dev/null and b/docs/images/LogAddSequenceDiagram2.png differ
diff --git a/docs/images/LogDeleteSequenceDiagram-Logic.png b/docs/images/LogDeleteSequenceDiagram-Logic.png
new file mode 100644
index 00000000000..9c8b3e1426a
Binary files /dev/null and b/docs/images/LogDeleteSequenceDiagram-Logic.png differ
diff --git a/docs/images/LogDeleteSequenceDiagram2.png b/docs/images/LogDeleteSequenceDiagram2.png
new file mode 100644
index 00000000000..0761732f824
Binary files /dev/null and b/docs/images/LogDeleteSequenceDiagram2.png differ
diff --git a/docs/images/LogEditSequenceDiagram-Logic.png b/docs/images/LogEditSequenceDiagram-Logic.png
new file mode 100644
index 00000000000..8a4545b570a
Binary files /dev/null and b/docs/images/LogEditSequenceDiagram-Logic.png differ
diff --git a/docs/images/LogEditSequenceDiagram2.png b/docs/images/LogEditSequenceDiagram2.png
new file mode 100644
index 00000000000..2dfd1710fc1
Binary files /dev/null and b/docs/images/LogEditSequenceDiagram2.png differ
diff --git a/docs/images/LogFindSequenceDiagram-Logic.png b/docs/images/LogFindSequenceDiagram-Logic.png
new file mode 100644
index 00000000000..d99a471ae5b
Binary files /dev/null and b/docs/images/LogFindSequenceDiagram-Logic.png differ
diff --git a/docs/images/LogFindSequenceDiagram2.png b/docs/images/LogFindSequenceDiagram2.png
new file mode 100644
index 00000000000..c720254b78d
Binary files /dev/null and b/docs/images/LogFindSequenceDiagram2.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index a19fb1b4ac8..260bf156c15 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/PairSequenceDiagram-Logic.png b/docs/images/PairSequenceDiagram-Logic.png
new file mode 100644
index 00000000000..63159457d1c
Binary files /dev/null and b/docs/images/PairSequenceDiagram-Logic.png differ
diff --git a/docs/images/PairSequenceDiagram2.png b/docs/images/PairSequenceDiagram2.png
new file mode 100644
index 00000000000..0f0783e1602
Binary files /dev/null and b/docs/images/PairSequenceDiagram2.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 18fa4d0d51f..d2864edb900 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..26d9763f45f 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png
index 11f06d68671..11a8485c298 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/UndoRedoState0-Initial_state.png b/docs/images/UndoRedoState0-Initial_state.png
new file mode 100644
index 00000000000..0303991b12e
Binary files /dev/null and b/docs/images/UndoRedoState0-Initial_state.png differ
diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png
index c5f91b58533..0303991b12e 100644
Binary files a/docs/images/UndoRedoState0.png and b/docs/images/UndoRedoState0.png differ
diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png
index 2d3ad09c047..407e3cf379d 100644
Binary files a/docs/images/UndoRedoState1.png and b/docs/images/UndoRedoState1.png differ
diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png
index 20853694e03..c0dddff6471 100644
Binary files a/docs/images/UndoRedoState2.png and b/docs/images/UndoRedoState2.png differ
diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png
index 1a9551b31be..0516fc0ae39 100644
Binary files a/docs/images/UndoRedoState3.png and b/docs/images/UndoRedoState3.png differ
diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png
index 46dfae78c94..ce51cfc7570 100644
Binary files a/docs/images/UndoRedoState4.png and b/docs/images/UndoRedoState4.png differ
diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png
index f45889b5fdf..296a2e2734c 100644
Binary files a/docs/images/UndoRedoState5.png and b/docs/images/UndoRedoState5.png differ
diff --git a/docs/images/UndoSequenceDiagram-Logic.png b/docs/images/UndoSequenceDiagram-Logic.png
index 78e95214294..b9f443306eb 100644
Binary files a/docs/images/UndoSequenceDiagram-Logic.png and b/docs/images/UndoSequenceDiagram-Logic.png differ
diff --git a/docs/images/UndoSequenceDiagram-Model.png b/docs/images/UndoSequenceDiagram-Model.png
index f0f3da6ae50..b51b7d69803 100644
Binary files a/docs/images/UndoSequenceDiagram-Model.png and b/docs/images/UndoSequenceDiagram-Model.png differ
diff --git a/docs/images/UserInterface.jpg b/docs/images/UserInterface.jpg
new file mode 100644
index 00000000000..8de736a87ab
Binary files /dev/null and b/docs/images/UserInterface.jpg differ
diff --git a/docs/images/addJohnResult.png b/docs/images/addJohnResult.png
new file mode 100644
index 00000000000..8c35345e784
Binary files /dev/null and b/docs/images/addJohnResult.png differ
diff --git a/docs/images/befriendeeCard.png b/docs/images/befriendeeCard.png
new file mode 100644
index 00000000000..c7e9993ea4c
Binary files /dev/null and b/docs/images/befriendeeCard.png differ
diff --git a/docs/images/chaaaaun.png b/docs/images/chaaaaun.png
new file mode 100644
index 00000000000..4ed4516f60f
Binary files /dev/null and b/docs/images/chaaaaun.png differ
diff --git a/docs/images/cjerrong.png b/docs/images/cjerrong.png
new file mode 100644
index 00000000000..03129900e6c
Binary files /dev/null and b/docs/images/cjerrong.png differ
diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png
index 235da1c273e..635b6e03951 100644
Binary files a/docs/images/findAlexDavidResult.png and b/docs/images/findAlexDavidResult.png differ
diff --git a/docs/images/findBerniceResult.png b/docs/images/findBerniceResult.png
new file mode 100644
index 00000000000..ab19e903603
Binary files /dev/null and b/docs/images/findBerniceResult.png differ
diff --git a/docs/images/findJohnResult.png b/docs/images/findJohnResult.png
new file mode 100644
index 00000000000..a3a377b9fde
Binary files /dev/null and b/docs/images/findJohnResult.png differ
diff --git a/docs/images/gerteck.png b/docs/images/gerteck.png
new file mode 100644
index 00000000000..eb3afbd9830
Binary files /dev/null and b/docs/images/gerteck.png differ
diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png
index b1f70470137..45ca2f9f066 100644
Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ
diff --git a/docs/images/logCard.png b/docs/images/logCard.png
new file mode 100644
index 00000000000..70623264b38
Binary files /dev/null and b/docs/images/logCard.png differ
diff --git a/docs/images/logaddResult.png b/docs/images/logaddResult.png
new file mode 100644
index 00000000000..2b18cf28dbe
Binary files /dev/null and b/docs/images/logaddResult.png differ
diff --git a/docs/images/logfindResult.jpeg b/docs/images/logfindResult.jpeg
new file mode 100644
index 00000000000..94fa9d851e8
Binary files /dev/null and b/docs/images/logfindResult.jpeg differ
diff --git a/docs/images/logfindResult.png b/docs/images/logfindResult.png
new file mode 100644
index 00000000000..05fc0265685
Binary files /dev/null and b/docs/images/logfindResult.png differ
diff --git a/docs/images/pairResult.png b/docs/images/pairResult.png
new file mode 100644
index 00000000000..d1437a358a4
Binary files /dev/null and b/docs/images/pairResult.png differ
diff --git a/docs/images/sdevih.png b/docs/images/sdevih.png
new file mode 100644
index 00000000000..aa34781d073
Binary files /dev/null and b/docs/images/sdevih.png differ
diff --git a/docs/images/soons1.png b/docs/images/soons1.png
new file mode 100644
index 00000000000..2db553dbd73
Binary files /dev/null and b/docs/images/soons1.png differ
diff --git a/docs/images/unpairResult.png b/docs/images/unpairResult.png
new file mode 100644
index 00000000000..972c22f9b64
Binary files /dev/null and b/docs/images/unpairResult.png differ
diff --git a/docs/images/volunteerCard.png b/docs/images/volunteerCard.png
new file mode 100644
index 00000000000..1f989b1028c
Binary files /dev/null and b/docs/images/volunteerCard.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..898364770ad 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,37 @@
---
layout: page
-title: AddressBook Level-3
+title: Elder Scrolls
---
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
-[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3)
+[![CI Status](https://github.com/AY2324S2-CS2103T-T09-3/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2324S2-CS2103T-T09-3/tp/actions)
+[![codecov](https://codecov.io/gh/AY2324S2-CS2103T-T09-3/tp/graph/badge.svg?token=7N2EZAM58I)](https://codecov.io/gh/AY2324S2-CS2103T-T09-3/tp)
-![Ui](images/Ui.png)
+
+
+
Elder Scrolls
+
-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
+{: style="text-align: justify" }
+**Elder Scrolls is a** ___free Volunteer Management System (VMS)___ that streamlines the coordination of volunteers and befriendees, with a particular focus on elderly befriending programs. Elder Scrolls combines the _speed of Command Line Interface (CLI) interaction_ with the _benefits of a Graphical User Interface (GUI)_. Whether you prefer the agility of typing or the convenience of visual interaction, Elder Scrolls ensures that your volunteer management tasks are completed swiftly and seamlessly.
-* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
-* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
+{: style="text-align: justify" }
+**Elder Scrolls is crafted with _volunteer managers_, and _organizers_ involved in elderly befriending initiatives in mind**. Whether you're managing volunteers for elderly care, community outreach, or other social services, Elder Scrolls offers a comprehensive solution for you to simplify and enhance your volunteer management efforts.
+
+{: style="text-align: justify" }
+**With its extendable features and flexible architecture**, our open-source VMS can be extended to cater to various types of volunteer management programs with minor adjustments. Whether you're managing volunteers for elderly care, community outreach, or other social services, Elder Scrolls offers a comprehensive solution to simplify and enhance your volunteer management efforts.
+
+{: style="text-align: justify" }
+**No more cumbersome bookkeeping**: manage volunteers and befriendees seamlessly in one intuitive platform. Say goodbye to endless spreadsheets – Elder Scrolls centralizes tasks, making them faster and more effective, with **two main features**: ___Volunteer/Befriendee Management___, and ___Log Management___. These features allow you to effortlessly add, update and manage your volunteers' and befriendees' information, and easily keep track of all activities and interactions between them.
+
+{: style="text-align: justify" }
+Developed for efficiency by our team, ___Elder Scrolls lets you focus on what matters most – making a difference in the lives of others___.
+
+
+* If you are interested in using Elder Scrolls, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#1-quick-start).
+* If you are interested in developing Elder Scrolls, or extending it to further fit your personal needs, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
**Acknowledgements**
* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
+* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
diff --git a/docs/team/chaaaaun.md b/docs/team/chaaaaun.md
new file mode 100644
index 00000000000..3c04c0006ce
--- /dev/null
+++ b/docs/team/chaaaaun.md
@@ -0,0 +1,8 @@
+---
+layout: page
+title: Tu Jia En's Project Portfolio Page
+---
+
+### Project: Elder Scrolls
+
+Elder Scrolls is a Volunteer Management System (VMS) designed to streamline the coordination of volunteers and befriendees, with a particular focus on elderly befriending programs. It is a brownfield project based on AddressBook-Level3, which is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java. After our development, our project now has about 23 kLoC.
diff --git a/docs/team/cjerrong.md b/docs/team/cjerrong.md
new file mode 100644
index 00000000000..4cf1cc29132
--- /dev/null
+++ b/docs/team/cjerrong.md
@@ -0,0 +1,46 @@
+ ---
+layout: page
+title: CJerrong's Project Portfolio Page
+---
+
+### Project: Elder Scrolls
+
+Elder Scrolls is a Volunteer Management System (VMS) designed to streamline the coordination of volunteers and befriendees, with a particular focus on elderly befriending programs. It is a brownfield project based on AddressBook-Level3, which is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java. After our development, our project now has about 23 kLoC.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the ability to undo/redo previous commands.
+ * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command.
+ * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them.
+ * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands.
+ * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}*
+
+* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys.
+
+* **Code contributed**: [RepoSense link]()
+
+* **Project management**:
+ * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub
+
+* **Enhancements to existing features**:
+ * Updated the GUI color scheme (Pull requests [\#33](), [\#34]())
+ * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]())
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `delete` and `find` [\#72]()
+ * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]()
+ * Developer Guide:
+ * Added implementation details of the `delete` feature.
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]()
+ * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]())
+ * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]())
+ * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]())
+
+* **Tools**:
+ * Integrated a third party library (Natty) to the project ([\#42]())
+ * Integrated a new Github plugin (CircleCI) to the team repo
+
+* _{you can add/remove categories in the list above}_
diff --git a/docs/team/gerteck.md b/docs/team/gerteck.md
new file mode 100644
index 00000000000..d8ed9084a6f
--- /dev/null
+++ b/docs/team/gerteck.md
@@ -0,0 +1,53 @@
+---
+layout: page
+title: Ger Teck's Project Portfolio Page
+---
+
+### Project: Elder Scrolls
+
+Elder Scrolls is a Volunteer Management System (VMS) designed to streamline the coordination of volunteers and befriendees, with a particular focus on elderly befriending programs. It is a brownfield project based on AddressBook-Level3, which is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java. After our development, our project now has about 23 kLoC.
+
+Given below are my contributions to the project.
+
+* **New Features**:
+ * _Added support for adding befriendee & volunteer persons._
+ * What it does: This feature enables users to add both befriendee and volunteer persons to the system.
+ * Justification: By allowing the addition of both types of persons, the system becomes more versatile and can accommodate a wider range of users' needs.
+ * Highlights: Implementation involved significant design analysis and modification of existing commands. Challenges were encountered during implementation due to the need for changes in existing functionalities.
+ * _Added new `logfind` feature to support searching for logs_:
+ * What it does: This feature allows users to search for specific logs within the system.
+ * Justification: By implementing a search functionality for logs, users can quickly retrieve relevant information, enhancing the usability and efficiency of the system.
+ * Highlights: Testing was crucial to ensure accurate and reliable search results.
+
+* **Enhancements to existing features**:
+ * Wrote additional tests for existing features to increase coverage [\#40](https://github.com/AY2324S2-CS2103T-T09-3/tp/pull/40), [\#88](https://github.com/AY2324S2-CS2103T-T09-3/tp/pull/88), among others.
+ * Refactored Pair and Unpair commands to enforce immutability [\#62](https://github.com/AY2324S2-CS2103T-T09-3/tp/pull/62)
+ * Extended functionality to Find Command to support searching in separate role lists, search by tags, search by pair status and search by name [\#89](https://github.com/AY2324S2-CS2103T-T09-3/tp/pull/89), [\#96](https://github.com/AY2324S2-CS2103T-T09-3/tp/pull/96)
+ * Updated the GUI color scheme, added relevant SampleData shown upon startup [\#119](https://github.com/AY2324S2-CS2103T-T09-3/tp/pull/119)
+
+* **Documentation**:
+ * _User Guide_:
+ * Updated main landing page, UG structure and features to reflect new features [\#87](https://github.com/AY2324S2-CS2103T-T09-3/tp/pull/87)
+ * Authored User Interface section of the UG [\#119](https://github.com/AY2324S2-CS2103T-T09-3/tp/pull/119)
+ * Added documentation for the features `logfind` and `find`
+ * Added FAQs section to the UG.
+ * Formatted the UG for better readability and navigation when printing to PDF.
+
+ * _Developer Guide_:
+ * Updated medium priority user stories and relevant use cases. [\#24](https://github.com/AY2324S2-CS2103T-T09-3/tp/pull/24)
+ * Updated PUML diagrams to reflect implementation changes [\#53](https://github.com/AY2324S2-CS2103T-T09-3/tp/pull/53)
+ * Added implementation details of the `find`, `logfind` features.
+ * Added manual testing section for the `list`, `find`, `logfind`, `clear` features.
+ * Added planned enhancements for the project.
+
+* **Project management**:
+ * Updated release `v1.2` on GitHub with added features and screenshots (Release [\v1.2](https://github.com/AY2324S2-CS2103T-T09-3/tp/releases/tag/v1.2))
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): ([\#93](https://github.com/AY2324S2-CS2103T-T09-3/tp/pull/93), [\#91](https://github.com/AY2324S2-CS2103T-T09-3/tp/pull/91), among others)
+ * Contributed to forum discussions (examples: [1](https://github.com/nus-cs2103-AY2324S2/forum/issues/251))
+ * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/AY2324S2-CS2103-F15-4/tp/issues/174), [2](https://github.com/AY2324S2-CS2103-F15-4/tp/issues/167), [3](https://github.com/AY2324S2-CS2103-F15-4/tp/issues/172))
+
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s2.github.io/tp-dashboard/?search=gerteck&breakdown=true)
+
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
index 773a07794e2..d46ee3f0d0a 100644
--- a/docs/team/johndoe.md
+++ b/docs/team/johndoe.md
@@ -1,11 +1,11 @@
----
+_---
layout: page
title: John Doe's Project Portfolio Page
---
-### Project: AddressBook Level 3
+### Project: Elder Scrolls
-AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.
+Elder Scrolls is a Volunteer Management System (VMS) designed to streamline the coordination of volunteers and befriendees, with a particular focus on elderly befriending programs. It is a brownfield project based on AddressBook-Level3, which is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java. After our development, our project now has about 23 kLoC.
Given below are my contributions to the project.
@@ -43,4 +43,4 @@ Given below are my contributions to the project.
* Integrated a third party library (Natty) to the project ([\#42]())
* Integrated a new Github plugin (CircleCI) to the team repo
-* _{you can add/remove categories in the list above}_
+* _{you can add/remove categories in the list above}__
diff --git a/docs/team/sdevih.md b/docs/team/sdevih.md
new file mode 100644
index 00000000000..bf7bec213d7
--- /dev/null
+++ b/docs/team/sdevih.md
@@ -0,0 +1,46 @@
+---
+layout: page
+title: S Devi Harshitha's Project Portfolio Page
+---
+
+### Project: Elder Scrolls
+
+Elder Scrolls is a Volunteer Management System (VMS) designed to streamline the coordination of volunteers and befriendees, with a particular focus on elderly befriending programs. It is a brownfield project based on AddressBook-Level3, which is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java. After our development, our project now has about 23 kLoC.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the ability to undo/redo previous commands.
+ * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command.
+ * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them.
+ * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands.
+ * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}*
+
+* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys.
+
+* **Code contributed**: [RepoSense link]()
+
+* **Project management**:
+ * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub
+
+* **Enhancements to existing features**:
+ * Updated the GUI color scheme (Pull requests [\#33](), [\#34]())
+ * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]())
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `delete` and `find` [\#72]()
+ * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]()
+ * Developer Guide:
+ * Added implementation details of the `delete` feature.
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]()
+ * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]())
+ * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]())
+ * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]())
+
+* **Tools**:
+ * Integrated a third party library (Natty) to the project ([\#42]())
+ * Integrated a new Github plugin (CircleCI) to the team repo
+
+* _{you can add/remove categories in the list above}_
diff --git a/docs/team/soons1.md b/docs/team/soons1.md
new file mode 100644
index 00000000000..21f8dfc4d9a
--- /dev/null
+++ b/docs/team/soons1.md
@@ -0,0 +1,46 @@
+---
+layout: page
+title: John Doe's Project Portfolio Page
+---
+
+### Project: Elder Scrolls
+
+Elder Scrolls is a Volunteer Management System (VMS) designed to streamline the coordination of volunteers and befriendees, with a particular focus on elderly befriending programs. It is a brownfield project based on AddressBook-Level3, which is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java. After our development, our project now has about 23 kLoC.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the ability to undo/redo previous commands.
+ * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command.
+ * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them.
+ * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands.
+ * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}*
+
+* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys.
+
+* **Code contributed**: [RepoSense link]()
+
+* **Project management**:
+ * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub
+
+* **Enhancements to existing features**:
+ * Updated the GUI color scheme (Pull requests [\#33](), [\#34]())
+ * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]())
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `delete` and `find` [\#72]()
+ * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]()
+ * Developer Guide:
+ * Added implementation details of the `delete` feature.
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]()
+ * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]())
+ * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]())
+ * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]())
+
+* **Tools**:
+ * Integrated a third party library (Natty) to the project ([\#42]())
+ * Integrated a new Github plugin (CircleCI) to the team repo
+
+* _{you can add/remove categories in the list above}_
diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md
index d98f38982e7..b4a7d0ca9c1 100644
--- a/docs/tutorials/AddRemark.md
+++ b/docs/tutorials/AddRemark.md
@@ -25,7 +25,7 @@ For now, let’s keep `RemarkCommand` as simple as possible and print some outpu
``` java
package seedu.address.logic.commands;
-import seedu.address.model.Model;
+import model.scrolls.elder.Model;
/**
* Changes the remark of an existing person in the address book.
@@ -91,7 +91,7 @@ Let’s change `RemarkCommand` to parse input from the user.
We start by modifying the constructor of `RemarkCommand` to accept an `Index` and a `String`. While we are at it, let’s change the error message to echo the values. While this is not a replacement for tests, it is an obvious way to tell if our code is functioning as intended.
``` java
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static util.commons.scrolls.elder.CollectionUtil.requireAllNonNull;
//...
public class RemarkCommand extends Command {
//...
@@ -240,7 +240,7 @@ Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark`
Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person.
-Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688).
+Simply add the following to [`ui.scrolls.elder.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688).
**`PersonCard.java`:**
diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md
index f29169bc924..cb7685c697c 100644
--- a/docs/tutorials/RemovingFields.md
+++ b/docs/tutorials/RemovingFields.md
@@ -28,7 +28,7 @@ IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a re
### Assisted refactoring
-The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
+The `address` field in `Person` is actually an instance of the `person.model.scrolls.elder.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
* :bulb: To make things simpler, you can unselect the options `Search in comments and strings` and `Search for text occurrences`
![Usages detected](../images/remove/UnsafeDelete.png)
diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md
index 4fb62a83ef6..0920149b55d 100644
--- a/docs/tutorials/TracingCode.md
+++ b/docs/tutorials/TracingCode.md
@@ -39,7 +39,7 @@ In our case, we would want to begin the tracing at the very point where the App
-According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.address.logic.Logic`.
+According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `logic.scrolls.elder.Logic`.
@@ -48,7 +48,7 @@ According to the sequence diagram you saw earlier (and repeated above for refere
:bulb: **Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`.
-A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for.
+A quick look at the `logic.scrolls.elder.Logic` (an extract given below) confirms that this indeed might be what we’re looking for.
```java
public interface Logic {
diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/scrolls/elder/AppParameters.java
similarity index 92%
rename from src/main/java/seedu/address/AppParameters.java
rename to src/main/java/scrolls/elder/AppParameters.java
index 3d603622d4e..3d0248ea34f 100644
--- a/src/main/java/seedu/address/AppParameters.java
+++ b/src/main/java/scrolls/elder/AppParameters.java
@@ -1,4 +1,4 @@
-package seedu.address;
+package scrolls.elder;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -7,9 +7,9 @@
import java.util.logging.Logger;
import javafx.application.Application;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.util.FileUtil;
-import seedu.address.commons.util.ToStringBuilder;
+import scrolls.elder.commons.core.LogsCenter;
+import scrolls.elder.commons.util.FileUtil;
+import scrolls.elder.commons.util.ToStringBuilder;
/**
* Represents the parsed command-line parameters given to the application.
diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/scrolls/elder/Main.java
similarity index 96%
rename from src/main/java/seedu/address/Main.java
rename to src/main/java/scrolls/elder/Main.java
index ec1b7958746..567ef7b9f8b 100644
--- a/src/main/java/seedu/address/Main.java
+++ b/src/main/java/scrolls/elder/Main.java
@@ -1,9 +1,9 @@
-package seedu.address;
+package scrolls.elder;
import java.util.logging.Logger;
import javafx.application.Application;
-import seedu.address.commons.core.LogsCenter;
+import scrolls.elder.commons.core.LogsCenter;
/**
* The main entry point to the application.
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/scrolls/elder/MainApp.java
similarity index 66%
rename from src/main/java/seedu/address/MainApp.java
rename to src/main/java/scrolls/elder/MainApp.java
index 3d6bd06d5af..c6c2113bbf3 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/scrolls/elder/MainApp.java
@@ -1,4 +1,4 @@
-package seedu.address;
+package scrolls.elder;
import java.io.IOException;
import java.nio.file.Path;
@@ -7,29 +7,29 @@
import javafx.application.Application;
import javafx.stage.Stage;
-import seedu.address.commons.core.Config;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.core.Version;
-import seedu.address.commons.exceptions.DataLoadingException;
-import seedu.address.commons.util.ConfigUtil;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.Logic;
-import seedu.address.logic.LogicManager;
-import seedu.address.model.AddressBook;
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-import seedu.address.model.util.SampleDataUtil;
-import seedu.address.storage.AddressBookStorage;
-import seedu.address.storage.JsonAddressBookStorage;
-import seedu.address.storage.JsonUserPrefsStorage;
-import seedu.address.storage.Storage;
-import seedu.address.storage.StorageManager;
-import seedu.address.storage.UserPrefsStorage;
-import seedu.address.ui.Ui;
-import seedu.address.ui.UiManager;
+import scrolls.elder.commons.core.Config;
+import scrolls.elder.commons.core.LogsCenter;
+import scrolls.elder.commons.core.Version;
+import scrolls.elder.commons.exceptions.DataLoadingException;
+import scrolls.elder.commons.util.ConfigUtil;
+import scrolls.elder.commons.util.StringUtil;
+import scrolls.elder.logic.Logic;
+import scrolls.elder.logic.LogicManager;
+import scrolls.elder.model.Datastore;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.ModelManager;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.ReadOnlyUserPrefs;
+import scrolls.elder.model.UserPrefs;
+import scrolls.elder.model.util.SampleDataUtil;
+import scrolls.elder.storage.DatastoreStorage;
+import scrolls.elder.storage.JsonDatastoreStorage;
+import scrolls.elder.storage.JsonUserPrefsStorage;
+import scrolls.elder.storage.Storage;
+import scrolls.elder.storage.StorageManager;
+import scrolls.elder.storage.UserPrefsStorage;
+import scrolls.elder.ui.Ui;
+import scrolls.elder.ui.UiManager;
/**
* Runs the application.
@@ -48,7 +48,7 @@ public class MainApp extends Application {
@Override
public void init() throws Exception {
- logger.info("=============================[ Initializing AddressBook ]===========================");
+ logger.info("=============================[ Initializing Elder Scrolls ]===========================");
super.init();
AppParameters appParameters = AppParameters.parse(getParameters());
@@ -57,8 +57,8 @@ public void init() throws Exception {
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefs userPrefs = initPrefs(userPrefsStorage);
- AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ DatastoreStorage datastoreStorage = new JsonDatastoreStorage(userPrefs.getDatastoreFilePath());
+ storage = new StorageManager(datastoreStorage, userPrefsStorage);
model = initModelManager(storage, userPrefs);
@@ -68,26 +68,26 @@ public void init() throws Exception {
}
/**
- * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found,
- * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book.
+ * Returns a {@code ModelManager} with the data from {@code storage}'s datastore and {@code userPrefs}.
+ * The data from the sample datastore will be used instead if {@code storage}'s datastore is not found,
+ * or an empty datastore will be used instead if errors occur when reading {@code storage}'s datastore.
*/
private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
- logger.info("Using data file : " + storage.getAddressBookFilePath());
+ logger.info("Using data file : " + storage.getDatastoreFilePath());
- Optional
addressBookOptional;
- ReadOnlyAddressBook initialData;
+ Optional datastoreOptional;
+ ReadOnlyDatastore initialData;
try {
- addressBookOptional = storage.readAddressBook();
- if (!addressBookOptional.isPresent()) {
- logger.info("Creating a new data file " + storage.getAddressBookFilePath()
- + " populated with a sample AddressBook.");
+ datastoreOptional = storage.readDatastore();
+ if (datastoreOptional.isEmpty()) {
+ logger.info("Creating a new data file " + storage.getDatastoreFilePath()
+ + " populated with sample data.");
}
- initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
+ initialData = datastoreOptional.orElseGet(SampleDataUtil::getSampleDatastore);
} catch (DataLoadingException e) {
- logger.warning("Data file at " + storage.getAddressBookFilePath() + " could not be loaded."
- + " Will be starting with an empty AddressBook.");
- initialData = new AddressBook();
+ logger.warning("Data file at " + storage.getDatastoreFilePath() + " could not be loaded."
+ + " Will be starting no data.");
+ initialData = new Datastore();
}
return new ModelManager(initialData, userPrefs);
@@ -170,13 +170,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
@Override
public void start(Stage primaryStage) {
- logger.info("Starting AddressBook " + MainApp.VERSION);
+ logger.info("Starting Elder Scrolls " + MainApp.VERSION);
ui.start(primaryStage);
}
@Override
public void stop() {
- logger.info("============================ [ Stopping Address Book ] =============================");
+ logger.info("============================ [ Stopping Elder Scrolls ] =============================");
try {
storage.saveUserPrefs(model.getUserPrefs());
} catch (IOException e) {
diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/scrolls/elder/commons/core/Config.java
similarity index 84%
rename from src/main/java/seedu/address/commons/core/Config.java
rename to src/main/java/scrolls/elder/commons/core/Config.java
index 485f85a5e05..18f90086c5a 100644
--- a/src/main/java/seedu/address/commons/core/Config.java
+++ b/src/main/java/scrolls/elder/commons/core/Config.java
@@ -1,11 +1,13 @@
-package seedu.address.commons.core;
+package scrolls.elder.commons.core;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.logging.Level;
-import seedu.address.commons.util.ToStringBuilder;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import scrolls.elder.commons.util.ToStringBuilder;
/**
* Config values used by the app
@@ -30,6 +32,11 @@ public Path getUserPrefsFilePath() {
return userPrefsFilePath;
}
+ @JsonProperty("userPrefsFilePath")
+ public String getUserPrefsFilePathString() {
+ return userPrefsFilePath.toString();
+ }
+
public void setUserPrefsFilePath(Path userPrefsFilePath) {
this.userPrefsFilePath = userPrefsFilePath;
}
diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/scrolls/elder/commons/core/GuiSettings.java
similarity index 94%
rename from src/main/java/seedu/address/commons/core/GuiSettings.java
rename to src/main/java/scrolls/elder/commons/core/GuiSettings.java
index a97a86ee8d7..71c2dfec126 100644
--- a/src/main/java/seedu/address/commons/core/GuiSettings.java
+++ b/src/main/java/scrolls/elder/commons/core/GuiSettings.java
@@ -1,10 +1,10 @@
-package seedu.address.commons.core;
+package scrolls.elder.commons.core;
import java.awt.Point;
import java.io.Serializable;
import java.util.Objects;
-import seedu.address.commons.util.ToStringBuilder;
+import scrolls.elder.commons.util.ToStringBuilder;
/**
* A Serializable class that contains the GUI settings.
@@ -13,7 +13,7 @@
public class GuiSettings implements Serializable {
private static final double DEFAULT_HEIGHT = 600;
- private static final double DEFAULT_WIDTH = 740;
+ private static final double DEFAULT_WIDTH = 1000;
private final double windowWidth;
private final double windowHeight;
diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/scrolls/elder/commons/core/LogsCenter.java
similarity index 99%
rename from src/main/java/seedu/address/commons/core/LogsCenter.java
rename to src/main/java/scrolls/elder/commons/core/LogsCenter.java
index 8cf8e15a0f0..685948f2d22 100644
--- a/src/main/java/seedu/address/commons/core/LogsCenter.java
+++ b/src/main/java/scrolls/elder/commons/core/LogsCenter.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package scrolls.elder.commons.core;
import static java.util.Objects.requireNonNull;
diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/scrolls/elder/commons/core/Version.java
similarity index 98%
rename from src/main/java/seedu/address/commons/core/Version.java
rename to src/main/java/scrolls/elder/commons/core/Version.java
index 491d24559b4..e3c6aa4b96d 100644
--- a/src/main/java/seedu/address/commons/core/Version.java
+++ b/src/main/java/scrolls/elder/commons/core/Version.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package scrolls.elder.commons.core;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/scrolls/elder/commons/core/index/Index.java
similarity index 95%
rename from src/main/java/seedu/address/commons/core/index/Index.java
rename to src/main/java/scrolls/elder/commons/core/index/Index.java
index dd170d8b68d..5ff7c7d9dac 100644
--- a/src/main/java/seedu/address/commons/core/index/Index.java
+++ b/src/main/java/scrolls/elder/commons/core/index/Index.java
@@ -1,6 +1,6 @@
-package seedu.address.commons.core.index;
+package scrolls.elder.commons.core.index;
-import seedu.address.commons.util.ToStringBuilder;
+import scrolls.elder.commons.util.ToStringBuilder;
/**
* Represents a zero-based or one-based index.
diff --git a/src/main/java/seedu/address/commons/exceptions/DataLoadingException.java b/src/main/java/scrolls/elder/commons/exceptions/DataLoadingException.java
similarity index 82%
rename from src/main/java/seedu/address/commons/exceptions/DataLoadingException.java
rename to src/main/java/scrolls/elder/commons/exceptions/DataLoadingException.java
index 9904ba47afe..2deee7c1550 100644
--- a/src/main/java/seedu/address/commons/exceptions/DataLoadingException.java
+++ b/src/main/java/scrolls/elder/commons/exceptions/DataLoadingException.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.exceptions;
+package scrolls.elder.commons.exceptions;
/**
* Represents an error during loading of data from a file.
diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/scrolls/elder/commons/exceptions/IllegalValueException.java
similarity index 93%
rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
rename to src/main/java/scrolls/elder/commons/exceptions/IllegalValueException.java
index 19124db485c..16000e48edc 100644
--- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
+++ b/src/main/java/scrolls/elder/commons/exceptions/IllegalValueException.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.exceptions;
+package scrolls.elder.commons.exceptions;
/**
* Signals that some given data does not fulfill some constraints.
diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/scrolls/elder/commons/util/AppUtil.java
similarity index 94%
rename from src/main/java/seedu/address/commons/util/AppUtil.java
rename to src/main/java/scrolls/elder/commons/util/AppUtil.java
index 87aa89c0326..d365894932d 100644
--- a/src/main/java/seedu/address/commons/util/AppUtil.java
+++ b/src/main/java/scrolls/elder/commons/util/AppUtil.java
@@ -1,9 +1,9 @@
-package seedu.address.commons.util;
+package scrolls.elder.commons.util;
import static java.util.Objects.requireNonNull;
import javafx.scene.image.Image;
-import seedu.address.MainApp;
+import scrolls.elder.MainApp;
/**
* A container for App specific utility functions
diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/scrolls/elder/commons/util/CollectionUtil.java
similarity index 96%
rename from src/main/java/seedu/address/commons/util/CollectionUtil.java
rename to src/main/java/scrolls/elder/commons/util/CollectionUtil.java
index eafe4dfd681..b12670fa57f 100644
--- a/src/main/java/seedu/address/commons/util/CollectionUtil.java
+++ b/src/main/java/scrolls/elder/commons/util/CollectionUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package scrolls.elder.commons.util;
import static java.util.Objects.requireNonNull;
diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/scrolls/elder/commons/util/ConfigUtil.java
similarity index 77%
rename from src/main/java/seedu/address/commons/util/ConfigUtil.java
rename to src/main/java/scrolls/elder/commons/util/ConfigUtil.java
index 7b829c3c4cc..351ecbf05c9 100644
--- a/src/main/java/seedu/address/commons/util/ConfigUtil.java
+++ b/src/main/java/scrolls/elder/commons/util/ConfigUtil.java
@@ -1,11 +1,11 @@
-package seedu.address.commons.util;
+package scrolls.elder.commons.util;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
-import seedu.address.commons.core.Config;
-import seedu.address.commons.exceptions.DataLoadingException;
+import scrolls.elder.commons.core.Config;
+import scrolls.elder.commons.exceptions.DataLoadingException;
/**
* A class for accessing the Config File.
diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/scrolls/elder/commons/util/FileUtil.java
similarity index 98%
rename from src/main/java/seedu/address/commons/util/FileUtil.java
rename to src/main/java/scrolls/elder/commons/util/FileUtil.java
index b1e2767cdd9..dc8b8637518 100644
--- a/src/main/java/seedu/address/commons/util/FileUtil.java
+++ b/src/main/java/scrolls/elder/commons/util/FileUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package scrolls.elder.commons.util;
import java.io.IOException;
import java.nio.file.Files;
diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/scrolls/elder/commons/util/JsonUtil.java
similarity index 95%
rename from src/main/java/seedu/address/commons/util/JsonUtil.java
rename to src/main/java/scrolls/elder/commons/util/JsonUtil.java
index 100cb16c395..a8a3818c424 100644
--- a/src/main/java/seedu/address/commons/util/JsonUtil.java
+++ b/src/main/java/scrolls/elder/commons/util/JsonUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package scrolls.elder.commons.util;
import static java.util.Objects.requireNonNull;
@@ -20,8 +20,8 @@
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.exceptions.DataLoadingException;
+import scrolls.elder.commons.core.LogsCenter;
+import scrolls.elder.commons.exceptions.DataLoadingException;
/**
* Converts a Java object instance to JSON and vice versa
@@ -30,7 +30,7 @@ public class JsonUtil {
private static final Logger logger = LogsCenter.getLogger(JsonUtil.class);
- private static ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules()
+ private static final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules()
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/scrolls/elder/commons/util/StringUtil.java
similarity index 95%
rename from src/main/java/seedu/address/commons/util/StringUtil.java
rename to src/main/java/scrolls/elder/commons/util/StringUtil.java
index 61cc8c9a1cb..02acd9af924 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/scrolls/elder/commons/util/StringUtil.java
@@ -1,7 +1,7 @@
-package seedu.address.commons.util;
+package scrolls.elder.commons.util;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
+import static scrolls.elder.commons.util.AppUtil.checkArgument;
import java.io.PrintWriter;
import java.io.StringWriter;
diff --git a/src/main/java/seedu/address/commons/util/ToStringBuilder.java b/src/main/java/scrolls/elder/commons/util/ToStringBuilder.java
similarity index 97%
rename from src/main/java/seedu/address/commons/util/ToStringBuilder.java
rename to src/main/java/scrolls/elder/commons/util/ToStringBuilder.java
index d979b926734..e8c7549ad2d 100644
--- a/src/main/java/seedu/address/commons/util/ToStringBuilder.java
+++ b/src/main/java/scrolls/elder/commons/util/ToStringBuilder.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package scrolls.elder.commons.util;
/**
* Builds a string representation of an object that is suitable as the return value of {@link Object#toString()}.
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/scrolls/elder/logic/Logic.java
similarity index 52%
rename from src/main/java/seedu/address/logic/Logic.java
rename to src/main/java/scrolls/elder/logic/Logic.java
index 92cd8fa605a..686a8275c47 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/scrolls/elder/logic/Logic.java
@@ -1,14 +1,15 @@
-package seedu.address.logic;
+package scrolls.elder.logic;
import java.nio.file.Path;
import javafx.collections.ObservableList;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
+import scrolls.elder.commons.core.GuiSettings;
+import scrolls.elder.logic.commands.CommandResult;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.log.Log;
+import scrolls.elder.model.person.Person;
/**
* API of the Logic component
@@ -23,20 +24,25 @@ public interface Logic {
*/
CommandResult execute(String commandText) throws CommandException, ParseException;
- /**
- * Returns the AddressBook.
- *
- * @see seedu.address.model.Model#getAddressBook()
- */
- ReadOnlyAddressBook getAddressBook();
-
/** Returns an unmodifiable view of the filtered list of persons */
ObservableList getFilteredPersonList();
+ /** Returns an unmodifiable view of the filtered list of persons */
+ ObservableList getFilteredVolunteerList();
+
+ /** Returns an unmodifiable view of the filtered list of persons */
+ ObservableList getFilteredBefriendeeList();
+
+ /** Returns an unmodifiable view of the filtered list of logs */
+ ObservableList getLogList();
+
+ /** Returns the datastore of the model */
+ ReadOnlyDatastore getDatastore();
+
/**
* Returns the user prefs' address book file path.
*/
- Path getAddressBookFilePath();
+ Path getDatastoreFilePath();
/**
* Returns the user prefs' GUI settings.
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/scrolls/elder/logic/LogicManager.java
similarity index 60%
rename from src/main/java/seedu/address/logic/LogicManager.java
rename to src/main/java/scrolls/elder/logic/LogicManager.java
index 5aa3b91c7d0..ab70cc444aa 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/scrolls/elder/logic/LogicManager.java
@@ -1,4 +1,4 @@
-package seedu.address.logic;
+package scrolls.elder.logic;
import java.io.IOException;
import java.nio.file.AccessDeniedException;
@@ -6,17 +6,18 @@
import java.util.logging.Logger;
import javafx.collections.ObservableList;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.AddressBookParser;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.Model;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
-import seedu.address.storage.Storage;
+import scrolls.elder.commons.core.GuiSettings;
+import scrolls.elder.commons.core.LogsCenter;
+import scrolls.elder.logic.commands.Command;
+import scrolls.elder.logic.commands.CommandResult;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.logic.parser.AddressBookParser;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.log.Log;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.storage.Storage;
/**
* The main LogicManager of the app.
@@ -51,7 +52,7 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
commandResult = command.execute(model);
try {
- storage.saveAddressBook(model.getAddressBook());
+ storage.saveDatastore(model.getDatastore());
} catch (AccessDeniedException e) {
throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e);
} catch (IOException ioe) {
@@ -62,18 +63,33 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
}
@Override
- public ReadOnlyAddressBook getAddressBook() {
- return model.getAddressBook();
+ public ObservableList getFilteredPersonList() {
+ return model.getDatastore().getPersonStore().getFilteredPersonList();
}
@Override
- public ObservableList getFilteredPersonList() {
- return model.getFilteredPersonList();
+ public ObservableList getFilteredVolunteerList() {
+ return model.getDatastore().getPersonStore().getFilteredVolunteerList();
+ }
+
+ @Override
+ public ObservableList getFilteredBefriendeeList() {
+ return model.getDatastore().getPersonStore().getFilteredBefriendeeList();
+ }
+
+ @Override
+ public ObservableList getLogList() {
+ return model.getDatastore().getLogStore().getLogList();
+ }
+
+ @Override
+ public ReadOnlyDatastore getDatastore() {
+ return model.getDatastore();
}
@Override
- public Path getAddressBookFilePath() {
- return model.getAddressBookFilePath();
+ public Path getDatastoreFilePath() {
+ return model.getDatastoreFilePath();
}
@Override
diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/scrolls/elder/logic/Messages.java
similarity index 50%
rename from src/main/java/seedu/address/logic/Messages.java
rename to src/main/java/scrolls/elder/logic/Messages.java
index ecd32c31b53..a132ea203f5 100644
--- a/src/main/java/seedu/address/logic/Messages.java
+++ b/src/main/java/scrolls/elder/logic/Messages.java
@@ -1,11 +1,13 @@
-package seedu.address.logic;
+package scrolls.elder.logic;
+import java.time.LocalDate;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import seedu.address.logic.parser.Prefix;
-import seedu.address.model.person.Person;
+import scrolls.elder.logic.parser.Prefix;
+import scrolls.elder.model.log.Log;
+import scrolls.elder.model.person.Person;
/**
* Container for user visible messages.
@@ -14,10 +16,17 @@ public class Messages {
public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
- public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
+ public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid.";
+ public static final String MESSAGE_INVALID_LOG_DISPLAYED_INDEX = "The log index provided is invalid.";
public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
+ public static final String MESSAGE_PERSONS_LISTED_OVERVIEW_WITH_ROLE = "%1$d persons listed with role %2$s!";
+
public static final String MESSAGE_DUPLICATE_FIELDS =
"Multiple values specified for the following single-valued field(s): ";
+ public static final String MESSAGE_CONTACT_PAIRED_BEFORE_DELETE =
+ "Contact is paired. Please unpair before deleting.";
+ public static final String MESSAGE_CONTACT_LOG_BEFORE_DELETE =
+ "Contact has a log in Elder Scrolls. Please delete the log before deleting the contact.";
/**
* Returns an error message indicating the duplicate prefixes.
@@ -34,9 +43,11 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref
/**
* Formats the {@code person} for display to the user.
*/
- public static String format(Person person) {
+ public static String formatPerson(Person person) {
final StringBuilder builder = new StringBuilder();
builder.append(person.getName())
+ .append("; Role: ")
+ .append(person.getRole())
.append("; Phone: ")
.append(person.getPhone())
.append("; Email: ")
@@ -48,4 +59,24 @@ public static String format(Person person) {
return builder.toString();
}
+ /**
+ * Formats the {@code log} for display to the user.
+ */
+ public static String formatLog(Log log) {
+ LocalDate startDateWithoutTime = log.getStartDate()
+ .toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate();
+
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Title: ")
+ .append(log.getLogTitle())
+ .append("; Start Date: ")
+ .append(startDateWithoutTime)
+ .append("; Duration: ")
+ .append(log.getDuration())
+ .append("; Remarks: ")
+ .append(log.getRemarks());
+ return builder.toString();
+ }
+
+
}
diff --git a/src/main/java/scrolls/elder/logic/commands/AddCommand.java b/src/main/java/scrolls/elder/logic/commands/AddCommand.java
new file mode 100644
index 00000000000..d3ce0cb147c
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/commands/AddCommand.java
@@ -0,0 +1,88 @@
+package scrolls.elder.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_NAME;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_PHONE;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_ROLE;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_TAG;
+
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.logic.Messages;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.PersonStore;
+import scrolls.elder.model.person.Person;
+
+/**
+ * Adds a person to the address book.
+ */
+public class AddCommand extends Command {
+
+ public static final String COMMAND_WORD = "add";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
+ + "\nParameters: "
+ + PREFIX_NAME + "NAME " + PREFIX_ROLE + "{VOLUNTEER or BEFRIENDEE} "
+ + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL "
+ + PREFIX_ADDRESS + "ADDRESS " + "[" + PREFIX_TAG + "TAG]...\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_NAME + "John Doe " + PREFIX_ROLE + "volunteer " + PREFIX_PHONE + "98765432 "
+ + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
+ + PREFIX_TAG + "experienced " + PREFIX_TAG + "student ";
+
+ public static final String MESSAGE_SUCCESS = "New person added: %1$s";
+ public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
+
+ /**
+ * Contains data for the person to be added.
+ * Does not contain the final person ID.
+ */
+ private final Person toAdd;
+
+ /**
+ * Creates an AddCommand to add the specified {@code Person}
+ */
+ public AddCommand(Person person) {
+ requireNonNull(person);
+ toAdd = person;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ PersonStore store = model.getMutableDatastore().getMutablePersonStore();
+
+ if (store.hasPerson(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ }
+
+ store.addPerson(toAdd);
+ model.getDatastore().getPersonStore().updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ model.commitDatastore();
+ return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.formatPerson(toAdd)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof AddCommand)) {
+ return false;
+ }
+
+ AddCommand otherAddCommand = (AddCommand) other;
+ return toAdd.equals(otherAddCommand.toAdd);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("toAdd", toAdd)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/scrolls/elder/logic/commands/ClearCommand.java
similarity index 59%
rename from src/main/java/seedu/address/logic/commands/ClearCommand.java
rename to src/main/java/scrolls/elder/logic/commands/ClearCommand.java
index 9c86b1fa6e4..2be57fb8ec6 100644
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ b/src/main/java/scrolls/elder/logic/commands/ClearCommand.java
@@ -1,9 +1,9 @@
-package seedu.address.logic.commands;
+package scrolls.elder.logic.commands;
import static java.util.Objects.requireNonNull;
-import seedu.address.model.AddressBook;
-import seedu.address.model.Model;
+import scrolls.elder.model.Datastore;
+import scrolls.elder.model.Model;
/**
* Clears the address book.
@@ -17,7 +17,9 @@ public class ClearCommand extends Command {
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
- model.setAddressBook(new AddressBook());
+ model.setDatastore(new Datastore());
+ model.getDatastore().getPersonStore().updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ model.commitDatastore();
return new CommandResult(MESSAGE_SUCCESS);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/scrolls/elder/logic/commands/Command.java
similarity index 78%
rename from src/main/java/seedu/address/logic/commands/Command.java
rename to src/main/java/scrolls/elder/logic/commands/Command.java
index 64f18992160..321cae569c7 100644
--- a/src/main/java/seedu/address/logic/commands/Command.java
+++ b/src/main/java/scrolls/elder/logic/commands/Command.java
@@ -1,7 +1,7 @@
-package seedu.address.logic.commands;
+package scrolls.elder.logic.commands;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.model.Model;
/**
* Represents a command with hidden internal logic and the ability to be executed.
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/scrolls/elder/logic/commands/CommandResult.java
similarity index 95%
rename from src/main/java/seedu/address/logic/commands/CommandResult.java
rename to src/main/java/scrolls/elder/logic/commands/CommandResult.java
index 249b6072d0d..50b3fafedd1 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/scrolls/elder/logic/commands/CommandResult.java
@@ -1,10 +1,10 @@
-package seedu.address.logic.commands;
+package scrolls.elder.logic.commands;
import static java.util.Objects.requireNonNull;
import java.util.Objects;
-import seedu.address.commons.util.ToStringBuilder;
+import scrolls.elder.commons.util.ToStringBuilder;
/**
* Represents the result of a command execution.
diff --git a/src/main/java/scrolls/elder/logic/commands/DeleteCommand.java b/src/main/java/scrolls/elder/logic/commands/DeleteCommand.java
new file mode 100644
index 00000000000..9007d5978af
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/commands/DeleteCommand.java
@@ -0,0 +1,111 @@
+package scrolls.elder.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_ROLE;
+
+import java.util.List;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.logic.Messages;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.PersonStore;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.Role;
+
+/**
+ * Deletes a person identified using its displayed index from the address book.
+ */
+public class DeleteCommand extends Command {
+
+ public static final String COMMAND_WORD_DELETE = "delete";
+ public static final String COMMAND_WORD_DEL = "del";
+ public static final String COMMAND_WORD_RM = "rm";
+ public static final String COMMAND_WORD_REMOVE = "remove";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD_DELETE
+ + ": Deletes the person identified by the index number used in the displayed person list and their type."
+ + "\nParameters: "
+ + "INDEX "
+ + PREFIX_ROLE + "{VOLUNTEER or BEFRIENDEE} "
+ + "\nExample: " + COMMAND_WORD_DELETE + " 1 " + PREFIX_ROLE + "{VOLUNTEER or BEFRIENDEE}\n"
+ + "Alternatively, you can also delete a person using the following commands as well.\n"
+ + "Example: " + COMMAND_WORD_DEL + " 1 " + PREFIX_ROLE + "{VOLUNTEER or BEFRIENDEE}\n"
+ + "Example: " + COMMAND_WORD_RM + " 1 " + PREFIX_ROLE + "{VOLUNTEER or BEFRIENDEE}\n"
+ + "Example: " + COMMAND_WORD_REMOVE + " 1" + PREFIX_ROLE + "{VOLUNTEER or BEFRIENDEE}\n";
+
+ public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
+ public static final String MESSAGE_DELETE_PERSON_ERROR = "Unable to delete contact: ";
+
+ public static final String MESSAGE_NO_ROLE = "Role must be specified when deleting a person.";
+
+ private final Index targetIndex;
+ private final Role role;
+
+ /**
+ * Creates a DeleteCommand to delete the person at the specified {@code targetIndex}.
+ */
+ public DeleteCommand(Index targetIndex, Role role) {
+ this.targetIndex = targetIndex;
+ this.role = role;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ PersonStore store = model.getMutableDatastore().getMutablePersonStore();
+
+ List lastShownList;
+ if (role.isVolunteer()) {
+ lastShownList = store.getFilteredVolunteerList();
+ } else {
+ lastShownList = store.getFilteredBefriendeeList();
+ }
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_DELETE_PERSON_ERROR + Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
+
+ //Check if the person to be deleted is paired with another person.
+ if (personToDelete.isPaired()) {
+ throw new CommandException(MESSAGE_DELETE_PERSON_ERROR + Messages.MESSAGE_CONTACT_PAIRED_BEFORE_DELETE);
+ }
+
+ //Check if the person has logs in Elder Scrolls
+ if (personToDelete.isLatestLogPresent()) {
+ throw new CommandException(MESSAGE_DELETE_PERSON_ERROR + Messages.MESSAGE_CONTACT_LOG_BEFORE_DELETE);
+ }
+
+ store.removePerson(personToDelete);
+ store.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ model.commitDatastore();
+ return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.formatPerson(personToDelete)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DeleteCommand)) {
+ return false;
+ }
+
+ DeleteCommand otherDeleteCommand = (DeleteCommand) other;
+ return targetIndex.equals(otherDeleteCommand.targetIndex);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("targetIndex", targetIndex)
+ .add("role", role)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/scrolls/elder/logic/commands/EditCommand.java
similarity index 56%
rename from src/main/java/seedu/address/logic/commands/EditCommand.java
rename to src/main/java/scrolls/elder/logic/commands/EditCommand.java
index 4b581c7331e..815b47993d2 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/scrolls/elder/logic/commands/EditCommand.java
@@ -1,12 +1,12 @@
-package seedu.address.logic.commands;
+package scrolls.elder.logic.commands;
import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_NAME;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_PHONE;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_ROLE;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_TAG;
import java.util.Collections;
import java.util.HashSet;
@@ -15,18 +15,23 @@
import java.util.Optional;
import java.util.Set;
-import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.CollectionUtil;
-import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.logic.Messages;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.commons.util.CollectionUtil;
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.logic.Messages;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.model.LogStore;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.PersonStore;
+import scrolls.elder.model.person.Address;
+import scrolls.elder.model.person.Email;
+import scrolls.elder.model.person.Name;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.PersonFactory;
+import scrolls.elder.model.person.Phone;
+import scrolls.elder.model.person.Role;
+import scrolls.elder.model.tag.Tag;
+
/**
* Edits the details of an existing person in the address book.
@@ -38,25 +43,25 @@ public class EditCommand extends Command {
public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified "
+ "by the index number used in the displayed person list. "
+ "Existing values will be overwritten by the input values.\n"
- + "Parameters: INDEX (must be a positive integer) "
- + "[" + PREFIX_NAME + "NAME] "
- + "[" + PREFIX_PHONE + "PHONE] "
- + "[" + PREFIX_EMAIL + "EMAIL] "
- + "[" + PREFIX_ADDRESS + "ADDRESS] "
+ + "Parameters: INDEX " + PREFIX_ROLE + "ROLE "
+ + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] "
+ + "[" + PREFIX_EMAIL + "EMAIL] " + "[" + PREFIX_ADDRESS + "ADDRESS] "
+ "[" + PREFIX_TAG + "TAG]...\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_ROLE + "volunteer "
+ PREFIX_PHONE + "91234567 "
+ PREFIX_EMAIL + "johndoe@example.com";
public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s";
public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
+ public static final String MESSAGE_NO_ROLE = "Role must be specified for indexing when editing a person.";
+ public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
private final Index index;
private final EditPersonDescriptor editPersonDescriptor;
/**
- * @param index of the person in the filtered person list to edit
+ * @param index of the person in the filtered person list to edit
* @param editPersonDescriptor details to edit the person with
*/
public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
@@ -67,10 +72,66 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor);
}
+ /**
+ * Creates and returns a {@code Person} with the details of {@code personToEdit}
+ * edited with {@code editPersonDescriptor}.
+ */
+ private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) {
+ assert personToEdit != null;
+
+ Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
+ Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
+ Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
+ Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
+ Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
+ Role role = editPersonDescriptor.getRole().orElse(personToEdit.getRole());
+ Optional pairedWithName = personToEdit.getPairedWithName();
+ Optional pairedWithId = personToEdit.getPairedWithId();
+ int timeServed = personToEdit.getTimeServed();
+ Optional latestLogId = personToEdit.getLatestLogId();
+
+ return PersonFactory.withIdFromParams(personToEdit.getPersonId(), updatedName, updatedPhone, updatedEmail,
+ updatedAddress, role, updatedTags, pairedWithName, pairedWithId, timeServed,
+ latestLogId);
+ }
+
+ private static Person createEditedPair(Person editedPerson, Person originalPair) {
+ requireNonNull(editedPerson);
+
+ // Will check before calling this function that the editedPerson is paired
+ Name updatedName = originalPair.getName();
+ Phone updatedPhone = originalPair.getPhone();
+ Email updatedEmail = originalPair.getEmail();
+ Address updatedAddress = originalPair.getAddress();
+ Set updatedTags = originalPair.getTags();
+ Role role = originalPair.getRole();
+ Optional updatedPairedWithName = Optional.of(editedPerson.getName());
+ Optional updatedPairedWithID = Optional.of(editedPerson.getPersonId());
+ int timeServed = originalPair.getTimeServed();
+ Optional updatedLatestLogId = originalPair.getLatestLogId();
+
+ return PersonFactory.withIdFromParams(editedPerson.getPersonId(), updatedName, updatedPhone, updatedEmail,
+ updatedAddress, role, updatedTags,
+ updatedPairedWithName, updatedPairedWithID, timeServed, updatedLatestLogId);
+ }
+
@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
+
+ PersonStore store = model.getMutableDatastore().getMutablePersonStore();
+ LogStore logStore = model.getMutableDatastore().getMutableLogStore();
+
+ if (editPersonDescriptor.getRole().isEmpty()) {
+ throw new CommandException(MESSAGE_NO_ROLE);
+ }
+
+ List lastShownList;
+ if (editPersonDescriptor.getRole().get().isVolunteer()) {
+ lastShownList = store.getFilteredVolunteerList();
+ } else {
+ lastShownList = store.getFilteredBefriendeeList();
+ }
if (index.getZeroBased() >= lastShownList.size()) {
throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
@@ -79,29 +140,28 @@ public CommandResult execute(Model model) throws CommandException {
Person personToEdit = lastShownList.get(index.getZeroBased());
Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
+ assert editedPerson.isSameId(personToEdit) : "Edited person should be the same person";
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)));
- }
-
- /**
- * Creates and returns a {@code Person} with the details of {@code personToEdit}
- * edited with {@code editPersonDescriptor}.
- */
- private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) {
- assert personToEdit != null;
+ // Check if there is another person in Elder Scrolls with the same edited name
+ if (store.hasPerson(editedPerson)) {
+ // The same name check is triggered because the name of the person did not change
+ if (!personToEdit.isSamePerson(editedPerson)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ }
+ }
- Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
- Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
- Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
- Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
- Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
+ if (editedPerson.isPaired()) {
+ Person pairedWith = store.getPersonFromID(editedPerson.getPairedWithId().get());
+ Person pairedWithUpdated = createEditedPair(editedPerson, pairedWith);
+ store.setPerson(pairedWith, pairedWithUpdated);
+ }
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ store.setPerson(personToEdit, editedPerson);
+ store.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ logStore.updateFilteredLogList(LogStore.PREDICATE_SHOW_ALL_LOGS);
+ logStore.updateFilteredLogListByPersonId(null);
+ model.commitDatastore();
+ return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.formatPerson(editedPerson)));
}
@Override
@@ -138,8 +198,10 @@ public static class EditPersonDescriptor {
private Email email;
private Address address;
private Set tags;
+ private Role role;
- public EditPersonDescriptor() {}
+ public EditPersonDescriptor() {
+ }
/**
* Copy constructor.
@@ -151,6 +213,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setEmail(toCopy.email);
setAddress(toCopy.address);
setTags(toCopy.tags);
+ setRole(toCopy.role);
}
/**
@@ -160,36 +223,45 @@ public boolean isAnyFieldEdited() {
return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
}
+ public Optional getName() {
+ return Optional.ofNullable(name);
+ }
+
public void setName(Name name) {
this.name = name;
}
- public Optional getName() {
- return Optional.ofNullable(name);
+ public Optional getPhone() {
+ return Optional.ofNullable(phone);
}
public void setPhone(Phone phone) {
this.phone = phone;
}
- public Optional getPhone() {
- return Optional.ofNullable(phone);
+ public Optional getEmail() {
+ return Optional.ofNullable(email);
}
public void setEmail(Email email) {
this.email = email;
}
- public Optional getEmail() {
- return Optional.ofNullable(email);
+ public Optional getAddress() {
+ return Optional.ofNullable(address);
}
public void setAddress(Address address) {
this.address = address;
}
- public Optional getAddress() {
- return Optional.ofNullable(address);
+ /**
+ * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code tags} is null.
+ */
+ public Optional> getTags() {
+ return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
}
/**
@@ -200,13 +272,12 @@ public void setTags(Set tags) {
this.tags = (tags != null) ? new HashSet<>(tags) : null;
}
- /**
- * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
- * Returns {@code Optional#empty()} if {@code tags} is null.
- */
- public Optional> getTags() {
- return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
+ public Optional getRole() {
+ return Optional.ofNullable(role);
+ }
+
+ public void setRole(Role role) {
+ this.role = role;
}
@Override
@@ -225,7 +296,8 @@ public boolean equals(Object other) {
&& Objects.equals(phone, otherEditPersonDescriptor.phone)
&& Objects.equals(email, otherEditPersonDescriptor.email)
&& Objects.equals(address, otherEditPersonDescriptor.address)
- && Objects.equals(tags, otherEditPersonDescriptor.tags);
+ && Objects.equals(tags, otherEditPersonDescriptor.tags)
+ && Objects.equals(role, otherEditPersonDescriptor.role);
}
@Override
@@ -236,6 +308,7 @@ public String toString() {
.add("email", email)
.add("address", address)
.add("tags", tags)
+ .add("role", role)
.toString();
}
}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/scrolls/elder/logic/commands/ExitCommand.java
similarity index 84%
rename from src/main/java/seedu/address/logic/commands/ExitCommand.java
rename to src/main/java/scrolls/elder/logic/commands/ExitCommand.java
index 3dd85a8ba90..9a015bb046d 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/scrolls/elder/logic/commands/ExitCommand.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.commands;
+package scrolls.elder.logic.commands;
-import seedu.address.model.Model;
+import scrolls.elder.model.Model;
/**
* Terminates the program.
diff --git a/src/main/java/scrolls/elder/logic/commands/FindCommand.java b/src/main/java/scrolls/elder/logic/commands/FindCommand.java
new file mode 100644
index 00000000000..1a129139989
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/commands/FindCommand.java
@@ -0,0 +1,157 @@
+package scrolls.elder.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.logic.Messages;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.PersonStore;
+import scrolls.elder.model.person.NameContainsKeywordsPredicate;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.TagListContainsTagsPredicate;
+
+
+/**
+ * Finds and lists all persons in address book whose name contains any of the argument keywords.
+ * Keyword matching is case-insensitive.
+ */
+public class FindCommand extends Command {
+
+ public static final String COMMAND_WORD_FIND = "find";
+ public static final String COMMAND_WORD_SEARCH = "search";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD_FIND + ": Finds all persons whose names contain any of "
+ + "the specified keywords (case-insensitive), displays them in the respective lists with index numbers.\n"
+ + "Parameters: [r/ROLE] [t/TAG] [--paired]/[--unpaired] KEYWORD [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD_FIND + " alex david roy --paired"
+ + "\nExample: " + COMMAND_WORD_FIND + " alex r/volunteer";
+
+ private final NameContainsKeywordsPredicate namePredicate;
+
+ private final TagListContainsTagsPredicate tagPredicate;
+ private final Boolean isSearchingVolunteer;
+ private final Boolean isSearchingBefriendee;
+ private final Boolean isSearchingNamePredicate;
+ private final Boolean isSearchingTagPredicate;
+ private final Boolean isSearchingPaired;
+ private final Boolean isSearchingUnpaired;
+
+
+ /**
+ * Creates a FindCommand to find the specified {@code NameContainsKeywordsPredicate}
+ */
+ public FindCommand(NameContainsKeywordsPredicate namePredicate, TagListContainsTagsPredicate tagPredicate,
+ Boolean isSearchingVolunteer, Boolean isSearchingBefriendee,
+ Boolean isSearchingPaired, Boolean isSearchingUnpaired) {
+ this.namePredicate = namePredicate;
+ this.isSearchingNamePredicate = !namePredicate.isEmpty();
+ this.tagPredicate = tagPredicate;
+ this.isSearchingTagPredicate = !tagPredicate.isEmpty();
+ this.isSearchingBefriendee = isSearchingBefriendee;
+ this.isSearchingVolunteer = isSearchingVolunteer;
+
+ this.isSearchingPaired = isSearchingPaired;
+ this.isSearchingUnpaired = isSearchingUnpaired;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ PersonStore store = model.getMutableDatastore().getMutablePersonStore();
+ Predicate combinedPredicate = getCombinedPredicate();
+
+ assert (isSearchingVolunteer || isSearchingBefriendee)
+ : "At least one or both isSearchingVolunteer and isSearchingBefriendee should be true.";
+
+ if (isSearchingVolunteer && isSearchingBefriendee) {
+ return searchAllPersons(store, combinedPredicate);
+
+ } else if (isSearchingVolunteer) {
+ return searchVolunteerOnly(store, combinedPredicate);
+
+ } else {
+ return searchBefriendeeOnly(store, combinedPredicate);
+ }
+
+ }
+
+ private Predicate getCombinedPredicate() {
+ List> predicates = new ArrayList<>();
+
+ if (isSearchingPaired && !isSearchingUnpaired) {
+ predicates.add(Person::isPaired);
+ } else if (isSearchingUnpaired && !isSearchingPaired) {
+ predicates.add(Person -> !Person.isPaired());
+ }
+
+ if (isSearchingNamePredicate) {
+ predicates.add(namePredicate);
+ }
+ if (isSearchingTagPredicate) {
+ predicates.add(tagPredicate);
+ }
+
+ return predicates.stream().reduce(Predicate::and).orElse(person -> true);
+ }
+
+ private CommandResult searchAllPersons(PersonStore store, Predicate combinedPredicate) {
+ store.updateFilteredPersonList(combinedPredicate);
+
+ return new CommandResult(
+ String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, store.getFilteredPersonList().size()));
+ }
+
+ private CommandResult searchVolunteerOnly(PersonStore store, Predicate combinedPredicate) {
+ store.updateFilteredVolunteerList(combinedPredicate);
+
+ return new CommandResult(
+ String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW_WITH_ROLE,
+ store.getFilteredVolunteerList().size(),
+ "volunteer"));
+ }
+
+ private CommandResult searchBefriendeeOnly(PersonStore store, Predicate combinedPredicate) {
+ store.updateFilteredBefriendeeList(combinedPredicate);
+
+ return new CommandResult(
+ String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW_WITH_ROLE,
+ store.getFilteredBefriendeeList().size(),
+ "befriendee"));
+ }
+
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof FindCommand)) {
+ return false;
+ }
+
+ FindCommand otherFindCommand = (FindCommand) other;
+ return namePredicate.equals(otherFindCommand.namePredicate)
+ && tagPredicate.equals(otherFindCommand.tagPredicate)
+ && isSearchingVolunteer.equals(otherFindCommand.isSearchingVolunteer)
+ && isSearchingBefriendee.equals(otherFindCommand.isSearchingBefriendee);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("namePredicate", namePredicate)
+ .add("tagPredicate", tagPredicate)
+ .add("isSearchingVolunteer", isSearchingVolunteer)
+ .add("isSearchingBefriendee", isSearchingBefriendee)
+ .add("isSearchingPaired", isSearchingPaired)
+ .add("isSearchingUnpaired", isSearchingUnpaired)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/scrolls/elder/logic/commands/HelpCommand.java
similarity index 88%
rename from src/main/java/seedu/address/logic/commands/HelpCommand.java
rename to src/main/java/scrolls/elder/logic/commands/HelpCommand.java
index bf824f91bd0..8f5d3048ce1 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/scrolls/elder/logic/commands/HelpCommand.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.commands;
+package scrolls.elder.logic.commands;
-import seedu.address.model.Model;
+import scrolls.elder.model.Model;
/**
* Format full help instructions for every command for display.
diff --git a/src/main/java/scrolls/elder/logic/commands/ListCommand.java b/src/main/java/scrolls/elder/logic/commands/ListCommand.java
new file mode 100644
index 00000000000..84e66677848
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/commands/ListCommand.java
@@ -0,0 +1,33 @@
+package scrolls.elder.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import scrolls.elder.model.LogStore;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.ReadOnlyLogStore;
+import scrolls.elder.model.ReadOnlyPersonStore;
+
+/**
+ * Lists all persons in the address book to the user.
+ */
+public class ListCommand extends Command {
+
+ public static final String COMMAND_WORD = "list";
+
+ public static final String MESSAGE_SUCCESS = "Listed all persons and logs!";
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ ReadOnlyLogStore logStore = model.getDatastore().getLogStore();
+ ReadOnlyPersonStore personStore = model.getDatastore().getPersonStore();
+
+ personStore.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ logStore.updateFilteredLogList(LogStore.PREDICATE_SHOW_ALL_LOGS);
+ logStore.updateFilteredLogListByPersonId(null);
+
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/scrolls/elder/logic/commands/LogAddCommand.java b/src/main/java/scrolls/elder/logic/commands/LogAddCommand.java
new file mode 100644
index 00000000000..89e30066059
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/commands/LogAddCommand.java
@@ -0,0 +1,199 @@
+package scrolls.elder.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_DURATION;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_REMARKS;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_START;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_TITLE;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.logic.Messages;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.model.LogStore;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.PersonStore;
+import scrolls.elder.model.log.Log;
+import scrolls.elder.model.person.Address;
+import scrolls.elder.model.person.Email;
+import scrolls.elder.model.person.Name;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.PersonFactory;
+import scrolls.elder.model.person.Phone;
+import scrolls.elder.model.person.Role;
+import scrolls.elder.model.tag.Tag;
+
+/**
+ * Adds a person to the address book.
+ */
+public class LogAddCommand extends Command {
+
+ public static final String COMMAND_WORD = "logadd";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a log to the address book. "
+ + "Parameters: BEFRIENDEE_INDEX VOLUNTEER_INDEX "
+ + PREFIX_TITLE + "TITLE "
+ + PREFIX_START + "START_DATE (yyyy-MM-dd) "
+ + PREFIX_DURATION + "DURATION (in hours) "
+ + PREFIX_REMARKS + "REMARKS "
+ + "\n"
+ + "Example: " + COMMAND_WORD + " 1 2 "
+ + PREFIX_TITLE + "Icebreaker session "
+ + PREFIX_START + "2021-03-01 "
+ + PREFIX_DURATION + "2 "
+ + PREFIX_REMARKS + "was a good session ";
+
+ public static final String MESSAGE_SUCCESS = "New log added!";
+ public static final String MESSAGE_NEGATIVE_DURATION = "Duration must be positive.";
+ public static final String MESSAGE_PERSONS_NOT_PAIRED = "The volunteer and befriendee are not paired.";
+
+ /**
+ * Contains data for the log to be added.
+ * Does not contain the final log ID.
+ */
+ private final String title;
+ private final Index volunteerIndex;
+ private final Index befriendeeIndex;
+ private final int duration;
+ private final Date startDate;
+ private final String remarks;
+
+ /**
+ * Creates an LogAddCommand to add the specified {@code Log}
+ */
+ public LogAddCommand(String title, Index befriendeeIndex, Index volunteerIndex, int duration,
+ Date startDate, String remarks) {
+ this.title = title;
+ this.volunteerIndex = volunteerIndex;
+ this.befriendeeIndex = befriendeeIndex;
+ this.duration = duration;
+ this.startDate = startDate;
+ this.remarks = remarks;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (duration <= 0) {
+ throw new CommandException(MESSAGE_NEGATIVE_DURATION);
+ }
+
+ PersonStore personStore = model.getMutableDatastore().getMutablePersonStore();
+ LogStore logStore = model.getMutableDatastore().getMutableLogStore();
+
+ List lastShownBList = personStore.getFilteredBefriendeeList();
+ List lastShownVList = personStore.getFilteredVolunteerList();
+
+ if (befriendeeIndex.getZeroBased() >= lastShownBList.size()
+ || volunteerIndex.getZeroBased() >= lastShownVList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person befriendee = lastShownBList.get(befriendeeIndex.getZeroBased());
+ Person volunteer = lastShownVList.get(volunteerIndex.getZeroBased());
+
+ if (!befriendee.isPairedWith(volunteer) && !volunteer.isPairedWith(befriendee)) {
+ throw new CommandException(MESSAGE_PERSONS_NOT_PAIRED);
+ }
+
+ Log toAdd =
+ new Log(model.getDatastore(), title, volunteer.getPersonId(), befriendee.getPersonId(),
+ duration, startDate, remarks);
+
+ Integer logId = logStore.addLog(toAdd);
+
+ Integer latestLogIdBefriendee = getLatestLogId(befriendee, toAdd, logStore, logId);
+ Integer latestLogIdVolunteer = getLatestLogId(volunteer, toAdd, logStore, logId);
+
+ // create updated persons
+ Person updatedBefriendee = createUpdatedPerson(befriendee, duration, latestLogIdBefriendee);
+ Person updatedVolunteer = createUpdatedPerson(volunteer, duration, latestLogIdVolunteer);
+
+ personStore.setPerson(befriendee, updatedBefriendee);
+ personStore.setPerson(volunteer, updatedVolunteer);
+ personStore.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ logStore.updateFilteredLogList(LogStore.PREDICATE_SHOW_ALL_LOGS);
+ logStore.updateFilteredLogListByPersonId(null);
+ model.commitDatastore();
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+
+ /**
+ * Create and return a {@code Person} with the details of {@code personToUpdate}
+ * edited with the updated timeServed and latestLogId.
+ */
+ private Person createUpdatedPerson(Person personToUpdate, int duration, Integer logId) {
+ assert personToUpdate != null;
+
+ Name name = personToUpdate.getName();
+ Phone phone = personToUpdate.getPhone();
+ Email email = personToUpdate.getEmail();
+ Address address = personToUpdate.getAddress();
+ Set tags = personToUpdate.getTags();
+ Role role = personToUpdate.getRole();
+ Optional pairedWithName = personToUpdate.getPairedWithName();
+ Optional pairedWithId = personToUpdate.getPairedWithId();
+ int updatedTimeServed = personToUpdate.getTimeServed() + duration;
+ Optional latestLogId = Optional.of(logId);
+
+ return PersonFactory.withIdFromParams(personToUpdate.getPersonId(), name, phone, email, address, role,
+ tags, pairedWithName, pairedWithId, updatedTimeServed, latestLogId);
+ }
+
+ /**
+ * Returns the ID of the latest log for the person.
+ */
+ private Integer getLatestLogId(Person person, Log toAdd, LogStore logStore, Integer toAddId) {
+ Date toAddDate = toAdd.getStartDate();
+
+ if (person.isLatestLogPresent()) {
+ Log currentLatest = logStore.getLogById(person.getLatestLogId().get());
+ Date latestLogDate = currentLatest.getStartDate();
+
+ if (toAddDate.before(latestLogDate)) {
+ return currentLatest.getLogId();
+ } else {
+ return toAddId;
+ }
+ }
+
+ return toAddId;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof LogAddCommand)) {
+ return false;
+ }
+
+ LogAddCommand otherAddCommand = (LogAddCommand) other;
+ return otherAddCommand.title.equals(title)
+ && otherAddCommand.volunteerIndex.equals(volunteerIndex)
+ && otherAddCommand.befriendeeIndex.equals(befriendeeIndex)
+ && otherAddCommand.duration == duration
+ && otherAddCommand.startDate.equals(startDate)
+ && otherAddCommand.remarks.equals(remarks);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("title", title)
+ .add("volunteerIndex", volunteerIndex)
+ .add("befriendeeIndex", befriendeeIndex)
+ .add("duration", duration)
+ .add("startDate", startDate)
+ .add("remarks", remarks)
+ .toString();
+ }
+}
diff --git a/src/main/java/scrolls/elder/logic/commands/LogDeleteCommand.java b/src/main/java/scrolls/elder/logic/commands/LogDeleteCommand.java
new file mode 100644
index 00000000000..d23b4a61814
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/commands/LogDeleteCommand.java
@@ -0,0 +1,173 @@
+package scrolls.elder.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.logic.Messages;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.model.LogStore;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.PersonStore;
+import scrolls.elder.model.log.Log;
+import scrolls.elder.model.person.Address;
+import scrolls.elder.model.person.Email;
+import scrolls.elder.model.person.Name;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.PersonFactory;
+import scrolls.elder.model.person.Phone;
+import scrolls.elder.model.person.Role;
+import scrolls.elder.model.tag.Tag;
+
+
+/**
+ * Deletes the log identified using its displayed index from the address book.
+ */
+public class LogDeleteCommand extends Command {
+
+ public static final String COMMAND_WORD_LOG_DELETE = "logdelete";
+ public static final String COMMAND_WORD_LOG_DEL = "logdel";
+ public static final String COMMAND_WORD_LOG_RM = "logrm";
+ public static final String COMMAND_WORD_LOG_REMOVE = "logremove";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD_LOG_DELETE
+ + ": Deletes the log identified by the index number used in the displayed log list.\n"
+ + "Parameters: INDEX \n"
+ + "Example: " + COMMAND_WORD_LOG_DELETE + " 1\n"
+ + "Alternatively, you can also delete a log using the following commands as well.\n"
+ + "Example: " + COMMAND_WORD_LOG_DEL + " 1\n"
+ + "Example: " + COMMAND_WORD_LOG_RM + " 1\n"
+ + "Example: " + COMMAND_WORD_LOG_REMOVE + " 1";
+
+ public static final String MESSAGE_DELETE_LOG_SUCCESS = "Deleted Log: %1$s";
+ public static final String MESSAGE_DELETE_LOG_ERROR = "Unable to delete log: ";
+ private final Index targetIndex;
+
+ /**
+ * Creates a LogDeleteCommand to delete the log at the specified {@code targetIndex}.
+ */
+ public LogDeleteCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ LogStore logStore = model.getMutableDatastore().getMutableLogStore();
+ PersonStore personStore = model.getMutableDatastore().getMutablePersonStore();
+
+ List lastShownList = logStore.getLogList();
+ List lastShownPList = personStore.getPersonList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_DELETE_LOG_ERROR + Messages.MESSAGE_INVALID_LOG_DISPLAYED_INDEX);
+ }
+
+ Log logToDelete = lastShownList.get(targetIndex.getZeroBased());
+ int logIdToDelete = logToDelete.getLogId();
+
+ int durationToDelete = logToDelete.getDuration();
+
+ // Retrieve the volunteer and befriendee from the log to be deleted
+ Person volunteer = personStore.getPersonFromID(logToDelete.getVolunteerId());
+ Person befriendee = personStore.getPersonFromID(logToDelete.getBefriendeeId());
+
+ // Get the latest log id of the volunteer and befriendee after deletion
+ Optional latestLogIdBefriendee =
+ getLatestLogId(befriendee, logStore, logToDelete.getLogId());
+ Optional latestLogIdVolunteer =
+ getLatestLogId(volunteer, logStore, logToDelete.getLogId());
+
+ // Update the volunteer and befriendee with the new timeServed and latestLogId
+ Person updatedVolunteer = createUpdatedPerson(volunteer, durationToDelete, latestLogIdVolunteer);
+ Person updatedBefriendee = createUpdatedPerson(befriendee, durationToDelete, latestLogIdBefriendee);
+ personStore.setPerson(volunteer, updatedVolunteer);
+ personStore.setPerson(befriendee, updatedBefriendee);
+ personStore.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+
+ logStore.removeLog(logIdToDelete);
+ logStore.updateFilteredLogList(LogStore.PREDICATE_SHOW_ALL_LOGS);
+ logStore.updateFilteredLogListByPersonId(null);
+ model.commitDatastore();
+ return new CommandResult(String.format(MESSAGE_DELETE_LOG_SUCCESS, Messages.formatLog(logToDelete)));
+ }
+
+ /**
+ * Create and return a {@code Person} with the details of {@code personToUpdate}
+ * edited with the updated timeServed and latestLogId.
+ */
+ private Person createUpdatedPerson(Person personToUpdate, int duration, Optional logId) {
+ assert personToUpdate != null;
+
+ Name name = personToUpdate.getName();
+ Phone phone = personToUpdate.getPhone();
+ Email email = personToUpdate.getEmail();
+ Address address = personToUpdate.getAddress();
+ Set tags = personToUpdate.getTags();
+ Role role = personToUpdate.getRole();
+ Optional pairedWithName = personToUpdate.getPairedWithName();
+ Optional pairedWithId = personToUpdate.getPairedWithId();
+ int updatedTimeServed = personToUpdate.getTimeServed() - duration;
+ Optional latestLogId = logId;
+
+ return PersonFactory.withIdFromParams(personToUpdate.getPersonId(), name, phone, email, address, role,
+ tags, pairedWithName, pairedWithId, updatedTimeServed, latestLogId);
+ }
+
+ /**
+ * Returns the latest log id of the person after the log is deleted.
+ */
+ private Optional getLatestLogId(Person person, LogStore logStore, Integer toDeleteId) {
+
+ if (person.isLatestLogPresent()) {
+ Log currentLatest = logStore.getLogById(person.getLatestLogId().get());
+
+ if (toDeleteId != currentLatest.getLogId()) {
+ return Optional.of(currentLatest.getLogId());
+ }
+ // the log deleted is displayed as the current latest log
+ // the next latest log is then retrieved
+ Log newCurrentLatest = logStore.getLogList().stream()
+ .filter(log -> log.getBefriendeeId() == person.getPersonId()
+ || log.getVolunteerId() == person.getPersonId())
+ .filter(log -> log.getLogId() != toDeleteId)
+ .max(Comparator.comparing(Log::getStartDate)).orElse(null);
+
+ // newCurrentLatest is returned as latest log if present
+ if (newCurrentLatest != null) {
+ return Optional.of(newCurrentLatest.getLogId());
+ } else {
+ return Optional.empty();
+ }
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ //instanceof handles nulls
+ if (!(other instanceof LogDeleteCommand)) {
+ return false;
+ }
+
+ LogDeleteCommand otherLogDeleteCommand = (LogDeleteCommand) other;
+ return targetIndex.equals(otherLogDeleteCommand.targetIndex);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("targetIndex", targetIndex)
+ .toString();
+ }
+}
diff --git a/src/main/java/scrolls/elder/logic/commands/LogEditCommand.java b/src/main/java/scrolls/elder/logic/commands/LogEditCommand.java
new file mode 100644
index 00000000000..807b26c8d3c
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/commands/LogEditCommand.java
@@ -0,0 +1,341 @@
+package scrolls.elder.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_DURATION;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_REMARKS;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_START;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_TITLE;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.commons.util.CollectionUtil;
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.logic.Messages;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.model.LogStore;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.PersonStore;
+import scrolls.elder.model.log.Log;
+import scrolls.elder.model.person.Address;
+import scrolls.elder.model.person.Email;
+import scrolls.elder.model.person.Name;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.PersonFactory;
+import scrolls.elder.model.person.Phone;
+import scrolls.elder.model.person.Role;
+import scrolls.elder.model.tag.Tag;
+
+
+/**
+ * Edits the details of an existing log in the address book.
+ */
+public class LogEditCommand extends Command {
+
+ public static final String COMMAND_WORD = "logedit";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the log specified "
+ + "by the log index number used in the displayed log list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: INDEX "
+ + "[" + PREFIX_TITLE + "TITLE] "
+ + "[" + PREFIX_START + "START_DATE (yyyy-MM-dd)] "
+ + "[" + PREFIX_DURATION + "DURATION (in hours)] "
+ + "[" + PREFIX_REMARKS + "REMARKS]\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_TITLE + "Icebreaker session "
+ + PREFIX_START + "2021-03-01 "
+ + PREFIX_DURATION + "2 "
+ + PREFIX_REMARKS + "was a good session ";
+
+ public static final String MESSAGE_EDIT_LOG_SUCCESS = "Edited Log successfully: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_NEGATIVE_DURATION = "Duration must be positive.";
+
+ private final Index index;
+ private final EditLogDescriptor editLogDescriptor;
+
+ /**
+ * @param index of the log in the filtered log list to edit
+ * @param editLogDescriptor details to edit the log with
+ */
+ public LogEditCommand(Index index, EditLogDescriptor editLogDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editLogDescriptor);
+
+ this.index = index;
+ this.editLogDescriptor = new EditLogDescriptor(editLogDescriptor);
+ }
+
+ /**
+ * Creates and returns a {@code Log} with the details of {@code logToEdit}
+ * edited with {@code editLogDescriptor}.
+ */
+ private static Log createEditedLog(Log logToEdit, EditLogDescriptor editLogDescriptor) {
+ assert logToEdit != null;
+
+ int volunteerId;
+ int befriendeeId;
+
+ if (editLogDescriptor.getVolunteerIndex().isPresent()) {
+ volunteerId = editLogDescriptor.getVolunteerIndex().get().getZeroBased();
+ } else {
+ volunteerId = logToEdit.getVolunteerId();
+ }
+
+ if (editLogDescriptor.getBefriendeeIndex().isPresent()) {
+ befriendeeId = editLogDescriptor.getBefriendeeIndex().get().getZeroBased();
+ } else {
+ befriendeeId = logToEdit.getBefriendeeId();
+ }
+
+ String title = editLogDescriptor.getTitle().orElse(logToEdit.getLogTitle());
+ Integer duration = editLogDescriptor.getDuration().orElse(logToEdit.getDuration());
+ Date startDate = editLogDescriptor.getStartDate().orElse(logToEdit.getStartDate());
+ String remarks = editLogDescriptor.getRemarks().orElse(logToEdit.getRemarks());
+
+ return new Log(logToEdit.getLogId(), title, volunteerId, befriendeeId, duration, startDate, remarks);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ PersonStore personStore = model.getMutableDatastore().getMutablePersonStore();
+ LogStore store = model.getMutableDatastore().getMutableLogStore();
+
+ List lastShownList = store.getLogList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_LOG_DISPLAYED_INDEX);
+ }
+
+ Log logToEdit = lastShownList.get(index.getZeroBased());
+ Log editedLog = createEditedLog(logToEdit, editLogDescriptor);
+
+ if (!logToEdit.areValidIds(model.getDatastore(), editedLog.getVolunteerId(), editedLog.getBefriendeeId())) {
+ throw new CommandException(Log.MESSAGE_INVALID_ID);
+ }
+
+ if (editedLog.getDuration() <= 0) {
+ throw new CommandException(MESSAGE_NEGATIVE_DURATION);
+ }
+
+ int durationDiff = editedLog.getDuration() - logToEdit.getDuration();
+
+ // Retrieve the volunteer and befriendee from the log to be edited
+ Person befriendee = personStore.getPersonFromID(logToEdit.getBefriendeeId());
+ Person volunteer = personStore.getPersonFromID(logToEdit.getVolunteerId());
+
+ // Get the latest log id of the volunteer and befriendee after editing the log
+ Integer latestLogIdBefriendee = getLatestLogId(befriendee, editedLog, store, editedLog.getLogId());
+ Integer latestLogIdVolunteer = getLatestLogId(volunteer, editedLog, store, editedLog.getLogId());
+
+ // Update the volunteer and befriendee with the new timeServed and latestLogId
+ Person updatedBefriendee = createUpdatedPerson(befriendee, durationDiff, latestLogIdBefriendee);
+ Person updatedVolunteer = createUpdatedPerson(volunteer, durationDiff, latestLogIdVolunteer);
+ personStore.setPerson(befriendee, updatedBefriendee);
+ personStore.setPerson(volunteer, updatedVolunteer);
+ store.setLog(editedLog);
+
+ personStore.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ store.updateFilteredLogList(LogStore.PREDICATE_SHOW_ALL_LOGS);
+ store.updateFilteredLogListByPersonId(null);
+
+ model.commitDatastore();
+ return new CommandResult(String.format(MESSAGE_EDIT_LOG_SUCCESS, Messages.formatLog(editedLog)));
+ }
+
+ /**
+ * Create and return a {@code Person} with the details of {@code personToUpdate}
+ * edited with the updated timeServed and latestLogId.
+ */
+ private Person createUpdatedPerson(Person personToUpdate, int duration, Integer logId) {
+ assert personToUpdate != null;
+
+ Name name = personToUpdate.getName();
+ Phone phone = personToUpdate.getPhone();
+ Email email = personToUpdate.getEmail();
+ Address address = personToUpdate.getAddress();
+ Set tags = personToUpdate.getTags();
+ Role role = personToUpdate.getRole();
+ Optional pairedWithName = personToUpdate.getPairedWithName();
+ Optional pairedWithId = personToUpdate.getPairedWithId();
+ int updatedTimeServed = personToUpdate.getTimeServed() + duration;
+ Optional latestLogId = Optional.of(logId);
+
+ return PersonFactory.withIdFromParams(personToUpdate.getPersonId(), name, phone, email, address, role,
+ tags, pairedWithName, pairedWithId, updatedTimeServed, latestLogId);
+ }
+
+ /**
+ * Returns the latest log id of the person after editing the log.
+ */
+ private Integer getLatestLogId(Person person, Log editedLog, LogStore logStore, Integer toAddId) {
+ Date editedDate = editedLog.getStartDate();
+
+ if (person.isLatestLogPresent()) {
+ Log currentLatest = logStore.getLogById(person.getLatestLogId().get());
+ Date latestLogDate = currentLatest.getStartDate();
+ Integer currentLatestLogId = currentLatest.getLogId();
+
+ if (editedDate.before(latestLogDate)) {
+ return currentLatestLogId;
+ } else if (editedDate.equals(latestLogDate)) {
+ // If current latest was added chronologically later than the editedLog, return current latest
+ if (currentLatestLogId > toAddId) {
+ return currentLatestLogId;
+ } else {
+ return toAddId;
+ }
+ } else {
+ return toAddId;
+ }
+ }
+
+ return toAddId;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof LogEditCommand)) {
+ return false;
+ }
+
+ LogEditCommand otherLogEditCommand = (LogEditCommand) other;
+ return index.equals(otherLogEditCommand.index)
+ && editLogDescriptor.equals(otherLogEditCommand.editLogDescriptor);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("index", index)
+ .add("editLogDescriptor", editLogDescriptor)
+ .toString();
+ }
+
+ /**
+ * Stores the details to edit the log with. Each non-empty field value will replace the
+ * corresponding field value of the log.
+ */
+ public static class EditLogDescriptor {
+ private String title;
+ private Index volunteerIndex;
+ private Index befriendeeIndex;
+ private Integer duration;
+ private Date startDate;
+ private String remarks;
+
+ public EditLogDescriptor() {
+ }
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditLogDescriptor(EditLogDescriptor toCopy) {
+ setTitle(toCopy.title);
+ setVolunteerIndex(toCopy.volunteerIndex);
+ setBefriendeeIndex(toCopy.befriendeeIndex);
+ setDuration(toCopy.duration);
+ setStartDate(toCopy.startDate);
+ setRemarks(toCopy.remarks);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(title, volunteerIndex, befriendeeIndex, duration, startDate, remarks);
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public Optional getTitle() {
+ return Optional.ofNullable(title);
+ }
+
+ public void setVolunteerIndex(Index volunteerIndex) {
+ this.volunteerIndex = volunteerIndex;
+ }
+
+ public Optional getVolunteerIndex() {
+ return Optional.ofNullable(volunteerIndex);
+ }
+
+ public void setBefriendeeIndex(Index befriendeeIndex) {
+ this.befriendeeIndex = befriendeeIndex;
+ }
+
+ public Optional getBefriendeeIndex() {
+ return Optional.ofNullable(befriendeeIndex);
+ }
+
+ public void setDuration(Integer duration) {
+ this.duration = duration;
+ }
+
+ public Optional getDuration() {
+ return Optional.ofNullable(duration);
+ }
+
+ public void setStartDate(Date startDate) {
+ this.startDate = startDate;
+ }
+
+ public Optional getStartDate() {
+ return Optional.ofNullable(startDate);
+ }
+
+ public void setRemarks(String remarks) {
+ this.remarks = remarks;
+ }
+
+ public Optional getRemarks() {
+ return Optional.ofNullable(remarks);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof EditLogDescriptor)) {
+ return false;
+ }
+
+ EditLogDescriptor otherEditLogDescriptor = (EditLogDescriptor) other;
+ return Objects.equals(title, otherEditLogDescriptor.title)
+ && Objects.equals(volunteerIndex, otherEditLogDescriptor.volunteerIndex)
+ && Objects.equals(befriendeeIndex, otherEditLogDescriptor.befriendeeIndex)
+ && Objects.equals(duration, otherEditLogDescriptor.duration)
+ && Objects.equals(startDate, otherEditLogDescriptor.startDate)
+ && Objects.equals(remarks, otherEditLogDescriptor.remarks);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("title", title)
+ .add("volunteerIndex", volunteerIndex)
+ .add("befriendeeIndex", befriendeeIndex)
+ .add("duration", duration)
+ .add("startDate", startDate)
+ .add("remarks", remarks)
+ .toString();
+ }
+ }
+}
diff --git a/src/main/java/scrolls/elder/logic/commands/LogFindCommand.java b/src/main/java/scrolls/elder/logic/commands/LogFindCommand.java
new file mode 100644
index 00000000000..d9633f19d92
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/commands/LogFindCommand.java
@@ -0,0 +1,106 @@
+package scrolls.elder.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_ROLE;
+
+import java.util.List;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.logic.Messages;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.ReadOnlyLogStore;
+import scrolls.elder.model.ReadOnlyPersonStore;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.Role;
+
+/**
+ * Finds an existing logs related to specified Person in the address book.
+ */
+public class LogFindCommand extends Command {
+
+ public static final String COMMAND_WORD_LOGFIND = "logfind";
+ public static final String COMMAND_WORD_FINDLOG = "findlog";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD_LOGFIND + ": Finds logs by person. "
+ + "Parameters: INDEX "
+ + PREFIX_ROLE + "ROLE "
+ + "\n"
+ + "Example: " + COMMAND_WORD_LOGFIND + " 1 "
+ + PREFIX_ROLE + "volunteer";
+
+ // Sample Format: logfind 1 r/volunteer
+
+ public static final String MESSAGE_SUCCESS = "logs found!";
+
+ public static final String MESSAGE_FINDLOG_PERSON_ERROR = "Unable to find logs: ";
+
+ private final Role role;
+ private final Index targetIndex;
+
+ /**
+ * @param targetIndex of the specified person in the filtered list
+ * @param role of the person to differentiate between volunteer and befriendee
+ */
+ public LogFindCommand(Index targetIndex, Role role) {
+ this.targetIndex = targetIndex;
+ this.role = role;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ ReadOnlyPersonStore personStore = model.getDatastore().getPersonStore();
+ ReadOnlyLogStore logStore = model.getDatastore().getLogStore();
+
+ List lastShownList;
+ if (role.isVolunteer()) {
+ lastShownList = personStore.getFilteredVolunteerList();
+ } else {
+ lastShownList = personStore.getFilteredBefriendeeList();
+ }
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_FINDLOG_PERSON_ERROR + Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToFindLog = lastShownList.get(targetIndex.getZeroBased());
+ int personID = personToFindLog.getPersonId();
+ logStore.updateFilteredLogListByPersonId(personID);
+
+ int logsFound = logStore.getFilteredLogList().size();
+
+ return new CommandResult(logsFound + " " + MESSAGE_SUCCESS);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls and different types
+ if (!(other instanceof LogFindCommand)) {
+ return false;
+ }
+
+ // state check
+ LogFindCommand otherFindCommand = (LogFindCommand) other;
+ return otherFindCommand.role.equals(role)
+ && otherFindCommand.targetIndex.equals(targetIndex);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("role", role)
+ .add("listIndex", targetIndex)
+ .toString();
+ }
+
+
+
+}
diff --git a/src/main/java/scrolls/elder/logic/commands/PairCommand.java b/src/main/java/scrolls/elder/logic/commands/PairCommand.java
new file mode 100644
index 00000000000..cd3d8823dfc
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/commands/PairCommand.java
@@ -0,0 +1,156 @@
+package scrolls.elder.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.logic.Messages;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.PersonStore;
+import scrolls.elder.model.person.Address;
+import scrolls.elder.model.person.Email;
+import scrolls.elder.model.person.Name;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.PersonFactory;
+import scrolls.elder.model.person.Phone;
+import scrolls.elder.model.person.Role;
+import scrolls.elder.model.tag.Tag;
+
+/**
+ * Pairs a volunteer and a befriendee in the address book.
+ */
+public class PairCommand extends Command {
+
+ public static final String COMMAND_WORD = "pair";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Pairs a volunteer and a befriendee specified "
+ + "by their index numbers used in the displayed person list.\n"
+ + "Parameters: INDEX1 INDEX2 (both must be a positive integers)\n"
+ + "Example: " + COMMAND_WORD + " 1 2";
+
+ public static final String MESSAGE_PAIR_SUCCESS = "Paired: %1$s and %2$s";
+
+ public static final String MESSAGE_DUPLICATE_PERSON = "Cannot pair the same person with themselves.";
+ public static final String MESSAGE_DIFFERENT_PERSON_TYPE =
+ "Pairing can only be done between a volunteer and a befriendee.";
+
+ public static final String MESSAGE_ALREADY_PAIRED =
+ "One or both of the persons are already paired, unpair and try again.";
+
+ private final Index index1;
+
+ private final Index index2;
+
+ /**
+ * @param index1 of the first person to be paired
+ * @param index2 of the second person to be paired with
+ */
+ public PairCommand(Index index1, Index index2) {
+ requireNonNull(index1);
+ requireNonNull(index2);
+
+ this.index1 = index1;
+ this.index2 = index2;
+ }
+
+ /**
+ * Creates and returns a {@code Person} with the details of {@code personToEdit}
+ * edited with {@code editPairedPersonDescriptor}.
+ */
+ private static Person createEditedPairedPerson(Person personToEdit, Optional updatedPairName,
+ Optional updatedPairID) {
+ requireNonNull(personToEdit);
+
+ int personId = personToEdit.getPersonId();
+ Name updatedName = personToEdit.getName();
+ Phone updatedPhone = personToEdit.getPhone();
+ Email updatedEmail = personToEdit.getEmail();
+ Address updatedAddress = personToEdit.getAddress();
+ Set updatedTags = personToEdit.getTags();
+ Role role = personToEdit.getRole();
+ int updatedTimeServed = personToEdit.getTimeServed();
+ Optional updatedLatestLogId = personToEdit.getLatestLogId();
+
+ return PersonFactory.withIdFromParams(personId, updatedName, updatedPhone, updatedEmail, updatedAddress, role,
+ updatedTags, updatedPairName, updatedPairID, updatedTimeServed,
+ updatedLatestLogId);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ PersonStore store = model.getMutableDatastore().getMutablePersonStore();
+
+ List lastShownBList = store.getFilteredBefriendeeList();
+ List lastShownVList = store.getFilteredVolunteerList();
+
+ if (index1.getZeroBased() >= lastShownBList.size() || index2.getZeroBased() >= lastShownVList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToPair1 = lastShownBList.get(index1.getZeroBased());
+ Person personToPair2 = lastShownVList.get(index2.getZeroBased());
+
+ // Check if the two persons are the same person
+ if (personToPair1.isSamePerson(personToPair2)
+ && store.hasPerson(personToPair1)
+ && store.hasPerson(personToPair2)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ }
+
+ // Check if the two persons are of different types (eg. volunteer and befriendee)
+ if (personToPair1.getRole().equals(personToPair2.getRole())) {
+ throw new CommandException(MESSAGE_DIFFERENT_PERSON_TYPE);
+ }
+
+ // Check if any of the persons are already paired
+ if (personToPair1.isPaired() || personToPair2.isPaired()) {
+ throw new CommandException(MESSAGE_ALREADY_PAIRED);
+ }
+
+ Person newPerson1 = createEditedPairedPerson(personToPair1, Optional.of(personToPair2.getName()),
+ Optional.of(personToPair2.getPersonId()));
+ Person newPerson2 = createEditedPairedPerson(personToPair2, Optional.of(personToPair1.getName()),
+ Optional.of(personToPair1.getPersonId()));
+
+ store.setPerson(personToPair1, newPerson1);
+ store.setPerson(personToPair2, newPerson2);
+ store.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ model.commitDatastore();
+
+ return new CommandResult(
+ String.format(MESSAGE_PAIR_SUCCESS,
+ Messages.formatPerson(personToPair1), Messages.formatPerson(personToPair2)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof PairCommand)) {
+ return false;
+ }
+
+ PairCommand otherPairCommand = (PairCommand) other;
+ return index1.equals(otherPairCommand.index1)
+ && index2.equals(otherPairCommand.index2);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("index1", index1)
+ .add("index2", index2)
+ .toString();
+ }
+
+}
diff --git a/src/main/java/scrolls/elder/logic/commands/RedoCommand.java b/src/main/java/scrolls/elder/logic/commands/RedoCommand.java
new file mode 100644
index 00000000000..1df37ad2577
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/commands/RedoCommand.java
@@ -0,0 +1,30 @@
+package scrolls.elder.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.model.LogStore;
+import scrolls.elder.model.Model;
+
+/**
+ * Reverses the effect of a previous undo operation on the Datastore.
+ */
+public class RedoCommand extends Command {
+ public static final String COMMAND_WORD = "redo";
+ public static final String MESSAGE_SUCCESS = "Previous undo operation has been reversed!";
+ public static final String MESSAGE_REDO_ERROR = "No previous undo operation to be reversed.";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ if (!model.canRedoDatastore()) {
+ throw new CommandException(MESSAGE_REDO_ERROR);
+ }
+
+ model.redoChanges();
+ model.getDatastore().getPersonStore().updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ model.getDatastore().getLogStore().updateFilteredLogList(LogStore.PREDICATE_SHOW_ALL_LOGS);
+ model.getDatastore().getLogStore().updateFilteredLogListByPersonId(null);
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/scrolls/elder/logic/commands/UndoCommand.java b/src/main/java/scrolls/elder/logic/commands/UndoCommand.java
new file mode 100644
index 00000000000..886179d4a18
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/commands/UndoCommand.java
@@ -0,0 +1,30 @@
+package scrolls.elder.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.model.LogStore;
+import scrolls.elder.model.Model;
+
+/**
+ * Reverses the effect of a previous operation on the Datastore.
+ */
+public class UndoCommand extends Command {
+ public static final String COMMAND_WORD = "undo";
+ public static final String MESSAGE_SUCCESS = "Previous operation has been undone!";
+ public static final String MESSAGE_UNDO_ERROR = "No previous operation to be undone.";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ if (!model.canUndoDatastore()) {
+ throw new CommandException(MESSAGE_UNDO_ERROR);
+ }
+
+ model.undoChanges();
+ model.getDatastore().getPersonStore().updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ model.getDatastore().getLogStore().updateFilteredLogList(LogStore.PREDICATE_SHOW_ALL_LOGS);
+ model.getDatastore().getLogStore().updateFilteredLogListByPersonId(null);
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/scrolls/elder/logic/commands/UnpairCommand.java b/src/main/java/scrolls/elder/logic/commands/UnpairCommand.java
new file mode 100644
index 00000000000..a515cb20d2b
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/commands/UnpairCommand.java
@@ -0,0 +1,143 @@
+package scrolls.elder.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.logic.Messages;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.model.Model;
+import scrolls.elder.model.PersonStore;
+import scrolls.elder.model.person.Address;
+import scrolls.elder.model.person.Email;
+import scrolls.elder.model.person.Name;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.PersonFactory;
+import scrolls.elder.model.person.Phone;
+import scrolls.elder.model.person.Role;
+import scrolls.elder.model.tag.Tag;
+
+/**
+ * Unpairs a volunteer and a befriendee who were paired in the address book.
+ */
+public class UnpairCommand extends Command {
+ public static final String COMMAND_WORD = "unpair";
+ public static final String MESSAGE_USAGE =
+ COMMAND_WORD + ": Unpairs a volunteer and a befriendee specified "
+ + "by their index numbers used in the displayed person list.\n"
+ + "Parameters: INDEX1 INDEX2 (both must be a positive integers)\n"
+ + "Example: " + COMMAND_WORD + " 1 2";
+
+ public static final String MESSAGE_UNPAIR_SUCCESS = "Unpaired: %1$s and %2$s";
+
+ public static final String MESSAGE_DUPLICATE_PERSON = "Cannot unpair the same person from themselves.";
+ public static final String MESSAGE_NOT_PAIRED = "The two persons are not paired, so unpairing is not possible.";
+
+ private final Index index1;
+
+ private final Index index2;
+
+ /**
+ * @param index1 of the first person to be paired
+ * @param index2 of the second person to be paired with
+ */
+ public UnpairCommand(Index index1, Index index2) {
+ requireNonNull(index1);
+ requireNonNull(index2);
+
+ this.index1 = index1;
+ this.index2 = index2;
+ }
+
+ /**
+ * Creates and returns a {@code Person} with the details of {@code personToEdit}
+ * edited with {@code editPairedPersonDescriptor}.
+ */
+ private static Person createEditedPairedPerson(Person personToEdit,
+ Optional updatedPairName, Optional updatedPairID) {
+ assert personToEdit != null;
+
+ int personId = personToEdit.getPersonId();
+ Name updatedName = personToEdit.getName();
+ Phone updatedPhone = personToEdit.getPhone();
+ Email updatedEmail = personToEdit.getEmail();
+ Address updatedAddress = personToEdit.getAddress();
+ Set updatedTags = personToEdit.getTags();
+ Role role = personToEdit.getRole();
+ int updatedTimeServed = personToEdit.getTimeServed();
+ Optional updatedLatestLogId = personToEdit.getLatestLogId();
+
+ return PersonFactory.withIdFromParams(personId, updatedName, updatedPhone, updatedEmail, updatedAddress, role,
+ updatedTags, updatedPairName, updatedPairID, updatedTimeServed,
+ updatedLatestLogId);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ PersonStore store = model.getMutableDatastore().getMutablePersonStore();
+
+ List lastShownBList = store.getFilteredBefriendeeList();
+ List lastShownVList = store.getFilteredVolunteerList();
+
+ if (index1.getZeroBased() >= lastShownBList.size() || index2.getZeroBased() >= lastShownVList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToUnpair1 = lastShownBList.get(index1.getZeroBased());
+ Person personToUnpair2 = lastShownVList.get(index2.getZeroBased());
+
+ // Check if the two persons are the same person
+ if (personToUnpair1.isSamePerson(personToUnpair2)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ }
+
+ // Check if the two persons are paired
+ if (!personToUnpair1.getPairedWithName().equals(Optional.of(personToUnpair2.getName()))
+ || !personToUnpair2.getPairedWithName().equals(Optional.of(personToUnpair1.getName()))) {
+ throw new CommandException(MESSAGE_NOT_PAIRED);
+ }
+
+ // Unset the pairedWith attribute of the befriendee and volunteer
+ Person newPerson1 = createEditedPairedPerson(personToUnpair1, Optional.empty(), Optional.empty());
+ Person newPerson2 = createEditedPairedPerson(personToUnpair2, Optional.empty(), Optional.empty());
+ store.setPerson(personToUnpair1, newPerson1);
+ store.setPerson(personToUnpair2, newPerson2);
+
+ model.commitDatastore();
+ store.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ return new CommandResult(
+ String.format(
+ MESSAGE_UNPAIR_SUCCESS,
+ Messages.formatPerson(personToUnpair1), Messages.formatPerson(personToUnpair2)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof UnpairCommand)) {
+ return false;
+ }
+
+ UnpairCommand otherUnpairCommand = (UnpairCommand) other;
+ return index1.equals(otherUnpairCommand.index1)
+ && index2.equals(otherUnpairCommand.index2);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("index1", index1)
+ .add("index2", index2)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/scrolls/elder/logic/commands/exceptions/CommandException.java
similarity index 89%
rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java
rename to src/main/java/scrolls/elder/logic/commands/exceptions/CommandException.java
index a16bd14f2cd..f58f1ca0fb3 100644
--- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java
+++ b/src/main/java/scrolls/elder/logic/commands/exceptions/CommandException.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.commands.exceptions;
+package scrolls.elder.logic.commands.exceptions;
/**
* Represents an error which occurs during execution of a {@link Command}.
diff --git a/src/main/java/scrolls/elder/logic/parser/AddCommandParser.java b/src/main/java/scrolls/elder/logic/parser/AddCommandParser.java
new file mode 100644
index 00000000000..3813f960f9e
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/parser/AddCommandParser.java
@@ -0,0 +1,91 @@
+package scrolls.elder.logic.parser;
+
+import static scrolls.elder.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import scrolls.elder.logic.commands.AddCommand;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+import scrolls.elder.model.person.Address;
+import scrolls.elder.model.person.Befriendee;
+import scrolls.elder.model.person.Email;
+import scrolls.elder.model.person.Name;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.Phone;
+import scrolls.elder.model.person.Role;
+import scrolls.elder.model.person.Volunteer;
+import scrolls.elder.model.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new AddCommand object
+ */
+public class AddCommandParser implements Parser {
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddCommand
+ * and returns an AddCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args,
+ CliSyntax.PREFIX_ROLE,
+ CliSyntax.PREFIX_PHONE,
+ CliSyntax.PREFIX_EMAIL,
+ CliSyntax.PREFIX_ADDRESS,
+ CliSyntax.PREFIX_TAG,
+ CliSyntax.PREFIX_NAME);
+
+ // Check for all Prefixes present
+ if (!arePrefixesPresent(argMultimap,
+ CliSyntax.PREFIX_ROLE, CliSyntax.PREFIX_NAME,
+ CliSyntax.PREFIX_ADDRESS, CliSyntax.PREFIX_PHONE,
+ CliSyntax.PREFIX_EMAIL) || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
+ }
+
+ // Check No duplicates for PREFIXES
+ argMultimap.verifyNoDuplicatePrefixesFor(CliSyntax.PREFIX_ROLE,
+ CliSyntax.PREFIX_PHONE,
+ CliSyntax.PREFIX_EMAIL,
+ CliSyntax.PREFIX_ADDRESS,
+ CliSyntax.PREFIX_NAME);
+
+ Name name = ParserUtil.parseName(argMultimap.getValue(CliSyntax.PREFIX_NAME).get());
+ Phone phone = ParserUtil.parsePhone(argMultimap.getValue(CliSyntax.PREFIX_PHONE).get());
+ Email email = ParserUtil.parseEmail(argMultimap.getValue(CliSyntax.PREFIX_EMAIL).get());
+ Address address = ParserUtil.parseAddress(argMultimap.getValue(CliSyntax.PREFIX_ADDRESS).get());
+ Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(CliSyntax.PREFIX_TAG));
+ Role role = ParserUtil.parseRole(argMultimap.getValue(CliSyntax.PREFIX_ROLE).get());
+ Optional pairedWithNone = Optional.empty();
+ Optional pairedWithNoID = Optional.empty();
+ int timeServed = 0;
+ Optional latestLogId = Optional.empty();
+
+ // temporary solution, delete after merging
+ Person person = null;
+
+ if (role.isVolunteer()) {
+ person = new Volunteer(name, phone, email, address, tagList, pairedWithNone, pairedWithNoID,
+ timeServed, latestLogId);
+ } else {
+ assert role.isBefriendee();
+ person = new Befriendee(name, phone, email, address, tagList, pairedWithNone, pairedWithNoID,
+ timeServed, latestLogId);
+ }
+
+ return new AddCommand(person);
+ }
+
+}
diff --git a/src/main/java/scrolls/elder/logic/parser/AddressBookParser.java b/src/main/java/scrolls/elder/logic/parser/AddressBookParser.java
new file mode 100644
index 00000000000..ac56118db61
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/parser/AddressBookParser.java
@@ -0,0 +1,126 @@
+package scrolls.elder.logic.parser;
+
+import static scrolls.elder.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static scrolls.elder.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
+
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import scrolls.elder.commons.core.LogsCenter;
+import scrolls.elder.logic.commands.AddCommand;
+import scrolls.elder.logic.commands.ClearCommand;
+import scrolls.elder.logic.commands.Command;
+import scrolls.elder.logic.commands.DeleteCommand;
+import scrolls.elder.logic.commands.EditCommand;
+import scrolls.elder.logic.commands.ExitCommand;
+import scrolls.elder.logic.commands.FindCommand;
+import scrolls.elder.logic.commands.HelpCommand;
+import scrolls.elder.logic.commands.ListCommand;
+import scrolls.elder.logic.commands.LogAddCommand;
+import scrolls.elder.logic.commands.LogDeleteCommand;
+import scrolls.elder.logic.commands.LogEditCommand;
+import scrolls.elder.logic.commands.LogFindCommand;
+import scrolls.elder.logic.commands.PairCommand;
+import scrolls.elder.logic.commands.RedoCommand;
+import scrolls.elder.logic.commands.UndoCommand;
+import scrolls.elder.logic.commands.UnpairCommand;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses user input.
+ */
+public class AddressBookParser {
+
+ /**
+ * Used for initial separation of command word and args.
+ */
+ private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)");
+ private static final Logger logger = LogsCenter.getLogger(AddressBookParser.class);
+
+ /**
+ * Parses user input into command for execution.
+ *
+ * @param userInput full user input string
+ * @return the command based on the user input
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public Command parseCommand(String userInput) throws ParseException {
+ final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
+ if (!matcher.matches()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
+ }
+
+ final String commandWord = matcher.group("commandWord");
+ final String arguments = matcher.group("arguments");
+
+ // Note to developers: Change the log level in config.json to enable lower level (i.e., FINE, FINER and lower)
+ // log messages such as the one below.
+ // Lower level log messages are used sparingly to minimize noise in the code.
+ logger.fine("Command word: " + commandWord + "; Arguments: " + arguments);
+
+ switch (commandWord) {
+
+ case AddCommand.COMMAND_WORD:
+ return new AddCommandParser().parse(arguments);
+
+ case EditCommand.COMMAND_WORD:
+ return new EditCommandParser().parse(arguments);
+
+ case DeleteCommand.COMMAND_WORD_DELETE:
+ case DeleteCommand.COMMAND_WORD_DEL:
+ case DeleteCommand.COMMAND_WORD_RM:
+ case DeleteCommand.COMMAND_WORD_REMOVE:
+ return new DeleteCommandParser().parse(arguments);
+
+ case ClearCommand.COMMAND_WORD:
+ return new ClearCommand();
+
+ case FindCommand.COMMAND_WORD_FIND:
+ case FindCommand.COMMAND_WORD_SEARCH:
+ return new FindCommandParser().parse(arguments);
+
+ case ListCommand.COMMAND_WORD:
+ return new ListCommand();
+
+ case ExitCommand.COMMAND_WORD:
+ return new ExitCommand();
+
+ case HelpCommand.COMMAND_WORD:
+ return new HelpCommand();
+
+ case PairCommand.COMMAND_WORD:
+ return new PairCommandParser().parse(arguments);
+
+ case UnpairCommand.COMMAND_WORD:
+ return new UnpairCommandParser().parse(arguments);
+
+ case LogAddCommand.COMMAND_WORD:
+ return new LogAddCommandParser().parse(arguments);
+
+ case LogEditCommand.COMMAND_WORD:
+ return new LogEditCommandParser().parse(arguments);
+
+ case LogDeleteCommand.COMMAND_WORD_LOG_DELETE:
+ case LogDeleteCommand.COMMAND_WORD_LOG_DEL:
+ case LogDeleteCommand.COMMAND_WORD_LOG_RM:
+ case LogDeleteCommand.COMMAND_WORD_LOG_REMOVE:
+ return new LogDeleteCommandParser().parse(arguments);
+
+ case LogFindCommand.COMMAND_WORD_LOGFIND:
+ case LogFindCommand.COMMAND_WORD_FINDLOG:
+ return new LogFindCommandParser().parse(arguments);
+
+ case UndoCommand.COMMAND_WORD:
+ return new UndoCommand();
+
+ case RedoCommand.COMMAND_WORD:
+ return new RedoCommand();
+
+ default:
+ logger.finer("This user input caused a ParseException: " + userInput);
+ throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/scrolls/elder/logic/parser/ArgumentMultimap.java
similarity index 95%
rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
rename to src/main/java/scrolls/elder/logic/parser/ArgumentMultimap.java
index 21e26887a83..9e4a31cd139 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
+++ b/src/main/java/scrolls/elder/logic/parser/ArgumentMultimap.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.parser;
+package scrolls.elder.logic.parser;
import java.util.ArrayList;
import java.util.HashMap;
@@ -7,8 +7,8 @@
import java.util.Optional;
import java.util.stream.Stream;
-import seedu.address.logic.Messages;
-import seedu.address.logic.parser.exceptions.ParseException;
+import scrolls.elder.logic.Messages;
+import scrolls.elder.logic.parser.exceptions.ParseException;
/**
* Stores mapping of prefixes to their respective arguments.
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/scrolls/elder/logic/parser/ArgumentTokenizer.java
similarity index 99%
rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
rename to src/main/java/scrolls/elder/logic/parser/ArgumentTokenizer.java
index 5c9aebfa488..b3c183dd13f 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
+++ b/src/main/java/scrolls/elder/logic/parser/ArgumentTokenizer.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.parser;
+package scrolls.elder.logic.parser;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/scrolls/elder/logic/parser/CliSyntax.java
similarity index 56%
rename from src/main/java/seedu/address/logic/parser/CliSyntax.java
rename to src/main/java/scrolls/elder/logic/parser/CliSyntax.java
index 75b1a9bf119..18ddd3c58ba 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/scrolls/elder/logic/parser/CliSyntax.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.parser;
+package scrolls.elder.logic.parser;
/**
* Contains Command Line Interface (CLI) syntax definitions common to multiple commands
@@ -11,5 +11,10 @@ public class CliSyntax {
public static final Prefix PREFIX_EMAIL = new Prefix("e/");
public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
+ public static final Prefix PREFIX_ROLE = new Prefix("r/");
+ public static final Prefix PREFIX_TITLE = new Prefix("t/");
+ public static final Prefix PREFIX_START = new Prefix("s/");
+ public static final Prefix PREFIX_DURATION = new Prefix("d/");
+ public static final Prefix PREFIX_REMARKS = new Prefix("r/");
}
diff --git a/src/main/java/scrolls/elder/logic/parser/DeleteCommandParser.java b/src/main/java/scrolls/elder/logic/parser/DeleteCommandParser.java
new file mode 100644
index 00000000000..b200d28ee82
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/parser/DeleteCommandParser.java
@@ -0,0 +1,47 @@
+package scrolls.elder.logic.parser;
+
+import static scrolls.elder.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_ROLE;
+
+import java.util.stream.Stream;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.logic.commands.DeleteCommand;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteCommand object
+ */
+public class DeleteCommandParser implements Parser {
+
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteCommand
+ * and returns a DeleteCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_ROLE);
+ Index index;
+
+ if (argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE));
+ }
+
+ if (!arePrefixesPresent(argMultimap, CliSyntax.PREFIX_ROLE)) {
+ throw new ParseException(String.format(DeleteCommand.MESSAGE_NO_ROLE, DeleteCommand.MESSAGE_USAGE));
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(CliSyntax.PREFIX_ROLE);
+
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+
+ return new DeleteCommand(index, ParserUtil.parseRole(argMultimap.getValue(PREFIX_ROLE).get()));
+
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/scrolls/elder/logic/parser/EditCommandParser.java
similarity index 65%
rename from src/main/java/seedu/address/logic/parser/EditCommandParser.java
rename to src/main/java/scrolls/elder/logic/parser/EditCommandParser.java
index 46b3309a78b..e83bd6ba82f 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/scrolls/elder/logic/parser/EditCommandParser.java
@@ -1,23 +1,23 @@
-package seedu.address.logic.parser;
+package scrolls.elder.logic.parser;
import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static scrolls.elder.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_NAME;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_PHONE;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_ROLE;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_TAG;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.tag.Tag;
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.logic.commands.EditCommand;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+import scrolls.elder.model.tag.Tag;
/**
* Parses input arguments and creates a new EditCommand object
@@ -32,19 +32,26 @@ public class EditCommandParser implements Parser {
public EditCommand parse(String args) throws ParseException {
requireNonNull(args);
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args,
+ PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG, PREFIX_ROLE);
Index index;
+ // Check for Index
try {
index = ParserUtil.parseIndex(argMultimap.getPreamble());
} catch (ParseException pe) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
}
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
+ // Check for Role for Indexing
+ if (argMultimap.getValue(PREFIX_ROLE).isEmpty()) {
+ throw new ParseException(String.format(EditCommand.MESSAGE_NO_ROLE, EditCommand.MESSAGE_USAGE));
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_ROLE);
- EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
+ EditCommand.EditPersonDescriptor editPersonDescriptor = new EditCommand.EditPersonDescriptor();
if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
@@ -58,6 +65,10 @@ public EditCommand parse(String args) throws ParseException {
if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
}
+ if (argMultimap.getValue(PREFIX_ROLE).isPresent()) {
+ editPersonDescriptor.setRole(ParserUtil.parseRole(argMultimap.getValue(PREFIX_ROLE).get()));
+ }
+
parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
if (!editPersonDescriptor.isAnyFieldEdited()) {
diff --git a/src/main/java/scrolls/elder/logic/parser/FindCommandParser.java b/src/main/java/scrolls/elder/logic/parser/FindCommandParser.java
new file mode 100644
index 00000000000..2c366764a10
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/parser/FindCommandParser.java
@@ -0,0 +1,180 @@
+package scrolls.elder.logic.parser;
+
+import static scrolls.elder.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_ROLE;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import scrolls.elder.logic.commands.FindCommand;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+import scrolls.elder.model.person.NameContainsKeywordsPredicate;
+import scrolls.elder.model.person.TagListContainsTagsPredicate;
+import scrolls.elder.model.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new FindCommand object
+ */
+public class FindCommandParser implements Parser {
+
+ enum FindType {
+ SEARCH_VOLUNTEER_ONLY,
+ SEARCH_BEFRIENDEE_ONLY,
+ SEARCH_BOTH,
+
+ SEARCH_PAIRED,
+ SEARCH_UNPAIRED
+ }
+
+ public static final String PAIRED_FLAG = "--paired";
+ public static final String UNPAIRED_FLAG = "--unpaired";
+
+ public static final String SEARCH_VOLUNTEER_FLAG = PREFIX_ROLE + "volunteer";
+ public static final String SEARCH_BEFRIENDEE_FLAG = PREFIX_ROLE + "befriendee";
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindCommand
+ * and returns a FindCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ }
+
+ // Handle Role Parsing
+ FindType findTypeRole = parseForRoles(trimmedArgs.split("\\s+"));
+ String args2 = args.replace(SEARCH_VOLUNTEER_FLAG, "").replace(SEARCH_BEFRIENDEE_FLAG, "");
+ String trimmedArgs2 = args2.trim();
+ if (trimmedArgs2.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ }
+
+ // Handle Pair Flag Parsing
+ FindType findTypePairStatus = parsePairFlag(trimmedArgs2.split("\\s+"));
+ String args3 = trimmedArgs2.replace(PAIRED_FLAG, "").replace(UNPAIRED_FLAG, "");
+ String trimmedArgs3 = args3.trim();
+
+ // Handle Tag Parsing
+ String[] keywordsWithoutRoles = trimmedArgs3.split("\\s+");
+ TagListContainsTagsPredicate tagPredicate = parseForTags(keywordsWithoutRoles);;
+
+ // Handle Name Parsing
+ List nameKeywordList = new ArrayList<>();
+ for (String keyword : keywordsWithoutRoles) {
+ if (!isValidTagName(keyword) && !keyword.trim().equals("")) {
+ nameKeywordList.add(keyword.trim());
+ }
+ }
+ NameContainsKeywordsPredicate namePredicate = new NameContainsKeywordsPredicate(nameKeywordList);
+
+ boolean isSearchingVolunteer = true;
+ boolean isSearchingBefriendee = true;
+ boolean isSearchingPaired = true;
+ boolean isSearchingUnpaired = true;
+
+ if (findTypeRole.equals(FindType.SEARCH_BEFRIENDEE_ONLY)) {
+ isSearchingVolunteer = false;
+ }
+ if (findTypeRole.equals(FindType.SEARCH_VOLUNTEER_ONLY)) {
+ isSearchingBefriendee = false;
+ }
+
+
+ if (findTypePairStatus.equals(FindType.SEARCH_UNPAIRED)) {
+ isSearchingPaired = false;
+ }
+ if (findTypePairStatus.equals(FindType.SEARCH_PAIRED)) {
+ isSearchingUnpaired = false;
+ }
+
+ return new FindCommand(namePredicate, tagPredicate,
+ isSearchingVolunteer, isSearchingBefriendee,
+ isSearchingPaired, isSearchingUnpaired);
+
+ }
+
+ private static FindType parseForRoles(String[] nameKeywords) throws ParseException {
+ boolean isSearchingVolunteer = false;
+ boolean isSearchingBefriendee = false;
+
+ // If both Volunteer and Befriendee roles are present, show error.
+ if (Arrays.asList(nameKeywords).contains(SEARCH_VOLUNTEER_FLAG)
+ && Arrays.asList(nameKeywords).contains(SEARCH_BEFRIENDEE_FLAG)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ }
+
+ if (Arrays.asList(nameKeywords).contains(SEARCH_VOLUNTEER_FLAG)) {
+ isSearchingVolunteer = true;
+ isSearchingBefriendee = false;
+ }
+
+ if (Arrays.asList(nameKeywords).contains(SEARCH_BEFRIENDEE_FLAG)) {
+ isSearchingBefriendee = true;
+ isSearchingVolunteer = false;
+ }
+
+ if (isSearchingVolunteer) {
+ return FindType.SEARCH_VOLUNTEER_ONLY;
+ } else if (isSearchingBefriendee) {
+ return FindType.SEARCH_BEFRIENDEE_ONLY;
+ } else {
+ return FindType.SEARCH_BOTH;
+ }
+
+ }
+
+ private static FindType parsePairFlag(String[] nameKeywords) throws ParseException {
+ boolean isSearchingPaired = true;
+ boolean isSearchingUnpaired = true;
+
+ // If both pair and unpair flags are present, show error.
+ if (Arrays.asList(nameKeywords).contains(PAIRED_FLAG)
+ && Arrays.asList(nameKeywords).contains(UNPAIRED_FLAG)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ }
+
+ if (Arrays.asList(nameKeywords).contains(PAIRED_FLAG)) {
+ isSearchingUnpaired = false;
+ }
+
+ if (Arrays.asList(nameKeywords).contains(UNPAIRED_FLAG)) {
+ isSearchingPaired = false;
+ }
+
+ if (isSearchingPaired && isSearchingUnpaired) {
+ return FindType.SEARCH_BOTH;
+ } else if (isSearchingPaired) {
+ return FindType.SEARCH_PAIRED;
+ } else {
+ return FindType.SEARCH_UNPAIRED;
+ }
+ }
+
+ private static TagListContainsTagsPredicate parseForTags(String[] tagKeywords) {
+ Set tagList = new HashSet<>();
+
+ for (String tagName : tagKeywords) {
+ if (isValidTagName(tagName)) {
+ String pureTagName = tagName.substring(2);
+ tagList.add(new Tag(pureTagName));
+ }
+ }
+
+ return new TagListContainsTagsPredicate(tagList);
+ }
+
+ private static boolean isValidTagName(String test) {
+ final String tagValidationRegex = "^t/.+$";
+ return test.matches(tagValidationRegex);
+ }
+
+}
diff --git a/src/main/java/scrolls/elder/logic/parser/LogAddCommandParser.java b/src/main/java/scrolls/elder/logic/parser/LogAddCommandParser.java
new file mode 100644
index 00000000000..0f621e543ae
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/parser/LogAddCommandParser.java
@@ -0,0 +1,66 @@
+package scrolls.elder.logic.parser;
+
+import static scrolls.elder.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Date;
+import java.util.stream.Stream;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.logic.commands.LogAddCommand;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new LogAddCommand object
+ */
+public class LogAddCommandParser implements Parser {
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the LogAddCommand
+ * and returns an LogAddCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public LogAddCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args,
+ CliSyntax.PREFIX_TITLE,
+ CliSyntax.PREFIX_START,
+ CliSyntax.PREFIX_DURATION,
+ CliSyntax.PREFIX_REMARKS);
+
+ // Check for all Prefixes present
+ if (!arePrefixesPresent(argMultimap,
+ CliSyntax.PREFIX_TITLE,
+ CliSyntax.PREFIX_START, CliSyntax.PREFIX_DURATION,
+ CliSyntax.PREFIX_REMARKS) || argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LogAddCommand.MESSAGE_USAGE));
+ }
+
+ String[] pairIndexes = argMultimap.getPreamble().split("\\s+");
+ Index index1;
+ Index index2;
+
+ try {
+ index1 = ParserUtil.parseIndex(pairIndexes[0]);
+ index2 = ParserUtil.parseIndex(pairIndexes[1]);
+ } catch (ParseException | ArrayIndexOutOfBoundsException e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LogAddCommand.MESSAGE_USAGE), e);
+ }
+
+ String title = argMultimap.getValue(CliSyntax.PREFIX_TITLE).get().trim();
+ Date start = ParserUtil.parseDate(argMultimap.getValue(CliSyntax.PREFIX_START).get());
+ int duration = ParserUtil.parseInt(argMultimap.getValue(CliSyntax.PREFIX_DURATION).get());
+ String remarks = argMultimap.getValue(CliSyntax.PREFIX_REMARKS).get().trim();
+
+ return new LogAddCommand(title, index1, index2, duration, start, remarks);
+ }
+
+}
diff --git a/src/main/java/scrolls/elder/logic/parser/LogDeleteCommandParser.java b/src/main/java/scrolls/elder/logic/parser/LogDeleteCommandParser.java
new file mode 100644
index 00000000000..fb176422641
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/parser/LogDeleteCommandParser.java
@@ -0,0 +1,37 @@
+package scrolls.elder.logic.parser;
+
+import static scrolls.elder.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.logic.commands.LogDeleteCommand;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new LogDeleteCommand object
+ */
+public class LogDeleteCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the LogDeleteCommand
+ * and returns a LogDeleteCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public LogDeleteCommand parse(String args) throws ParseException {
+ try {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args);
+ Index index;
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LogDeleteCommand.MESSAGE_USAGE),
+ pe);
+ }
+ return new LogDeleteCommand(index);
+
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, LogDeleteCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/scrolls/elder/logic/parser/LogEditCommandParser.java b/src/main/java/scrolls/elder/logic/parser/LogEditCommandParser.java
new file mode 100644
index 00000000000..06814d61136
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/parser/LogEditCommandParser.java
@@ -0,0 +1,62 @@
+package scrolls.elder.logic.parser;
+
+import static scrolls.elder.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_DURATION;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_REMARKS;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_START;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_TITLE;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.logic.commands.LogEditCommand;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new LogEditCommand object
+ */
+public class LogEditCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the LogEditCommand
+ * and returns an LogEditCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public LogEditCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args,
+ PREFIX_TITLE, PREFIX_START, PREFIX_DURATION, PREFIX_REMARKS);
+
+ Index index;
+
+ //Check for Index
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LogEditCommand.MESSAGE_USAGE), pe);
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_TITLE, PREFIX_START, PREFIX_DURATION, PREFIX_REMARKS);
+
+ LogEditCommand.EditLogDescriptor editLogDescriptor = new LogEditCommand.EditLogDescriptor();
+
+ if (argMultimap.getValue(PREFIX_TITLE).isPresent()) {
+ editLogDescriptor.setTitle(argMultimap.getValue(PREFIX_TITLE).get());
+ }
+ if (argMultimap.getValue(PREFIX_START).isPresent()) {
+ editLogDescriptor.setStartDate(ParserUtil.parseDate(argMultimap.getValue(PREFIX_START).get()));
+ }
+ if (argMultimap.getValue(PREFIX_DURATION).isPresent()) {
+ editLogDescriptor.setDuration(ParserUtil.parseInt(argMultimap.getValue(PREFIX_DURATION).get()));
+ }
+ if (argMultimap.getValue(PREFIX_REMARKS).isPresent()) {
+ editLogDescriptor.setRemarks(argMultimap.getValue(PREFIX_REMARKS).get());
+ }
+
+ if (!editLogDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(LogEditCommand.MESSAGE_NOT_EDITED);
+ }
+
+ LogEditCommand test = new LogEditCommand(index, editLogDescriptor);
+
+ return new LogEditCommand(index, editLogDescriptor);
+ }
+}
diff --git a/src/main/java/scrolls/elder/logic/parser/LogFindCommandParser.java b/src/main/java/scrolls/elder/logic/parser/LogFindCommandParser.java
new file mode 100644
index 00000000000..cf0bee78278
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/parser/LogFindCommandParser.java
@@ -0,0 +1,41 @@
+package scrolls.elder.logic.parser;
+
+import static scrolls.elder.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static scrolls.elder.logic.parser.CliSyntax.PREFIX_ROLE;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.logic.commands.LogFindCommand;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new LogFindCommand object
+ */
+public class LogFindCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the LogFindCommand
+ * and returns an LogFindCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public LogFindCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_ROLE);
+ Index index;
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_ROLE);
+
+ if (argMultimap.getValue(PREFIX_ROLE).isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LogFindCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LogFindCommand.MESSAGE_USAGE),
+ pe);
+ }
+ return new LogFindCommand(index, ParserUtil.parseRole(argMultimap.getValue(PREFIX_ROLE).get()));
+
+ }
+
+}
diff --git a/src/main/java/scrolls/elder/logic/parser/PairCommandParser.java b/src/main/java/scrolls/elder/logic/parser/PairCommandParser.java
new file mode 100644
index 00000000000..ec4612eafd0
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/parser/PairCommandParser.java
@@ -0,0 +1,37 @@
+package scrolls.elder.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static scrolls.elder.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.logic.commands.PairCommand;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new PairCommand object
+ */
+public class PairCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the PairCommand
+ * and returns an PairCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public PairCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args);
+
+ String[] pairIndexes = argMultimap.getPreamble().split("\\s+");
+ Index index1;
+ Index index2;
+
+ try {
+ index1 = ParserUtil.parseIndex(pairIndexes[0]);
+ index2 = ParserUtil.parseIndex(pairIndexes[1]);
+ } catch (ParseException | ArrayIndexOutOfBoundsException e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, PairCommand.MESSAGE_USAGE), e);
+ }
+
+ return new PairCommand(index1, index2);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/scrolls/elder/logic/parser/Parser.java
similarity index 72%
rename from src/main/java/seedu/address/logic/parser/Parser.java
rename to src/main/java/scrolls/elder/logic/parser/Parser.java
index d6551ad8e3f..d11e9c16495 100644
--- a/src/main/java/seedu/address/logic/parser/Parser.java
+++ b/src/main/java/scrolls/elder/logic/parser/Parser.java
@@ -1,7 +1,7 @@
-package seedu.address.logic.parser;
+package scrolls.elder.logic.parser;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.parser.exceptions.ParseException;
+import scrolls.elder.logic.commands.Command;
+import scrolls.elder.logic.parser.exceptions.ParseException;
/**
* Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}.
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/scrolls/elder/logic/parser/ParserUtil.java
similarity index 62%
rename from src/main/java/seedu/address/logic/parser/ParserUtil.java
rename to src/main/java/scrolls/elder/logic/parser/ParserUtil.java
index b117acb9c55..7f31ab9aece 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/scrolls/elder/logic/parser/ParserUtil.java
@@ -1,19 +1,22 @@
-package seedu.address.logic.parser;
+package scrolls.elder.logic.parser;
import static java.util.Objects.requireNonNull;
+import java.text.SimpleDateFormat;
import java.util.Collection;
+import java.util.Date;
import java.util.HashSet;
import java.util.Set;
-import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.commons.util.StringUtil;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+import scrolls.elder.model.person.Address;
+import scrolls.elder.model.person.Email;
+import scrolls.elder.model.person.Name;
+import scrolls.elder.model.person.Phone;
+import scrolls.elder.model.person.Role;
+import scrolls.elder.model.tag.Tag;
/**
* Contains utility methods used for parsing strings in the various *Parser classes.
@@ -25,6 +28,7 @@ public class ParserUtil {
/**
* Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
* trimmed.
+ *
* @throws ParseException if the specified index is invalid (not non-zero unsigned integer).
*/
public static Index parseIndex(String oneBasedIndex) throws ParseException {
@@ -95,6 +99,21 @@ public static Email parseEmail(String email) throws ParseException {
return new Email(trimmedEmail);
}
+ /**
+ * Parses a {@code String role} into an {@code Role}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code role} is invalid.
+ */
+ public static Role parseRole(String role) throws ParseException {
+ requireNonNull(role);
+ String trimmedRole = role.trim();
+ if (!Role.isValidRole(trimmedRole)) {
+ throw new ParseException(Role.MESSAGE_CONSTRAINTS);
+ }
+ return new Role(trimmedRole);
+ }
+
/**
* Parses a {@code String tag} into a {@code Tag}.
* Leading and trailing whitespaces will be trimmed.
@@ -121,4 +140,40 @@ public static Set parseTags(Collection tags) throws ParseException
}
return tagSet;
}
+
+ /**
+ * Parses a {@code String date} into a {@code Date}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code date} is invalid.
+ */
+ public static Date parseDate(String date) throws ParseException {
+ requireNonNull(date);
+ String trimmedDate = date.trim();
+
+ SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
+ formatter.setLenient(false); // to make sure that the date strictly follows the format "yyyy-MM-dd"
+ try {
+ return formatter.parse(trimmedDate);
+ } catch (java.text.ParseException e) {
+ throw new ParseException("Invalid date format. Expected format is yyyy-MM-dd.", e);
+ }
+ }
+
+ /**
+ * Parses a {@code String role} into an {@code Role}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code role} is invalid.
+ */
+ public static Integer parseInt(String num) throws ParseException {
+ requireNonNull(num);
+
+ String trimmedNum = num.trim();
+ try {
+ return Integer.parseInt(trimmedNum);
+ } catch (NumberFormatException e) {
+ throw new ParseException("Invalid number format. Expected an integer.", e);
+ }
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/scrolls/elder/logic/parser/Prefix.java
similarity index 95%
rename from src/main/java/seedu/address/logic/parser/Prefix.java
rename to src/main/java/scrolls/elder/logic/parser/Prefix.java
index 348b7686c8a..ca93f669ef3 100644
--- a/src/main/java/seedu/address/logic/parser/Prefix.java
+++ b/src/main/java/scrolls/elder/logic/parser/Prefix.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.parser;
+package scrolls.elder.logic.parser;
/**
* A prefix that marks the beginning of an argument in an arguments string.
diff --git a/src/main/java/scrolls/elder/logic/parser/UnpairCommandParser.java b/src/main/java/scrolls/elder/logic/parser/UnpairCommandParser.java
new file mode 100644
index 00000000000..0b7a68eb65b
--- /dev/null
+++ b/src/main/java/scrolls/elder/logic/parser/UnpairCommandParser.java
@@ -0,0 +1,37 @@
+package scrolls.elder.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static scrolls.elder.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import scrolls.elder.commons.core.index.Index;
+import scrolls.elder.logic.commands.UnpairCommand;
+import scrolls.elder.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new UnpairCommand object
+ */
+public class UnpairCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the PairCommand
+ * and returns an PairCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public UnpairCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args);
+
+ String[] pairIndexes = argMultimap.getPreamble().split("\\s+");
+ Index index1;
+ Index index2;
+
+ try {
+ index1 = ParserUtil.parseIndex(pairIndexes[0]);
+ index2 = ParserUtil.parseIndex(pairIndexes[1]);
+ } catch (ParseException | ArrayIndexOutOfBoundsException e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnpairCommand.MESSAGE_USAGE), e);
+ }
+
+ return new UnpairCommand(index1, index2);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/scrolls/elder/logic/parser/exceptions/ParseException.java
similarity index 73%
rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java
rename to src/main/java/scrolls/elder/logic/parser/exceptions/ParseException.java
index 158a1a54c1c..1573870e68f 100644
--- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java
+++ b/src/main/java/scrolls/elder/logic/parser/exceptions/ParseException.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.parser.exceptions;
+package scrolls.elder.logic.parser.exceptions;
-import seedu.address.commons.exceptions.IllegalValueException;
+import scrolls.elder.commons.exceptions.IllegalValueException;
/**
* Represents a parse error encountered by a parser.
diff --git a/src/main/java/scrolls/elder/model/Datastore.java b/src/main/java/scrolls/elder/model/Datastore.java
new file mode 100644
index 00000000000..58ef85f88f5
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/Datastore.java
@@ -0,0 +1,86 @@
+package scrolls.elder.model;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Objects;
+
+/**
+ * Represents the in-memory model of the application data.
+ * The interface through which all other components access application data.
+ * Does not provide mutators, as that is handled by the individual stores.
+ */
+public class Datastore implements ReadOnlyDatastore {
+ private final PersonStore persons;
+ private final LogStore logs;
+
+ /**
+ * Creates an empty Datastore.
+ */
+ public Datastore() {
+ persons = new PersonStore();
+ logs = new LogStore();
+ }
+
+ /**
+ * Creates a Datastore using the data in {@code toBeCopied}
+ */
+ public Datastore(ReadOnlyDatastore toBeCopied) {
+ this();
+ resetData(toBeCopied);
+ }
+
+ //// Datastore accessors
+
+ @Override
+ public ReadOnlyPersonStore getPersonStore() {
+ return persons;
+ }
+
+ public PersonStore getMutablePersonStore() {
+ return persons;
+ }
+
+ @Override
+ public ReadOnlyLogStore getLogStore() {
+ return logs;
+ }
+
+ public LogStore getMutableLogStore() {
+ return logs;
+ }
+
+ /**
+ * Resets the existing data of this {@code Datastore} with {@code newData}.
+ */
+ public void resetData(ReadOnlyDatastore newData) {
+ requireNonNull(newData);
+ persons.resetData(newData.getPersonStore());
+ logs.resetData(newData.getLogStore());
+ }
+
+ //// Overrides
+
+ @Override
+ public String toString() {
+ return String.format("Datastore{persons=%s, logs=%s}", persons, logs);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Datastore)) {
+ return false;
+ }
+
+ Datastore otherDatastore = (Datastore) other;
+ return persons.equals(otherDatastore.persons) && logs.equals(otherDatastore.logs);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(persons, logs);
+ }
+}
diff --git a/src/main/java/scrolls/elder/model/DatastoreVersionStorage.java b/src/main/java/scrolls/elder/model/DatastoreVersionStorage.java
new file mode 100644
index 00000000000..27c1c035a78
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/DatastoreVersionStorage.java
@@ -0,0 +1,111 @@
+package scrolls.elder.model;
+
+import java.util.ArrayList;
+
+/**
+ * Represents a storage for snapshots of the Datastore.
+ * After every operation where the Datastore of the application is mutated in someway,
+ * there will be an entry created in the DatastoreVersionStorage to enable undo and redo operations.
+ */
+public class DatastoreVersionStorage {
+ /**
+ * The list of datastore versions stored in this storage.
+ */
+ private ArrayList datastoreVersions;
+
+ /**
+ * The pointer indicating the current position in the list of versions.
+ */
+ private int currentStatePointer;
+
+ /**
+ * Constructs a new DatastoreVersionStorage with an initial snapshot of the provided datastore.
+ *
+ * @param datastore The initial state of the Datastore.
+ */
+ public DatastoreVersionStorage(ReadOnlyDatastore datastore) {
+ this.datastoreVersions = new ArrayList();
+ // Note that snapshot must be a deep copy.
+ datastoreVersions.add(new Datastore(datastore));
+ this.currentStatePointer = 0;
+ }
+
+ /**
+ * Checks if an undo operation is possible.
+ *
+ * @return true if an undo operation can be performed, false otherwise.
+ */
+ public boolean canUndo() {
+ // Pointer has at least 1 version of datastore to its left
+ if (currentStatePointer > 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if a redo operation is possible.
+ *
+ * @return true if a redo operation can be performed, false otherwise.
+ */
+ public boolean canRedo() {
+ int size = datastoreVersions.size() - 1;
+
+ // pointer is not at the end of the version history
+ if (currentStatePointer < size) {
+ return true;
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Executes the undo operation by reverting to the previous version of the Datastore.
+ *
+ * @return The Datastore in its previous state after undoing an operation.
+ */
+ public ReadOnlyDatastore executeUndo() {
+ assert this.canUndo() : "Undo command cannot be executed";
+
+ currentStatePointer--;
+ ReadOnlyDatastore prevDatastore = datastoreVersions.get(currentStatePointer);
+ return prevDatastore;
+ }
+
+ /**
+ * Executes the redo operation by moving forward to the next version of the Datastore.
+ *
+ * @return The Datastore in its next state after reversing an undo operation.
+ */
+ public ReadOnlyDatastore executeRedo() {
+ assert this.canRedo() : "Redo command cannot be executed";
+
+ currentStatePointer++;
+ ReadOnlyDatastore nextDatastore = datastoreVersions.get(currentStatePointer);
+ return nextDatastore;
+ }
+
+ /**
+ * Commits a new snapshot of the Datastore to the storage.
+ * If the currentStatePointer is not at the end of the list, purges
+ * the subsequent snapshots before adding the new one.
+ *
+ * @param datastore The updated state of the Datastore to be committed.
+ */
+ public void commitDatastore(ReadOnlyDatastore datastore) {
+ int size = this.datastoreVersions.size();
+
+ // If not at end of list, purge the data before adding new datastore snapshot
+ if (currentStatePointer < size - 1) {
+ int indexToRemove = currentStatePointer + 1;
+ for (int i = currentStatePointer + 1; i < size; i++) {
+ this.datastoreVersions.remove(indexToRemove);
+ }
+ }
+
+ this.datastoreVersions.add(new Datastore(datastore));
+ currentStatePointer++;
+ }
+}
diff --git a/src/main/java/scrolls/elder/model/LogStore.java b/src/main/java/scrolls/elder/model/LogStore.java
new file mode 100644
index 00000000000..0f8e02b7c3a
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/LogStore.java
@@ -0,0 +1,255 @@
+package scrolls.elder.model;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javafx.collections.FXCollections;
+import javafx.collections.MapChangeListener;
+import javafx.collections.ObservableList;
+import javafx.collections.ObservableMap;
+import javafx.collections.transformation.FilteredList;
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.model.log.Log;
+
+/**
+ * Wraps data for all logs stored.
+ * All logs stored are guaranteed to have IDs consistent with the {@code logIdSequence} of the {@code LogStore}
+ * instance.
+ */
+public class LogStore implements ReadOnlyLogStore {
+ public static final Predicate PREDICATE_SHOW_ALL_LOGS = unused -> true;
+ private final ObservableMap logs;
+ private final ObservableMap> normalisedLogsByPerson;
+ private final ObservableList logList;
+ private final FilteredList filteredLogList;
+
+ /**
+ * The sequence number that determines the ID of the next log to be added.
+ */
+ private int logIdSequence;
+
+ /**
+ * Creates an empty LogStore.
+ */
+ public LogStore() {
+ this.logIdSequence = 0;
+
+ this.logs = FXCollections.observableHashMap();
+ this.normalisedLogsByPerson = FXCollections.observableHashMap();
+
+ this.logList = FXCollections.observableArrayList();
+ this.filteredLogList = new FilteredList<>(logList);
+
+ // Binds the backing Map to:
+ // - The main ObservableList of logs
+ // - The normalised map of logs by person
+ MapChangeListener super Integer, ? super Log> listener = change -> {
+ if (change.wasRemoved()) {
+ logList.remove(change.getValueRemoved());
+ deepRemoveId(normalisedLogsByPerson, change.getValueRemoved().getVolunteerId(),
+ change.getValueRemoved().getLogId());
+ deepRemoveId(normalisedLogsByPerson, change.getValueRemoved().getBefriendeeId(),
+ change.getValueRemoved().getLogId());
+ }
+ if (change.wasAdded()) {
+ logList.add(change.getValueAdded());
+ deepAddId(normalisedLogsByPerson, change.getValueAdded().getVolunteerId(),
+ change.getValueAdded().getLogId());
+ deepAddId(normalisedLogsByPerson, change.getValueAdded().getBefriendeeId(),
+ change.getValueAdded().getLogId());
+ }
+ };
+ logs.addListener(listener);
+ }
+
+ /**
+ * Creates an LogStore using the Logs in {@code toBeCopied}
+ */
+ public LogStore(ReadOnlyLogStore toBeCopied) {
+ this();
+ resetData(toBeCopied);
+ }
+
+ //// Collection-level getters and setters
+ @Override
+ public ObservableList getUnfilteredAllLogsList() {
+ return logs.values()
+ .stream()
+ .collect(Collectors.collectingAndThen(Collectors.toList(), FXCollections::observableArrayList));
+ }
+
+ @Override
+ public ObservableList getLogList() {
+ return FXCollections.unmodifiableObservableList(logList);
+ }
+
+ @Override
+ public ObservableList getFilteredLogList() {
+ return filteredLogList;
+ }
+
+ @Override
+ public void updateFilteredLogList(Predicate predicate) {
+ requireNonNull(predicate);
+ filteredLogList.setPredicate(predicate);
+ }
+
+ @Override
+ public void updateFilteredLogListByPersonId(Integer personId) {
+ // Reset when no filter is applied
+ if (personId == null) {
+ logList.setAll(logs.values());
+ return;
+ }
+
+ // Filter by personId
+ logList.clear();
+
+ // if logs do not exist
+ if (normalisedLogsByPerson.get(personId) == null || normalisedLogsByPerson.get(personId).isEmpty()) {
+ return;
+ }
+
+ normalisedLogsByPerson.get(personId).forEach(logId -> {
+ Log log = logs.get(logId);
+ if (log != null) {
+ logList.add(log);
+ }
+ });
+ }
+
+ /**
+ * Replaces the contents of the log list with {@code logs}.
+ */
+ public void setLogList(List logs) {
+ this.logs.clear();
+ int maxId = -1;
+ for (Log log : logs) {
+ maxId = Math.max(maxId, log.getLogId());
+ this.logs.put(log.getLogId(), log);
+ }
+ logIdSequence = maxId + 1;
+ }
+
+ /**
+ * Resets the existing data of this {@code LogStore} with {@code newData}.
+ */
+ public void resetData(ReadOnlyLogStore newData) {
+ requireNonNull(newData);
+ setLogList(newData.getLogList());
+ }
+
+ //// Log-level CRUD operations
+
+ /**
+ * Returns true if a log with the same identity as {@code log} exists in the store.
+ */
+ public boolean hasLog(Log log) {
+ requireNonNull(log);
+
+ return logs.containsKey(log.getLogId());
+ }
+
+ /**
+ * Adds a log to the store.
+ * Any relational validation (i.e., volunteerId) should be done before calling this method.
+ * Returns the log id for the newly added log.
+ */
+ public Integer addLog(Log newLog) {
+ requireNonNull(newLog);
+ int prevLogIdSequence = logIdSequence;
+ logs.put(logIdSequence, new Log(logIdSequence, newLog));
+ logIdSequence++;
+ return prevLogIdSequence;
+ }
+
+ /**
+ * Adds an existing log to the store.
+ * For the case where existing logs are read from storage.
+ */
+ public void addLogWithId(Log newLog) {
+ requireNonNull(newLog);
+
+ logs.put(newLog.getLogId(), newLog);
+ if (newLog.getLogId() >= logIdSequence) {
+ logIdSequence = newLog.getLogId() + 1;
+ }
+ }
+
+ @Override
+ public Log getLogById(int logId) {
+ return logs.get(logId);
+ }
+
+ /**
+ * Updates a log with the given data from {@code editedLog}.
+ * {@code editedLog} must contain an ID that matches an existing log in the store.
+ */
+ public void setLog(Log editedLog) {
+ requireNonNull(editedLog);
+
+ logs.put(editedLog.getLogId(), editedLog);
+ }
+
+ /**
+ * Removes the log with the given ID from the store.
+ * {@code idToRemove} must exist in the store.
+ */
+ public void removeLog(Integer idToRemove) {
+ logs.remove(idToRemove);
+ }
+
+ /**
+ * Adds a logId to the normalised map.
+ * Must replace the entire list to trigger the listener.
+ */
+ private void deepAddId(ObservableMap> map, int key, int value) {
+ ArrayList oldList = map.getOrDefault(key, new ArrayList<>());
+ ArrayList temp = new ArrayList<>(oldList);
+ temp.add(value);
+ map.put(key, temp);
+ }
+
+ /**
+ * Removes a logId from the normalised map.
+ * Must replace the entire list to trigger the listener.
+ */
+ private void deepRemoveId(ObservableMap> map, int key, Integer value) {
+ ArrayList oldList = map.getOrDefault(key, new ArrayList<>());
+ ArrayList temp = new ArrayList<>(oldList);
+ temp.remove(value);
+ map.put(key, temp);
+ }
+
+ //// Overrides
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("logs", logList)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof LogStore)) {
+ return false;
+ }
+
+ LogStore otherLogStore = (LogStore) other;
+ return logs.equals(otherLogStore.logs);
+ }
+
+ @Override
+ public int hashCode() {
+ return logs.hashCode();
+ }
+}
diff --git a/src/main/java/scrolls/elder/model/Model.java b/src/main/java/scrolls/elder/model/Model.java
new file mode 100644
index 00000000000..23f50432970
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/Model.java
@@ -0,0 +1,105 @@
+package scrolls.elder.model;
+
+import java.nio.file.Path;
+import java.util.function.Predicate;
+
+import com.sun.jdi.request.InvalidRequestStateException;
+
+import scrolls.elder.commons.core.GuiSettings;
+import scrolls.elder.model.log.Log;
+import scrolls.elder.model.person.Person;
+
+/**
+ * The API of the Model component. Controls in-memory data of the application.
+ */
+public interface Model {
+ /**
+ * {@code Predicate} that always evaluate to true
+ */
+ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_LOGS = unused -> true;
+
+ //// UserPrefs getters and setters
+
+ /**
+ * Returns the user prefs.
+ */
+ ReadOnlyUserPrefs getUserPrefs();
+
+ /**
+ * Replaces user prefs data with the data in {@code userPrefs}.
+ */
+ void setUserPrefs(ReadOnlyUserPrefs userPrefs);
+
+ /**
+ * Returns the user prefs' GUI settings.
+ */
+ GuiSettings getGuiSettings();
+
+ /**
+ * Sets the user prefs' GUI settings.
+ */
+ void setGuiSettings(GuiSettings guiSettings);
+
+ //// Datastore getters and setters
+
+ /**
+ * Returns the user prefs' datastore file path.
+ */
+ Path getDatastoreFilePath();
+
+ /**
+ * Sets the user prefs' datastore file path.
+ */
+ void setDatastoreFilePath(Path datastoreFilePath);
+
+ //// Datastore getters and setters
+
+ /**
+ * Returns a readonly view of the Datastore
+ */
+ ReadOnlyDatastore getDatastore();
+
+ /**
+ * Returns a mutable view of the Datastore
+ */
+ Datastore getMutableDatastore();
+
+ /**
+ * Replaces Datastore with the data in {@code datastore}.
+ */
+ void setDatastore(ReadOnlyDatastore datastore);
+
+ /**
+ * Returns a view of the DatastoreVersionStorage
+ */
+ DatastoreVersionStorage getDatastoreVersionStorage();
+
+ /**
+ * Commits Datastore to the DatastoreVersionStorage with the cuurent data in {@code datastore}.
+ */
+ void commitDatastore();
+
+ /**
+ * Reverts the datastore to its previous state immediately before the current datastore state.
+ * @throws InvalidRequestStateException If there are no changes to undo.
+ */
+ void undoChanges();
+
+ /**
+ * Reverses the effects of the most recent undo operation.
+ * @throws InvalidRequestStateException If there are no undo operations to reverse.
+ */
+ void redoChanges();
+
+ /**
+ * Returns a boolean value on whether undo operations can be carried out on the current datastore.
+ */
+ boolean canUndoDatastore();
+
+ /**
+ * Returns a boolean value on whether redo operations can be carried out on the current datastore.
+ */
+ boolean canRedoDatastore();
+
+}
diff --git a/src/main/java/scrolls/elder/model/ModelManager.java b/src/main/java/scrolls/elder/model/ModelManager.java
new file mode 100644
index 00000000000..768e895efd7
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/ModelManager.java
@@ -0,0 +1,147 @@
+package scrolls.elder.model;
+
+import static java.util.Objects.requireNonNull;
+
+import java.nio.file.Path;
+import java.util.logging.Logger;
+
+import scrolls.elder.commons.core.GuiSettings;
+import scrolls.elder.commons.core.LogsCenter;
+import scrolls.elder.commons.util.CollectionUtil;
+
+/**
+ * Represents the in-memory model of the address book data.
+ */
+public class ModelManager implements Model {
+ private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
+
+ private final UserPrefs userPrefs;
+ private final Datastore datastore;
+ private final DatastoreVersionStorage datastoreVersionStorage;
+
+ /**
+ * Initializes a ModelManager with the given addressBook and userPrefs.
+ */
+ public ModelManager(ReadOnlyDatastore datastore, ReadOnlyUserPrefs userPrefs) {
+ CollectionUtil.requireAllNonNull(datastore, userPrefs);
+
+ logger.fine("Initializing with datastore: " + datastore + " and user prefs " + userPrefs);
+
+ this.datastore = new Datastore(datastore);
+ this.userPrefs = new UserPrefs(userPrefs);
+ this.datastoreVersionStorage = new DatastoreVersionStorage(this.datastore);
+ }
+
+ /**
+ * Initializes a ModelManager with the default datastore and user preferences.
+ */
+ public ModelManager() {
+ this(new Datastore(), new UserPrefs());
+ }
+
+ //=========== UserPrefs ==================================================================================
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ return userPrefs;
+ }
+
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ requireNonNull(userPrefs);
+ this.userPrefs.resetData(userPrefs);
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ return userPrefs.getGuiSettings();
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ requireNonNull(guiSettings);
+ userPrefs.setGuiSettings(guiSettings);
+ }
+
+ @Override
+ public Path getDatastoreFilePath() {
+ return userPrefs.getDatastoreFilePath();
+ }
+
+ @Override
+ public void setDatastoreFilePath(Path datastoreFilePath) {
+ requireNonNull(datastoreFilePath);
+ userPrefs.setDatastoreFilePath(datastoreFilePath);
+ }
+
+ //=========== Datastore ================================================================================
+ @Override
+ public ReadOnlyDatastore getDatastore() {
+ return datastore;
+ }
+
+ @Override
+ public Datastore getMutableDatastore() {
+ return datastore;
+ }
+
+ @Override
+ public void setDatastore(ReadOnlyDatastore d) {
+ datastore.resetData(d);
+ }
+
+ @Override
+ public DatastoreVersionStorage getDatastoreVersionStorage() {
+ return datastoreVersionStorage;
+ }
+
+ @Override
+ public void commitDatastore() {
+ this.datastoreVersionStorage.commitDatastore(this.datastore);
+ }
+
+ @Override
+ public void undoChanges() {
+ assert this.datastoreVersionStorage.canUndo() : "Undo command cannot be carried out";
+
+ ReadOnlyDatastore prevDatastore = this.datastoreVersionStorage.executeUndo();
+ this.setDatastore(prevDatastore);
+ }
+
+ @Override
+ public void redoChanges() {
+ assert this.datastoreVersionStorage.canRedo() : "Redo command cannot be carried out";
+
+ ReadOnlyDatastore nextDatastore = this.datastoreVersionStorage.executeRedo();
+ this.setDatastore(nextDatastore);
+ }
+
+ @Override
+ public boolean canUndoDatastore() {
+ return this.datastoreVersionStorage.canUndo();
+ }
+
+ @Override
+ public boolean canRedoDatastore() {
+ return this.datastoreVersionStorage.canRedo();
+ }
+
+
+ //=========== Overrides ================================================================================
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof ModelManager)) {
+ return false;
+ }
+
+ ModelManager otherModelManager = (ModelManager) other;
+ return datastore.equals(otherModelManager.datastore)
+ && userPrefs.equals(otherModelManager.userPrefs);
+ }
+
+}
diff --git a/src/main/java/scrolls/elder/model/PersonStore.java b/src/main/java/scrolls/elder/model/PersonStore.java
new file mode 100644
index 00000000000..01f8facf338
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/PersonStore.java
@@ -0,0 +1,218 @@
+package scrolls.elder.model;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.FilteredList;
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.model.person.Name;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.PersonFactory;
+import scrolls.elder.model.person.UniquePersonList;
+
+/**
+ * Wraps all data for all persons stored.
+ * Duplicates are not allowed (by .isSamePerson comparison)
+ */
+public class PersonStore implements ReadOnlyPersonStore {
+
+ private static final Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ private final UniquePersonList persons;
+ private int personIdSequence;
+ private final FilteredList filteredPersons;
+ private final FilteredList filteredVolunteers;
+ private final FilteredList filteredBefriendees;
+ /**
+ * Creates an empty PersonStore.
+ */
+ public PersonStore() {
+ this.personIdSequence = 0;
+ this.persons = new UniquePersonList();
+
+ ObservableList personList = persons.asUnmodifiableObservableList();
+ filteredPersons = new FilteredList<>(personList);
+ filteredVolunteers = new FilteredList<>(personList, person -> person.isVolunteer());
+ filteredBefriendees = new FilteredList<>(personList, person -> !(person.isVolunteer()));
+ }
+
+ /**
+ * Creates a PersonStore using the data in the {@code toBeCopied}
+ */
+ public PersonStore(ReadOnlyPersonStore toBeCopied) {
+ this();
+ resetData(toBeCopied);
+ }
+
+ //// Collection-level getters and setters
+
+ @Override
+ public ObservableList getPersonList() {
+ return persons.asUnmodifiableObservableList();
+ }
+
+ @Override
+ public ObservableList getFilteredPersonList() {
+ return filteredPersons;
+ }
+
+ @Override
+ public ObservableList getFilteredVolunteerList() {
+ return filteredVolunteers;
+ }
+
+ @Override
+ public ObservableList getFilteredBefriendeeList() {
+ return filteredBefriendees;
+ }
+
+ @Override
+ public void updateFilteredPersonList(Predicate predicate) {
+ requireNonNull(predicate);
+ filteredPersons.setPredicate(predicate);
+ filteredVolunteers.setPredicate(person -> predicate.test(person) && person.isVolunteer());
+ filteredBefriendees.setPredicate(person -> predicate.test(person) && person.isBefriendee());
+ }
+
+ @Override
+ public void updateFilteredVolunteerList(Predicate predicate) {
+ requireNonNull(predicate);
+
+ // Only apply predicate to volunteers
+ filteredPersons.setPredicate(person -> person.isBefriendee() || predicate.test(person));
+ filteredVolunteers.setPredicate(person -> predicate.test(person) && person.isVolunteer());
+ }
+
+ @Override
+ public void updateFilteredBefriendeeList(Predicate predicate) {
+ requireNonNull(predicate);
+
+ // Only apply predicate to befriendees
+ filteredPersons.setPredicate(person -> predicate.test(person) || person.isVolunteer());
+ filteredBefriendees.setPredicate(person -> predicate.test(person) && person.isBefriendee());
+ }
+
+
+
+ /**
+ * Replaces the contents of the person list with {@code persons}.
+ * {@code persons} must not contain duplicate persons.
+ */
+ public void setPersonList(List persons) {
+ this.persons.setPersons(persons);
+ }
+
+ /**
+ * Resets the existing data of this {@code PersonStore} with {@code newData}.
+ * Implicitly sets the {@code personIdSequence} to the maximum ID in the new data plus one.
+ */
+ public void resetData(ReadOnlyPersonStore newData) {
+ requireNonNull(newData);
+
+ this.personIdSequence = newData.getPersonList()
+ .stream()
+ .map(Person::getPersonId)
+ .max(Integer::compare)
+ .map(max -> max + 1)
+ .orElse(0);
+ setPersonList(newData.getPersonList());
+ updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ }
+
+ //// Person-level CRUD operations
+
+ /**
+ * Returns true if a person with the same identity as {@code person} exists in the store.
+ */
+ public boolean hasPerson(Person person) {
+ requireNonNull(person);
+ return persons.contains(person);
+ }
+
+ /**
+ * Returns the person with the given ID.
+ */
+ public Person getPersonFromID(int id) {
+ return persons.getPersonFromID(id);
+ }
+
+ /**
+ * Returns the person's name with the given ID.
+ */
+ @Override
+ public Name getNameFromID(int id) {
+ Person target = persons.getPersonFromID(id);
+ return target.getName();
+ }
+
+ /**
+ * Adds a person to the store.
+ * The person must not already exist in the store.
+ */
+ public void addPerson(Person p) {
+ Person withId = PersonFactory.withIdFromPerson(personIdSequence, p);
+ persons.add(withId);
+ personIdSequence++;
+ }
+
+ /**
+ * Adds an existing person to the store.
+ * For the case where existing persons are read from storage.
+ */
+ public void addPersonWithId(Person p) {
+ persons.add(p);
+ if (p.getPersonId() >= personIdSequence) {
+ personIdSequence = p.getPersonId() + 1;
+ }
+ }
+
+ /**
+ * Replaces the given person {@code target} in the list with {@code editedPerson}.
+ * {@code target} must exist in the address book.
+ * The person identity of {@code editedPerson} must not be the same as another existing person in the address book.
+ */
+ public void setPerson(Person target, Person editedPerson) {
+ requireNonNull(editedPerson);
+ Person withId = PersonFactory.withIdFromPerson(target.getPersonId(), editedPerson);
+ persons.setPerson(target, withId);
+ }
+
+ /**
+ * Removes {@code key} from this {@code AddressBook}.
+ * {@code key} must exist in the address book.
+ */
+ public void removePerson(Person key) {
+ persons.remove(key);
+ }
+
+ //// Util methods
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("persons", persons)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof PersonStore)) {
+ return false;
+ }
+
+ PersonStore otherPersonStore = (PersonStore) other;
+ return persons.equals(otherPersonStore.persons);
+ }
+
+ @Override
+ public int hashCode() {
+ return persons.hashCode();
+ }
+}
diff --git a/src/main/java/scrolls/elder/model/ReadOnlyDatastore.java b/src/main/java/scrolls/elder/model/ReadOnlyDatastore.java
new file mode 100644
index 00000000000..3a750560360
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/ReadOnlyDatastore.java
@@ -0,0 +1,17 @@
+package scrolls.elder.model;
+
+/**
+ * Unmodifiable view of the entire datastore.
+ */
+public interface ReadOnlyDatastore {
+ /**
+ * Returns an unmodifiable view of the persons store.
+ * This list will not contain any duplicate persons.
+ */
+ ReadOnlyPersonStore getPersonStore();
+
+ /**
+ * Returns an unmodifiable view of the logs store.
+ */
+ ReadOnlyLogStore getLogStore();
+}
diff --git a/src/main/java/scrolls/elder/model/ReadOnlyLogStore.java b/src/main/java/scrolls/elder/model/ReadOnlyLogStore.java
new file mode 100644
index 00000000000..739918eddb2
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/ReadOnlyLogStore.java
@@ -0,0 +1,53 @@
+package scrolls.elder.model;
+
+import java.util.function.Predicate;
+
+import javafx.collections.ObservableList;
+import scrolls.elder.model.log.Log;
+
+
+/**
+ * Unmodifiable view of the log store.
+ */
+public interface ReadOnlyLogStore {
+
+ /**
+ * Returns an unmodifiable view of all logs in an unfiltered list.
+ * This list is unaffected by any filtering operations.
+ */
+ ObservableList getUnfilteredAllLogsList();
+
+ /**
+ * Returns an unmodifiable view of the log list.
+ * This Log List is affected by personID filtering operations.
+ * However, it is not affected by Predicate<Log> filtering operations.
+ */
+ ObservableList getLogList();
+
+ /**
+ * Returns a filtered view of the log list.
+ * This Log List is affected by personID filtering operations.
+ */
+ ObservableList getFilteredLogList();
+
+ /**
+ * Updates the filter of the filtered log list to filter by the given {@code predicate}.
+ * Predicate<Log> filters are independent of PersonID filters.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredLogList(Predicate predicate);
+
+ /**
+ * Returns the log with the given ID.
+ * {@code logId} must exist in the store.
+ */
+ Log getLogById(int logId);
+
+ /**
+ * Updates the log list to filter by the given {@code personId}.
+ * Supply null to reset the filter.
+ * This filter will persist for all filtering done with {@link LogStore#updateFilteredLogList}.
+ */
+ void updateFilteredLogListByPersonId(Integer personId);
+
+}
diff --git a/src/main/java/scrolls/elder/model/ReadOnlyPersonStore.java b/src/main/java/scrolls/elder/model/ReadOnlyPersonStore.java
new file mode 100644
index 00000000000..20bf1e261be
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/ReadOnlyPersonStore.java
@@ -0,0 +1,61 @@
+package scrolls.elder.model;
+
+import java.util.function.Predicate;
+
+import javafx.collections.ObservableList;
+import scrolls.elder.model.person.Name;
+import scrolls.elder.model.person.Person;
+
+/**
+ * Unmodifiable view of the person store.
+ */
+public interface ReadOnlyPersonStore {
+ /**
+ * Returns an unmodifiable view of the persons list.
+ * This list will not contain any duplicate persons.
+ */
+ ObservableList getPersonList();
+
+ /**
+ * Returns an unmodifiable view of the filtered person list.
+ * To update this view, see {@link #updateFilteredPersonList(Predicate)}
+ */
+ ObservableList getFilteredPersonList();
+
+ /**
+ * Returns an unmodifiable view of the filtered volunteer list.
+ * To update this view, see {@link #updateFilteredPersonList(Predicate)}
+ */
+ ObservableList getFilteredVolunteerList();
+
+ /**
+ * Returns an unmodifiable view of the filtered befriendee list.
+ * To update this view, see {@link #updateFilteredPersonList(Predicate)}
+ */
+ ObservableList getFilteredBefriendeeList();
+
+ /**
+ * Returns the name of the person in the list with the given id.
+ */
+ Name getNameFromID(int id);
+
+ /**
+ * Updates the filter of the filtered person list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredPersonList(Predicate predicate);
+
+ /**
+ * Updates the filter of the filtered volunteer list to filter by the given {@code predicate}.
+ * Befriendee list is not filtered.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredVolunteerList(Predicate predicate);
+
+ /**
+ * Updates the filter of the filtered befriendee list to filter by the given {@code predicate}.
+ * Volunteer list is not filtered.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredBefriendeeList(Predicate predicate);
+}
diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/scrolls/elder/model/ReadOnlyUserPrefs.java
similarity index 57%
rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
rename to src/main/java/scrolls/elder/model/ReadOnlyUserPrefs.java
index befd58a4c73..64a0021ad4a 100644
--- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
+++ b/src/main/java/scrolls/elder/model/ReadOnlyUserPrefs.java
@@ -1,8 +1,8 @@
-package seedu.address.model;
+package scrolls.elder.model;
import java.nio.file.Path;
-import seedu.address.commons.core.GuiSettings;
+import scrolls.elder.commons.core.GuiSettings;
/**
* Unmodifiable view of user prefs.
@@ -11,6 +11,6 @@ public interface ReadOnlyUserPrefs {
GuiSettings getGuiSettings();
- Path getAddressBookFilePath();
+ Path getDatastoreFilePath();
}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/scrolls/elder/model/UserPrefs.java
similarity index 66%
rename from src/main/java/seedu/address/model/UserPrefs.java
rename to src/main/java/scrolls/elder/model/UserPrefs.java
index 6be655fb4c7..6f1550a93fe 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/scrolls/elder/model/UserPrefs.java
@@ -1,4 +1,4 @@
-package seedu.address.model;
+package scrolls.elder.model;
import static java.util.Objects.requireNonNull;
@@ -6,7 +6,9 @@
import java.nio.file.Paths;
import java.util.Objects;
-import seedu.address.commons.core.GuiSettings;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import scrolls.elder.commons.core.GuiSettings;
/**
* Represents User's preferences.
@@ -14,7 +16,7 @@
public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
- private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path datastoreFilePath = Paths.get("data", "datastore.json");
/**
* Creates a {@code UserPrefs} with default values.
@@ -35,7 +37,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) {
public void resetData(ReadOnlyUserPrefs newUserPrefs) {
requireNonNull(newUserPrefs);
setGuiSettings(newUserPrefs.getGuiSettings());
- setAddressBookFilePath(newUserPrefs.getAddressBookFilePath());
+ setDatastoreFilePath(newUserPrefs.getDatastoreFilePath());
}
public GuiSettings getGuiSettings() {
@@ -47,13 +49,18 @@ public void setGuiSettings(GuiSettings guiSettings) {
this.guiSettings = guiSettings;
}
- public Path getAddressBookFilePath() {
- return addressBookFilePath;
+ public Path getDatastoreFilePath() {
+ return datastoreFilePath;
+ }
+
+ @JsonProperty("datastoreFilePath")
+ public String getDatastoreFilePathString() {
+ return datastoreFilePath.toString();
}
- public void setAddressBookFilePath(Path addressBookFilePath) {
- requireNonNull(addressBookFilePath);
- this.addressBookFilePath = addressBookFilePath;
+ public void setDatastoreFilePath(Path datastoreFilePath) {
+ requireNonNull(datastoreFilePath);
+ this.datastoreFilePath = datastoreFilePath;
}
@Override
@@ -69,19 +76,19 @@ public boolean equals(Object other) {
UserPrefs otherUserPrefs = (UserPrefs) other;
return guiSettings.equals(otherUserPrefs.guiSettings)
- && addressBookFilePath.equals(otherUserPrefs.addressBookFilePath);
+ && datastoreFilePath.equals(otherUserPrefs.datastoreFilePath);
}
@Override
public int hashCode() {
- return Objects.hash(guiSettings, addressBookFilePath);
+ return Objects.hash(guiSettings, datastoreFilePath);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Gui Settings : " + guiSettings);
- sb.append("\nLocal data file location : " + addressBookFilePath);
+ sb.append("\nLocal data file location : " + datastoreFilePath);
return sb.toString();
}
diff --git a/src/main/java/scrolls/elder/model/log/Log.java b/src/main/java/scrolls/elder/model/log/Log.java
new file mode 100644
index 00000000000..16f86a6fc5d
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/log/Log.java
@@ -0,0 +1,159 @@
+package scrolls.elder.model.log;
+
+import java.util.Date;
+import java.util.Objects;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import javafx.collections.ObservableList;
+import scrolls.elder.commons.util.AppUtil;
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.person.Person;
+
+/**
+ * Represents a log of a volunteer's interaction with a befriendee.
+ * Guarantees: immutable; IDs of both Persons are valid.
+ */
+public class Log {
+ public static final String MESSAGE_INVALID_ID = "The volunteer ID or befriendee ID is invalid.";
+ public static final int PLACEHOLDER_ID = -1;
+ private final int logId;
+ private final String title;
+ private final int volunteerId;
+ private final int befriendeeId;
+ private final int duration;
+ private final Date startDate;
+ private final String remarks;
+
+ /**
+ * Creates a log with all given fields.
+ */
+ @JsonCreator
+ public Log(@JsonProperty("logId") int logId, @JsonProperty("title") String title,
+ @JsonProperty("volunteerId") int volunteerId,
+ @JsonProperty("befriendeeId") int befriendeeId, @JsonProperty("duration") int duration,
+ @JsonProperty("startDate") Date startDate, @JsonProperty("remarks") String remarks) {
+ this.logId = logId;
+ this.title = title;
+ this.volunteerId = volunteerId;
+ this.befriendeeId = befriendeeId;
+ this.duration = duration;
+ this.startDate = startDate;
+ this.remarks = remarks;
+ }
+
+ /**
+ * Creates a log with the given volunteer ID and befriendee ID.
+ */
+ public Log(ReadOnlyDatastore datastore, String title, int volunteerId, int befriendeeId, int duration,
+ Date startDate, String remarks) {
+ AppUtil.checkArgument(areValidIds(datastore, volunteerId, befriendeeId), MESSAGE_INVALID_ID);
+ this.logId = PLACEHOLDER_ID;
+ this.title = title;
+ this.volunteerId = volunteerId;
+ this.befriendeeId = befriendeeId;
+ this.duration = duration;
+ this.startDate = startDate;
+ this.remarks = remarks;
+ }
+
+ /**
+ * Creates a log with the given log ID from the data of another Log.
+ */
+ public Log(int logId, Log log) {
+ this.logId = logId;
+ this.title = log.title;
+ this.volunteerId = log.volunteerId;
+ this.befriendeeId = log.befriendeeId;
+ this.duration = log.duration;
+ this.startDate = log.startDate;
+ this.remarks = log.remarks;
+ }
+
+ /**
+ * Checks if the given IDs are valid for the given datastore.
+ */
+ public boolean areValidIds(ReadOnlyDatastore datastore, int vid, int bid) {
+ ObservableList persons = datastore.getPersonStore().getPersonList();
+ boolean volunteerIdExists = false;
+ boolean befriendeeIdExists = false;
+
+ for (Person person : persons) {
+ if (person.isVolunteer() && person.getPersonId() == vid) {
+ volunteerIdExists = true;
+ }
+ if (!person.isVolunteer() && person.getPersonId() == bid) {
+ befriendeeIdExists = true;
+ }
+ }
+
+ return volunteerIdExists && befriendeeIdExists;
+ }
+
+ public int getLogId() {
+ return logId;
+ }
+
+ public String getLogTitle() {
+ return title;
+ }
+
+ public int getVolunteerId() {
+ return volunteerId;
+ }
+
+ public int getBefriendeeId() {
+ return befriendeeId;
+ }
+
+ public int getDuration() {
+ return duration;
+ }
+
+ public Date getStartDate() {
+ return startDate;
+ }
+
+ public String getRemarks() {
+ return remarks;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Log)) {
+ return false;
+ }
+
+ Log otherLog = (Log) other;
+ return title == otherLog.title
+ && volunteerId == otherLog.volunteerId
+ && befriendeeId == otherLog.befriendeeId
+ && duration == otherLog.duration
+ && startDate.equals(otherLog.startDate)
+ && remarks.equals(otherLog.remarks);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(title, volunteerId, befriendeeId, duration, startDate, remarks);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("Log ID", logId)
+ .add("Title", title)
+ .add("Volunteer ID", volunteerId)
+ .add("Befriendee ID", befriendeeId)
+ .add("Duration", duration)
+ .add("Start Date", startDate)
+ .add("Remarks", remarks)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/scrolls/elder/model/person/Address.java
similarity index 89%
rename from src/main/java/seedu/address/model/person/Address.java
rename to src/main/java/scrolls/elder/model/person/Address.java
index 469a2cc9a1e..ea0bb3094e4 100644
--- a/src/main/java/seedu/address/model/person/Address.java
+++ b/src/main/java/scrolls/elder/model/person/Address.java
@@ -1,7 +1,8 @@
-package seedu.address.model.person;
+package scrolls.elder.model.person;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import scrolls.elder.commons.util.AppUtil;
/**
* Represents a Person's address in the address book.
@@ -26,7 +27,7 @@ public class Address {
*/
public Address(String address) {
requireNonNull(address);
- checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS);
+ AppUtil.checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS);
value = address;
}
diff --git a/src/main/java/scrolls/elder/model/person/Befriendee.java b/src/main/java/scrolls/elder/model/person/Befriendee.java
new file mode 100644
index 00000000000..bcc692d9d06
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/person/Befriendee.java
@@ -0,0 +1,87 @@
+package scrolls.elder.model.person;
+
+import java.util.Optional;
+import java.util.Set;
+
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.model.tag.Tag;
+
+/**
+ * Represents a Befriendee in the address book.
+ * Guarantees: details are present and not null, field values are validated, immutable.
+ */
+public class Befriendee extends Person {
+ /**
+ * Creates a befriendee with the data from the relevant parameters
+ */
+ public Befriendee(Name name, Phone phone, Email email, Address address, Set tags,
+ Optional pairedWithName, Optional pairedWithId, int timeServed,
+ Optional latestLogId) {
+ super(name, phone, email, address, tags, new Role("befriendee"), pairedWithName, pairedWithId,
+ timeServed, latestLogId);
+ }
+
+ /**
+ * Creates a befriendee with the data of {@code p} and corresponding ID.
+ */
+ public Befriendee(int id, Person p) {
+ super(id, p);
+ }
+
+ @Override
+ public boolean isVolunteer() {
+ return false;
+ }
+
+ @Override
+ public boolean isBefriendee() {
+ return true;
+ }
+
+
+ @Override
+ public Role getRole() {
+ return this.role;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Befriendee)) {
+ return false;
+ }
+
+ // TODO figure out how to assert equals for date, without GitHub actions acting up, try using LocalDate
+ Befriendee otherBefriendee = (Befriendee) other;
+ return personId == otherBefriendee.personId
+ && name.equals(otherBefriendee.name)
+ && phone.equals(otherBefriendee.phone)
+ && email.equals(otherBefriendee.email)
+ && address.equals(otherBefriendee.address)
+ && tags.equals(otherBefriendee.tags)
+ && pairedWithName.equals(otherBefriendee.pairedWithName)
+ && pairedWithId.equals(otherBefriendee.pairedWithId)
+ && timeServed == otherBefriendee.timeServed
+ && latestLogId.equals(otherBefriendee.latestLogId);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("name", name)
+ .add("phone", phone)
+ .add("email", email)
+ .add("address", address)
+ .add("tags", tags)
+ .add("role", role)
+ .add("pairedWithName", pairedWithName.orElse(Name.getNone()))
+ .add("pairedWithId", pairedWithId.orElse(-1))
+ .add("timeServed", timeServed)
+ .add("latestLogId", latestLogId.orElse(-1))
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/scrolls/elder/model/person/Email.java
similarity index 94%
rename from src/main/java/seedu/address/model/person/Email.java
rename to src/main/java/scrolls/elder/model/person/Email.java
index c62e512bc29..0921f6aadae 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/scrolls/elder/model/person/Email.java
@@ -1,7 +1,8 @@
-package seedu.address.model.person;
+package scrolls.elder.model.person;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import scrolls.elder.commons.util.AppUtil;
/**
* Represents a Person's email in the address book.
@@ -40,7 +41,7 @@ public class Email {
*/
public Email(String email) {
requireNonNull(email);
- checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS);
+ AppUtil.checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS);
value = email;
}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/scrolls/elder/model/person/Name.java
similarity index 80%
rename from src/main/java/seedu/address/model/person/Name.java
rename to src/main/java/scrolls/elder/model/person/Name.java
index 173f15b9b00..c95e5c05aed 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/scrolls/elder/model/person/Name.java
@@ -1,7 +1,8 @@
-package seedu.address.model.person;
+package scrolls.elder.model.person;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import scrolls.elder.commons.util.AppUtil;
/**
* Represents a Person's name in the address book.
@@ -17,6 +18,7 @@ public class Name {
* otherwise " " (a blank string) becomes a valid input.
*/
public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*";
+ private static final Name NONE = new Name("None");
public final String fullName;
@@ -27,7 +29,7 @@ public class Name {
*/
public Name(String name) {
requireNonNull(name);
- checkArgument(isValidName(name), MESSAGE_CONSTRAINTS);
+ AppUtil.checkArgument(isValidName(name), MESSAGE_CONSTRAINTS);
fullName = name;
}
@@ -38,6 +40,9 @@ public static boolean isValidName(String test) {
return test.matches(VALIDATION_REGEX);
}
+ public static Name getNone() {
+ return NONE;
+ }
@Override
public String toString() {
@@ -56,7 +61,7 @@ public boolean equals(Object other) {
}
Name otherName = (Name) other;
- return fullName.equals(otherName.fullName);
+ return fullName.toLowerCase().equals(otherName.fullName.toLowerCase());
}
@Override
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/scrolls/elder/model/person/NameContainsKeywordsPredicate.java
similarity index 85%
rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
rename to src/main/java/scrolls/elder/model/person/NameContainsKeywordsPredicate.java
index 62d19be2977..1529621539c 100644
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ b/src/main/java/scrolls/elder/model/person/NameContainsKeywordsPredicate.java
@@ -1,10 +1,10 @@
-package seedu.address.model.person;
+package scrolls.elder.model.person;
import java.util.List;
import java.util.function.Predicate;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.commons.util.ToStringBuilder;
+import scrolls.elder.commons.util.StringUtil;
+import scrolls.elder.commons.util.ToStringBuilder;
/**
* Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
@@ -16,6 +16,10 @@ public NameContainsKeywordsPredicate(List keywords) {
this.keywords = keywords;
}
+ public boolean isEmpty() {
+ return keywords.isEmpty();
+ }
+
@Override
public boolean test(Person person) {
return keywords.stream()
diff --git a/src/main/java/scrolls/elder/model/person/Person.java b/src/main/java/scrolls/elder/model/person/Person.java
new file mode 100644
index 00000000000..0a5a8ba815e
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/person/Person.java
@@ -0,0 +1,226 @@
+package scrolls.elder.model.person;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import scrolls.elder.commons.util.CollectionUtil;
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.model.tag.Tag;
+
+/**
+ * Represents a Person in the address book.
+ * Guarantees: details are present and not null, field values are validated, immutable.
+ */
+public abstract class Person {
+ private static final int PLACEHOLDER_ID = -1;
+
+ // Identity fields
+ protected final int personId;
+ protected final Name name;
+ protected final Phone phone;
+ protected final Email email;
+ protected final Role role;
+
+ // Data fields
+ protected final Address address;
+ protected final Set tags = new HashSet<>();
+ protected final Optional pairedWithName;
+ protected final Optional pairedWithId;
+ protected final int timeServed;
+ protected final Optional latestLogId;
+
+ /**
+ * Every field must be present and not null.
+ */
+ public Person(Name name, Phone phone, Email email, Address address, Set tags, Role role,
+ Optional pairedWithName, Optional pairedWithId, int timeServed,
+ Optional latestLogId) {
+ CollectionUtil.requireAllNonNull(name, phone, email, address, tags, role, pairedWithName, pairedWithId);
+ this.personId = PLACEHOLDER_ID;
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+ this.address = address;
+ this.tags.addAll(tags);
+ this.role = role;
+ this.pairedWithName = pairedWithName;
+ this.pairedWithId = pairedWithId;
+ assert timeServed >= 0 : "Time served must be non-negative";
+ this.timeServed = timeServed;
+ this.latestLogId = latestLogId;
+ }
+
+ /**
+ * Creates a person with the given ID and data of {@code p}.
+ */
+ public Person(int personId, Person p) {
+ this.personId = personId;
+ this.name = p.getName();
+ this.phone = p.getPhone();
+ this.email = p.getEmail();
+ this.address = p.getAddress();
+ this.tags.addAll(p.getTags());
+ this.role = p.getRole();
+ this.pairedWithName = p.getPairedWithName();
+ this.pairedWithId = p.getPairedWithId();
+ this.timeServed = p.getTimeServed();
+ this.latestLogId = p.getLatestLogId();
+ }
+
+ public int getPersonId() {
+ return personId;
+ }
+
+ public Name getName() {
+ return name;
+ }
+
+ public Phone getPhone() {
+ return phone;
+ }
+
+ public Email getEmail() {
+ return email;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ /**
+ * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ */
+ public Set getTags() {
+ return Collections.unmodifiableSet(tags);
+ }
+
+ public Optional getPairedWithName() {
+ return pairedWithName;
+ }
+
+ public Optional getPairedWithId() {
+ return pairedWithId;
+ }
+ public int getTimeServed() {
+ return timeServed;
+ }
+
+ public Optional getLatestLogId() {
+ return latestLogId;
+ }
+
+ public boolean isPairPresent(Person person) {
+ return person.getPairedWithName().isPresent();
+ }
+
+ public boolean isPaired() {
+ return pairedWithName.isPresent();
+ }
+
+ public boolean isPairedWith(Person otherPerson) {
+ return pairedWithId.isPresent() && pairedWithId.get() == otherPerson.getPersonId();
+ }
+
+ /**
+ * Returns true if both persons have the same name.
+ * This defines a weaker notion of equality between two persons.
+ */
+ public boolean isSamePerson(Person otherPerson) {
+ if (otherPerson == this) {
+ return true;
+ }
+ return otherPerson != null && otherPerson.getName().equals(this.getName());
+ }
+
+ /**
+ * Returns true if both persons have the same id.
+ */
+ public boolean isSameId(Person otherPerson) {
+ if (otherPerson == this) {
+ return true;
+ }
+
+ return otherPerson != null && otherPerson.getPersonId() == this.getPersonId();
+ }
+
+ /**
+ * Returns true if the latest log id is present
+ */
+ public boolean isLatestLogPresent() {
+ return latestLogId.isPresent();
+ }
+
+ /**
+ * Returns true if person is a volunteer, and false if person is not a volunteer
+ */
+ public abstract boolean isVolunteer();
+
+ /**
+ * Returns true if person is a befriendee, and false if person is not a befriendee
+ */
+ public abstract boolean isBefriendee();
+
+ public abstract Role getRole();
+
+ //// Overrides
+
+ /**
+ * Returns true if both persons have the same identity and data fields.
+ * This defines a stronger notion of equality between two persons.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Person)) {
+ return false;
+ }
+
+ Person otherPerson = (Person) other;
+ return personId == otherPerson.personId
+ && name.equals(otherPerson.name)
+ && phone.equals(otherPerson.phone)
+ && email.equals(otherPerson.email)
+ && address.equals(otherPerson.address)
+ && tags.equals(otherPerson.tags)
+ && role.equals(otherPerson.role)
+ && pairedWithName.equals(otherPerson.pairedWithName)
+ && pairedWithId.equals(otherPerson.pairedWithId)
+ && timeServed == otherPerson.timeServed
+ && latestLogId.equals(latestLogId);
+ }
+
+ @Override
+ public int hashCode() {
+ // use this method for custom fields hashing instead of implementing your own
+ return Objects
+ .hash(personId, name, phone, email, address, tags, role, pairedWithName, pairedWithId,
+ timeServed, latestLogId);
+ }
+
+ // TODO potential issues with date
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("id", personId)
+ .add("name", name)
+ .add("phone", phone)
+ .add("email", email)
+ .add("address", address)
+ .add("tags", tags)
+ .add("role", role)
+ .add("pairedWithName", pairedWithName.orElse(Name.getNone()))
+ .add("pairedWithId", pairedWithId.orElse(-1))
+ .add("timeServed", timeServed)
+ .add("latestLogId", latestLogId.orElse(-1))
+ .toString();
+ }
+
+}
diff --git a/src/main/java/scrolls/elder/model/person/PersonFactory.java b/src/main/java/scrolls/elder/model/person/PersonFactory.java
new file mode 100644
index 00000000000..1ac2d5b94d8
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/person/PersonFactory.java
@@ -0,0 +1,48 @@
+package scrolls.elder.model.person;
+
+import java.util.Optional;
+import java.util.Set;
+
+import scrolls.elder.model.tag.Tag;
+
+/**
+ * Provides a factory method to create a concrete Person object.
+ */
+public class PersonFactory {
+ /**
+ * Creates a Person object without ID from the given parameters.
+ */
+ public static Person fromParams(Name modelName, Phone modelPhone, Email modelEmail, Address modelAddress,
+ Role modelRole, Set modelTags, Optional modelPairedWithName,
+ Optional modelPairedWithID, int modelTimeServed,
+ Optional modelLatestLogId) {
+ if (modelRole.isVolunteer()) {
+ return new Volunteer(modelName, modelPhone, modelEmail, modelAddress, modelTags,
+ modelPairedWithName, modelPairedWithID, modelTimeServed, modelLatestLogId);
+ } else {
+ assert modelRole.isBefriendee();
+ return new Befriendee(modelName, modelPhone, modelEmail, modelAddress, modelTags,
+ modelPairedWithName, modelPairedWithID, modelTimeServed, modelLatestLogId);
+ }
+ }
+
+ /**
+ * Creates a Person object with valid ID from the given parameters.
+ */
+ public static Person withIdFromParams(int id, Name modelName, Phone modelPhone, Email modelEmail,
+ Address modelAddress,
+ Role modelRole, Set modelTags, Optional modelPairedWithName,
+ Optional modelPairedWithID, int modelTimeServed,
+ Optional modelLatestLogId) {
+ return withIdFromPerson(id, fromParams(modelName, modelPhone, modelEmail, modelAddress,
+ modelRole, modelTags, modelPairedWithName, modelPairedWithID, modelTimeServed,
+ modelLatestLogId));
+ }
+
+ /**
+ * Creates a Person object from another Person object.
+ */
+ public static Person withIdFromPerson(int id, Person p) {
+ return p.isVolunteer() ? new Volunteer(id, p) : new Befriendee(id, p);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/scrolls/elder/model/person/Phone.java
similarity index 89%
rename from src/main/java/seedu/address/model/person/Phone.java
rename to src/main/java/scrolls/elder/model/person/Phone.java
index d733f63d739..440ae8a983e 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/scrolls/elder/model/person/Phone.java
@@ -1,7 +1,8 @@
-package seedu.address.model.person;
+package scrolls.elder.model.person;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import scrolls.elder.commons.util.AppUtil;
/**
* Represents a Person's phone number in the address book.
@@ -22,7 +23,7 @@ public class Phone {
*/
public Phone(String phone) {
requireNonNull(phone);
- checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS);
+ AppUtil.checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS);
value = phone;
}
diff --git a/src/main/java/scrolls/elder/model/person/Role.java b/src/main/java/scrolls/elder/model/person/Role.java
new file mode 100644
index 00000000000..3f278700dd2
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/person/Role.java
@@ -0,0 +1,76 @@
+package scrolls.elder.model.person;
+
+import static java.util.Objects.requireNonNull;
+
+import scrolls.elder.commons.util.AppUtil;
+
+/**
+ * Represents a Person's name in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidRole(String)}
+ */
+public class Role {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Role should only be either 'volunteer' or befriendee', and it should not be blank.";
+
+ /*
+ * The first character of the address must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "^befriendee|volunteer$";
+
+ public final String value;
+
+ /**
+ * Constructs a {@code Name}.
+ *
+ * @param role A valid name.
+ */
+ public Role(String role) {
+ requireNonNull(role);
+ AppUtil.checkArgument(isValidRole(role), MESSAGE_CONSTRAINTS);
+ this.value = role.toLowerCase();
+ }
+
+ /**
+ * Returns true if a given string is a valid name.
+ */
+ public static boolean isValidRole(String test) {
+ return test.toLowerCase().matches(VALIDATION_REGEX);
+ }
+
+ public boolean isVolunteer() {
+ return this.value.equals("volunteer");
+ }
+
+ public boolean isBefriendee() {
+ return this.value.equals("befriendee");
+ }
+
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Role)) {
+ return false;
+ }
+
+ Role otherName = (Role) other;
+ return value.equals(otherName.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/scrolls/elder/model/person/TagListContainsTagsPredicate.java b/src/main/java/scrolls/elder/model/person/TagListContainsTagsPredicate.java
new file mode 100644
index 00000000000..a6a4921b05e
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/person/TagListContainsTagsPredicate.java
@@ -0,0 +1,51 @@
+package scrolls.elder.model.person;
+
+import java.util.Set;
+import java.util.function.Predicate;
+
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.model.tag.Tag;
+
+/**
+ * Tests that a {@code Person}'s {@code Tags} matches any of the tags given.
+ */
+public class TagListContainsTagsPredicate implements Predicate {
+
+ private final Set tagList;
+
+ public TagListContainsTagsPredicate(Set tagList) {
+ this.tagList = tagList;
+ }
+
+ public boolean isEmpty() {
+ return tagList.isEmpty();
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return tagList.stream()
+ .anyMatch(tag -> person.getTags().contains(tag));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof TagListContainsTagsPredicate)) {
+ return false;
+ }
+
+ TagListContainsTagsPredicate otherTagListContainsTagsPredicate = (TagListContainsTagsPredicate) other;
+ return tagList.equals(otherTagListContainsTagsPredicate.tagList);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("tags", tagList).toString();
+ }
+}
+
+
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/scrolls/elder/model/person/UniquePersonList.java
similarity index 85%
rename from src/main/java/seedu/address/model/person/UniquePersonList.java
rename to src/main/java/scrolls/elder/model/person/UniquePersonList.java
index cc0a68d79f9..a1a0d79ce58 100644
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ b/src/main/java/scrolls/elder/model/person/UniquePersonList.java
@@ -1,15 +1,16 @@
-package seedu.address.model.person;
+package scrolls.elder.model.person;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
import java.util.Iterator;
import java.util.List;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
-import seedu.address.model.person.exceptions.DuplicatePersonException;
-import seedu.address.model.person.exceptions.PersonNotFoundException;
+import javafx.collections.transformation.FilteredList;
+import scrolls.elder.commons.util.CollectionUtil;
+import scrolls.elder.model.person.exceptions.DuplicatePersonException;
+import scrolls.elder.model.person.exceptions.PersonNotFoundException;
/**
* A list of persons that enforces uniqueness between its elements and does not allow nulls.
@@ -54,7 +55,7 @@ public void add(Person toAdd) {
* The person identity of {@code editedPerson} must not be the same as another existing person in the list.
*/
public void setPerson(Person target, Person editedPerson) {
- requireAllNonNull(target, editedPerson);
+ CollectionUtil.requireAllNonNull(target, editedPerson);
int index = internalList.indexOf(target);
if (index == -1) {
@@ -89,7 +90,7 @@ public void setPersons(UniquePersonList replacement) {
* {@code persons} must not contain duplicate persons.
*/
public void setPersons(List persons) {
- requireAllNonNull(persons);
+ CollectionUtil.requireAllNonNull(persons);
if (!personsAreUnique(persons)) {
throw new DuplicatePersonException();
}
@@ -103,7 +104,14 @@ public void setPersons(List persons) {
public ObservableList asUnmodifiableObservableList() {
return internalUnmodifiableList;
}
-
+ public Person getPersonFromID(int i) {
+ FilteredList listByID = internalList.filtered(person -> person.getPersonId() == i);
+ // TODO exception for duplicate id
+ if (listByID.size() != 1) {
+ System.out.println("List has duplicate id, should not be the case");
+ }
+ return listByID.get(0);
+ }
@Override
public Iterator iterator() {
return internalList.iterator();
diff --git a/src/main/java/scrolls/elder/model/person/Volunteer.java b/src/main/java/scrolls/elder/model/person/Volunteer.java
new file mode 100644
index 00000000000..7938f8e5424
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/person/Volunteer.java
@@ -0,0 +1,87 @@
+package scrolls.elder.model.person;
+
+import java.util.Optional;
+import java.util.Set;
+
+import scrolls.elder.commons.util.ToStringBuilder;
+import scrolls.elder.model.tag.Tag;
+
+/**
+ * Represents a Volunteer in the address book.
+ * Guarantees: details are present and not null, field values are validated, immutable.
+ */
+public class Volunteer extends Person {
+ /**
+ * Creates a volunteer with the data from the relevant parameters
+ */
+ public Volunteer(Name name, Phone phone, Email email, Address address, Set tags,
+ Optional pairedWithName, Optional pairedWithId, int timeServed,
+ Optional latestLogId) {
+ super(name, phone, email, address, tags, new Role("volunteer"), pairedWithName, pairedWithId,
+ timeServed, latestLogId);
+ }
+
+ /**
+ * Creates a volunteer with the data of {@code p} and corresponding ID.
+ */
+ public Volunteer(int id, Person p) {
+ super(id, p);
+ }
+
+ @Override
+ public boolean isVolunteer() {
+ return true;
+ }
+
+ @Override
+ public boolean isBefriendee() {
+ return false;
+ }
+
+ @Override
+ public Role getRole() {
+ return this.role;
+ }
+
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Volunteer)) {
+ return false;
+ }
+
+ // TODO figure out how to assert equals for date, without GitHub actions acting up, try using LocalDate
+ Volunteer otherVolunteer = (Volunteer) other;
+ return personId == otherVolunteer.personId
+ && name.equals(otherVolunteer.name)
+ && phone.equals(otherVolunteer.phone)
+ && email.equals(otherVolunteer.email)
+ && address.equals(otherVolunteer.address)
+ && tags.equals(otherVolunteer.tags)
+ && pairedWithName.equals(otherVolunteer.pairedWithName)
+ && pairedWithId.equals(otherVolunteer.pairedWithId)
+ && timeServed == otherVolunteer.timeServed
+ && latestLogId.equals(otherVolunteer.latestLogId);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("name", name)
+ .add("phone", phone)
+ .add("email", email)
+ .add("address", address)
+ .add("tags", tags)
+ .add("role", role)
+ .add("pairedWithName", pairedWithName.orElse(Name.getNone()))
+ .add("pairedWithId", pairedWithId.orElse(-1))
+ .add("timeServed", timeServed)
+ .add("latestLogId", latestLogId.orElse(-1))
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/scrolls/elder/model/person/exceptions/DuplicatePersonException.java
similarity index 87%
rename from src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java
rename to src/main/java/scrolls/elder/model/person/exceptions/DuplicatePersonException.java
index d7290f59442..fff1d3396c5 100644
--- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java
+++ b/src/main/java/scrolls/elder/model/person/exceptions/DuplicatePersonException.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person.exceptions;
+package scrolls.elder.model.person.exceptions;
/**
* Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same
diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/scrolls/elder/model/person/exceptions/PersonNotFoundException.java
similarity index 75%
rename from src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
rename to src/main/java/scrolls/elder/model/person/exceptions/PersonNotFoundException.java
index fa764426ca7..e05eb7fdef0 100644
--- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
+++ b/src/main/java/scrolls/elder/model/person/exceptions/PersonNotFoundException.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person.exceptions;
+package scrolls.elder.model.person.exceptions;
/**
* Signals that the operation is unable to find the specified person.
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/scrolls/elder/model/tag/Tag.java
similarity index 88%
rename from src/main/java/seedu/address/model/tag/Tag.java
rename to src/main/java/scrolls/elder/model/tag/Tag.java
index f1a0d4e233b..1ca9579e02c 100644
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ b/src/main/java/scrolls/elder/model/tag/Tag.java
@@ -1,7 +1,8 @@
-package seedu.address.model.tag;
+package scrolls.elder.model.tag;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import scrolls.elder.commons.util.AppUtil;
/**
* Represents a Tag in the address book.
@@ -21,7 +22,7 @@ public class Tag {
*/
public Tag(String tagName) {
requireNonNull(tagName);
- checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS);
+ AppUtil.checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS);
this.tagName = tagName;
}
diff --git a/src/main/java/scrolls/elder/model/util/SampleDataUtil.java b/src/main/java/scrolls/elder/model/util/SampleDataUtil.java
new file mode 100644
index 00000000000..1d99b0f9d99
--- /dev/null
+++ b/src/main/java/scrolls/elder/model/util/SampleDataUtil.java
@@ -0,0 +1,127 @@
+package scrolls.elder.model.util;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import scrolls.elder.model.Datastore;
+import scrolls.elder.model.LogStore;
+import scrolls.elder.model.PersonStore;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.log.Log;
+import scrolls.elder.model.person.Address;
+import scrolls.elder.model.person.Befriendee;
+import scrolls.elder.model.person.Email;
+import scrolls.elder.model.person.Name;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.Phone;
+import scrolls.elder.model.person.Volunteer;
+import scrolls.elder.model.tag.Tag;
+
+/**
+ * Contains utility methods for populating {@code AddressBook} with sample data.
+ */
+public class SampleDataUtil {
+
+ private static final Optional pairedWithNone = Optional.empty();
+ private static final Optional pairedWithNoID = Optional.empty();
+ private static final int sampleTimeServed = 0;
+ private static final Optional latestLogNoId = Optional.empty();
+
+ public static Person[] getSamplePersons() {
+
+ // First Pair 1, 1: Alex Yeoh and David Li
+ final Name alexYeohName = new Name("Alex Yeoh");
+ final Name davidLiName = new Name("David Li");
+
+ // Second Pair 2, 2: Bernice Yu and Irfan Ibrahim
+ final Name berniceYuName = new Name("Bernice Yu");
+ final Name irfanIbrahimName = new Name("Irfan Ibrahim");
+
+ return new Person[]{
+ new Volunteer(alexYeohName, new Phone("87438807"), new Email("alexyeoh@example.com"),
+ new Address("Blk 30 Geylang Street 29, #06-40"),
+ getTagSet("experienced"), Optional.of(davidLiName), Optional.of(3), 3,
+ Optional.of(1)),
+ new Volunteer(berniceYuName, new Phone("99272758"), new Email("berniceyu@example.com"),
+ new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"),
+ getTagSet("new", "student"), Optional.of(irfanIbrahimName), Optional.of(4), 3,
+ Optional.of(2)),
+ new Volunteer(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
+ new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), Collections.emptySet(),
+ pairedWithNone, pairedWithNoID, sampleTimeServed,
+ latestLogNoId),
+
+
+ new Befriendee(davidLiName, new Phone("91031282"), new Email("lidavid@example.com"),
+ new Address("Blk 436 Serangoon Gardens Street 26, #16-43"),
+ getTagSet("handicapped"), Optional.of(alexYeohName), Optional.of(0), 3,
+ Optional.of(1)),
+ new Befriendee(irfanIbrahimName, new Phone("92492021"), new Email("irfan@example.com"),
+ new Address("Blk 47 Tampines Street 20, #17-35"),
+ getTagSet("livesAlone"), Optional.of(berniceYuName), Optional.of(1), 3,
+ Optional.of(2)),
+ new Befriendee(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
+ new Address("Blk 45 Aljunied Street 85, #11-31"),
+ getTagSet("diabetic"), pairedWithNone, pairedWithNoID, sampleTimeServed,
+ latestLogNoId)
+ };
+ }
+
+ public static Log[] getSampleLogs(ReadOnlyDatastore datastore) {
+
+ Calendar calendar = new GregorianCalendar();
+
+ calendar.set(2024, Calendar.APRIL, 1);
+ final Date aprilFirst = calendar.getTime();
+ final Log logAlexToDavid1 = new Log(datastore, "First Visit",
+ 0, 3, 2, aprilFirst, "Was great! Alex and David had a good time.");
+
+ calendar.set(2024, Calendar.APRIL, 4);
+ final Date aprilFourth = calendar.getTime();
+ final Log logAlexToDavid2 = new Log(datastore, "Routine Check In",
+ 0, 3, 1, aprilFourth, "Alex dropped by to chat with David.");
+ final Log logBerniceToIrfan = new Log(datastore, "First Visit",
+ 1, 4, 3, aprilFourth, "Bernice and Irfan warmed up to each other.");
+
+
+ return new Log[]{
+ logAlexToDavid1,
+ logAlexToDavid2,
+ logBerniceToIrfan
+ };
+
+ }
+
+
+ public static ReadOnlyDatastore getSampleDatastore() {
+ Datastore sampleDs = new Datastore();
+
+ PersonStore sampleAb = sampleDs.getMutablePersonStore();
+ for (Person samplePerson : getSamplePersons()) {
+ sampleAb.addPerson(samplePerson);
+ }
+
+ LogStore sampleLb = sampleDs.getMutableLogStore();
+ for (Log sampleLog : getSampleLogs(sampleDs)) {
+ sampleLb.addLog(sampleLog);
+ }
+
+ return sampleDs;
+ }
+
+ /**
+ * Returns a tag set containing the list of strings given.
+ */
+ public static Set getTagSet(String... strings) {
+ return Arrays.stream(strings)
+ .map(Tag::new)
+ .collect(Collectors.toSet());
+ }
+
+}
diff --git a/src/main/java/scrolls/elder/storage/DatastoreStorage.java b/src/main/java/scrolls/elder/storage/DatastoreStorage.java
new file mode 100644
index 00000000000..1e928b78dc6
--- /dev/null
+++ b/src/main/java/scrolls/elder/storage/DatastoreStorage.java
@@ -0,0 +1,47 @@
+package scrolls.elder.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import scrolls.elder.commons.exceptions.DataLoadingException;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.ReadOnlyPersonStore;
+
+/**
+ * Represents a storage for {@link Datastore}.
+ */
+public interface DatastoreStorage {
+
+ /**
+ * Returns the file path of the data file.
+ */
+ Path getDatastoreFilePath();
+
+ /**
+ * Returns AddressBook data as a {@link ReadOnlyPersonStore}.
+ * Returns {@code Optional.empty()} if storage file is not found.
+ *
+ * @throws DataLoadingException if loading the data from storage failed.
+ */
+ Optional readDatastore() throws DataLoadingException;
+
+ /**
+ * @see #getDatastoreFilePath()
+ */
+ Optional readDatastore(Path filePath) throws DataLoadingException;
+
+ /**
+ * Saves the given {@link ReadOnlyPersonStore} to the storage.
+ *
+ * @param datastore cannot be null.
+ * @throws IOException if there was any problem writing to the file.
+ */
+ void saveDatastore(ReadOnlyDatastore datastore) throws IOException;
+
+ /**
+ * @see #saveDatastore(ReadOnlyDatastore)
+ */
+ void saveDatastore(ReadOnlyDatastore datastore, Path filePath) throws IOException;
+
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/scrolls/elder/storage/JsonAdaptedPerson.java
similarity index 50%
rename from src/main/java/seedu/address/storage/JsonAdaptedPerson.java
rename to src/main/java/scrolls/elder/storage/JsonAdaptedPerson.java
index bd1ca0f56c8..3e91ae9c377 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/scrolls/elder/storage/JsonAdaptedPerson.java
@@ -1,21 +1,24 @@
-package seedu.address.storage;
+package scrolls.elder.storage;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import scrolls.elder.commons.exceptions.IllegalValueException;
+import scrolls.elder.model.person.Address;
+import scrolls.elder.model.person.Email;
+import scrolls.elder.model.person.Name;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.PersonFactory;
+import scrolls.elder.model.person.Phone;
+import scrolls.elder.model.person.Role;
+import scrolls.elder.model.tag.Tag;
/**
* Jackson-friendly version of {@link Person}.
@@ -24,39 +27,67 @@ class JsonAdaptedPerson {
public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!";
+ private final String id;
private final String name;
private final String phone;
private final String email;
private final String address;
+ private final String role;
private final List tags = new ArrayList<>();
+ private final String pairedWithName;
+ private final String pairedWithId;
+ private final String timeServed;
+ private final String latestLogId;
/**
* Constructs a {@code JsonAdaptedPerson} with the given person details.
*/
@JsonCreator
- public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
- @JsonProperty("email") String email, @JsonProperty("address") String address,
- @JsonProperty("tags") List tags) {
+ public JsonAdaptedPerson(
+ @JsonProperty("id") String id,
+ @JsonProperty("name") String name,
+ @JsonProperty("phone") String phone,
+ @JsonProperty("email") String email,
+ @JsonProperty("address") String address,
+ @JsonProperty("role") String role,
+ @JsonProperty("tags") List tags,
+ @JsonProperty("pairedWithName") String pairedWithName,
+ @JsonProperty("pairedWithId") String pairedWithId,
+ @JsonProperty("timeServed") String timeServed,
+ @JsonProperty("latestLogId") String latestLogId) {
+
+ this.id = id;
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ this.role = role;
+ this.pairedWithName = pairedWithName;
if (tags != null) {
this.tags.addAll(tags);
}
+ this.pairedWithId = pairedWithId;
+ this.timeServed = timeServed;
+ this.latestLogId = latestLogId;
}
/**
* Converts a given {@code Person} into this class for Jackson use.
*/
public JsonAdaptedPerson(Person source) {
+ id = String.valueOf(source.getPersonId());
name = source.getName().fullName;
phone = source.getPhone().value;
email = source.getEmail().value;
address = source.getAddress().value;
+ role = source.getRole().value;
tags.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
+ pairedWithName = source.getPairedWithName().map(p -> p.fullName).orElse(null);
+ pairedWithId = source.getPairedWithId().map(Object::toString).orElse(null);
+ timeServed = String.valueOf(source.getTimeServed());
+ latestLogId = source.getLatestLogId().map(Object::toString).orElse(null);
}
/**
@@ -70,6 +101,11 @@ public Person toModelType() throws IllegalValueException {
personTags.add(tag.toModelType());
}
+ if (id == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, int.class.getSimpleName()));
+ }
+ final int modelId = Integer.parseInt(id);
+
if (name == null) {
throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
}
@@ -102,8 +138,28 @@ public Person toModelType() throws IllegalValueException {
}
final Address modelAddress = new Address(address);
+ final Optional modelPairedWithName;
+ modelPairedWithName = Optional.ofNullable(pairedWithName).map(Name::new);
+
+ final Optional modelPairedWithID;
+ modelPairedWithID = Optional.ofNullable(pairedWithId).map(Integer::parseInt);
+
+ Role modelRole;
+ if (role == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Role.class.getSimpleName()));
+ } else if (!Role.isValidRole(role)) {
+ throw new IllegalValueException(Role.MESSAGE_CONSTRAINTS);
+ } else {
+ modelRole = new Role(role);
+ }
final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
- }
+ final int modelTimeServed = Integer.parseInt(timeServed);
+
+ final Optional modelLatestLogId;
+ modelLatestLogId = Optional.ofNullable(latestLogId).map(Integer::parseInt);
+
+ return PersonFactory.withIdFromParams(modelId, modelName, modelPhone, modelEmail, modelAddress, modelRole,
+ modelTags, modelPairedWithName, modelPairedWithID, modelTimeServed, modelLatestLogId);
+ }
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/scrolls/elder/storage/JsonAdaptedTag.java
similarity index 89%
rename from src/main/java/seedu/address/storage/JsonAdaptedTag.java
rename to src/main/java/scrolls/elder/storage/JsonAdaptedTag.java
index 0df22bdb754..0c49e0ddf25 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java
+++ b/src/main/java/scrolls/elder/storage/JsonAdaptedTag.java
@@ -1,10 +1,10 @@
-package seedu.address.storage;
+package scrolls.elder.storage;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.tag.Tag;
+import scrolls.elder.commons.exceptions.IllegalValueException;
+import scrolls.elder.model.tag.Tag;
/**
* Jackson-friendly version of {@link Tag}.
diff --git a/src/main/java/scrolls/elder/storage/JsonDatastoreStorage.java b/src/main/java/scrolls/elder/storage/JsonDatastoreStorage.java
new file mode 100644
index 00000000000..9e4e4e189f1
--- /dev/null
+++ b/src/main/java/scrolls/elder/storage/JsonDatastoreStorage.java
@@ -0,0 +1,81 @@
+package scrolls.elder.storage;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import scrolls.elder.commons.core.LogsCenter;
+import scrolls.elder.commons.exceptions.DataLoadingException;
+import scrolls.elder.commons.exceptions.IllegalValueException;
+import scrolls.elder.commons.util.FileUtil;
+import scrolls.elder.commons.util.JsonUtil;
+import scrolls.elder.model.ReadOnlyDatastore;
+
+/**
+ * A class to access AddressBook data stored as a json file on the hard disk.
+ */
+public class JsonDatastoreStorage implements DatastoreStorage {
+
+ private static final Logger logger = LogsCenter.getLogger(JsonDatastoreStorage.class);
+
+ private final Path filePath;
+
+ public JsonDatastoreStorage(Path filePath) {
+ this.filePath = filePath;
+ }
+
+ public Path getDatastoreFilePath() {
+ return filePath;
+ }
+
+ @Override
+ public Optional readDatastore() throws DataLoadingException {
+ return readDatastore(filePath);
+ }
+
+ /**
+ * Similar to {@link DatastoreStorage#readDatastore()}.
+ *
+ * @param filePath location of the data. Cannot be null.
+ * @throws DataLoadingException if loading the data from storage failed.
+ */
+ public Optional readDatastore(Path filePath) throws DataLoadingException {
+ requireNonNull(filePath);
+
+ Optional jsonAddressBook = JsonUtil.readJsonFile(
+ filePath, JsonSerializableDatastore.class);
+ if (jsonAddressBook.isEmpty()) {
+ return Optional.empty();
+ }
+
+ try {
+ return Optional.of(jsonAddressBook.get().toModelType());
+ } catch (IllegalValueException ive) {
+ logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
+ throw new DataLoadingException(ive);
+ }
+ }
+
+ @Override
+ public void saveDatastore(ReadOnlyDatastore datastore) throws IOException {
+ saveDatastore(datastore, filePath);
+ }
+
+ /**
+ * Similar to {@link DatastoreStorage#saveDatastore(ReadOnlyDatastore)}.
+ *
+ * @param datastore
+ * @param filePath location of the data. Cannot be null.
+ */
+ public void saveDatastore(ReadOnlyDatastore datastore, Path filePath) throws IOException {
+ requireNonNull(datastore);
+ requireNonNull(filePath);
+
+ FileUtil.createIfMissing(filePath);
+ JsonUtil.saveJsonFile(new JsonSerializableDatastore(datastore), filePath);
+ }
+
+}
diff --git a/src/main/java/scrolls/elder/storage/JsonSerializableDatastore.java b/src/main/java/scrolls/elder/storage/JsonSerializableDatastore.java
new file mode 100644
index 00000000000..61e8aa2d7d0
--- /dev/null
+++ b/src/main/java/scrolls/elder/storage/JsonSerializableDatastore.java
@@ -0,0 +1,84 @@
+package scrolls.elder.storage;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonRootName;
+
+import scrolls.elder.commons.exceptions.IllegalValueException;
+import scrolls.elder.model.Datastore;
+import scrolls.elder.model.LogStore;
+import scrolls.elder.model.PersonStore;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.log.Log;
+import scrolls.elder.model.person.Person;
+
+/**
+ * An Immutable Datastore that is serializable to JSON format.
+ */
+@JsonRootName(value = "datastore")
+class JsonSerializableDatastore {
+
+ public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
+ public static final String MESSAGE_DUPLICATE_LOG = "Logs list contains duplicate log(s).";
+
+ private final List persons = new ArrayList<>();
+ private final List logs = new ArrayList<>();
+
+ /**
+ * Constructs a {@code JsonSerializableDatastore} with the given persons.
+ */
+ @JsonCreator
+ public JsonSerializableDatastore(
+ @JsonProperty("persons") List persons,
+ @JsonProperty("logs") List logs) {
+ this.persons.addAll(persons);
+ this.logs.addAll(logs);
+ }
+
+ /**
+ * Converts a given {@code ReadOnlyDatastore} into this class for Jackson use.
+ *
+ * @param source future changes to this will not affect the created {@code JsonSerializableDatastore}.
+ */
+ public JsonSerializableDatastore(ReadOnlyDatastore source) {
+ persons.addAll(source.getPersonStore()
+ .getPersonList()
+ .stream()
+ .map(JsonAdaptedPerson::new)
+ .collect(Collectors.toList()));
+
+ logs.addAll(source.getLogStore().getUnfilteredAllLogsList());
+ }
+
+ /**
+ * Converts this datastore into the model's {@code Datastore} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated.
+ */
+ public Datastore toModelType() throws IllegalValueException {
+ Datastore ds = new Datastore();
+ PersonStore personStore = ds.getMutablePersonStore();
+ LogStore logStore = ds.getMutableLogStore();
+
+ for (JsonAdaptedPerson jsonAdaptedPerson : persons) {
+ Person person = jsonAdaptedPerson.toModelType();
+ if (personStore.hasPerson(person)) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON);
+ }
+ personStore.addPersonWithId(person);
+ }
+ for (Log l : logs) {
+ if (logStore.hasLog(l)) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_LOG);
+ }
+ logStore.addLogWithId(l);
+ }
+
+ return ds;
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/scrolls/elder/storage/JsonUserPrefsStorage.java
similarity index 83%
rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
rename to src/main/java/scrolls/elder/storage/JsonUserPrefsStorage.java
index 48a9754807d..0a03bdc81fb 100644
--- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
+++ b/src/main/java/scrolls/elder/storage/JsonUserPrefsStorage.java
@@ -1,13 +1,13 @@
-package seedu.address.storage;
+package scrolls.elder.storage;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
-import seedu.address.commons.exceptions.DataLoadingException;
-import seedu.address.commons.util.JsonUtil;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
+import scrolls.elder.commons.exceptions.DataLoadingException;
+import scrolls.elder.commons.util.JsonUtil;
+import scrolls.elder.model.ReadOnlyUserPrefs;
+import scrolls.elder.model.UserPrefs;
/**
* A class to access UserPrefs stored in the hard disk as a json file
diff --git a/src/main/java/scrolls/elder/storage/Storage.java b/src/main/java/scrolls/elder/storage/Storage.java
new file mode 100644
index 00000000000..fe02f4021bc
--- /dev/null
+++ b/src/main/java/scrolls/elder/storage/Storage.java
@@ -0,0 +1,32 @@
+package scrolls.elder.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import scrolls.elder.commons.exceptions.DataLoadingException;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.ReadOnlyUserPrefs;
+import scrolls.elder.model.UserPrefs;
+
+/**
+ * API of the Storage component
+ */
+public interface Storage extends DatastoreStorage, UserPrefsStorage {
+
+ @Override
+ Optional readUserPrefs() throws DataLoadingException;
+
+ @Override
+ void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException;
+
+ @Override
+ Path getDatastoreFilePath();
+
+ @Override
+ Optional readDatastore() throws DataLoadingException;
+
+ @Override
+ void saveDatastore(ReadOnlyDatastore datastore) throws IOException;
+
+}
diff --git a/src/main/java/scrolls/elder/storage/StorageManager.java b/src/main/java/scrolls/elder/storage/StorageManager.java
new file mode 100644
index 00000000000..79141ecf9f0
--- /dev/null
+++ b/src/main/java/scrolls/elder/storage/StorageManager.java
@@ -0,0 +1,78 @@
+package scrolls.elder.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import scrolls.elder.commons.core.LogsCenter;
+import scrolls.elder.commons.exceptions.DataLoadingException;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.ReadOnlyUserPrefs;
+import scrolls.elder.model.UserPrefs;
+
+/**
+ * Manages storage of AddressBook data in local storage.
+ */
+public class StorageManager implements Storage {
+
+ private static final Logger logger = LogsCenter.getLogger(StorageManager.class);
+ private final DatastoreStorage datastoreStorage;
+ private final UserPrefsStorage userPrefsStorage;
+
+ /**
+ * Creates a {@code StorageManager} with the given {@code DatatstoreStorage} and {@code UserPrefStorage}.
+ */
+ public StorageManager(DatastoreStorage datastoreStorage, UserPrefsStorage userPrefsStorage) {
+ this.datastoreStorage = datastoreStorage;
+ this.userPrefsStorage = userPrefsStorage;
+ }
+
+ // ================ UserPrefs methods ==============================
+
+ @Override
+ public Path getUserPrefsFilePath() {
+ return userPrefsStorage.getUserPrefsFilePath();
+ }
+
+ @Override
+ public Optional readUserPrefs() throws DataLoadingException {
+ return userPrefsStorage.readUserPrefs();
+ }
+
+ @Override
+ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException {
+ userPrefsStorage.saveUserPrefs(userPrefs);
+ }
+
+
+ // ================ AddressBook methods ==============================
+
+ @Override
+ public Path getDatastoreFilePath() {
+ return datastoreStorage.getDatastoreFilePath();
+ }
+
+ @Override
+ public Optional readDatastore() throws DataLoadingException {
+ return readDatastore(datastoreStorage.getDatastoreFilePath());
+ }
+
+ @Override
+ public Optional readDatastore(Path filePath) throws DataLoadingException {
+ logger.fine("Attempting to read data from file: " + filePath);
+ return datastoreStorage.readDatastore(filePath);
+ }
+
+ @Override
+ public void saveDatastore(ReadOnlyDatastore datastore) throws IOException {
+ saveDatastore(datastore, datastoreStorage.getDatastoreFilePath());
+ }
+
+ @Override
+ public void saveDatastore(ReadOnlyDatastore datastore, Path filePath) throws IOException {
+ logger.fine("Attempting to write to data file: " + filePath);
+ datastoreStorage.saveDatastore(datastore, filePath);
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/scrolls/elder/storage/UserPrefsStorage.java
similarity index 69%
rename from src/main/java/seedu/address/storage/UserPrefsStorage.java
rename to src/main/java/scrolls/elder/storage/UserPrefsStorage.java
index e94ca422ea8..c71b28b7a82 100644
--- a/src/main/java/seedu/address/storage/UserPrefsStorage.java
+++ b/src/main/java/scrolls/elder/storage/UserPrefsStorage.java
@@ -1,15 +1,15 @@
-package seedu.address.storage;
+package scrolls.elder.storage;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
-import seedu.address.commons.exceptions.DataLoadingException;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
+import scrolls.elder.commons.exceptions.DataLoadingException;
+import scrolls.elder.model.ReadOnlyUserPrefs;
+import scrolls.elder.model.UserPrefs;
/**
- * Represents a storage for {@link seedu.address.model.UserPrefs}.
+ * Represents a storage for {@link UserPrefs}.
*/
public interface UserPrefsStorage {
@@ -27,7 +27,7 @@ public interface UserPrefsStorage {
Optional readUserPrefs() throws DataLoadingException;
/**
- * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage.
+ * Saves the given {@link ReadOnlyUserPrefs} to the storage.
* @param userPrefs cannot be null.
* @throws IOException if there was any problem writing to the file.
*/
diff --git a/src/main/java/scrolls/elder/ui/BefriendeeCard.java b/src/main/java/scrolls/elder/ui/BefriendeeCard.java
new file mode 100644
index 00000000000..fb5d00d686e
--- /dev/null
+++ b/src/main/java/scrolls/elder/ui/BefriendeeCard.java
@@ -0,0 +1,97 @@
+package scrolls.elder.ui;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Comparator;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.log.Log;
+import scrolls.elder.model.person.Person;
+
+/**
+ * An UI component that displays information of a {@code Person}.
+ */
+public class BefriendeeCard extends UiPart {
+
+ private static final String FXML = "BefriendeeListCard.fxml";
+ private static final String SMALL_LABEL = "list-cell-small-label";
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ public final Person befriendee;
+ private String dateFormatPattern = "dd MMM yyyy";
+ private DateFormat dateFormatter;
+ private ReadOnlyDatastore datastore;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label name;
+ @FXML
+ private Label id;
+ @FXML
+ private Label phone;
+ @FXML
+ private Label address;
+ @FXML
+ private Label email;
+ @FXML
+ private FlowPane tags;
+
+ @FXML
+ private Label pairedWith;
+ @FXML
+ private VBox latestLog;
+
+ /**
+ * Creates a {@code PersonCode} with the given {@code Person} and index to display.
+ */
+ public BefriendeeCard(Person person, int displayedIndex, ReadOnlyDatastore datastore) {
+ super(FXML);
+ this.befriendee = person;
+ dateFormatter = new SimpleDateFormat(dateFormatPattern);
+ this.datastore = datastore;
+ id.setText(displayedIndex + ". ");
+ name.setText(person.getName().fullName);
+ phone.setText(person.getPhone().value);
+ address.setText(person.getAddress().value);
+ email.setText(person.getEmail().value);
+ pairedWith.setText(person.getPairedWithName().map(p -> "Paired with: " + p.fullName).orElse("Not paired"));
+ person.getTags().stream()
+ .sorted(Comparator.comparing(tag -> tag.tagName))
+ .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+
+ // If latest log is present, add new log summary card, else add no logs
+ if (befriendee.isLatestLogPresent()) {
+ int latestLogId = befriendee.getLatestLogId().get();
+ Log latestLogInstance = datastore.getLogStore().getLogById(latestLogId);
+ String latestLogDateString = dateFormatter.format(latestLogInstance.getStartDate());
+ Label logTitle = new Label(latestLogInstance.getLogTitle());
+ Label logDate = new Label(latestLogDateString);
+ int partnerId = latestLogInstance.getVolunteerId();
+ String partnerName = datastore.getPersonStore().getNameFromID(partnerId).fullName;
+ Label logPartner = new Label("Volunteer: " + partnerName);
+ logTitle.getStyleClass().add(SMALL_LABEL);
+ logDate.getStyleClass().add(SMALL_LABEL);
+ logPartner.getStyleClass().add(SMALL_LABEL);
+ latestLog.getChildren().addAll(logTitle, logDate, logPartner);
+ } else {
+ Label noLog = new Label("No logs currently in Elder Scrolls");
+ noLog.getStyleClass().add(SMALL_LABEL);
+ latestLog.getStyleClass().remove("latest-log-card");
+ latestLog.getStyleClass().add("latest-log-card-disabled");
+ latestLog.getChildren().add(noLog);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/scrolls/elder/ui/CommandBox.java
similarity index 89%
rename from src/main/java/seedu/address/ui/CommandBox.java
rename to src/main/java/scrolls/elder/ui/CommandBox.java
index 9e75478664b..487b1d90a54 100644
--- a/src/main/java/seedu/address/ui/CommandBox.java
+++ b/src/main/java/scrolls/elder/ui/CommandBox.java
@@ -1,12 +1,13 @@
-package seedu.address.ui;
+package scrolls.elder.ui;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.scene.layout.Region;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.exceptions.ParseException;
+import scrolls.elder.logic.Logic;
+import scrolls.elder.logic.commands.CommandResult;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.logic.parser.exceptions.ParseException;
/**
* The UI component that is responsible for receiving user command inputs.
@@ -77,7 +78,7 @@ public interface CommandExecutor {
/**
* Executes the command and returns the result.
*
- * @see seedu.address.logic.Logic#execute(String)
+ * @see Logic#execute(String)
*/
CommandResult execute(String commandText) throws CommandException, ParseException;
}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/scrolls/elder/ui/HelpWindow.java
similarity index 93%
rename from src/main/java/seedu/address/ui/HelpWindow.java
rename to src/main/java/scrolls/elder/ui/HelpWindow.java
index 3f16b2fcf26..c61edae06d7 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/scrolls/elder/ui/HelpWindow.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package scrolls.elder.ui;
import java.util.logging.Logger;
@@ -8,14 +8,14 @@
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.stage.Stage;
-import seedu.address.commons.core.LogsCenter;
+import scrolls.elder.commons.core.LogsCenter;
/**
* Controller for a help page
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
+ public static final String USERGUIDE_URL = "https://ay2324s2-cs2103t-t09-3.github.io/tp/UserGuide.html";
public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
diff --git a/src/main/java/scrolls/elder/ui/LatestLogCard.java b/src/main/java/scrolls/elder/ui/LatestLogCard.java
new file mode 100644
index 00000000000..1695c91ec1a
--- /dev/null
+++ b/src/main/java/scrolls/elder/ui/LatestLogCard.java
@@ -0,0 +1,48 @@
+package scrolls.elder.ui;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.Region;
+import scrolls.elder.model.person.Name;
+
+/**
+ * A UI component that displays information of a {@code LatestLog}.
+ */
+public class LatestLogCard extends UiPart {
+ private static final String FXML = "LatestLogCard.fxml";
+
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ private String dateFormatPattern = "dd MMM yyyy";
+ private DateFormat dateFormatter;
+
+ @FXML
+ private Label title;
+ @FXML
+ private Label date;
+ @FXML
+ private Label partner;
+
+ /**
+ * Creates a {@code PersonCode} with the given {@code Person} and index to display.
+ */
+ public LatestLogCard(String titleString, Date startDate, Name partnerName) {
+ super(FXML);
+
+ dateFormatter = new SimpleDateFormat(dateFormatPattern);
+
+ title.setText(titleString);
+ date.setText(dateFormatter.format(startDate));
+ partner.setText("Pairee involved: " + partnerName.fullName);
+ }
+}
diff --git a/src/main/java/scrolls/elder/ui/LogCard.java b/src/main/java/scrolls/elder/ui/LogCard.java
new file mode 100644
index 00000000000..1b24cb8913c
--- /dev/null
+++ b/src/main/java/scrolls/elder/ui/LogCard.java
@@ -0,0 +1,68 @@
+package scrolls.elder.ui;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.log.Log;
+
+/**
+ * A UI component that displays information of a {@code Log}.
+ */
+public class LogCard extends UiPart {
+ private static final String FXML = "LogCard.fxml";
+
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ public final Log log;
+
+ private String dateFormatPattern = "dd MMM yyyy";
+ private DateFormat dateFormatter;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label id;
+ @FXML
+ private Label date;
+ @FXML
+ private Label title;
+ @FXML
+ private Label volunteer;
+ @FXML
+ private Label befriendee;
+ @FXML
+ private Label duration;
+ @FXML
+ private Label remarks;
+
+ /**
+ * Creates a {@code LogCode} with the given {@code Log} and index to display.
+ */
+ public LogCard(Log log, int displayedIndex, ReadOnlyDatastore datastore) {
+ super(FXML);
+
+ this.log = log;
+ dateFormatter = new SimpleDateFormat(dateFormatPattern);
+ String volunteerName = datastore.getPersonStore().getNameFromID(log.getVolunteerId()).fullName;
+ String befriendeeName = datastore.getPersonStore().getNameFromID(log.getBefriendeeId()).fullName;
+
+ id.setText(displayedIndex + ". ");
+ title.setText(log.getLogTitle());
+ befriendee.setText("Befriendee: " + befriendeeName);
+ volunteer.setText("Volunteer: " + volunteerName);
+ date.setText(dateFormatter.format(log.getStartDate()));
+ duration.setText("Duration: " + log.getDuration() + " hrs");
+ remarks.setText("Remarks: " + log.getRemarks());
+ }
+}
diff --git a/src/main/java/scrolls/elder/ui/LogListPanel.java b/src/main/java/scrolls/elder/ui/LogListPanel.java
new file mode 100644
index 00000000000..bd34a6d9bd8
--- /dev/null
+++ b/src/main/java/scrolls/elder/ui/LogListPanel.java
@@ -0,0 +1,56 @@
+package scrolls.elder.ui;
+
+import java.util.function.Predicate;
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+import scrolls.elder.commons.core.LogsCenter;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.log.Log;
+
+/**
+ * Panel containing the list of logs.
+ */
+public class LogListPanel extends UiPart {
+ public static final Predicate PREDICATE_SHOW_ALL_LOGS = unused -> true;
+ private static final String FXML = "LogListPanel.fxml";
+ private final Logger logger = LogsCenter.getLogger(LogListPanel.class);
+
+ private ReadOnlyDatastore datastore;
+
+ @FXML
+ private ListView logListView;
+
+ /**
+ * Creates a {@code PersonListPanel} with the given {@code ObservableList}.
+ */
+ public LogListPanel(ReadOnlyDatastore datastore) {
+ super(FXML);
+
+ this.datastore = datastore;
+ ObservableList logList = datastore.getLogStore().getFilteredLogList();
+ logListView.setItems(logList);
+ logListView.setCellFactory(listView -> new LogListPanel.LogListViewCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Log} using a {@code LogCard}.
+ */
+ class LogListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Log log, boolean empty) {
+ super.updateItem(log, empty);
+
+ if (empty || log == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new LogCard(log, getIndex() + 1, datastore).getRoot());
+ }
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/scrolls/elder/ui/MainWindow.java
similarity index 78%
rename from src/main/java/seedu/address/ui/MainWindow.java
rename to src/main/java/scrolls/elder/ui/MainWindow.java
index 79e74ef37c0..f88ee97cacb 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/scrolls/elder/ui/MainWindow.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package scrolls.elder.ui;
import java.util.logging.Logger;
@@ -10,12 +10,12 @@
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.logic.Logic;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.exceptions.ParseException;
+import scrolls.elder.commons.core.GuiSettings;
+import scrolls.elder.commons.core.LogsCenter;
+import scrolls.elder.logic.Logic;
+import scrolls.elder.logic.commands.CommandResult;
+import scrolls.elder.logic.commands.exceptions.CommandException;
+import scrolls.elder.logic.parser.exceptions.ParseException;
/**
* The Main Window. Provides the basic application layout containing
@@ -31,7 +31,9 @@ public class MainWindow extends UiPart {
private Logic logic;
// Independent Ui parts residing in this Ui container
- private PersonListPanel personListPanel;
+ private PersonListPanel befriendeeListPanel;
+ private PersonListPanel volunteerListPanel;
+ private LogListPanel logListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
@@ -42,7 +44,11 @@ public class MainWindow extends UiPart {
private MenuItem helpMenuItem;
@FXML
- private StackPane personListPanelPlaceholder;
+ private StackPane befriendeeListPanelPlaceholder;
+ @FXML
+ private StackPane volunteerListPanelPlaceholder;
+ @FXML
+ private StackPane logListPanelPlaceholder;
@FXML
private StackPane resultDisplayPlaceholder;
@@ -110,17 +116,23 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) {
* Fills up all the placeholders of this window.
*/
void fillInnerParts() {
- personListPanel = new PersonListPanel(logic.getFilteredPersonList());
- personListPanelPlaceholder.getChildren().add(personListPanel.getRoot());
+ befriendeeListPanel = new PersonListPanel(logic.getFilteredBefriendeeList(), logic.getDatastore());
+ befriendeeListPanelPlaceholder.getChildren().add(befriendeeListPanel.getRoot());
resultDisplay = new ResultDisplay();
resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot());
- StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath());
+ StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getDatastoreFilePath());
statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot());
CommandBox commandBox = new CommandBox(this::executeCommand);
commandBoxPlaceholder.getChildren().add(commandBox.getRoot());
+
+ volunteerListPanel = new PersonListPanel(logic.getFilteredVolunteerList(), logic.getDatastore());
+ volunteerListPanelPlaceholder.getChildren().add(volunteerListPanel.getRoot());
+
+ logListPanel = new LogListPanel(logic.getDatastore());
+ logListPanelPlaceholder.getChildren().add(logListPanel.getRoot());
}
/**
@@ -163,20 +175,24 @@ private void handleExit() {
primaryStage.hide();
}
- public PersonListPanel getPersonListPanel() {
- return personListPanel;
+ public PersonListPanel getBefriendeeListPanel() {
+ return befriendeeListPanel;
+ }
+
+ public PersonListPanel getVolunteerListPanel() {
+ return volunteerListPanel;
}
/**
* Executes the command and returns the result.
*
- * @see seedu.address.logic.Logic#execute(String)
+ * @see Logic#execute(String)
*/
private CommandResult executeCommand(String commandText) throws CommandException, ParseException {
try {
CommandResult commandResult = logic.execute(commandText);
logger.info("Result: " + commandResult.getFeedbackToUser());
- resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());
+ resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser(), false);
if (commandResult.isShowHelp()) {
handleHelp();
@@ -189,7 +205,7 @@ private CommandResult executeCommand(String commandText) throws CommandException
return commandResult;
} catch (CommandException | ParseException e) {
logger.info("An error occurred while executing command: " + commandText);
- resultDisplay.setFeedbackToUser(e.getMessage());
+ resultDisplay.setFeedbackToUser(e.getMessage(), true);
throw e;
}
}
diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/scrolls/elder/ui/PersonListPanel.java
similarity index 58%
rename from src/main/java/seedu/address/ui/PersonListPanel.java
rename to src/main/java/scrolls/elder/ui/PersonListPanel.java
index f4c501a897b..95d1fa8c0f8 100644
--- a/src/main/java/seedu/address/ui/PersonListPanel.java
+++ b/src/main/java/scrolls/elder/ui/PersonListPanel.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package scrolls.elder.ui;
import java.util.logging.Logger;
@@ -7,8 +7,10 @@
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.Region;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.model.person.Person;
+import scrolls.elder.commons.core.LogsCenter;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.person.Person;
+import scrolls.elder.model.person.Volunteer;
/**
* Panel containing the list of persons.
@@ -16,6 +18,7 @@
public class PersonListPanel extends UiPart {
private static final String FXML = "PersonListPanel.fxml";
private final Logger logger = LogsCenter.getLogger(PersonListPanel.class);
+ private ReadOnlyDatastore datastore;
@FXML
private ListView personListView;
@@ -23,8 +26,9 @@ public class PersonListPanel extends UiPart {
/**
* Creates a {@code PersonListPanel} with the given {@code ObservableList}.
*/
- public PersonListPanel(ObservableList personList) {
+ public PersonListPanel(ObservableList personList, ReadOnlyDatastore datastore) {
super(FXML);
+ this.datastore = datastore;
personListView.setItems(personList);
personListView.setCellFactory(listView -> new PersonListViewCell());
}
@@ -41,7 +45,14 @@ protected void updateItem(Person person, boolean empty) {
setGraphic(null);
setText(null);
} else {
- setGraphic(new PersonCard(person, getIndex() + 1).getRoot());
+ if (person.isVolunteer()) {
+ // if statement checks if person is a volunteer, hence safe to cast to type Volunteer
+ @SuppressWarnings("unchecked")
+ Volunteer vol = (Volunteer) person;
+ setGraphic(new VolunteerCard(vol, getIndex() + 1, datastore).getRoot());
+ } else {
+ setGraphic(new BefriendeeCard(person, getIndex() + 1, datastore).getRoot());
+ }
}
}
}
diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/scrolls/elder/ui/ResultDisplay.java
similarity index 53%
rename from src/main/java/seedu/address/ui/ResultDisplay.java
rename to src/main/java/scrolls/elder/ui/ResultDisplay.java
index 7d98e84eedf..a2c93905f2d 100644
--- a/src/main/java/seedu/address/ui/ResultDisplay.java
+++ b/src/main/java/scrolls/elder/ui/ResultDisplay.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package scrolls.elder.ui;
import static java.util.Objects.requireNonNull;
@@ -20,8 +20,16 @@ public ResultDisplay() {
super(FXML);
}
- public void setFeedbackToUser(String feedbackToUser) {
+ public void setFeedbackToUser(String feedbackToUser, boolean isErrorMsg) {
requireNonNull(feedbackToUser);
+ if (isErrorMsg) {
+ // Remove the error style class if it is present, so as to not duplicate the error style class
+ resultDisplay.getStyleClass().remove("result-display-error");
+ resultDisplay.getStyleClass().add("result-display-error");
+ } else {
+ resultDisplay.getStyleClass().remove("result-display-error");
+ }
+
resultDisplay.setText(feedbackToUser);
}
diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/scrolls/elder/ui/StatusBarFooter.java
similarity index 96%
rename from src/main/java/seedu/address/ui/StatusBarFooter.java
rename to src/main/java/scrolls/elder/ui/StatusBarFooter.java
index b577f829423..c0e1e09cb08 100644
--- a/src/main/java/seedu/address/ui/StatusBarFooter.java
+++ b/src/main/java/scrolls/elder/ui/StatusBarFooter.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package scrolls.elder.ui;
import java.nio.file.Path;
import java.nio.file.Paths;
diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/scrolls/elder/ui/Ui.java
similarity index 86%
rename from src/main/java/seedu/address/ui/Ui.java
rename to src/main/java/scrolls/elder/ui/Ui.java
index 17aa0b494fe..37ceb7ba75b 100644
--- a/src/main/java/seedu/address/ui/Ui.java
+++ b/src/main/java/scrolls/elder/ui/Ui.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package scrolls.elder.ui;
import javafx.stage.Stage;
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/scrolls/elder/ui/UiManager.java
similarity index 94%
rename from src/main/java/seedu/address/ui/UiManager.java
rename to src/main/java/scrolls/elder/ui/UiManager.java
index fdf024138bc..976c60842ad 100644
--- a/src/main/java/seedu/address/ui/UiManager.java
+++ b/src/main/java/scrolls/elder/ui/UiManager.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package scrolls.elder.ui;
import java.util.logging.Logger;
@@ -7,10 +7,10 @@
import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image;
import javafx.stage.Stage;
-import seedu.address.MainApp;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.Logic;
+import scrolls.elder.MainApp;
+import scrolls.elder.commons.core.LogsCenter;
+import scrolls.elder.commons.util.StringUtil;
+import scrolls.elder.logic.Logic;
/**
* The manager of the UI component.
diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/scrolls/elder/ui/UiPart.java
similarity index 97%
rename from src/main/java/seedu/address/ui/UiPart.java
rename to src/main/java/scrolls/elder/ui/UiPart.java
index fc820e01a9c..31d0bfcd3e1 100644
--- a/src/main/java/seedu/address/ui/UiPart.java
+++ b/src/main/java/scrolls/elder/ui/UiPart.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package scrolls.elder.ui;
import static java.util.Objects.requireNonNull;
@@ -6,7 +6,7 @@
import java.net.URL;
import javafx.fxml.FXMLLoader;
-import seedu.address.MainApp;
+import scrolls.elder.MainApp;
/**
* Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc.
diff --git a/src/main/java/scrolls/elder/ui/VolunteerCard.java b/src/main/java/scrolls/elder/ui/VolunteerCard.java
new file mode 100644
index 00000000000..5c0996bec5b
--- /dev/null
+++ b/src/main/java/scrolls/elder/ui/VolunteerCard.java
@@ -0,0 +1,103 @@
+package scrolls.elder.ui;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Comparator;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import scrolls.elder.model.ReadOnlyDatastore;
+import scrolls.elder.model.log.Log;
+import scrolls.elder.model.person.Volunteer;
+
+/**
+ * An UI component that displays information of a {@code Person}.
+ */
+public class VolunteerCard extends UiPart {
+
+ private static final String FXML = "VolunteerListCard.fxml";
+ private static final String SMALL_LABEL = "list-cell-small-label";
+
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ public final Volunteer vol;
+ private String dateFormatPattern = "dd MMM yyyy";
+ private DateFormat dateFormatter;
+ private ReadOnlyDatastore datastore;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label name;
+ @FXML
+ private Label id;
+ @FXML
+ private Label phone;
+ @FXML
+ private Label address;
+ @FXML
+ private Label email;
+ @FXML
+ private FlowPane tags;
+
+ @FXML
+ private Label pairedWith;
+ @FXML
+ private Label timeServed;
+ @FXML
+ private VBox latestLog;
+
+ /**
+ * Creates a {@code PersonCode} with the given {@code Person} and index to display.
+ */
+ public VolunteerCard(Volunteer vol, int displayedIndex, ReadOnlyDatastore datastore) {
+ super(FXML);
+ this.vol = vol;
+ dateFormatter = new SimpleDateFormat(dateFormatPattern);
+ this.datastore = datastore;
+
+ id.setText(displayedIndex + ". ");
+ name.setText(vol.getName().fullName);
+ phone.setText(vol.getPhone().value);
+ address.setText(vol.getAddress().value);
+ email.setText(vol.getEmail().value);
+ pairedWith.setText(vol.getPairedWithName().map(p -> "Paired with: " + p.fullName).orElse("Not paired"));
+ timeServed.setText("Time Served: " + vol.getTimeServed() + " hours");
+ vol.getTags().stream()
+ .sorted(Comparator.comparing(tag -> tag.tagName))
+ .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+
+
+ // If latest log is present, add new log summary card, else add no logs
+ if (vol.isLatestLogPresent()) {
+ int latestLogId = vol.getLatestLogId().get();
+ Log latestLogInstance = datastore.getLogStore().getLogById(latestLogId);
+ String latestLogDateString = dateFormatter.format(latestLogInstance.getStartDate());
+ Label logTitle = new Label(latestLogInstance.getLogTitle());
+ Label logDate = new Label(latestLogDateString);
+ int partnerId = latestLogInstance.getBefriendeeId();
+ String partnerName = datastore.getPersonStore().getNameFromID(partnerId).fullName;
+ Label logPartner = new Label("Befriendee: " + partnerName);
+ logTitle.getStyleClass().add(SMALL_LABEL);
+ logDate.getStyleClass().add(SMALL_LABEL);
+ logPartner.getStyleClass().add(SMALL_LABEL);
+ latestLog.getChildren().addAll(logTitle, logDate, logPartner);
+ } else {
+ Label noLog = new Label("No logs currently in Elder Scrolls");
+ noLog.getStyleClass().add(SMALL_LABEL);
+ latestLog.getStyleClass().remove("latest-log-card");
+ latestLog.getStyleClass().add("latest-log-card-disabled");
+ latestLog.getChildren().add(noLog);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
deleted file mode 100644
index 5d7185a9680..00000000000
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.logic.Messages;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
-
-/**
- * Adds a person to the address book.
- */
-public class AddCommand extends Command {
-
- public static final String COMMAND_WORD = "add";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
- + "Parameters: "
- + PREFIX_NAME + "NAME "
- + PREFIX_PHONE + "PHONE "
- + PREFIX_EMAIL + "EMAIL "
- + PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
- + "Example: " + COMMAND_WORD + " "
- + PREFIX_NAME + "John Doe "
- + PREFIX_PHONE + "98765432 "
- + PREFIX_EMAIL + "johnd@example.com "
- + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
- + PREFIX_TAG + "friends "
- + PREFIX_TAG + "owesMoney";
-
- public static final String MESSAGE_SUCCESS = "New person added: %1$s";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
-
- private final Person toAdd;
-
- /**
- * Creates an AddCommand to add the specified {@code Person}
- */
- public AddCommand(Person person) {
- requireNonNull(person);
- toAdd = person;
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
-
- if (model.hasPerson(toAdd)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
-
- model.addPerson(toAdd);
- return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd)));
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof AddCommand)) {
- return false;
- }
-
- AddCommand otherAddCommand = (AddCommand) other;
- return toAdd.equals(otherAddCommand.toAdd);
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("toAdd", toAdd)
- .toString();
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
deleted file mode 100644
index 1135ac19b74..00000000000
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.List;
-
-import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.logic.Messages;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
-
-/**
- * Deletes a person identified using it's displayed index from the address book.
- */
-public class DeleteCommand extends Command {
-
- public static final String COMMAND_WORD = "delete";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Deletes the person identified by the index number used in the displayed person list.\n"
- + "Parameters: INDEX (must be a positive integer)\n"
- + "Example: " + COMMAND_WORD + " 1";
-
- public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
-
- private final Index targetIndex;
-
- public DeleteCommand(Index targetIndex) {
- this.targetIndex = targetIndex;
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
-
- if (targetIndex.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
- model.deletePerson(personToDelete);
- return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete)));
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof DeleteCommand)) {
- return false;
- }
-
- DeleteCommand otherDeleteCommand = (DeleteCommand) other;
- return targetIndex.equals(otherDeleteCommand.targetIndex);
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("targetIndex", targetIndex)
- .toString();
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
deleted file mode 100644
index 72b9eddd3a7..00000000000
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.logic.Messages;
-import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-
-/**
- * Finds and lists all persons in address book whose name contains any of the argument keywords.
- * Keyword matching is case insensitive.
- */
-public class FindCommand extends Command {
-
- public static final String COMMAND_WORD = "find";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
- + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
- + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
- + "Example: " + COMMAND_WORD + " alice bob charlie";
-
- private final NameContainsKeywordsPredicate predicate;
-
- public FindCommand(NameContainsKeywordsPredicate predicate) {
- this.predicate = predicate;
- }
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(predicate);
- return new CommandResult(
- String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof FindCommand)) {
- return false;
- }
-
- FindCommand otherFindCommand = (FindCommand) other;
- return predicate.equals(otherFindCommand.predicate);
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("predicate", predicate)
- .toString();
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
deleted file mode 100644
index 84be6ad2596..00000000000
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-
-import seedu.address.model.Model;
-
-/**
- * Lists all persons in the address book to the user.
- */
-public class ListCommand extends Command {
-
- public static final String COMMAND_WORD = "list";
-
- public static final String MESSAGE_SUCCESS = "Listed all persons";
-
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(MESSAGE_SUCCESS);
- }
-}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
deleted file mode 100644
index 4ff1a97ed77..00000000000
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import java.util.Set;
-import java.util.stream.Stream;
-
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Parses input arguments and creates a new AddCommand object
- */
-public class AddCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the AddCommand
- * and returns an AddCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public AddCommand parse(String args) throws ParseException {
- ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
-
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
- || !argMultimap.getPreamble().isEmpty()) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
- }
-
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
- Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
- Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
- Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
- Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
- Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
-
- Person person = new Person(name, phone, email, address, tagList);
-
- return new AddCommand(person);
- }
-
- /**
- * Returns true if none of the prefixes contains empty {@code Optional} values in the given
- * {@code ArgumentMultimap}.
- */
- private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
- return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
deleted file mode 100644
index 3149ee07e0b..00000000000
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
-
-import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.commands.ClearCommand;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.ExitCommand;
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.logic.commands.HelpCommand;
-import seedu.address.logic.commands.ListCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-
-/**
- * Parses user input.
- */
-public class AddressBookParser {
-
- /**
- * Used for initial separation of command word and args.
- */
- private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)");
- private static final Logger logger = LogsCenter.getLogger(AddressBookParser.class);
-
- /**
- * Parses user input into command for execution.
- *
- * @param userInput full user input string
- * @return the command based on the user input
- * @throws ParseException if the user input does not conform the expected format
- */
- public Command parseCommand(String userInput) throws ParseException {
- final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
- if (!matcher.matches()) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
- }
-
- final String commandWord = matcher.group("commandWord");
- final String arguments = matcher.group("arguments");
-
- // Note to developers: Change the log level in config.json to enable lower level (i.e., FINE, FINER and lower)
- // log messages such as the one below.
- // Lower level log messages are used sparingly to minimize noise in the code.
- logger.fine("Command word: " + commandWord + "; Arguments: " + arguments);
-
- switch (commandWord) {
-
- case AddCommand.COMMAND_WORD:
- return new AddCommandParser().parse(arguments);
-
- case EditCommand.COMMAND_WORD:
- return new EditCommandParser().parse(arguments);
-
- case DeleteCommand.COMMAND_WORD:
- return new DeleteCommandParser().parse(arguments);
-
- case ClearCommand.COMMAND_WORD:
- return new ClearCommand();
-
- case FindCommand.COMMAND_WORD:
- return new FindCommandParser().parse(arguments);
-
- case ListCommand.COMMAND_WORD:
- return new ListCommand();
-
- case ExitCommand.COMMAND_WORD:
- return new ExitCommand();
-
- case HelpCommand.COMMAND_WORD:
- return new HelpCommand();
-
- default:
- logger.finer("This user input caused a ParseException: " + userInput);
- throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
- }
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
deleted file mode 100644
index 3527fe76a3e..00000000000
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-
-/**
- * Parses input arguments and creates a new DeleteCommand object
- */
-public class DeleteCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the DeleteCommand
- * and returns a DeleteCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public DeleteCommand parse(String args) throws ParseException {
- try {
- Index index = ParserUtil.parseIndex(args);
- return new DeleteCommand(index);
- } catch (ParseException pe) {
- throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
- }
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
deleted file mode 100644
index 2867bde857b..00000000000
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-
-import java.util.Arrays;
-
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-
-/**
- * Parses input arguments and creates a new FindCommand object
- */
-public class FindCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the FindCommand
- * and returns a FindCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public FindCommand parse(String args) throws ParseException {
- String trimmedArgs = args.trim();
- if (trimmedArgs.isEmpty()) {
- throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
- }
-
- String[] nameKeywords = trimmedArgs.split("\\s+");
-
- return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
- }
-
-}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
deleted file mode 100644
index 73397161e84..00000000000
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package seedu.address.model;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.List;
-
-import javafx.collections.ObservableList;
-import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.UniquePersonList;
-
-/**
- * Wraps all data at the address-book level
- * Duplicates are not allowed (by .isSamePerson comparison)
- */
-public class AddressBook implements ReadOnlyAddressBook {
-
- private final UniquePersonList persons;
-
- /*
- * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication
- * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html
- *
- * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication
- * among constructors.
- */
- {
- persons = new UniquePersonList();
- }
-
- public AddressBook() {}
-
- /**
- * Creates an AddressBook using the Persons in the {@code toBeCopied}
- */
- public AddressBook(ReadOnlyAddressBook toBeCopied) {
- this();
- resetData(toBeCopied);
- }
-
- //// list overwrite operations
-
- /**
- * Replaces the contents of the person list with {@code persons}.
- * {@code persons} must not contain duplicate persons.
- */
- public void setPersons(List persons) {
- this.persons.setPersons(persons);
- }
-
- /**
- * Resets the existing data of this {@code AddressBook} with {@code newData}.
- */
- public void resetData(ReadOnlyAddressBook newData) {
- requireNonNull(newData);
-
- setPersons(newData.getPersonList());
- }
-
- //// person-level operations
-
- /**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
- */
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return persons.contains(person);
- }
-
- /**
- * Adds a person to the address book.
- * The person must not already exist in the address book.
- */
- public void addPerson(Person p) {
- persons.add(p);
- }
-
- /**
- * Replaces the given person {@code target} in the list with {@code editedPerson}.
- * {@code target} must exist in the address book.
- * The person identity of {@code editedPerson} must not be the same as another existing person in the address book.
- */
- public void setPerson(Person target, Person editedPerson) {
- requireNonNull(editedPerson);
-
- persons.setPerson(target, editedPerson);
- }
-
- /**
- * Removes {@code key} from this {@code AddressBook}.
- * {@code key} must exist in the address book.
- */
- public void removePerson(Person key) {
- persons.remove(key);
- }
-
- //// util methods
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("persons", persons)
- .toString();
- }
-
- @Override
- public ObservableList getPersonList() {
- return persons.asUnmodifiableObservableList();
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof AddressBook)) {
- return false;
- }
-
- AddressBook otherAddressBook = (AddressBook) other;
- return persons.equals(otherAddressBook.persons);
- }
-
- @Override
- public int hashCode() {
- return persons.hashCode();
- }
-}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
deleted file mode 100644
index d54df471c1f..00000000000
--- a/src/main/java/seedu/address/model/Model.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package seedu.address.model;
-
-import java.nio.file.Path;
-import java.util.function.Predicate;
-
-import javafx.collections.ObservableList;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.model.person.Person;
-
-/**
- * The API of the Model component.
- */
-public interface Model {
- /** {@code Predicate} that always evaluate to true */
- Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
-
- /**
- * Replaces user prefs data with the data in {@code userPrefs}.
- */
- void setUserPrefs(ReadOnlyUserPrefs userPrefs);
-
- /**
- * Returns the user prefs.
- */
- ReadOnlyUserPrefs getUserPrefs();
-
- /**
- * Returns the user prefs' GUI settings.
- */
- GuiSettings getGuiSettings();
-
- /**
- * Sets the user prefs' GUI settings.
- */
- void setGuiSettings(GuiSettings guiSettings);
-
- /**
- * Returns the user prefs' address book file path.
- */
- Path getAddressBookFilePath();
-
- /**
- * Sets the user prefs' address book file path.
- */
- void setAddressBookFilePath(Path addressBookFilePath);
-
- /**
- * Replaces address book data with the data in {@code addressBook}.
- */
- void setAddressBook(ReadOnlyAddressBook addressBook);
-
- /** Returns the AddressBook */
- ReadOnlyAddressBook getAddressBook();
-
- /**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
- */
- boolean hasPerson(Person person);
-
- /**
- * Deletes the given person.
- * The person must exist in the address book.
- */
- void deletePerson(Person target);
-
- /**
- * Adds the given person.
- * {@code person} must not already exist in the address book.
- */
- void addPerson(Person person);
-
- /**
- * Replaces the given person {@code target} with {@code editedPerson}.
- * {@code target} must exist in the address book.
- * The person identity of {@code editedPerson} must not be the same as another existing person in the address book.
- */
- void setPerson(Person target, Person editedPerson);
-
- /** Returns an unmodifiable view of the filtered person list */
- ObservableList getFilteredPersonList();
-
- /**
- * Updates the filter of the filtered person list to filter by the given {@code predicate}.
- * @throws NullPointerException if {@code predicate} is null.
- */
- void updateFilteredPersonList(Predicate predicate);
-}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
deleted file mode 100644
index 57bc563fde6..00000000000
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ /dev/null
@@ -1,148 +0,0 @@
-package seedu.address.model;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-
-import java.nio.file.Path;
-import java.util.function.Predicate;
-import java.util.logging.Logger;
-
-import javafx.collections.ObservableList;
-import javafx.collections.transformation.FilteredList;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.model.person.Person;
-
-/**
- * Represents the in-memory model of the address book data.
- */
-public class ModelManager implements Model {
- private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
-
- private final AddressBook addressBook;
- private final UserPrefs userPrefs;
- private final FilteredList filteredPersons;
-
- /**
- * Initializes a ModelManager with the given addressBook and userPrefs.
- */
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
- requireAllNonNull(addressBook, userPrefs);
-
- logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
-
- this.addressBook = new AddressBook(addressBook);
- this.userPrefs = new UserPrefs(userPrefs);
- filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
- }
-
- public ModelManager() {
- this(new AddressBook(), new UserPrefs());
- }
-
- //=========== UserPrefs ==================================================================================
-
- @Override
- public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
- requireNonNull(userPrefs);
- this.userPrefs.resetData(userPrefs);
- }
-
- @Override
- public ReadOnlyUserPrefs getUserPrefs() {
- return userPrefs;
- }
-
- @Override
- public GuiSettings getGuiSettings() {
- return userPrefs.getGuiSettings();
- }
-
- @Override
- public void setGuiSettings(GuiSettings guiSettings) {
- requireNonNull(guiSettings);
- userPrefs.setGuiSettings(guiSettings);
- }
-
- @Override
- public Path getAddressBookFilePath() {
- return userPrefs.getAddressBookFilePath();
- }
-
- @Override
- public void setAddressBookFilePath(Path addressBookFilePath) {
- requireNonNull(addressBookFilePath);
- userPrefs.setAddressBookFilePath(addressBookFilePath);
- }
-
- //=========== AddressBook ================================================================================
-
- @Override
- public void setAddressBook(ReadOnlyAddressBook addressBook) {
- this.addressBook.resetData(addressBook);
- }
-
- @Override
- public ReadOnlyAddressBook getAddressBook() {
- return addressBook;
- }
-
- @Override
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return addressBook.hasPerson(person);
- }
-
- @Override
- public void deletePerson(Person target) {
- addressBook.removePerson(target);
- }
-
- @Override
- public void addPerson(Person person) {
- addressBook.addPerson(person);
- updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- }
-
- @Override
- public void setPerson(Person target, Person editedPerson) {
- requireAllNonNull(target, editedPerson);
-
- addressBook.setPerson(target, editedPerson);
- }
-
- //=========== Filtered Person List Accessors =============================================================
-
- /**
- * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of
- * {@code versionedAddressBook}
- */
- @Override
- public ObservableList getFilteredPersonList() {
- return filteredPersons;
- }
-
- @Override
- public void updateFilteredPersonList(Predicate predicate) {
- requireNonNull(predicate);
- filteredPersons.setPredicate(predicate);
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof ModelManager)) {
- return false;
- }
-
- ModelManager otherModelManager = (ModelManager) other;
- return addressBook.equals(otherModelManager.addressBook)
- && userPrefs.equals(otherModelManager.userPrefs)
- && filteredPersons.equals(otherModelManager.filteredPersons);
- }
-
-}
diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
deleted file mode 100644
index 6ddc2cd9a29..00000000000
--- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package seedu.address.model;
-
-import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
-
-/**
- * Unmodifiable view of an address book
- */
-public interface ReadOnlyAddressBook {
-
- /**
- * Returns an unmodifiable view of the persons list.
- * This list will not contain any duplicate persons.
- */
- ObservableList getPersonList();
-
-}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
deleted file mode 100644
index abe8c46b535..00000000000
--- a/src/main/java/seedu/address/model/person/Person.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package seedu.address.model.person;
-
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-
-import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.model.tag.Tag;
-
-/**
- * Represents a Person in the address book.
- * Guarantees: details are present and not null, field values are validated, immutable.
- */
-public class Person {
-
- // Identity fields
- private final Name name;
- private final Phone phone;
- private final Email email;
-
- // Data fields
- private final Address address;
- private final Set tags = new HashSet<>();
-
- /**
- * Every field must be present and not null.
- */
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
- requireAllNonNull(name, phone, email, address, tags);
- this.name = name;
- this.phone = phone;
- this.email = email;
- this.address = address;
- this.tags.addAll(tags);
- }
-
- public Name getName() {
- return name;
- }
-
- public Phone getPhone() {
- return phone;
- }
-
- public Email getEmail() {
- return email;
- }
-
- public Address getAddress() {
- return address;
- }
-
- /**
- * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
- */
- public Set getTags() {
- return Collections.unmodifiableSet(tags);
- }
-
- /**
- * Returns true if both persons have the same name.
- * This defines a weaker notion of equality between two persons.
- */
- public boolean isSamePerson(Person otherPerson) {
- if (otherPerson == this) {
- return true;
- }
-
- return otherPerson != null
- && otherPerson.getName().equals(getName());
- }
-
- /**
- * Returns true if both persons have the same identity and data fields.
- * This defines a stronger notion of equality between two persons.
- */
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Person)) {
- return false;
- }
-
- Person otherPerson = (Person) other;
- return name.equals(otherPerson.name)
- && phone.equals(otherPerson.phone)
- && email.equals(otherPerson.email)
- && address.equals(otherPerson.address)
- && tags.equals(otherPerson.tags);
- }
-
- @Override
- public int hashCode() {
- // use this method for custom fields hashing instead of implementing your own
- return Objects.hash(name, phone, email, address, tags);
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("name", name)
- .add("phone", phone)
- .add("email", email)
- .add("address", address)
- .add("tags", tags)
- .toString();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
deleted file mode 100644
index 1806da4facf..00000000000
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package seedu.address.model.util;
-
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import seedu.address.model.AddressBook;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Contains utility methods for populating {@code AddressBook} with sample data.
- */
-public class SampleDataUtil {
- public static Person[] getSamplePersons() {
- return new Person[] {
- new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
- new Address("Blk 30 Geylang Street 29, #06-40"),
- getTagSet("friends")),
- new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"),
- new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"),
- getTagSet("colleagues", "friends")),
- new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
- new Address("Blk 11 Ang Mo Kio Street 74, #11-04"),
- getTagSet("neighbours")),
- new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"),
- new Address("Blk 436 Serangoon Gardens Street 26, #16-43"),
- getTagSet("family")),
- new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"),
- new Address("Blk 47 Tampines Street 20, #17-35"),
- getTagSet("classmates")),
- new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
- new Address("Blk 45 Aljunied Street 85, #11-31"),
- getTagSet("colleagues"))
- };
- }
-
- public static ReadOnlyAddressBook getSampleAddressBook() {
- AddressBook sampleAb = new AddressBook();
- for (Person samplePerson : getSamplePersons()) {
- sampleAb.addPerson(samplePerson);
- }
- return sampleAb;
- }
-
- /**
- * Returns a tag set containing the list of strings given.
- */
- public static Set getTagSet(String... strings) {
- return Arrays.stream(strings)
- .map(Tag::new)
- .collect(Collectors.toSet());
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java
deleted file mode 100644
index f2e015105ae..00000000000
--- a/src/main/java/seedu/address/storage/AddressBookStorage.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package seedu.address.storage;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-
-import seedu.address.commons.exceptions.DataLoadingException;
-import seedu.address.model.ReadOnlyAddressBook;
-
-/**
- * Represents a storage for {@link seedu.address.model.AddressBook}.
- */
-public interface AddressBookStorage {
-
- /**
- * Returns the file path of the data file.
- */
- Path getAddressBookFilePath();
-
- /**
- * Returns AddressBook data as a {@link ReadOnlyAddressBook}.
- * Returns {@code Optional.empty()} if storage file is not found.
- *
- * @throws DataLoadingException if loading the data from storage failed.
- */
- Optional readAddressBook() throws DataLoadingException;
-
- /**
- * @see #getAddressBookFilePath()
- */
- Optional readAddressBook(Path filePath) throws DataLoadingException;
-
- /**
- * Saves the given {@link ReadOnlyAddressBook} to the storage.
- * @param addressBook cannot be null.
- * @throws IOException if there was any problem writing to the file.
- */
- void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
-
- /**
- * @see #saveAddressBook(ReadOnlyAddressBook)
- */
- void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException;
-
-}
diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
deleted file mode 100644
index 41e06f264e1..00000000000
--- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package seedu.address.storage;
-
-import static java.util.Objects.requireNonNull;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-import java.util.logging.Logger;
-
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.exceptions.DataLoadingException;
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.commons.util.FileUtil;
-import seedu.address.commons.util.JsonUtil;
-import seedu.address.model.ReadOnlyAddressBook;
-
-/**
- * A class to access AddressBook data stored as a json file on the hard disk.
- */
-public class JsonAddressBookStorage implements AddressBookStorage {
-
- private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class);
-
- private Path filePath;
-
- public JsonAddressBookStorage(Path filePath) {
- this.filePath = filePath;
- }
-
- public Path getAddressBookFilePath() {
- return filePath;
- }
-
- @Override
- public Optional