Developer Guide
Table of Contents
- Acknowledgements
- Setting up, getting started
- Design
- Implementation
- Documentation, logging, testing, configuration, dev-ops
- Appendix: Requirements
- Appendix: Instructions for manual testing
Acknowledgements
- Hawking Date/Time Parser: Used for natural language date parsing for Event commands.
Setting up, getting started
Refer to the guide Setting up and getting started.
Design
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 (consisting of classes Main and MainApp) is in charge of the app launch and shut down.
- At app launch, it initializes the other components in the correct sequence, and connects them up with each other.
- At shut down, it shuts down the other components and invokes cleanup methods where necessary.
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),
- defines its API in an
interfacewith the same name as the Component. - implements its functionality using a concrete
{Component Name}Managerclass (which follows the corresponding APIinterfacementioned 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.
UI 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,
- executes user commands using the
Logiccomponent. - listens for changes to
Modeldata so that the UI can be updated with the modified data. - displays
Personobjects, including their associatedRelationships andTags, using thePersonCardwithin thePersonListPanel. - displays
Eventobjects using theEventCardwithin theEventListPanel(if implemented). - keeps a reference to the
Logiccomponent, because theUIrelies on theLogicto execute commands. - depends on some classes in the
Modelcomponent, as it displays objects residing in theModel.
Logic component
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.

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:
- When
Logicis called upon to execute a command, it is passed to anAddressBookParserobject which in turn creates a parser that matches the command (e.g.,DeleteCommandParser,AddRelationshipCommandParser) and uses it to parse the command. - This results in a
Commandobject (more precisely, an object of one of its subclasses e.g.,DeleteCommand,AddRelationshipCommand) which is executed by theLogicManager. - The command can communicate with the
Modelwhen it is executed (e.g. to delete a person, add a relationship, add an event).
Note that although this is shown as a single step in the diagram above (for simplicity), in the code it can take several interactions (between the command object and theModel) to achieve. - The result of the command execution is encapsulated as a
CommandResultobject which is returned back fromLogic.
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
AddressBookParserclass creates anXYZCommandParser(XYZis a placeholder for the specific command name e.g.,AddCommandParser,AddRelationshipCommandParser,AddEventCommandParser) which uses the other classes shown above to parse the user command and create aXYZCommandobject (e.g.,AddCommand,AddRelationshipCommand,AddEventCommand) which theAddressBookParserreturns back as aCommandobject. - All
XYZCommandParserclasses inherit from theParserinterface so that they can be treated similarly where possible e.g, during testing.
Parser utilities
-
ParserUtil: Provides utility methods for parsing common fields likeName,Phone,Email,Tag,Index, and now alsoEventNameandContact(as aPerson). -
DateParserUtil: Uses the Hawking library to provide natural language date parsing capabilities (e.g., “tomorrow”, “next Monday”, “2025-03-15”). This is used by event-related command parsers. -
CliSyntax: Defines the prefixes (e.g.,n/,p/,u/,fn/,en/,d/) used in the command line interface.
Model component
API : Model.java

The Model component,
- stores the address book data i.e., all
Personobjects (inUniquePersonList),Relationshipobjects (inUniqueRelationshipList), andEventobjects (in anObservableList). All these are encapsulated within theAddressBookobject. - stores the currently ‘selected’ objects (e.g., results of a search query) as separate filtered lists (
FilteredList<Person>,FilteredList<Relationship>,FilteredList<Event>) which are exposed to outsiders as unmodifiableObservableLists that can be ‘observed’. - stores a
UserPrefobject that represents the user’s preferences. This is exposed to the outside as aReadOnlyUserPrefobject. - does not depend on any of the other three components.
Key Data Structures:
-
Person: Represents a contact with fields likeName,Phone,Email,Address,Social,Tags, and a uniqueid. -
Relationship: Represents a directed connection between twoPersons identified by their unique IDs. It has aforwardName(e.g., “Boss of”), areverseName(e.g., “Reports to”), and can haveTags (e.g., “Work”). See the Relationship Feature section for details. -
Event: Represents an event with aname,date(LocalDateTime),location,description,Tags, and a list of associatedPersoncontacts (UniquePersonList). See the Event Feature section for details. -
UniquePersonList: Enforces uniqueness amongPersonobjects based onisSamePerson. -
UniqueRelationshipList: Enforces uniqueness amongRelationshipobjects based onisSameRelationship.
Storage component
API : Storage.java

The Storage component,
- can save
AddressBookdata (includingPersons,Relationships, andEvents) andUserPrefdata in JSON format, and read them back into corresponding objects. - uses Jackson-friendly intermediate classes like
JsonAdaptedPerson,JsonAdaptedRelationship, andJsonAdaptedEventto handle the serialization and deserialization process. - inherits from both
AddressBookStorageandUserPrefStorage. - depends on some classes in the
Modelcomponent. -
JsonAdaptedEventusesDateParserUtilfrom the Logic component’s parser utilities to ensure consistent date handling between user input and stored data. While this increases coupling slightly, it maintains backward compatibility and ensures data integrity.
Common classes
Classes used by multiple components are in the seedu.address.commons package. Examples include logging (LogsCenter), core utilities (Config, GuiSettings, Version), exceptions (DataLoadingException), and utility classes (AppUtil, StringUtil, CollectionUtil).
Implementation
This section describes some noteworthy details on how certain features are implemented.
Relationship Feature: Linking Contacts
The Relationship feature allows users to define connections between contacts.
Core Components:
-
RelationshipClass: Stores the core data:-
firstUserId,secondUserId: The unique IDs (Person::getId) of the two contacts involved. Using IDs makes the relationship robust against changes in contact details like name or phone. -
forwardName: The description of the relationship from the perspective offirstUserId(e.g., “Parent of”). -
reverseName: The description from the perspective ofsecondUserId(e.g., “Child of”). This allows for asymmetric relationships. -
tags: ASet<Tag>for categorizing the relationship (e.g., “Work”, “Family”).
-
-
UniqueRelationshipList: Manages the collection ofRelationshipobjects within theAddressBook. It ensures that duplicate relationships (based on the pair of users and the specific forward/reverse names) are not added. -
Relationship Commands: (
addRelationship,deleteRelationship,addRelationshipTag,deleteRelationshipTag,findRelationship) Provide the user interface for interacting with relationships. -
Relationship Parsers: Parse the user input for relationship commands, handling prefixes like
u/(userId),fn/(forward name),rn/(reverse name), andt/(tag). -
UI Display (
PersonCard): EachPersonCardin the list displays the relationships relevant to that person. It fetches the relationships involving the person’s ID from theModeland displays the appropriate relationship name (forwardNameorreverseName) along with the name of the other person involved. To get the other person’s name, it looks up the other person in theAddressBookusing their ID.
Implementation Details:
-
Identification: Relationships are primarily identified by the unique IDs of the two persons involved. When deleting or modifying tags, the user provides both IDs and one of the relationship names (either forward or reverse). The system checks against both possibilities (
forwardNameandreverseName) to find the correct relationship. SeeRelationship::isSameRelationship(String, String, String). -
Uniqueness:
UniqueRelationshipListprevents adding a relationship if another relationship with the exact same pair of IDs and same forward/reverse names already exists (considering both directions). -
Display Logic: The
PersonListPanellistens for changes in theObservableList<Relationship>from theModel. When relationships are added, deleted, or updated (e.g., tags changed), it identifies thePersonobjects involved and triggers a refresh of theListViewor specificPersonCards to reflect the changes.
Sequence Diagrams:
-
Adding a Relationship:
-
Deleting a Relationship:
-
Adding a Tag to a Relationship:
-
Deleting a Tag from a Relationship:
Class Diagrams for Detail:
Design Considerations:
-
Alternative 1: Using Person objects directly: Storing direct references to
Personobjects inRelationshipwould create strong coupling and potential issues if aPersonobject is deleted or modified in ways that affect equality.- Pros: Simpler lookup of person details.
- Cons: Less robust, harder to manage lifecycle, potential memory leaks if references aren’t cleared properly.
-
Alternative 2 (Current Choice): Using Person IDs: Storing unique, immutable
PersonIDs.-
Pros: Decouples
RelationshipfromPersonobject instances, robust againstPersonupdates, easier serialization. -
Cons: Requires looking up
Persondetails (like name) from theAddressBookorModelwhen displaying relationships.
-
Pros: Decouples
-
Relationship Name Storage: Using separate
forwardNameandreverseNameallows representing asymmetric relationships clearly. An alternative could be a single name and a direction flag, but the current approach is arguably more descriptive.
Event Feature: Managing Events
The Event feature allows users to schedule and manage events, associating contacts with them.
Core Components:
-
EventClass: Stores the event details:-
id: A unique identifier automatically generated for each event. -
name: The name of the event. -
date: The date and time of the event (LocalDateTime). -
location: The physical location of the event (optional). -
description: A description of the event (optional). -
tags: ASet<Tag>for categorizing the event (e.g., “Meeting”, “Conference”). -
contacts: AUniquePersonListcontaining thePersonobjects associated with this event.
-
-
AddressBook: Stores the list of allEventobjects (currently as a simpleObservableList). -
Event Commands: (
addEvent,deleteEvent,addEventContact,deleteEventContact,addEventTag,deleteEventTag,updateEventDate,updateEventDesc,updateEventLoc) Provide the user interface for managing events. -
Event Parsers: Parse user input for event commands, utilizing
DateParserUtilfor flexible date input. -
UI Display (
EventCard,EventListPanel): (If implemented) Display event information.EventCardshows details of a single event.EventListPanelshows the list of events.
Implementation Details:
-
Date Handling:
DateParserUtiluses the Hawking library to parse natural language dates provided by the user (e.g., “next Friday at 2pm”, “2025-04-10 10:00”). The parsed date is stored as a standardLocalDateTime. -
Contact Association: Events store references to actual
Personobjects within their internalUniquePersonList. When an event is added usingaddEvent, the command parser creates temporaryPersonobjects based on the names provided (c/Contact Name). TheAddEventCommandthen adds the event to the model and iterates through these temporary contacts. For each contact, it adds them to the event’s contact list and also ensures the contact exists in the mainAddressBookperson list (adding them if necessary with default details). This ensures consistency. -
Modification: Event properties (date, description, location) and associated data (tags, contacts) are modified using specific commands. Adding/removing tags or contacts, or updating properties, typically involves creating a new
Eventobject with the changes and replacing the old one in theModelusingModel::updateEvent.
Sequence Diagrams:
-
Adding an Event:
-
Updating Event Description:
-
Updating Event Location:
-
Updating Event Date:
-
Adding a Tag to an Event:
-
Deleting a Tag from an Event:
-
Adding a Contact to an Event:
-
Deleting a Contact from an Event:
-
Deleting an Event:
Design Considerations:
-
Event Storage: Currently uses a standard
ObservableListinAddressBook. AUniqueEventListcould be implemented if strict uniqueness based on certain criteria (e.g., name and date) is required, though generating a unique ID simplifies this. -
Contact Management: Storing
Personreferences directly in the event’s list simplifies access to contact details but requires careful handling, especially during deletion (ensuring contacts are not deleted from the main list just because they are removed from an event, and handling cases where a contact associated with an event is deleted from the main list). The current implementation adds contacts to the main list if they don’t exist, which is one approach to manage this.
Find Commands with Substring Matching
The findName, findEmail, findPhone, findAddress, findSocial, findTag, and findRelationship commands have been implemented to use case-insensitive substring matching instead of full word matching.
-
Mechanism: Each
findXYZcommand uses a correspondingXYZContainsKeywordsAsSubstringPredicate(e.g.,NameContainsKeywordsAsSubstringPredicate). -
Predicate Logic: Inside the
testmethod of each predicate, it streams through the user-provided keywords. For each keyword, it checks if the relevant field in thePersonobject (e.g.,person.getName().fullName) contains the keyword as a substring, ignoring case (toLowerCase().contains(keyword.toLowerCase())). The predicate returnstrueif any keyword matches as a substring within the field. -
Example (
findName): If a person’s name is “Alex Yeoh”,findName Alexwill match,findName Yeohwill match, andfindName lexwill also match. - Rationale: Substring matching provides more flexibility for users who may not remember the exact full name, email, etc., allowing for partial matches.
-
Special Note for
findRelationship: The current implementation ofRelationshipContainsKeywordsAsSubstringPredicatechecks whether the role name in the relationship matches the specified keyword. It should filter persons based on their specific role name in the relationship matching the keyword -
Example (
findRelationship): In a relationship where Person A has role “Boss” and Person B has role “Employee”,findRelationship Bossshould return only Person A andfindRelationship Employeeshould return only Person B
Sequence Diagram Example (findName):

(Similar diagrams exist for other find commands like findEmail, findPhone, etc.)
Sort Command
The sort command allows sorting the displayed person list based on one or more fields (name, phone, email, address, tags, socials). Field names are case-insensitive, so both sort name and sort Name will work the same way.
-
Mechanism: The
SortCommandParserparses the field names and an optional-rflag for reverse order. TheSortCommandthen creates aComparator<Person>based on the specified fields. -
Comparator Creation: It starts with a comparator for the first field. For subsequent fields, it uses
thenComparingto chain the sorting criteria. String fields (name,phone,email,address) use case-insensitive comparison. Collection fields (tags,socials) are compared using their string representation (HashSet’stoString()method), which means sorting is based on the lexicographical comparison of their string values rather than individual elements or properties of the sets. -
Execution: The
SortCommandcallsModel.updateSortedPersonList(comparator), which in turn callsAddressBook.sortPersons(comparator). This sorts the underlyingUniquePersonListwithin theAddressBook. Since theFilteredListinModelManagerwraps theObservableListfromAddressBook, changes in the source list’s order are reflected in the filtered/sorted list displayed in the UI.
Sequence Diagram:

Redo Command and Redo List Command
The redo and redoList commands allow users to re-execute or view recently executed commands.
-
CommandHistoryClass: A static class (seedu.address.model.commandhistory.CommandHistory) maintains aDeque<String>(double-ended queue) calledlastCommands. This history is session-based and does not persist between program executions; it is initialized as empty when the application starts and cleared when the application is terminated. -
Adding Commands: After a command is successfully parsed and before it’s executed by
LogicManager, the original command string is added to the front of thelastCommandsdeque usingCommandHistory.addCommandToHistory(userInput). If the deque size exceeds 10, the oldest command (at the end) is removed. Commands likeredoitself are not added to the history to prevent recursive loops. -
redoListCommand: This command retrieves all commands from theCommandHistorydeque and formats them into a numbered list for display to the user (currently thrown as aCommandExceptionfor display). The list only shows successfully executed commands from the current session, up to a maximum of 10 commands. -
redo NCommand:- Parses the integer
N. - Validates
N(must be between 1 and 10). - Retrieves the Nth command string from the
CommandHistoryusingCommandHistory.getCommandFromHistory(N). - Creates a new
AddressBookParserinstance. - Calls
parseCommandon the retrieved command string. - Executes the resulting
Commandobject. The result of this execution is returned to the user.
- Parses the integer
Sequence Diagram for Redo Command:

Sequence Diagram for Redo List Command:

Design Considerations:
- Storage: History is stored in-memory and is lost when the application closes. Persisting history could be a future enhancement.
-
Re-parsing vs. Storing Command Objects: The current approach stores command strings and re-parses them.
- Pros: Simpler to store, avoids potential issues with storing complex command object states.
- Cons: Relies on the parser being deterministic and available. Minor overhead of re-parsing.
-
Alternative: Store executed
Commandobjects directly. Requires commands to be potentially serializable or easily reconstructible, might be more complex.
Documentation, logging, testing, configuration, dev-ops
Appendix: Requirements
Product scope
Target user profile:
- early-stage startup founders
- has a need to manage a significant number of contacts, their relationships, and related events
- prefer desktop apps over other types
- can type fast
- prefers typing to mouse interactions
- is reasonably comfortable using CLI apps
Value proposition: Comprehensive knowledge store on potential business partners and events, optimized for speed and relationship mapping.
User stories
Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *
| Priority | As a … | I want to … | So that I can … |
|---|---|---|---|
* * * |
startup founder | add contacts | view contact information later |
* * * |
startup founder | list contacts | view contact information and their relationships |
* * * |
startup founder | delete contacts | remove contacts I no longer need |
* * * |
startup founder | add relationships between contacts | map out and visualize connections within my network |
* * * |
startup founder | delete relationships | remove outdated or incorrect connections |
* * * |
startup founder | search contacts by name | find contacts by name without looking through all contacts |
* * |
startup founder | edit contacts | change contact information |
* * |
startup founder | add tags to contacts | view contact categories later |
* * |
startup founder | delete tags from contacts | remove tags I no longer need |
* * |
startup founder | add tags to relationships | categorize connections (e.g., ‘Investor’, ‘Advisor’, ‘Competitor’) |
* * |
startup founder | delete tags from relationships | remove relationship tags I no longer need |
* * |
startup founder | add events | keep track of meetings, calls, and conferences |
* * |
startup founder | delete events | remove past or cancelled events |
* * |
startup founder | add contacts to events | track who attended or is involved in specific events |
* * |
startup founder | remove contacts from events | update event attendance or involvement |
* * |
startup founder | add tags to events | categorize events (e.g., ‘Networking’, ‘Funding’, ‘Product Launch’) |
* * |
startup founder | remove tags from events | remove event tags I no longer need |
* * |
startup founder | update event details | change the date, location, or description of an event |
* * |
startup founder | search contacts by tag | find contacts by tag without looking through all contacts |
* * |
startup founder | search contacts by relationship | find contacts connected in a specific way (e.g., find all ‘Advisors’) |
* * |
startup founder | search contacts by phone | find contacts by phone without looking through all contacts |
* * |
startup founder | search contacts by email | find contacts by email without looking through all contacts |
* * |
startup founder | search contacts by address | find contacts by address without looking through all contacts |
* * |
startup founder | search contacts by social | find contacts by social media handle without looking through all contacts |
* * |
startup founder | sort contacts by fields | quickly organize contacts by name, phone, or other fields |
* * |
startup founder | redo previous command | re-execute the last command(s) to avoid repeating steps manually |
* * |
startup founder | list last 10 commands | view recent commands executed to track the workflow or find a command to redo |
* * |
startup founder | view help message | access instructions on how to use the application features |
Use cases
(For all use cases below, the System is INcontact and the Actor is the startup founder, unless specified otherwise)
Use case: UC1 - Find persons by name
MSS
- Startup founder requests to find persons by name in the address book
- INcontact parses the request to extract the specified keywords
- INcontact searches for persons whose names contain any of the specified keywords as a substring (case-insensitive)
- INcontact displays the list of persons matching the search criteria with index numbers
-
Startup founder reviews the list of persons
Use case ends.
Extensions
-
2a. No persons match the specified keywords.
- 2a1. INcontact shows a message indicating no persons were found.
Use case ends.
-
3a. The user does not specify any keywords.
- 3a1. INcontact shows an error message indicating that keywords are required.
Use case resumes at step 2.
Use case: UC2 - Find persons by email
Similar to UC1 but find by email
MSS
- Startup founder requests to find persons by email in the address book
- INcontact parses the request to extract the specified email keywords
- INcontact searches for persons whose email contains any of the specified keywords as a substring (case-insensitive)
- INcontact displays the list of persons matching the search criteria with index numbers
-
Startup founder reviews the list of persons
Use case ends.
Extensions
-
2a. No persons match the specified email keywords.
- 2a1. INcontact shows a message indicating no persons were found.
Use case ends.
-
3a. The user does not specify any email keywords.
- 3a1. INcontact shows an error message indicating that email keywords are required.
Use case resumes at step 2.
Use case: UC3 - Find persons by phone
Similar to UC1 but find by phone
MSS
- Startup founder requests to find persons by phone in the address book
- INcontact parses the request to extract the specified phone number keywords
- INcontact searches for persons whose phone number contains any of the specified keywords as a substring (case-insensitive)
- INcontact displays the list of persons matching the search criteria with index numbers
-
Startup founder reviews the list of persons
Use case ends.
Extensions
-
2a. No persons match the specified phone number keywords.
- 2a1. INcontact shows a message indicating no persons were found.
Use case ends.
-
3a. The user does not specify any phone number keywords.
- 3a1. INcontact shows an error message indicating that phone number keywords are required.
Use case resumes at step 2.
Use case: UC4 - Find persons by address
Similar to UC1 but find by address
MSS
- Startup founder requests to find persons by address in the address book
- INcontact parses the request to extract the specified address keywords
- INcontact searches for persons whose address contains any of the specified keywords as a substring (case-insensitive)
- INcontact displays the list of persons matching the search criteria with index numbers
-
Startup founder reviews the list of persons
Use case ends.
Extensions
-
2a. No persons match the specified address keywords.
- 2a1. INcontact shows a message indicating no persons were found.
Use case ends.
-
3a. The user does not specify any address keywords.
- 3a1. INcontact shows an error message indicating that address keywords are required.
Use case resumes at step 2.
Use case: UC5 - Find persons by relationship
Similar to UC1 but find by relationship
MSS
- Startup founder requests to find persons by relationship in the address book
- INcontact parses the request to extract the specified relationship keywords
- INcontact searches for persons where their relationship role in any relationship contains any of the specified keywords as a substring (case-insensitive)
- INcontact displays the list of persons matching the search criteria with index numbers
-
Startup founder reviews the list of persons
Use case ends.
Extensions
-
2a. No persons match the specified relationship keywords.
- 2a1. INcontact shows a message indicating no persons were found.
Use case ends.
-
3a. The user does not specify any relationship keywords.
- 3a1. INcontact shows an error message indicating that relationship keywords are required.
Use case resumes at step 2.
Use case: UC6 - Find persons by social
Similar to UC1 but find by social
MSS
- Startup founder requests to find persons by social media information in the address book
- INcontact parses the request to extract the specified social media keywords
- INcontact searches for persons whose social media information contains any of the specified keywords as a substring (case-insensitive)
- INcontact displays the list of persons matching the search criteria with index numbers
-
Startup founder reviews the list of persons
Use case ends.
Extensions
-
2a. No persons match the specified social media keywords.
- 2a1. INcontact shows a message indicating no persons were found.
Use case ends.
-
3a. The user does not specify any social media keywords.
- 3a1. INcontact shows an error message indicating that social media keywords are required.
Use case resumes at step 2.
Use case: UC7 - Find persons by tag
Similar to UC1 but find by tag
MSS
- Startup founder requests to find persons by tag in the address book
- INcontact parses the request to extract the specified tag keywords
- INcontact searches for persons whose tags contain any of the specified keywords as a substring (case-insensitive)
- INcontact displays the list of persons matching the search criteria with index numbers
-
Startup founder reviews the list of persons
Use case ends.
Extensions
-
2a. No persons match the specified tag keywords.
- 2a1. INcontact shows a message indicating no persons were found.
Use case ends.
-
3a. The user does not specify any tag keywords.
- 3a1. INcontact shows an error message indicating that tag keywords are required.
Use case resumes at step 2.
Use case: UC8 - Sort persons by specified fields
MSS
- Startup founder requests to sort persons in the address book
- INcontact parses the request to extract the sorting fields and reverse flag (if specified)
- INcontact validates the specified fields to ensure they are valid (name, phone, email, address, tags, socials)
- INcontact sorts the address book based on the specified fields
- INcontact displays the sorted list of persons with index numbers
-
Startup founder reviews the sorted list of persons
Use case ends.
Extensions
-
2a. The user does not specify any sorting fields.
- 2a1. INcontact shows an error message indicating that indicating the invalid command format.
Use case ends.
-
3a. An invalid field is specified.
- 3a1. INcontact shows an error message indicating the invalid field.
Use case ends.
-
5a. The user requests sorting by multiple fields.
- 5a1. INcontact sorts the address book first by the primary field, then by subsequent fields.
Use case resumes at step 4.
Use case: UC9 - Redo a previously executed command
MSS
- Startup founder requests to redo a previously executed command by specifying its index in the command history
- INcontact parses the request to extract the command index
- INcontact validates that the command index is within the valid range (1 to 10)
- INcontact retrieves the specified command from the command history
- INcontact re-executes the specified command
-
INcontact displays the result of the re-executed command
Use case ends.
Extensions
-
2a. The user does not specify a valid command index.
- 2a1. INcontact shows an error message indicating that the command index must be between 1 and 10.
Use case ends.
-
3a. The command index is out of the valid range (less than 1 or greater than 10).
- 3a1. INcontact shows an error message indicating that the command index is invalid.
Use case ends.
-
4a. The command index does not exist in the history (not enough command history).
- 4a1. INcontact shows an error message indicating that there are not enough commands in history to redo.
Use case ends.
-
5a. An error occurs while re-executing the command.
- 5a1. INcontact shows an error message indicating that there was an issue re-executing the command.
Use case ends.
Use case: UC10 - List the last 10 commands in the command history
MSS
- Startup founder requests to list the last 10 commands from the command history
- INcontact parses the request and recognizes the
redoListcommand - INcontact retrieves the last 10 commands from the command history
- INcontact displays the list of the last 10 commands, showing each with an index number
-
Startup founder reviews the list of commands
Use case ends.
Extensions
-
2a. No commands exist in the history.
- 2a1. INcontact shows an error message indicating that no command history exists.
Use case ends.
-
3a. An error occurs while retrieving the commands from the history.
- 3a1. INcontact shows an error message indicating that there was an issue retrieving the command history.
Use case ends.
Use Case: UC-AddRel - Add a Relationship
MSS
- Startup founder requests to add a relationship between two existing contacts using their IDs and specifying the forward and reverse relationship names.
- INcontact parses the command, identifying the two user IDs, the forward name, the reverse name, and any tags.
- INcontact verifies that both user IDs correspond to existing persons in the address book.
- INcontact verifies that a relationship with the same users and names does not already exist.
- INcontact creates the
Relationshipobject. - INcontact adds the relationship to the address book.
- INcontact displays a success message confirming the relationship creation, showing the names of the involved persons. Use case ends.
Extensions
- 3a. One or both user IDs do not exist.
- 3a1. INcontact shows an error message “One or both contacts do not exist.” Use case ends.
- 4a. The relationship (or its reverse equivalent) already exists.
- 4a1. INcontact shows an error message “Relationship already exists.” Use case ends.
- 1a. User attempts to add a relationship between a person and themselves (same ID provided twice).
- 1a1. INcontact shows an error message “A contact cannot have a relationship with itself.” Use case ends.
- 1b. User provides an empty relationship name.
- 2b1. INcontact shows an error message “Relationship name cannot be empty.” Use case ends.
Use Case: UC-DelRel - Delete a Relationship
MSS
- Startup founder requests to delete a relationship using the IDs of the two involved persons and one of the relationship names (either forward or reverse).
- INcontact parses the command to identify the two user IDs and the relationship name.
- INcontact verifies that both user IDs correspond to existing persons.
- INcontact finds the relationship matching the two user IDs and the provided name (checking against both forward and reverse names).
- INcontact removes the relationship from the address book.
- INcontact displays a success message confirming the deletion, showing the names of the involved persons. Use case ends.
Extensions
- 3a. One or both user IDs do not exist.
- 3a1. INcontact shows an error message “One or both persons could not be found.” Use case ends.
- 4a. No relationship matching the criteria is found.
- 4a1. INcontact shows an error message “No relationship found with the specified details.” Use case ends.
- 1a. User provides empty User IDs or relationship name.
- 2a1. INcontact shows an error message “User IDs and relationship name cannot be empty.” Use case ends.
Use Case: UC-AddRelTag - Add Tag to Relationship
MSS
- Startup founder requests to add a tag to an existing relationship, identifying the relationship by the two user IDs and one of the relationship names, and specifying the tag to add.
- INcontact parses the command.
- INcontact finds the specified relationship.
- INcontact verifies the tag does not already exist on the relationship.
- INcontact creates an updated
Relationshipobject with the new tag added. - INcontact replaces the old relationship with the updated one in the address book.
- INcontact displays a success message showing the updated relationship. Use case ends.
Extensions
- 3a. The specified relationship is not found.
- 3a1. INcontact shows an error message “The specified relationship was not found.” Use case ends.
- 4a. The tag already exists on the relationship.
- 4a1. INcontact shows an error message “This tag already exists in the relationship.” Use case ends.
Use Case: UC-DelRelTag - Delete Tag from Relationship
MSS
- Startup founder requests to delete a tag from an existing relationship, identifying the relationship by the two user IDs and one of the relationship names, and specifying the tag to delete.
- INcontact parses the command.
- INcontact finds the specified relationship.
- INcontact verifies the tag exists on the relationship.
- INcontact creates an updated
Relationshipobject with the tag removed. - INcontact replaces the old relationship with the updated one in the address book.
- INcontact displays a success message showing the updated relationship. Use case ends.
Extensions
- 3a. The specified relationship is not found.
- 3a1. INcontact shows an error message “The specified relationship was not found.” Use case ends.
- 4a. The tag does not exist on the relationship.
- 4a1. INcontact shows an error message “This tag does not exist in the relationship.” Use case ends.
Use Case: UC-AddEvent - Add an Event
MSS
- Startup founder requests to add an event, providing name, date, and optionally location, description, tags, and contact names.
- INcontact parses the command, including parsing the date using natural language processing.
- INcontact validates the required fields (name, date).
- INcontact creates the
Eventobject (initially with an empty contact list). - INcontact verifies the event does not already exist (based on unique ID or other criteria if defined).
- INcontact adds the event to the address book.
- For each contact name provided:
- INcontact checks if a person with that name exists in the address book.
- If the person does not exist, INcontact adds a new person with that name and default details.
- INcontact adds the corresponding
Personobject to the event’s contact list.
- INcontact displays a success message confirming the event addition. Use case ends.
Extensions
- 3a. Required fields are missing or invalid (e.g., unparseable date).
- 3a1. INcontact shows an error message detailing the issue. Use case ends.
- 5a. An identical event already exists.
- 5a1. INcontact shows an error message “This event already exists in the address book.” Use case ends.
Use Case: UC-DelEvent - Delete an Event
MSS
- Startup founder requests to delete an event using its unique ID.
- INcontact parses the command to get the event ID.
- INcontact finds the event corresponding to the ID.
- INcontact removes the event from the address book.
- INcontact displays a success message. Use case ends.
Extensions
- 3a. No event with the specified ID is found.
- 3a1. INcontact shows an error message “Event not found.” Use case ends.
Use Case: UC-AddEventContact - Add Contact to Event
MSS
- Startup founder requests to add a contact to an event, specifying the event by index and the contact by name.
- INcontact parses the command.
- INcontact retrieves the event at the specified index.
- INcontact finds or creates the
Personobject corresponding to the contact name (similar to UC-AddEvent step 7). - INcontact verifies the contact is not already associated with the event.
- INcontact adds the
Personobject to the event’s contact list. (Note: May requireModel.updateEventifEvent’s contact list isn’t directly mutable or observed). - INcontact displays a success message. Use case ends.
Extensions
- 3a. Invalid event index provided.
- 3a1. INcontact shows an error message “The event index provided is invalid.” Use case ends.
- 5a. Contact already exists in the event.
- 5a1. INcontact shows an error message “This contact already exists in the event.” Use case ends.
Use Case: UC-DelEventContact - Delete Contact from Event
MSS
- Startup founder requests to remove a contact from an event, specifying the event by index and the contact by name.
- INcontact parses the command.
- INcontact retrieves the event at the specified index.
- INcontact finds the
Personobject corresponding to the contact name within the event’s contact list. - INcontact removes the
Personobject from the event’s contact list. (Note: May requireModel.updateEvent). - INcontact displays a success message. Use case ends.
Extensions
- 3a. Invalid event index provided.
- 3a1. INcontact shows an error message “The event index provided is invalid.” Use case ends.
- 4a. Contact is not found within the event’s contact list.
- 4a1. INcontact shows an error message “This contact does not exist in the event.” Use case ends.
(Use cases for adding/deleting event tags and updating event properties follow a similar pattern to relationship tag management and event contact management, involving parsing, finding the event, performing the modification, and updating the model.)
Non-Functional Requirements
- Should work on any mainstream OS as long as it has the exact version of Java
17installed according to the Java Installation Guides. - Should be able to hold up to 1000 contacts, 1000 relationships, and 500 events without a noticeable sluggishness in performance for typical usage.
- A user with above average typing speed for regular English text should be able to accomplish most of the tasks faster using commands than using the mouse.
- Data should be persisted between application sessions.
Glossary
- Mainstream OS: Windows, Linux, macOS
- Relationship: A directed connection between two contacts, having a forward name, a reverse name, and optional tags.
- Event: A scheduled item with a name, date/time, optional location/description, tags, and associated contacts.
- ID: A unique identifier automatically generated for each Person and Event. Used internally, but also shown to the user (e.g., for relationship commands).
Appendix: Instructions for manual testing
Given below are instructions to test the app manually.
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. Relationships (if any sample data added) and Events (if any sample data added) might be displayed or accessible. The window size may not be optimum. Check for
config.json,preferences.jsonanddata/addressbook.jsonfiles created.
- 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 Relationships
- Add a valid relationship
- Prerequisites: Have at least two contacts in the list. Note their IDs (shown in the contact card). Let IDs be
ID1andID2. - Test case:
addRelationship u/ID1 u/ID2 fn/Mentor rn/Mentee t/WorkExpected: Success message is shown. The contact cards for persons withID1andID2should now display the relationship (“Mentor MenteeName” on ID1’s card, “Mentee MentorName” on ID2’s card) and the “Work” tag under the relationship.
- Prerequisites: Have at least two contacts in the list. Note their IDs (shown in the contact card). Let IDs be
- Add a duplicate relationship
- Prerequisites: Relationship added in the previous step exists.
- Test case:
addRelationship u/ID1 u/ID2 fn/Mentor rn/MenteeExpected: Error message “Relationship already exists.” is shown. No changes in the UI. - Test case:
addRelationship u/ID2 u/ID1 fn/Mentee rn/Mentor(reversed roles) Expected: Error message “Relationship already exists.” is shown. No changes in the UI.
- Add relationship with non-existent ID
- Prerequisites: Get a valid ID
ID1. - Test case:
addRelationship u/ID1 u/NonExistentID fn/Friend rn/FriendExpected: Error message “One or both contacts do not exist.”
- Prerequisites: Get a valid ID
- Add relationship with self
- Prerequisites: Get a valid ID
ID1. - Test case:
addRelationship u/ID1 u/ID1 fn/Self rn/SelfExpected: Error message “A contact cannot have a relationship with itself.”
- Prerequisites: Get a valid ID
- Add relationship with missing/empty names
- Test case:
addRelationship u/ID1 u/ID2 fn/ rn/MenteeExpected: Error message “Relationship name cannot be empty.” or similar constraint violation. - Test case:
addRelationship u/ID1 u/ID2 fn/Mentor rn/Expected: Error message “Relationship name cannot be empty.” or similar constraint violation.
- Test case:
Deleting Relationships
- Delete an existing relationship
- Prerequisites: Add a relationship
addRelationship u/ID1 u/ID2 fn/Boss rn/Employee. - Test case:
deleteRelationship u/ID1 u/ID2 n/BossExpected: Success message shown. Relationship disappears from the contact cards of personsID1andID2. - Prerequisites: Add the relationship again
addRelationship u/ID1 u/ID2 fn/Boss rn/Employee. - Test case:
deleteRelationship u/ID1 u/ID2 n/Employee(using reverse name) Expected: Success message shown. Relationship disappears from the contact cards.
- Prerequisites: Add a relationship
- Delete non-existent relationship
- Prerequisites: No relationship named “Friend” exists between
ID1andID2. - Test case:
deleteRelationship u/ID1 u/ID2 n/FriendExpected: Error message “No relationship found…”
- Prerequisites: No relationship named “Friend” exists between
- Delete with non-existent ID
- Prerequisites:
NonExistentIDis not a valid person ID. - Test case:
deleteRelationship u/ID1 u/NonExistentID n/BossExpected: Error message “One or both persons could not be found.”
- Prerequisites:
Managing Relationship Tags
- Add a tag to a relationship
- Prerequisites: Add relationship
addRelationship u/ID1 u/ID2 fn/Partner rn/Partner. - Test case:
addRelationshipTag u/ID1 u/ID2 n/Partner t/ProjectXExpected: Success message. The “ProjectX” tag appears under the “Partner” relationship on the relevant contact cards. - Test case:
addRelationshipTag u/ID1 u/ID2 n/Partner t/ProjectX(duplicate tag) Expected: Error message “This tag already exists…”
- Prerequisites: Add relationship
- Delete a tag from a relationship
- Prerequisites: Relationship with tag “ProjectX” exists from previous step.
- Test case:
deleteRelationshipTag u/ID1 u/ID2 n/Partner t/ProjectXExpected: Success message. The “ProjectX” tag disappears from the relationship display. - Test case:
deleteRelationshipTag u/ID1 u/ID2 n/Partner t/NonExistentTagExpected: Error message “This tag does not exist…”
Adding Events
- Add a valid event
- Test case:
addEvent en/Team Meeting d/tomorrow 2pm l/Office desc/Weekly sync t/WorkExpected: Success message shown. (Event list panel should update if implemented). - Test case:
addEvent en/Conference d/2025-10-20 09:00 c/Alex Yeoh c/Bernice Yu(Assuming Alex and Bernice exist) Expected: Success message. Contacts Alex Yeoh and Bernice Yu should be associated with the event internally. - Test case:
addEvent en/New Client Pitch d/next monday c/David Li c/NewContactName(Assuming David Li exists, NewContactName does not) Expected: Success message. A new contact “NewContactName” with default details should be added to the main contact list. Both David Li and NewContactName should be associated with the event.
- Test case:
- Add event with missing required fields
- Test case:
addEvent d/todayExpected: Error message indicating required parameter for events. - Test case:
addEvent en/Party l/HomeExpected: Error message indicating required parameter for events.
- Test case:
- Add event with invalid date
- Test case:
addEvent en/Invalid Date Event d/invalid-date-formatExpected: Error message indicating the date could not be parsed.
- Test case:
Deleting Events
- Delete an existing event
- Prerequisites: Add an event, e.g.,
addEvent en/ToDelete d/today. Note its generated ID (requires viewing storage or a futurelistEventscommand). Let the ID beEventID1. - Test case:
deleteEvent u/EventID1Expected: Success message. Event is removed.
- Prerequisites: Add an event, e.g.,
- Delete non-existent event
- Test case:
deleteEvent u/NonExistentEventIDExpected: Error message “Event not found.”
- Test case:
Managing Event Contacts & Tags
- Add/Delete contact to/from event
- Prerequisites: Add an event
addEvent en/TestEvent d/today. Assume its index is 1. Add a personadd n/TestPerson. - Test case:
addEventContact 1 c/TestPersonExpected: Success message. “TestPerson” is associated with “TestEvent”. - Test case:
addEventContact 1 c/TestPerson(duplicate) Expected: Error message “This contact already exists…”. - Test case:
deleteEventContact 1 c/TestPersonExpected: Success message. “TestPerson” is removed from “TestEvent”. - Test case:
deleteEventContact 1 c/NonExistentContactInEventExpected: Error message “This contact does not exist…”.
- Prerequisites: Add an event
- Add/Delete tag to/from event
- Prerequisites: Event at index 1 exists.
- Test case:
addEventTag 1 t/UrgentExpected: Success message. Tag “Urgent” added to event. - Test case:
deleteEventTag 1 t/UrgentExpected: Success message. Tag “Urgent” removed.
Updating Event Properties
- Update date, location, description
- Prerequisites: Event at index 1 exists.
- Test case:
updateEventDate 1 d/2024-12-25T10:00Expected: Success message, event date updated. - Test case:
updateEventLoc 1 l/Virtual Meeting RoomExpected: Success message, event location updated. - Test case:
updateEventDesc 1 desc/Final project discussionExpected: Success message, event description updated. - Test case with invalid index or missing prefix:
updateEventDate 99 d/2024-01-01T00:00,updateEventLoc 1 New LocationExpected: Appropriate error messages.
Find Commands (Substring Matching)
- Find by name substring
- Prerequisites: Sample contacts loaded (includes “Alex Yeoh”, “Bernice Yu”).
- Test case:
findName AlexExpected: “Alex Yeoh” is listed. - Test case:
findName YeoExpected: “Alex Yeoh” is listed. - Test case:
findName iceExpected: “Bernice Yu” is listed. - Test case:
findName xyzExpected: Status message indicates 0 persons listed.
- Find by relationship keyword (using
findRelationship)- Prerequisites: Add relationships:
addRelationship u/ID1 u/ID2 fn/Business Partner rn/Business Partner t/Tech,addRelationship u/ID1 u/ID3 fn/Investor rn/Investee t/Finance. - Test case:
findRelationship PartnerExpected: Persons with ID1 and ID2 are listed. - Test case:
findRelationship InvestorExpected: Persons with ID1 are listed. - Test case:
findRelationship Tech(finding by relationship tag - verify if this is supported by the predicate) Expected: 0 persons listed (Note: The currentRelationshipContainsKeywordsAsSubstringPredicateonly checks if the person is involved in any relationship name role when the command is run, not if the relationship itself matches the keyword.)
- Prerequisites: Add relationships:
Sort Command
- Sort by single field
- Prerequisites: List persons.
- Test case:
sort nameExpected: Persons list is sorted alphabetically by name. - Test case:
sort -r phoneExpected: Persons list is sorted by phone number in descending order.
- Sort by multiple fields
- Prerequisites: Add persons with the same name but different phone numbers.
- Test case:
sort name phoneExpected: Persons list is sorted by name, and those with the same name are further sorted by phone number.
- Invalid sort field
- Test case:
sort invalidFieldExpected: Error message “Invalid field: invalidField”.
- Test case:
Redo and Redo List Commands
- List command history
- Prerequisites: Execute several valid commands (e.g.,
list,add n/Test,delete 1). - Test case:
redoListExpected: A numbered list of the recently executed commands (up to 10) is displayed in the result area (possibly as an error message, based on current implementation).redocommands themselves should not appear. Note: redoList only keeps track of the command history of the last 10 commands, and this history will be cleared when the program is terminated.
- Prerequisites: Execute several valid commands (e.g.,
- Redo a command
- Prerequisites: Execute
add n/RedoTestPerson. Then executelist. Theaddcommand should be at index 2 inredoList. - Test case:
redo 2Expected: Theadd n/RedoTestPersoncommand is executed again. - Test case:
delete 1. Thenredo 1. Expected: Thedelete 1command is executed again. If index 1 exists, it’s deleted. If not, an “invalid index” error occurs for the re-executed delete.
- Prerequisites: Execute
- Invalid redo index
- Test case:
redo 0Expected: Error message “Invalid command index…” - Test case:
redo 11Expected: Error message “Invalid command index…” - Test case:
redo 5(when only 3 commands are in history) Expected: Error message “Not enough commands in history…”
- Test case:
Saving data
- Dealing with missing/corrupted data files
- Simulate missing file: Close the app. Delete the
data/addressbook.jsonfile. Relaunch the app. Expected: App starts with sample data (or an empty state if sample data loading is disabled after first run). A message indicating a new file is created might be logged. - Simulate corrupted file: Close the app. Open
data/addressbook.jsonand replace its content with invalid JSON (e.g., “abcde”). Save the file. Relaunch the app. Expected: App starts with sample data or empty state. A warning about the corrupted file being loaded and replaced should appear in the logs or possibly as an error dialog.
- Simulate missing file: Close the app. Delete the
- Verify data persistence
- Add a person, a relationship, and an event.
- Close the application.
- Relaunch the application. Expected: The added person, relationship, and event are still present and correctly loaded. Check relationship display on person cards.


