Refer to the guide Setting up and getting started.
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main (consisting of classes Main and MainApp) is in charge of the app launch and shut down.
The bulk of the app's work is done by the following four components:
UI: The UI of the App.Logic: The command executor.Model: Holds the data of the App in memory.Storage: Reads data from, and writes data to, the hard disk.Commons represents a collection of classes used by multiple other components.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1.
Each of the four main components (also shown in the diagram above),
interface with the same name as the Component.{Component Name}Manager class (which follows the corresponding API interface mentioned in the previous point.For example, the Logic component defines its API in the Logic.java interface and implements its functionality using the LogicManager.java class which follows the Logic interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
The API of this component is specified in Ui.java
The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, PersonListPanel, StatusBarFooter etc. All these, including the MainWindow, inherit from the abstract UiPart class which captures the commonalities between classes that represent parts of the visible GUI.
The UI component uses the JavaFx UI framework. The layout of these UI parts are defined in matching .fxml files that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml
The UI component,
Logic component.Model data so that the UI can be updated with the modified data.Logic component, because the UI relies on the Logic to execute commands.Model component, as it displays Person object residing in the Model.API : Logic.java
Here's a (partial) class diagram of the Logic component:
The sequence diagram below illustrates the interactions within the Logic component, taking execute("delete 1") API call as an example.
Note: The lifeline for DeleteCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
How the Logic component works:
Logic is called upon to execute a command, it is passed to an AddressBookParser object which in turn creates a parser that matches the command (e.g., DeleteCommandParser) and uses it to parse the command.Command object (more precisely, an object of one of its subclasses e.g., DeleteCommand) which is executed by the LogicManager.Model when it is executed (e.g. to delete a person).Model) to achieve.CommandResult object which is returned back from Logic.Here are the other classes in Logic (omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
AddressBookParser class creates an XYZCommandParser (XYZ is a placeholder for the specific command name e.g., AddCommandParser) which uses the other classes shown above to parse the user command and create a XYZCommand object (e.g., AddCommand) which the AddressBookParser returns back as a Command object.XYZCommandParser classes (e.g., AddCommandParser, DeleteCommandParser, ...) inherit from the Parser interface so that they can be treated similarly where possible e.g, during testing.API : Model.java
The Model component,
Person objects (which are contained in a UniquePersonList object).Person objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiable ObservableList<Person> that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.UserPref object that represents the user’s preferences. This is exposed to the outside as a ReadOnlyUserPref objects.Model represents data entities of the domain, they should make sense on their own without depending on other components)Note: An alternative (arguably, a more OOP) model is given below. It has a Tag list in the AddressBook, which Person references. This allows AddressBook to only require one Tag object per unique tag, instead of each Person needing their own Tag objects.
API : Storage.java
The Storage component,
AddressBookStorage and UserPrefStorage, which means it can be treated as either one (if only the functionality of only one is needed).Model component (because the Storage component's job is to save/retrieve objects that belong to the Model)Classes used by multiple components are in the seedu.address.commons package.
This section describes some noteworthy details on how certain features are implemented.
The proposed undo/redo mechanism is facilitated by VersionedAddressBook. It extends AddressBook with an undo/redo history, stored internally as an addressBookStateList and currentStatePointer. Additionally, it implements the following operations:
VersionedAddressBook#commit() — Saves the current address book state in its history.VersionedAddressBook#undo() — Restores the previous address book state from its history.VersionedAddressBook#redo() — Restores a previously undone address book state from its history.These operations are exposed in the Model interface as Model#commitAddressBook(), Model#undoAddressBook() and Model#redoAddressBook() respectively.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
Step 1. The user launches the application for the first time. The VersionedAddressBook will be initialized with the initial address book state, and the currentStatePointer pointing to that single address book state.
Step 2. The user executes delete 5 command to delete the 5th person in the address book. The delete command calls Model#commitAddressBook(), causing the modified state of the address book after the delete 5 command executes to be saved in the addressBookStateList, and the currentStatePointer is shifted to the newly inserted address book state.
Step 3. The user executes add n/David … to add a new person. The add command also calls Model#commitAddressBook(), causing another modified address book state to be saved into the addressBookStateList.
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.
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.
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
than attempting to perform the undo.
The following sequence diagram shows how an undo operation goes through the Logic component:
Note: The lifeline for UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Similarly, how an undo operation goes through the Model component is shown below:
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.
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.
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 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.
The following activity diagram summarizes what happens when a user executes a new command:
Aspect: How undo & redo executes:
Alternative 1 (current choice): Saves the entire address book.
Alternative 2: Individual command knows how to undo/redo by itself.
delete, just save the person being deleted).Target user profile:
Value proposition:
ContactsForGood (CFG) helps NGO administrators efficiently manage donors, volunteers, and partners by organising contacts and tracking engagement. With its typing-focused interface and offline, editable data, CFG hopes to streamline contact management, allowing administrators to focus on outreach and mission-critical tasks.
Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *
| Priority | As a … | I want to … | So that… |
|---|---|---|---|
| *** | NGO administrator | view all contacts in the contact list | I can quickly find relevant information |
| *** | NGO administrator | search for contacts by name, role | I can quickly find relevant personnel |
| *** | NGO administrator | have different roles for contacts (e.g., volunteers, donors, partners) | I can manage communications more efficiently |
| *** | NGO administrator | add a new volunteer's contact information | I can add volunteers to my organisation |
| *** | NGO administrator | set a volunteer's participation hours | I can see volunteer's participation hours |
| *** | NGO administrator | edit a volunteer's information | I can keep volunteer records up to date |
| *** | NGO administrator | delete a volunteer's contact information | I can remove irrelevant or erroneous entries |
| ** | NGO administrator | sort contacts by alphabetical or insertion order | I can easily locate and manage contacts efficiently |
| ** | NGO administrator | copy to clipboard a list of all emails of addressees in the current search | I can easily send out a mass email to relevant personnel |
| ** | NGO administrator | create groups (e.g. blood drive) which contain contacts | I can group contacts by their involvement in an event |
| ** | NGO administrator | see all contacts in a group | I can easily see everyone who is involved in an event |
| ** | NGO administrator | add contacts to a group | I can easily add a new participant in an event to the existing group |
| ** | NGO administrator | remove contacts from a group | I can easily remove someone who is no longer involved in an event from an existing group |
| ** | NGO administrator | delete a group | I can delete groups after the event has concluded to avoid clutter |
(For all use cases below, the System is ContactsForGood (CFG) and the Actor is the user, unless specified otherwise)
Use case: UC01 - Search for person(s)
MSS
User searches for a person by name.
CFG shows a list of persons which fit the criteria.
Use case ends.
Extensions
1a. User can also search by tag/role... etc.
Use case resumes at step 2.
2a. The list is empty.
Use case ends.
Use case: UC02 - Delete a person
MSS
User requests to list persons.
CFG shows a list of relevant persons.
User requests to delete a specific person in the list.
CFG deletes the person.
Use case ends.
Extensions
1a. User could also search for persons by name/tag/role... etc. (UC01).
Use case resumes at step 2.
2a. The list is empty.
Use case ends.
3a. The given index is invalid.
3a1. CFG shows an error message.
Use case resumes at step 2.
Use case: UC03 - Add volunteer hours
MSS
User requests to list persons.
CFG shows a list of relevant persons.
User requests to add some number of volunteer hours to a specific person in the list.
CFG adds the specified number of volunteer hours to the specified person's existing hours.
Use case ends.
Extensions
1a. User could also search for persons by name/tag/role... etc. (UC01).
Use case resumes at step 2.
2a. The list is empty.
Use case ends.
3a. The given index is invalid.
3a1. CFG shows an error message.
Use case resumes at step 2.
3b. The specified person is not a Volunteer.
3b1. CFG shows an error message.
Use case resumes at step 2.
Use case: UC04 - Get emails
MSS
User requests to list persons.
CFG shows a list of relevant persons.
User requests to get emails of persons on the list.
CFG copies to the user's clipboard a list of all emails of persons on the list.
Use case ends.
Extensions
1a. User could also search for persons by name/tag/role... etc. (UC01).
Use case resumes at step 2.
2a. The list is empty.
Use case ends.
Use case: UC05 - Create a group
MSS
User requests to list persons.
CFG shows a list of relevant persons.
User requests to create a group, with specified persons in the list as members.
CFG successfully creates the group.
Use case ends.
Extensions
1a. User could also search for persons by name/tag/role... etc. (UC01).
Use case resumes at step 2.
2a. The list is empty.
Use case ends.
3a. A group with the same name already exists.
3a1. CFG shows an error message.
Use case ends.
Use case: UC06 - List groups
MSS
User requests to see all existing groups.
CFG shows a list of all existing groups.
Use case ends.
Extensions
2a1. CFG shows an empty list.
Use case ends.
Use case: UC07 - View members of a group
MSS
User requests to see a list of groups (UC06).
User requests to see members of a group.
CFG lists all the members of the group.
Use case ends.
Extensions
1a. User may not require a list of groups.
Use case resumes at step 2.
2a. The group with specified name does not exist.
2a1. CFG shows an error message.
Use case ends.
3b. The group currently has no members.
3b1. CFG displays an empty list.
Use case ends.
Use case: UC08 - Add members to a group
MSS
User requests to list persons.
CFG shows a list of relevant persons.
User requests to add specified persons in the list to a group.
CFG adds the persons to the group.
Use case ends.
Extensions
1a. User could also search for persons by name/tag/role... etc. (UC01).
Use case resumes at step 2.
2a. The list is empty.
Use case ends.
3a. One or more of the specified indices is invalid.
3a1. CFG shows an error message.
Use case ends.
3b. The group does not exist.
3b1. CFG shows an error message.
Use case ends.
Use case: UC09 - Remove members from a group
MSS
User requests to see a list of groups (UC06).
User requests to view members of a group (UC07).
User requests to remove one or more members from a group.
CFG removes the specified members from the group.
Use case ends.
Extensions
1a. User may not require a list of groups.
Use case resumes at step 2.
2a. User may not require viewing members in a group.
Use case resumes at step 3.
3a. One or more of the specified indices is invalid.
3a1. CFG shows an error message.
Use case ends.
3b. The group does not exist.
3b1. CFG shows an error message.
Use case ends.
3c. One or more of the specified members is not currently in the group.
3c1. CFG shows an error message.
Use case ends.
Use case: UC10 - Rename a group
MSS
Extensions
1a. User may not require a list of groups.
Use case resumes at step 2.
2a. The group referenced by the user does not exist.
2a1. CFG shows an error message.
Use case ends.
2b. A group with the new name already exists.
2b1. CFG shows an error message.
Use case ends.
Use case: UC11 - Delete a group
MSS
User requests to see a list of groups (UC06).
User requests to delete a group.
CFG deletes the group.
Use case ends.
Extensions
1a. User may not require a list of groups.
Use case resumes at step 2.
2a. The group does not exist.
2a1. CFG shows an error message.
Use case ends.
Use case: UC12 - Sort contacts
MSS
User requests to list persons.
CFG shows a list of relevant persons.
User requests to sort the list by name.
CFG shows the list of persons sorted by name.
Use case ends.
Extensions
1a. User could also search for persons by name/tag/role... etc. (UC01).
Use case resumes at step 2.
2a. The list of persons is empty.
Use case ends
3a. User could also request to sort by default order (insertion order), volunteer hours, donation amount, or partnership end date.
3a1. CFG would sort by the specified sort option instead.
Use case resumes at step 4.
4a. User could continue to request for a different list of persons, by searching (UC01), viewing members in a group (UC07), etc.
4a1. Sort option persists for the new list.
Use case ends.
17 installed.| Term | Definition |
|---|---|
| Mainstream OS | Windows, Linux, Unix, MacOS |
| Human-Readable Format | A file format that can be easily understood and edited by people without specialized software (e.g., plain text or CSV) |
| Volunteer Hours | A record of the number of hours a volunteer has contributed to activities or events |
| Tag | A keyword or label applied to a contact for easier filtering or searching |
| Search Query | A user-provided input (e.g. name or role) to filter contacts in the list |
| Role | A Volunteer, Donor or Partner |
| Group | A named collection of contacts, allowing users to view specific subsets of people easily. |
| Partner | A contact who represents their company or organization as the main point of communication and collaboration. |
| Remote Server | A computer or system located on a network (e.g., the internet) that provides services or resources to other devices remotely. |
| CLI | A Command Line Interface |
Given below are instructions to test the app manually.
Note: These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.
Initial launch
Download the jar file and copy into an empty folder in which the app is allowed to create files.
Open a command window. Run the java -version command to ensure you are using Java 17.
Launch the jar file using the java -jar contactsforgood.jar command. The window size may not be optimum.
Saving window preferences
Resize the window to an optimum size. Move the window to a different location. Close the window.
Re-launch the app using the java -jar contactsforgood.jar command.
Expected: The most recent window size and location is retained.
Purpose: To ensure that commands can be executed in an empty state without crashing the application.
clear command.
Test case: list
Expected: "Listed all persons" should be printed.
The area below the output should not have any contacts displayed.
Test case: listGroup
Expected: "Listed all groups" should be printed.
The area below the output should not have any groups displayed.
Test case: delete 1
Expected: The error message "One or more indices provided are invalid" should be printed.
Test case: edit 1 n/John
Expected: The error message "One or more indices provided are invalid" should be printed.
Test case: search n/John
Expected: "0 person(s) listed!" should be printed.
The area below the output should not have any contacts displayed.
Test case: sort s/name
Expected: "No persons found. The list is reset to its default order." should be printed.
Test case: addToGroup g/Blood Drive m/1
Expected: The error message "There is no group with name Blood Drive." should be printed.
Test case: removeFromGroup g/Blood Drive m/1
Expected: The error message "There is no group with name Blood Drive." should be printed.
Test case: editGroupName g/Blood Drive g/Blood Collection
Expected: The error message "The existing group with the given name could not be found."
Other correct command formats can be tested and should print the similar messages as above.
Incorrect command formats can be tested and should print the respective error messages.
Lastly, after all these are done, one can test the following commands which will make the address book non-empty:
add and createGroup
Test case: add n/Nicholas Tan p/91234567 e/e1234567@u.nus.edu a/21 Lower Kent Ridge Rd, Singapore 119077
Expected: "New person added: Nicholas Tan; Phone: 91234567; Email: e1234567@u.nus.edu;
Address: 21 Lower Kent Ridge Rd, Singapore 119077; Tags: " should be printed.
Test case: createGroup g/NUS m/1
Expected: "Created group NUS" should be printed.
add n/Nicholas Tan p/91234567 e/e1234567@u.nus.edu a/21 Lower Kent Ridge Rd, Singapore 119077add n/Nicholas Tan p/91234567 e/e1234567@u.nus.edu a/21 Lower Kent Ridge Rd, Singapore 119077add n/Abel Lee p/81234567 e/d1234567@u.nus.edu a/20 Lower Kent Ridge Rd, Singapore 119077 t/Studentadd n/Jason Tan r/partner p/61234567 e/f1234567@u.nus.edu a/19 Lower Kent Ridge Rd, Singapore 119077 ped/2024-12-03add n/Bailey Ang r/volunteer p/91234568 e/g1234567@u.nus.edu a/18 Lower Kent Ridge Rd, Singapore 119077 h/19 Expected: "New volunteer added: Bailey Ang; Phone: 91234568; Email: g1234567@u.nus.edu; Address:
Singapore 119077; Tags: ; Hours: 19" is printed and the address book contains Bailey Ang, with the label
VOLUNTEER and fields with the correct details.add n/Tyson Chua r/donor p/61234568 e/tysonchua22@mailer.com a/17 Lower Kent Ridge Rd, Singapore 119077 d/12add n/Mary r/Tester p/61234568 e/tysonchua22@mailer.com a/17 Lower Kent Ridge Rd, Singapore 119077 d/12r/ together with t/.This test section's prerequisite is that you have gone through the add person section above, and therefore you should
have a person named "Jason Tan" who has the role Partner. It also assumes that Jason Tan has an index of 3 on the
displayed person list. If he does not, you should use the respective index that he has on the displayed list, for the
INDEX field in the edit command.
Editing a person's name
edit 3 n/Jason Ongedit 3 n/Abel Leeedit 3 n/Jason OngEditing a person's role attribute
edit 3 ped/2024-12-11edit 3 h/3Editing no attributes
edit 3The tester should attempt testing editing of other fields and other invalid inputs.
This test section's prerequisite is that you have gone through the add person section above, and therefore you should
have a person named "Tyson Chua" who has the role Donor. It also assumes that Tyson Chua has an index of 4 on the
displayed person list. If he does not, you should use the respective index that he has on the displayed list, for the
INDEX field in the edit command.
search n/Tysonsearch n/Tyson Jasonsearch n/Whoevenhassuchalongnameitsnotpossiblesearch name/Farhan Expected: The error message for invalid command, with examples of how to use the search command is printed.This test section's prerequisite is that you have gone through the add person section above and added a few contacts.
listlist n/This should list as usual should have the same behaviour as test case 1.This test section's prerequisite is that you have gone through the add person section above and added a few contacts.
sort s/namesort s/hourss/donations and s/end_date for donors and partners respectively.search r/personsort s/nameDeleting a person while all persons are being shown
Prerequisites: List all persons using the list command. Multiple persons in the list.
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.
Test case: delete 0
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
Other incorrect delete commands to try: delete, delete x, ... (where x is larger than the list size)
Expected: Similar to previous.
This test section's prerequisite is that you have gone through the add person section above and added at least 5 contacts.
createGroup g/Blood Drive m/1 2createGroup g/Blood Drive m/1 2createGroup g/Food Bank m/2-4createGroup g/Donation m/6INDICES.search r/personcreateGroup g/Donation m/1 2This test section's prerequisite is that you have gone through the add person section and create group section above.
addToGroup g/Blood Drive m/3 4addToGroup g/ThisGroupShouldNotExist m/1search r/personaddToGroup g/Food Bank m/1This test section's prerequisite is that you have gone through the add new members to existing group section.
removeFromGroup g/Blood Drive m/3 4removeFromGroup g/ThisGroupShouldNotExist m/1removeFromGroup g/Food Bank m/5search r/personremoveFromGroup g/Food Bank m/1This test section's prerequisite is that you have gone through the create group section and created a group named
Blood Drive and Food Bank.
editGroupName g/Blood Drive g/Blood Drive 2024editGroupName g/Blood Drive g/Blood Drive 2024editGroupName g/Blood Drive 2024 g/Food BankThis test section's prerequisite is that you have gone through the create group section above and created a few groups.
listGroupslistGroups n/This should list as usual should have the same behaviour as test case 1.This test section's prerequisite is that you have gone through the create group section above and created a few groups.
deleteGroup g/Blood Drive 2024deleteGroup g/Blood Drive 2024Team size: 5
n/John nn/Jack), the application treats the invalid prefix (nn/) as part of the value for a valid prefix (n/). This
can lead to confusion and incorrect data parsing. We plan to enhance the system to detect invalid prefixes and
provide clear feedback to users about their errors, ensuring proper parsing and validation of command inputs.