Developer Guide
Table of Contents
- Table of Contents
- Setting up and getting started
- Design
- Implementation
- Documentation, Logging, Dev-ops, Testing, and Configuration
- Appendix: Requirements
- Appendix: Instructions for manual testing
- Acknowledgements
Setting up and getting started
Refer to the guide Setting up and getting started.
Design
The
.puml
files used to create diagrams in this document can be found in the diagrams folder. Refer to the PlantUML Tutorial at se-edu/guides to learn how to create and edit diagrams.
Architecture
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
has two classes called Main
and MainApp
. It is responsible for,
- initializing the components in the correct sequence and connecting them up with each other during the app’s launch.
- shutting down the components and invoking cleanup methods where necessary when closing the app.
The rest of the app consists of four components.
-
UI
: Displays the user interface of the app. -
Logic
: Parses and executes the commands. -
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 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_contact 1
.
Each of the four main components (also shown in the diagram above),
- defines its API in an
interface
with the same name as the component. - implements its functionality using a concrete
{Component Name}Manager
class.
For example, the Logic
component defines its API in the Logic.java
interface and implements its functionality using the LogicManager.java
class. Other components interact with a given component through its interface, rather than the concrete class. The reasoning is to prevent outside component’s from being coupled to the implementation of a component, which is illustrated by the diagram below.
The sections below give more details of each component.
UI Component
The API of this component is specified in Ui.java
.
Here’s a partial class diagram of the UI
component without any of the task/contact management panels.
The UI consists of a MainWindow
that is made up of parts, e.g.CommandBox
, ResultDisplay
, PersonListPanel
, StatusBarFooter
, and etc. All these parts, including the MainWindow
, inherit from the abstract UiPart
class which captures the commonalities between classes that represent parts of the visible GUI.
Here’s another partial class diagram of the UI
component with the task and contact management panels.
The UI keeps track of which tab the user is currently viewing with the isContactTabShown
boolean. If the contacts tab is currently in view, MainWindow
contains PersonListPanel
and PersonTaskListPanel
, and it contains TaskListPanel
and TaskPersonListPanel
if otherwise.
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,
- executes user commands using the
Logic
component. - prompts users with command suggestions and allows them to auto-complete them using the
Logic
component. - listens for changes to
Model
data so that the UI can be updated with the modified data. - keeps a reference to the
Logic
component, because theUI
relies on theLogic
to execute commands. - depends on some classes in the
Model
component, as it displaysPerson
andTask
objects located in theModel
.
Logic Component
The API of this component is specified in Logic.java
.
Here’s a partial class diagram of the Logic
component:
How the Logic
component works:
- When
Logic
is called upon to execute a command, it uses theAddressBookParser
class to parse the user command. - This results in a
Command
object (more precisely, an object of one of its subclasses e.g.,AddContactCommand
) which is executed by theLogicManager
. - The command can communicate with the
Model
when it is executed (e.g. to add a person). - The result of the command execution is encapsulated as a
CommandResult
object which is returned fromLogic
.
The sequence diagram below illustrates the interactions within the Logic
component for the execute("delete_contact 1")
API call.
The lifeline for
DeleteContactCommandParser
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
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:
- When called upon to parse a user command, the
AddressBookParser
class creates anXYZCommandParser
(XYZ
is a placeholder for the specific command name e.g.,AddContactCommandParser
) - The
XYZCommandParser
class then uses the other classes shown above to parse the user command and create aXYZCommand
object (e.g.,AddContactCommand
), which theAddressBookParser
returns back as aCommand
object. - All
XYZCommandParser
classes (e.g.,AddContactCommandParser
,DeleteTaskCommandParser
, …) inherit from theParser
interface, so that they can be treated similarly where possible, e.g. during testing.
Model Component
The API of this component is specified in Model.java
.
Here’s a partial class diagram of the Model
component:
The
Task
and PersonTaskBridge
classes are left out of the above diagram for simplicity. Compared to the Person
class, they follow a similar structure of attribute composition.
The Model
component,
- stores the address book data, i.e. all
Person
objects (which are contained in aUniquePersonList
object). - stores the currently ‘selected’
Person
objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiableObservableList<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. - stores a
UserPref
object that represents the user’s preferences. This is exposed to the outside as aReadOnlyUserPref
object. - does not depend on any of the other three components, because the
Model
represents data entities of the domain, and they should make sense on their own without depending on other components.
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.Storage Component
The API of this component is specified in Storage.java
.
Here’s a partial class diagram of the Storage
component:
The Storage
component,
- can save both address book data and user preference data in
.json
format, and read them back into corresponding objects. - inherits from both
AddressBookStorage
andUserPrefStorage
, which means it can be treated as either one (if only the functionality of only one is needed). - depends on some classes in the
Model
component, because theStorage
component’s job is to save/retrieve objects that belong to theModel
.
Common classes
Classes used by multiple components are in the swift.commons
package.
Implementation
This section describes some noteworthy details on how certain features are implemented.
Many-to-many relationship between Person
and Task
The implementation of the contact-task relation is facilitated by PersonTaskBridge
and PersonTaskBridgeList
.
PersonTaskBridge
is a class containing a Person
UUID and a Task
UUID, representing a relation between a Person
and a Task
.
PersonTaskBridgeList
is a class containing a list of PersonTaskBridge
objects, representing all the relations between Person
and Task
objects in the AddressBook
. Additionally, it implements the following operations:
-
PersonTaskBridgeList#add(PersonTaskBridge)
- Saves a new relation between aPerson
and aTask
in the list. -
PersonTaskBridgeList#remove(PersonTaskBridge)
- Removes an existing relation between aPerson
and aTask
from the list. -
PersonTaskBridgeList#removePerson(Person)
andPersonTaskBridgeList#removeTask(Task)
- Removes all existing relations between aPerson
andTask
objects from the list.
These operations will be exposed in the Model
interface.
The following class diagram summarizes the relationship between PersonTaskBridge
and other classes:
Design Considerations
Aspect: How Person
and Task
are associated with PersonTaskBridge
-
Alternative 1 (current choice): Stores
Person
andTask
UUID inPersonTaskBridge
.- Pros: No need to handle the case of changing index when
Person
orTask
are filtered. Easier to maintain data integrity. - Cons: Requires changes in
Person
andTask
schema and storage.
- Pros: No need to handle the case of changing index when
-
Alternative 2: Stores
Person
andTask
index inPersonTaskBridge
.- Pros: No change is needed for
Person
andTask
schema. - Cons: Requires changes to
PersonTaskBridge
objects every time a command changesPerson
orTask
object index.
- Pros: No change is needed for
Optional Description
and Deadline
Fields
The Description
and Deadline
fields for tasks are optional for the users fill in. The implementation of this optionality is
facilitated by wrapping the values using the java.util.Optional<T>
class.
By doing so, we took advantage of the provided methods, e.g. orElse
, or
, and etc. This Optional
class thus helps to encapsulate
the logic of methods that depend on the presence or absence of the contained value.
Difference from Optional Tag
It is also optional for a Person
to have tags. To achieve this, a Person
stores the tags in a HashSet
.
If no tags are assigned to a Person
, the HashSet
will be empty. By doing so, a Person
can have any number of tags.
This differs from the implementation of optionality for Description
and Deadline
. For Description
and Deadline
,
a Task
can either contain the value or no value at all. Thus, due to the differing multiplicities, we could not use the
same implementation as tags.
View Task Details
The implementation of the task tab UI is facilitated by TaskCard
and TaskListPanel
.
TaskCard
and TaskListPanel
extends the superclass UiPart<Region>
and fills the UI container with a panel that displays the list of tasks, along with their assigned contacts and deadlines.
TaskListPanel
in is responsible for displaying the graphics of a task using a TaskCard
.
Command Suggestions and Command Auto-Completion
The implementation of Command Suggestions and Command Auto-Completion is facilitated by CommandSuggestor
in the Logic
Component. The CommandBox
UI component listens for changes in the command box textField and calls methods from CommandSuggestor
to reflect command suggestions and allow autocompletion.
CommandSuggestor
mainly implements the following operations:
-
CommandSuggestor#suggestCommand
- Suggests a command with the corresponding syntax based on the user’s current input -
CommandSuggestor#autocompleteCommand
- Completes the current user input according to the shown command suggestion
Design Considerations
Aspect: How to provide command suggestions to users
-
Alternative 1 (current choice): Provide command suggestion over the command box.
- Pros: Uses less screen real estate
- Cons: Only able to view one possible command
-
Alternative 2: Provide command suggestions in a separate display box itself.
- Pros: Able to display all possible commands
- Cons: Uses more screen real estate
Aspect: How to autocomplete commands for users
-
Alternative 1: Autocomplete up to next prefix according displayed command suggestion.
- Pros: Users can easily autocomplete the command shown with just one tab
- Cons: Users might have to backspace and complete the command again for commands with common prefixes. Eg.
add_contact
,add_task
-
Alternative 2 (current choice): Autocomplete up to the longest matching prefix of all possible commands.
- Pros: Easy to autocomplete commands with common prefixes
- Cons: Users might have to type a few characters more
Documentation, Logging, Dev-ops, Testing, and Configuration
To understand how to set up and maintain this project website, head over to the Documentation Guide.
You can learn how to run tests on Swift+ by going to the Testing Guide page.
To learn how to run and release Swift+ using Gradle, please visit the DevOps Guide page.
Please visit the Logging Guide to learn how we implement logging.
We also have files to configure properties of the app, which are detailed in the Configuration Guide.
Appendix: Requirements
This section covers the user requirements we attempt to meet in Swift+.
Target User Profile
Swift+ is designed for software engineering project leads who,
- need to keep track of many tasks with clients and colleagues.
- can type fast.
- prefer typing to mouse interactions.
- prefer desktop apps over other types.
Value proposition
Swift+ allows users to manage tasks with clients and colleagues faster than a typical GUI-driven app.
User stories
Priority levels:
- 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 contact | add a new contact to keep track of |
* * * |
user | view all contacts | get an overview of all contacts in my app |
* * * |
user | update a contact | update the particulars of a contact |
* * * |
user | delete a contact | remove contacts that I no longer need |
* * * |
user | find contacts by name | locate details of contacts without having to go through the entire list |
* * * |
user | add task for contact | add a task to a contact to keep track of |
* * * |
user | view tasks by contact | view tasks belonging to a contact |
* * * |
user | delete a task | remove tasks that I no longer need |
* * |
user | update a task | update the particulars of a task |
* * |
user | list all tasks | get an overview of all tasks in my app |
* * |
user | find tasks by name | locate details of tasks without having to go through the entire list |
* * |
forgetful user | autocomplete my commands | conveniently type commands without referring to the user guide excessively |
Use cases
For all use cases below, the system is Swift+
and the actor is the user
, unless specified otherwise.
Use case: UC1 - Create a contact
MSS:
- User requests to add a contact.
- Swift+ creates the contact.
Use case ends.
Extensions:
- 1a. Swift+ detects an error in the entered data.
- 1a1. Swift+ requests for the correct data.
- Use case resumes from step 1.
Use case: UC2 - Update a contact
MSS:
- User requests to view all contacts.
- Swift+ returns a list of all contacts.
- User requests to edit a specific contact in the list.
- Swift+ edits the details of the specified contact.
Use case ends.
Extensions:
- 2a. Swift+ returns an empty list.
- Use case ends.
- 3a. Swift+ detects the given index to be invalid.
- 3a1. Swift+ requests for a valid index.
- Use case resumes from step 3.
- 3b. Swift+ detects an error in the entered data.
- 3b1. Swift+ requests for the correct data.
- Use case resumes from step 3.
Use case: UC3 - Delete a person
MSS:
- User requests to view all contacts.
- Swift+ returns a list of all contacts.
- User requests to delete a specific contact in the list.
- Swift+ deletes the specified contact.
Use case ends.
Extensions:
- 2a. Swift+ returns an empty list.
- Use case ends.
- 3a. Swift+ detects the given index to be invalid.
- 3a1. Swift+ requests for a valid index.
- Use case resumes from step 3.
Use case: UC4 - Create a task
MSS:
- User requests to add a task.
- Swift+ creates the task.
Use case ends.
Extensions:
- 1a. Swift+ detects an error in the entered data.
- 1a1. Swift+ requests for the correct data.
- Use case resumes from step 1.
Use case: UC5 - Update a task
MSS:
- User requests to view all tasks.
- Swift+ returns a list of all tasks.
- User requests to edit a specific task in the list.
- Swift+ edits the details of the specified task.
Use case ends.
Extensions:
- 2a. Swift+ returns an empty list.
- Use case ends.
- 3a. Swift+ detects the given index to be invalid.
- 3a1. Swift+ requests for a valid index.
- Use case resumes from step 3.
- 3b. Swift+ detects an error in the entered data.
- 3b1. Swift+ requests for the correct data.
- Use case resumes from step 3.
Use case: UC6 - Delete a task
MSS:
- User requests to view all tasks.
- Swift+ returns a list of all tasks.
- User requests to delete a specific task in the list.
- Swift+ deletes the specified task.
Use case ends.
Extensions:
- 2a. Swift+ returns an empty list.
- Use case ends.
- 3a. Swift+ detects the given index to be invalid.
- 3a1. Swift+ requests for a valid index.
- Use case resumes from step 3.
Use case: UC7 - View tasks associated with a contact
MSS:
- User requests to view all contacts.
- Swift+ returns a list of all contacts.
- User requests to view tasks associated with a specified contact.
- Swift+ returns the contact and all associated tasks.
Extensions:
- 2a. Swift+ returns an empty list.
- Use case ends.
- 3a. Swift+ detects the given index to be invalid.
- 3a1. Swift+ requests for a valid index.
- Use case resumes from step 3.
Use case: UC8 - Mark a task as completed
MSS:
- User requests to view all tasks.
- Swift+ returns a list of all tasks.
- User requests to mark a task as completed.
- Swift+ marks the specified task as completed.
Extensions:
- 2a. Swift+ returns an empty list.
- Use case ends.
- 3a. Swift+ detects the given index to be invalid.
- 3a1. Swift+ requests for a valid index.
- Use case resumes from step 3.
- 3b. Swift+ detects that the specified task is already completed.
- Swift+ indicates that task is already completed.
- Use case ends.
Use case: UC9 - Assign a task to a contact
MSS:
- User requests to view all tasks and contacts.
- Swift+ returns all tasks and contacts.
- User requests to assign a task to a contact.
- Swift+ assigns the specified task to the specified contact.
Extensions:
- 2a. Swift+ returns an empty list of contacts.
- Use case ends.
- 2b. Swift+ returns an empty list of tasks.
- Use case ends.
- 3a. Swift+ detects given contact or task index to be invalid.
- Swift+ requests for a valid index.
- Use case resumes from step 3.
- 3b. Swift+ detects that the specified task is already assigned to the specified contact.
- Swift+ indicates that task is already assigned to the contact.
- Use case ends.
Non-Functional Requirements
- Should work on any mainstream OS as long as it has Java
11
or above installed. - Should be able to hold up to 1000 tasks and contacts without a noticeable sluggishness in performance for typical usage.
- 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.
- Data should be persistent and stored in the local machine’s storage.
- Product is designed for single user and is not required to handle collaboration between multiple users.
Glossary
- API: Stands for application programming interface, which is a set of definitions and protocols for building and integrating application software.
- Bridge: Maps a relationship between a contact and a task.
- GUI: Stands for graphical user interface, which is a system interface that uses visual icons, menus, and a mouse to manage interactions with the system.
- Mainstream OS: Stands for mainstream operating systems, which includes Windows, Linux, Unix, and OS-X.
- UUID: Stands for universally unique identifier, which is used for identifying information that needs to be unique within a system.
Appendix: Instructions for manual testing
Given below are instructions to test the app manually.
These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.
Launch and shutdown
-
Initial launch
- Download the jar file and copy into an empty folder.
- Double-click the jar file Expected: Shows the GUI with a set of sample contacts. 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 by double-clicking the jar file.
Expected: The most recent window size and location is retained.
Adding a Task
-
Adding a Task
- Prerequisites: task “Foo” isn’t already added. If added, delete it first.
- Test case:
add_task n/Foo d/Foo
Expected: Task “Foo” is added to the task list. Details of the added task shown in the status message. - Test case:
add_task n/Foo d/Foo
Expected: No task is added. Error details shown in the status message.
-
Adding a Task with deadline
- Prerequisites: deadline “Bar” isn’t already added. If added, delete it first.
- Test case:
add_task n/Bar d/Bar dl/02-02-2022 2200
Expected: Deadline “Bar” is added to the task list. Details of the added deadline shown in the status message. - Test case:
add_task n/Bar d/Bar dl/02-02-2022 2200
Expected: No deadline is added. Error details shown in the status message.
Mark / Unmark a Task
-
Marking a Task as completed
- Prerequisites: task “Foo” is already added. If not added, add it first. Foo should be the first index in the task list.
- Test case:
mark 1
Expected: Task “Foo” is marked as completed. Details of the completed task shown in the status message. - Test case:
mark 1
Expected: No task is marked as completed. Error details shown in the status message. - Test case:
mark 0
Expected: No task is marked as completed. Error details shown in the status message.
-
Unmarking a Task as incomplete
- Prerequisites: task “Bar” is already added. If not added, add it first. Foo should be the first index in the task list and already marked as complete.
- Test case:
unmark 1
Expected: Task “Bar” is marked as uncompleted. Details of the uncompleted task shown in the status message. - Test case:
unmark 1
Expected: No task is marked as uncompleted. Error details shown in the status message. - Test case:
unmark 0
Expected: No task is marked as uncompleted. Error details shown in the status message.
Switching Lists
-
List all tasks
- Test case:
list_task
Expected: All tasks are listed in the task list and, if not already on the “Task List” view it’s swapped to it. Details of the listed tasks shown in the status message. - Test case:
list_contact
Expected: All contacts are listed in the contact list and, if not already on the “Contact List” view it’s swapped to it. Details of the listed contacts shown in the status message. - Test case: type
ctrl + tab
Expected: The current list is switched to the other list.
- Test case:
Viewing Task and Contact Assocation
-
Select Task to view Contact Assocation
- Prerequisites: task “Foo” is already added and assigned to contact “Alex”. If not added, add it first. Foo should be the first index in the task list.
- Test case:
select_task 1
Expected: Task “Foo” is selected, the view is swapped to the “Task List” and the contact list is updated to show all contacts associated with the task. Details of the selected task shown in the status message. - Test case:
select_task 0
Expected: No task is selected. Error details shown in the status message.
-
Select Contact to view Task Association
- Prerequisites: contact “Alex” is already added and assigned to task “Foo”. If not added, add it first. Alex should be the first index in the contact list.
- Test case:
select_contact 1
Expected: Contact “Alex” is selected, the view is swapped to the “Contact List” and the task list is updated to show all tasks associated with the contact. Details of the selected contact shown in the status message. - Test case:
select_contact 0
Expected: No contact is selected. Error details shown in the status message.
Finding Task or Contact
-
Find Task
- Prerequisites: task “Foo” is already added and “Bar” is not added. If not added, add it first.
- Test case:
find_task Foo
Expected: All tasks containing “Foo” are listed in the task list. Details of the listed tasks shown in the status message. - Test case:
find_task Bar
Expected: No tasks are listed in the task list. Details of the listed tasks shown in the status message.
-
Find Contact
- Prerequisites: contact “Alex” is already added and “Bob is not added”. If not added, add it first.
- Test case:
find_contact Alex
Expected: All contacts containing “Alex” are listed in the contact list. Details of the listed contacts shown in the status message. - Test case:
find_contact Bob
Expected: No contacts are listed in the contact list. Details of the listed contacts shown in the status message.
Deleting a Task or Contact
-
Deleting a Contact or Task while all Contact or Task are being shown
- Prerequisites: List all Contacts or Tasks using the
list_contact
orlist_task
command. - Test case:
delete_contact 1
ordelete_task 1
Expected: First Contact or Task is deleted from the list. Details of the deleted Contact or Task shown in the status message. - Test case:
delete_contact 0
ordelete_task 0
Expected: No Contact, Task is deleted. Error details shown in the status message. Status bar remains the same. - Other incorrect delete commands to try:
delete_contact
/delete_task
,delete_contact x
/delete_task x
,...
(where x is larger than the list size)
Expected: Similar to previous.
- Prerequisites: List all Contacts or Tasks using the
Editing a Task or Contact
-
Editing a Contact or Task while all Contact or Task are being shown
- Prerequisites: List all Contacts or Tasks using the
list_contact
orlist_task
command. There should be at least one contact or task. - Test case:
edit_contact 1 n/Alex Yeoh p/98765432
Expected: First Contact is edited to have the name “Alex Yeoh” and phone number “98765432”. Details of the edited Contact shown in the status message. - Test case:
edit_contact 0 n/Alex Yeoh p/98765432
Expected: No Contact is edited. Error details shown in the status message. Status bar remains the same. - Test case:
edit_task 1 n/Bar d/Foo dl/02-02-2022 2200
Expected: First Task is edited to have the name “Bar”, description “Foo” and deadline “02-02-2022 2200”. Details of the edited Task shown in the status message. - Test case:
edit_task 0 n/Bar d/Foo dl/02-02-2022 2200
Expected: No Task is edited. Error details shown in the status message. Status bar remains the same. - Other incorrect edit commands to try:
edit_contact
/edit_task
,edit_contact x ...
/edit_task x ...
,...
(where x is larger than the list size)
Expected: Similar to previous.
- Prerequisites: List all Contacts or Tasks using the
Autocomplete
- type
li
then presstab
Expected: Autocomplete the command based on the current command text, autocompleting it tolist\_
. - type
list_t
then presstab
Expected: Autocomplete the command based on the current command text, autocompleting it tolist_task
.
Clearing all entries
- Clear all exisiting data in the application
- Test case:
clear
Expected: All data is cleared from the application. Status message shows the number of contacts and tasks cleared.
- Test case:
Saving data
- Shutdown the app by typing
exit
into the command box. - Re-launch the app by double-clicking the jar file.
Expected: The most recent state is saved. - Dealing with missing/corrupted data files
- Corrupt the data file in
data/addressbook.json
by adding random characters to make the JSON file unreadable or by simply deleting it. - Re-launch the app by double-clicking the jar file.
Expected: The app will start with an empty address book. - After new data is added, the corrupted data file will be overwritten by the app. Any missing file will be replaced by the app.
- Corrupt the data file in
Viewing Help
- View Help
- Test case:
help
Expected: Help window opens.
- Test case:
Acknowledgements
- This project is based on the AddressBook-Level3 project created by the SE-EDU initiative.
- Libraries used: JavaFX, Jackson, JUnit5
- UI color scheme inspired by TailwindUI