Developer Guide
- Table of Contents:
- Acknowledgements
- Setting Up, getting started
- Design
- Implementation
- Documentation, testing, configurations, dev-ops
- Appendix:
- Product scope
- User stories
- Use cases
- Planned Enhancements
- Non-functional Requirements
- Glossary
- Instructions for manual testing
Acknowledgements
-
NomNomNotifier is adapted from the AddressBook Level-3 created by the SE-EDU initiative.
-
Below are the Java libraries used in this project:
- JavaFX for UI
- JUnit5 and TestFX for Testing
- Jackson for Storage
Setting up, getting started
Refer to the guide Setting up and getting started.
Design

.puml
files used to create diagrams in this document docs/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
(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
interface
with the same name as the Component. - implements its functionality using a concrete
{Component Name}Manager
class (which follows the corresponding APIinterface
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.
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
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
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
object 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
Logic
is called upon to execute a command, it is passed to anAddressBookParser
object which in turn creates a parser that matches the command (e.g.,DeleteCommandParser
) and uses it to parse the command. - This results in a
Command
object (more precisely, an object of one of its subclasses e.g.,DeleteCommand
) which is executed by theLogicManager
. - The command can communicate with the
Model
when it is executed (e.g. to delete a person).
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
CommandResult
object 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
AddressBookParser
class creates anXYZCommandParser
(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 aXYZCommand
object (e.g.,AddCommand
) which theAddressBookParser
returns back as aCommand
object. - All
XYZCommandParser
classes (e.g.,AddCommandParser
,DeleteCommandParser
, …) inherit from theParser
interface so that they can be treated similarly where possible e.g, during testing.
Model component
API : Model.java
The Model
component,
- stores address book data, including:
- all
Person
objects (which are contained in aUniquePersonList
object). - A list of shortcuts, represented by
UniqueShortCutList
, which containsShortCut
objects. EachShortCut
maps an alias (represented byAlias
) to a full tag name (represented byFullTagName
).
- all
- 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
objects. - does not depend on any of the other three components (as the
Model
represents data entities of the domain, they should make sense on their own without depending on other components)
Person Model Components
-
Person: Represents a single individual in the address book, encapsulating multiple details about the person.
-
Name
: Represents the name of the person. Enforces validation rules to ensure data integrity. -
Phone
: Represents the phone number of the person. It includes validation to ensure that the data matches a specific format. -
Email
: Represents the email address of the person, with validation for standard email formats. -
Address
: Represents the residential or business address of the person. -
PostalCode
: Part of theAddress
class, further specifying the person’s address. -
Tag
: A list of tags associated with the person, allowing categorization of people (e.g., “friend”, “colleague”). -
OrderTracker
: Keeps track of the orders associated with the person.
-
Order Model Components
- Order: Represents a single order that is stored in the UniqueOrderList
- OrderHistory: Represents a single order that is that placed at a particular time
-
OrderTracker: Represents a list of
OrderHistory
, this class is used to track the order of a customer/person -
UniqueOrderList: Manages the collection of all
Order
objects within theAddressBook
. EachOrder
in the list is unique by its name.
Shortcut Model Components
The shortcut functionality has been added to enhance the tagging process within the AddressBook
. It includes the following components:
-
UniqueShortCutList
: Manages the collection of allShortCut
objects within theAddressBook
. Each shortcut in the list is unique by alias. -
ShortCut
: Represents a mapping between an alias (shortcut) and a full tag name. -
Alias
: A class representing the alias of a tag, enforcing specific validation rules. -
FullTagName
: A class representing the full name of the tag associated with an alias, also enforcing validation rules.

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
API : Storage.java
The Storage
component is responsible for managing data persistence, including saving and loading both the address book data and user preference settings. It stores data in JSON format and reads them back into corresponding objects.
Structure
The Storage
component consists of the following main packages:
- User Preferences Storage
- Address Book Storage
Each of these storage packages provides interfaces and implementations for reading and writing data in a structured way. The StorageManager
serves as the central manager for handling both user preferences and address book data storage.
Key Classes and Interfaces
-
Storage
(interface):- The primary interface for storage functionality, extending both
AddressBookStorage
andUserPrefsStorage
. - This design allows
Storage
to be used as either an address book storage handler or a user preferences storage handler, depending on the context.
- The primary interface for storage functionality, extending both
-
StorageManager
(class):- Implements the
Storage
interface, consolidating bothAddressBookStorage
andUserPrefsStorage
functionality. - Manages both types of storage using their respective implementations (
JsonAddressBookStorage
for address book data andJsonUserPrefsStorage
for user preferences).
- Implements the
-
UserPrefsStorage
(interface):- Provides methods for reading and writing user preferences data.
- Implemented by
JsonUserPrefsStorage
, which reads/writes user preferences in JSON format.
-
AddressBookStorage
(interface):- Defines methods for reading and writing the address book data.
- Implemented by
JsonAddressBookStorage
, which handles JSON storage forAddressBook
.
JSON-Based Storage for Address Book Data
The Address Book data storage relies on JSON serialization with the following classes:
-
JsonAddressBookStorage
(class):- Implements
AddressBookStorage
and manages the reading/writing of JSON data for the address book.
- Implements
-
JsonSerializableAddressBook
(class):- Provides the schema for serializing the
AddressBook
data, includingPerson
andShortcut
objects.
- Provides the schema for serializing the
-
JsonAdaptedShortCut
,JsonAdaptedPerson
,JsonAdaptedOrder
,JsonAdaptedOrderHistory
andJsonAdaptedTag
(classes):- Adapter classes for each entity within the
AddressBook
. - These classes are responsible for converting each respective model component into a format suitable for JSON serialization and deserialization.
- Adapter classes for each entity within the
Common classes
Classes used by multiple components are in the seedu.address.commons
package.
Implementation
This section describes some noteworthy details on how certain features are implemented.
Add Shortcut feature
Implementation
The addShortCut
feature enables users to create shortcuts for tags in the address book. This process includes parsing the command input, checking for existing aliases or tag names in Model
to avoid duplication, and adding the shortcut in UniqueShortCutList
in Model
.
Given below is an example usage scenario and how the add shortcut mechanism works at each step.
Step 1: Command Execution by LogicManager
:
- The
LogicManager
receives theexecute("addShortCut al/v tn/Vegan")
command. This initiates the process of adding a shortcut whereal/v
represents the alias (v
), andtn/Vegan
represents the full tag name (Vegan
).
Step 2: Parsing the Command:
-
LogicManager
callsparseCommand
onAddressBookParser
with the command string to interpret the input. -
AddressBookParser
creates anAddShortCutCommandParser
to handle the specifics of parsing theaddShortCut
command.
Step 3: Tokenizing and Parsing Arguments:
- The
AddShortCutCommandParser
tokenizes the command arguments (al/v tn/Vegan
) to identify the alias and full tag name. - It uses
ArgumentTokenizer
to break down the input andParserUtil
to validate and extract the alias (v
) and tag name (Vegan
).
Step 4: Creating AddShortCutCommand
:
- With the parsed alias and tag name,
AddShortCutCommandParser
creates a newAddShortCutCommand
instance with these values and returns it toAddressBookParser
, which then passes it back toLogicManager
.
Step 5: Executing AddShortCutCommand
:
-
LogicManager
calls theexecute
method ofAddShortCutCommand
, passing in theModel
instance (m
) to access and update the data.
Step 6: Checking for Existing Aliases and Tag Names:
- The
AddShortCutCommand
performs checks in theModel
:-
Alias Check: It calls
Model.hasAlias("v")
to see if the alias already exists. If it does, the command throws aCommandException
with the message"Alias already exists"
, and the process stops. -
Tag Name Check: If the alias is unique,
AddShortCutCommand
proceeds to check if the tag name (Vegan
) already exists inModel
usingModel.hasTagName("Vegan")
. If the tag name exists, aCommandException
is thrown with the message"Tag name already exists"
.
-
Alias Check: It calls
Step 7: Adding the New Shortcut:
- If both checks pass (i.e., the alias and tag name are unique),
AddShortCutCommand
callsModel.addShortCut(toAdd)
to add the new shortcut to theModel
. - Following this, it calls
Tag.updateShortCutMappings(m)
to update the shortcut mappings in theTag
component, ensuring the new shortcut is recognized.
Step 8: Returning the Result:
- After successfully adding the shortcut,
AddShortCutCommand
creates aCommandResult
with a success message, such as"New Shortcut added: v -> Vegan"
. - The result is returned back to
LogicManager
, completing theaddShortCut
command execution.

Figure: Add Shortcut Sequence Diagram
Delete Shortcut feature
Implementation
The delShortCut
feature allows users to delete existing shortcuts for tags in the address book. This process involves parsing the command input, checking if the specified shortcut exists in Model
, and removing it if it does. If the shortcut does not exist, an error message is returned.
Below is a step-by-step usage scenario of the delShortCut
feature, illustrating how each component interacts in the deletion process.
Step 1: Command Execution by LogicManager
:
- The
LogicManager
receives theexecute("delShortCut al/v tn/Vegan")
command. This starts the deletion process for the shortcut whereal/v
represents the alias (v
), andtn/Vegan
represents the full tag name (Vegan
).
Step 2: Parsing the Command:
-
LogicManager
callsparseCommand
onAddressBookParser
with the command string to interpret the input. -
AddressBookParser
creates aDelShortCutCommandParser
to handle the specifics of parsing thedelShortCut
command.
Step 3: Tokenizing and Parsing Arguments:
-
DelShortCutCommandParser
tokenizes the command arguments (al/v tn/Vegan
) to extract the alias and full tag name. - It uses
ArgumentTokenizer
to break down the input andParserUtil
to parse and validate the alias (v
) and tag name (Vegan
).
Step 4: Creating DelShortCutCommand
:
- With the parsed alias and tag name,
DelShortCutCommandParser
creates a newDelShortCutCommand
instance with these values and returns it toAddressBookParser
, which then passes it back toLogicManager
.
Step 5: Executing DelShortCutCommand
:
-
LogicManager
calls theexecute
method ofDelShortCutCommand
, passing in theModel
instance (m
) to access and update data.
Step 6: Checking for the Shortcut’s Existence:
- Inside
execute
,DelShortCutCommand
first checks if the shortcut exists by callingModel.hasShortCut(toRemove)
:-
Shortcut Exists: If
hasShortCut
returnstrue
, the command proceeds to delete the shortcut. -
Shortcut Does Not Exist: If
hasShortCut
returnsfalse
, aCommandException
is thrown with the message"Shortcut not found"
, and the process stops.
-
Shortcut Exists: If
Step 7: Deleting the Shortcut and Updating Mappings:
- If the shortcut exists,
DelShortCutCommand
callsModel.removeShortCut(toRemove)
to remove the shortcut fromModel
. - It then calls
Tag.updateShortCutMappings(m)
to ensure theTag
component reflects the updated shortcuts.
Step 8: Returning the Result:
- After successfully deleting the shortcut,
DelShortCutCommand
creates aCommandResult
with the success message"Shortcut Deleted: v"
. - The result is returned to
LogicManager
, completing thedelShortCut
command execution.

Figure: Delete Shortcut Sequence Diagram
Additional Info
- This implementation ensures users can delete shortcuts with feedback on success or failure. If the shortcut does not exist, the system provides a clear error message, and
Tag.updateShortCutMappings
ensures shortcut mappings remain up-to-date across components. - This implementation allows users to create new shortcuts while enforcing uniqueness. It provides meaningful error feedback if a conflict exists, and updates the model with new shortcuts. The
Tag.updateShortCutMappings
method ensures all components are aware of the new mapping, maintaining consistency across the application.
Design Explanation:
When creating a Shortcut, ALIAS and TAG_NAME are case-insensitive: the methods
Model.hasAlias
andModel.hasTagName
checks if the value exist in storage regardless of how it is captialised. This was done to avoid confusion between different aliases set. eg. “V” and “v”. We still wanted to enforce case-sensitivity for commands witht/
when creating custom tags to offer flexibility to the user
Add an Order
to customer using put
Implementation
The put command enables users to add an order for a specified contact in the address book. This process includes parsing the command input to retrieve the order and contact name, checking in Model if the order and person exist, and, if valid, adding the order to the contact’s record.
Below is an example usage scenario and how the put command mechanism works at each step.
-
Execution Begins
The
LogicManager
receives the"put cake n/Alex"
command and calls itsexecute
method. -
Command Parsing
-
LogicManager
sends aparseCommand("put cake n/Alex")
request toAddressBookParser
to interpret the command. -
AddressBookParser
creates a new instance ofPutOrderCommandParser
and forwards the request to it by callingparse("cake n/Alex")
.
-
-
Command Creation
-
PutOrderCommandParser
creates anew PutOrderCommand
object with the order “cake” and person “Alex” as parameters.
-
-
Order Existence Check
-
PutOrderCommand
sends ahasOrder(order)
request toModel
to check if the order “cake” exists. -
Model
returns true if the order exists (indicating that “cake” is a valid order) and the command proceeds to the next step. - If the order does not exist,
Model
returns false, andPutOrderCommand
throws aCommandException
with the messageMESSAGE_ORDER_NOT_FOUND
back toLogicManager
, ending the execution.
-
-
Person Existence Check (if the order exists)
-
PutOrderCommand
callsfindPersonByName(Name)
onModel
to search for a person with the name “Alex”. - If “Alex” exists in
Model
,Model
returns thePerson
object representing “Alex”. - If “Alex” does not exist,
Model
returns null, andPutOrderCommand
throws aCommandException
with the messageMESSAGE_PERSON_NOT_FOUND
back toLogicManager
, ending the execution.
-
-
Order Placement (if both the order and person exist)
-
PutOrderCommand
sends aputOrder(Order)
message to thePerson
object to assign the order “cake” to “Alex”. - After successfully placing the order,
PutOrderCommand
returns aCommandResult
with the success messageMESSAGE_SUCCESS
back toLogicManager
.
-
-
Execution Completion
The command completes its execution, and
LogicManager
processes the finalCommandResult
.

Figure: Put Order Sequence Diagram
Download Command
Implementation
The download
command allows users to export the currently displayed customer data in CSV format. Users can also
filter the exported data by specific tags to narrow down the content. The generated CSV file contains customer details such as name, phone, email, postal code, address, and tags.
Usage
download [t/TAG_NAME...]
-
t/TAG_NAME
: Optional. Specifies the tags to filter customers. Only customers with the specified tags will be included in the exported file. - If no tags are provided, all customer data will be exported.
Example
- Export all the currently displayed customer data:
download
Expected Outcome: A CSV file containing all customer data is created in the
./data
subdirectory. - Export data for customers tagged with “VIP”:
download t/VIP
Expected Outcome: A CSV file containing only customers tagged with “VIP” is created in the
./data
subdirectory. - Export data for customers with multiple tags:
download t/VIP t/Regular
Expected Outcome: A CSV file containing customers tagged with either “VIP” or “Regular” is created.
- Export data with non-matching tags:
download t/NonExistentTag
Expected Outcome: An error message is displayed and no file is generated.
Implementation Steps
-
Command Execution:
- The
LogicManager
receives thedownload
command and forwards it toAddressBookParser
.
- The
-
Command Parsing:
-
AddressBookParser
creates aDownloadCommandParser
, which:- Tokenizes the input to extract the
TAG_NAME
arguments. - Validates the arguments using
ParserUtil
. - Creates a new
DownloadCommand
with the extracted tag names.
- Tokenizes the input to extract the
-
-
File Generation:
-
DownloadCommand
is executed with access to theModel
:- Retrieves the list of customers using
Model#getFilteredPersonList
. - Filters the customers based on the provided tags, if any.
-
Empty Result Handling:
- If the filtered list is empty:
- Return an error message.
- Skip the CSV generation process.
- If the list is not empty:
- Converts the customer data to CSV format.
- Writes the CSV data to a file in the
./data
subdirectory.
- If the filtered list is empty:
- Retrieves the list of customers using
-
Sequence Diagram
Below is the sequence diagram for the download
command:
Delete by postal code feature
Implementation
The deletePC feature allows users to delete all contacts associated with a specified postal code. This process involves parsing the command input, validating the postal code format, retrieving the list of people matching the postal code from Model, and, if matches are found, deleting each associated contact in Model.
Below is an example usage scenario and how the deletePC mechanism works at each step.
-
Execution Begins:
- The
LogicManager
receives the"deletePC 540123"
command and initiates theexecute
method.
- The
-
Command Parsing:
-
LogicManager
sends theparseCommand("deletePC 540123")
request toAddressBookParser
to interpret the command. -
AddressBookParser
creates aDeletePostalCodeCommandParser
instance and calls itsparse("deletePC 540123")
method.
-
-
Postal Code Validation:
-
DeletePostalCodeCommandParser
validates the format of the postal code"540123"
. -
If the format is invalid:
DeletePostalCodeCommandParser
returns aParseException
with the message “Invalid format” toLogicManager
, and the process terminates. -
If the format is valid:
DeletePostalCodeCommandParser
proceeds to the next step.
-
-
Postal Code Creation:
-
DeletePostalCodeCommandParser
creates a newPostalCode
object with the value"540123"
. - Once created,
PostalCode
returns itself (c
) toDeletePostalCodeCommandParser
.
-
-
Command Creation:
-
DeletePostalCodeCommandParser
creates a newDeletePostalCodeCommand
object using thePostalCode
instancec
. -
DeletePostalCodeCommand
returns itself toDeletePostalCodeCommandParser
. -
DeletePostalCodeCommandParser
then sendsDeletePostalCodeCommand
back toAddressBookParser
, completing the parsing.
-
-
Command Execution:
-
AddressBookParser
returnsDeletePostalCodeCommand
toLogicManager
. -
LogicManager
then callsexecute(m)
onDeletePostalCodeCommand
, passing theModel
instancem
as a parameter.
-
-
Finding People by Postal Code:
-
DeletePostalCodeCommand
requestsModel
to find all people associated with postal code"540123"
by callinggetPeopleByPostalCode(c)
. -
Model
returns a list of people (peopleToDelete
) associated with"540123"
.
-
-
Handling Results:
-
If
peopleToDelete
is empty:DeletePostalCodeCommand
returns aCommandException
with the message “No match” toLogicManager
, ending the execution. -
If
peopleToDelete
is not empty:DeletePostalCodeCommand
proceeds to delete each person in the list.
-
If
-
Deleting People:
- For each
Person
inpeopleToDelete
,DeletePostalCodeCommand
callsModel
’sdeletePerson(person)
to remove the person.
- For each
-
Generating Result:
- After all people are deleted,
DeletePostalCodeCommand
creates aCommandResult
with the message “Deleted customers with postal code 540123: [names]” to indicate the successful deletion. -
CommandResult
is returned toDeletePostalCodeCommand
, completing the command execution.
- After all people are deleted,
-
Completion:
-
DeletePostalCodeCommand
sends the finalCommandResult
(r
) toLogicManager
. -
LogicManager
processes the result, concluding the execution of"deletePC 540123"
.
-

Figure: Delete by Postal Code Command Sequence Diagram
List Shortcut feature
Implementation
The listShortCut
feature allows users to view all existing shortcuts for tags in the address book. This process involves parsing the command input, retrieving the list of shortcuts from the Model
, formatting the shortcuts into a readable format, and displaying them to the user.
Below is a step-by-step usage scenario of the listShortCut
feature, showing how each component interacts to achieve this functionality.
Step 1: Command Execution by LogicManager
:
- The
LogicManager
receives theexecute("listShortCut")
command, initiating the process of listing all tag shortcuts in the address book.
Step 2: Parsing the Command:
-
LogicManager
callsparseCommand
onAddressBookParser
with the command string to interpret the input. -
AddressBookParser
creates aListShortCutCommandParser
to handle the specifics of parsing thelistShortCut
command.
Step 3: Creating ListShortCutCommand
:
-
ListShortCutCommandParser
parses the command, confirming it matches the expected format. - After validating the command, it creates a new
ListShortCutCommand
instance and passes it back toAddressBookParser
, which in turn returns it toLogicManager
.
Step 4: Executing ListShortCutCommand
:
-
LogicManager
calls theexecute
method ofListShortCutCommand
, passing in theModel
instance (m
) to access stored shortcuts.
Step 5: Retrieving and Formatting Shortcuts:
-
ListShortCutCommand
callsModel.getShortCutList()
to retrieve the current list of shortcuts stored inUniqueShortCutList
. - It then formats the shortcuts by calling
formatShortCuts(shortcutList.toString())
, which organizes the list into a readable format for the user.
Step 6: Creating and Returning the Result:
- After formatting,
ListShortCutCommand
creates aCommandResult
with the message containing the formatted list of shortcuts (e.g.,"Shortcut Mappings\n<formatted shortcuts>"
). - The result is returned to
LogicManager
, completing thelistShortCut
command execution.
Filter feature
Implementation
The filter
feature enables users to filter the list of persons in the address book based on specific tag keywords. This process involves parsing the command input, generating a predicate for filtering, updating the filtered list in Model
, and displaying the results.
Below is a step-by-step usage scenario of the filter
feature, showing how each component interacts to achieve this functionality.
Step 1: Command Execution by LogicManager
:
- The
LogicManager
receives theexecute("filter v vg")
command, initiating the filtering process wherev
andvg
represent the tag keywords for filtering persons in the address book.
Step 2: Parsing the Command:
-
LogicManager
callsparseCommand
onAddressBookParser
with the command string to interpret the input. -
AddressBookParser
creates aFilterCommandParser
to handle the specifics of parsing thefilter
command.
Step 3: Creating TagsContainsKeywordsPredicate
:
-
FilterCommandParser
extracts the keywords (v
andvg
) from the command. - It then creates a new
TagsContainsKeywordsPredicate
instance, passing the keywords to this predicate, which is used to filter persons based on tags matching these keywords.
Step 4: Creating FilterCommand
:
-
FilterCommandParser
then creates aFilterCommand
with theTagsContainsKeywordsPredicate
and passes it back toAddressBookParser
, which returns it toLogicManager
.
Step 5: Executing FilterCommand
:
-
LogicManager
calls theexecute
method ofFilterCommand
, passing in theModel
instance (m
) to update the list of persons based on the filter criteria.
Step 6: Updating the Filtered List:
-
FilterCommand
callsModel.updateFilteredPersonList(predicate)
, which applies theTagsContainsKeywordsPredicate
to filter persons by tags. - The
Model
updates the filtered list of persons and returns confirmation.
Step 7: Retrieving and Displaying the Filtered List:
-
FilterCommand
then callsModel.getFilteredPersonList()
to retrieve the updated filtered list of persons matching the keywords. - A
CommandResult
is created with a message summarizing the number of persons listed after filtering (e.g.,"X persons listed"
).
Step 8: Returning the Result:
- The
CommandResult
is returned toLogicManager
, completing thefilter
command execution.
This implementation allows users to filter persons by tag keywords, enabling them to quickly locate individuals associated with specific tags. The feature provides feedback on the number of matching persons, enhancing the usability of the address book.
This implementation enables users to quickly view all tag shortcuts, providing a well-organized list of alias-to-tag mappings. The formatting method ensures that the displayed shortcuts are easy to read, enhancing the user experience.
[Proposed] Undo/redo feature
Proposed Implementation
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
.

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.

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:

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.

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:
Design considerations:
Aspect: How undo & redo executes:
-
Alternative 1 (current choice): Saves the entire address book.
- Pros: Easy to implement.
- Cons: May have performance issues in terms of memory usage.
-
Alternative 2: Individual command knows how to undo/redo by
itself.
- Pros: Will use less memory (e.g. for
delete
, just save the person being deleted). - Cons: We must ensure that the implementation of each individual command are correct.
- Pros: Will use less memory (e.g. for
Documentation, logging, testing, configuration, dev-ops
Appendix: Requirements
Product scope
Target user profile:
- Managers of smaller eateries/restaurants who need to maintain organised customer details for delivery purposes.
- prefer desktop apps over other types
- can type fast
- prefers typing to mouse interactions
Value proposition: For restaurants to efficiently and accurately manage customer contact and information
User stories
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * * |
manager | add a customer’s detail via a one-line CLI command | quickly add new customers without >1 step |
* * * |
manager | retrieve a customer’s detail via a one-line CLI command | view their details without having to scroll through pages |
* * * |
manager | delete customer data in one-line CLI | delete customers that I want to not deliver to anymore |
* * * |
manager | search for customers by name | quickly access customer profiles during phone orders |
* * * |
manager | search for customers by phone number | quickly access customer profiles during phone orders |
* * |
manager | edit customer data in one-line CLI | update any part of user data in 1 step |
* * |
manager | bulk-create multiple customers through a file | quickly populate the system |
* * |
manager | bulk update multiple customers | effectively manage large sets of customers |
* * |
manager | assign tags to customers easily by setting abbreviations to tagnames | not waste time typing out tag names that are already predetermined |
* * |
manager | be able to see order history of a customer | apply targeted advertising |
* * |
manager | be able to see order frequency of a customer | prepare suitable amount of ingredient beforehand |
* |
manager | add order to the application | track what is being served at the restaurant |
* |
manager | archive customer data instead of a hard delete | avoid losing this information permanently |
* |
manager | unarchive a customer | continue serving this customer |
* |
manager | list customers by recent order dates | quickly identify who is a repeat customer |
* |
manager | list customers by order frequency | identify regular customers for reward programmes |
* |
manager | export customer data as a CSV | provide the data to other people to use |
* |
manager | convert a CSV file into a readable state file | have a backup |
* |
manager | export CSV data using specific criteria | reduce the effort needed by others to parse the data |
* |
manager | validate attributes when adding a customer | avoid entering invalid data |
* |
manager | validate attributes when updating a customer | avoid entering invalid data |
* |
manager | categorise customers as VIP, regular, or new | know who to target for promotions |
* |
manager | tag customers with dietary restrictions | personalise orders and maintain customer service |
* |
manager | create a tag | tag customers with a different issue |
* |
manager | tag customers with multiple different tags | store a pattern of customers |
* |
manager | bulk tag customers with dietary restrictions | save time |
* |
manager | group customers by tag | easily find and manage customers for promotions |
Use cases
(For all use cases below, the System is the NomNomNotifier
and the Actor is the manager
, unless specified otherwise)
Use case: Add a customer
MSS
- Manager requests to add customer
- NomNomNotifier adds the person
- Use Case Ends
Extension
- 1a. Manager request/invalid/incomplete
- 1a1. NomNomNotifier shows an error message
- Use Case Ends
- 1b. Manager tags customer using pre-assigned shortcut
- 1b1. Abbreviation is mapped to pre-assigned tag name
- Use Case resumes at step 2
Use case: Edit for a customer
MSS
- Manager requests to list the customers
- NomNomNotifier shows a list of customers
- Manager requests to edit customer details at a given index
- NomNomNotifier changes the customer detail accordingly
- Use Case Ends
Extension
- 2a. List is empty
- Use Case Ends
- 3a. Manager request does not adhere to field’s restrictions
- 3a1. NomNomNotifier shows an error message
- Use Case resumes at 3
- 3b. Manager tags customers using pre-assigned shortcut
- 3b1. Abbreviation is mapped to pre-assigned tag name
- Use Case resumes at step 4
Use case: Search for a customer
MSS
- Manager requests to list the customers
- NomNomNotifier shows a list of customers
- Manager requests to search for customer by name/phone number
- NomNomNotifier shows the customer
- Use Case Ends
Extension
- 2a. The list is empty
- User Case Ends
- 3a. Manager request/invalid/incomplete
- 3a1. NomNomNotifier shows an error message.
- Use Case Ends
Use case: Delete a person
MSS
- Manager requests to list customers
- NomNomNotifier shows a list of customers
- Manager requests to delete a specific customers in the list
- NomNomNotifier deletes the customers
- Use case ends
Extensions
- 2a. The list is empty.
- Use case ends.
- 3a. The given index is invalid.
- 3a1. NomNomNotifier shows an error message.
- Use case resumes at step 2.
Use Case: Creating a shortcut
MSS
- Manager requests to add shortcut
- NomNomNotifier adds the shortcut with alias and tag name
- Use Case ends
Extension
- 1a. Shortcut format is invalid
- 1a1. NomNomNotifier shows error message
- Use Case ends
- 1b. Alias or tag name in shortcut already exists in NomNomNotifier
- 1b1. NomNomNotifier shows error message
- Use Case ends
Use Case: Deleting a shortcut
MSS
- Manager requests to delete shortcut
- NomNomNotifier deletes the shortcut with specified alias and tag name
- Use Case ends
Extension
- 1a. Shortcut format is invalid
- 1a1. NomNomNotifier shows error message
- Use Case ends
- 1b. ShortCut with same alias and tag name does not exist in NomNomNotifier
- 1b1. NomNomNotifier shows error message
- Use Case ends
Use Case: Adding an order to the application
MSS
- Manager requests to add an order
- NomNomNotifier add the order to UniqueOrderList in with the specified order name
- Use Case ends
Extension
- 1a. Order format is invalid
- 1a1. NomNomNotifier shows error message
- Use Case ends
- 1b. Order already exist in NomNomNotifier
- 1b1. NomNomNotifier shows error message
- Use Case ends
Use Case: Deleting an order from the application
MSS
- Manager requests to delete an order
- NomNomNotifier delete the order with the specified order name from the UniqueOrderList
- Use Case ends
Extension
- 1a. Order format is invalid
- 1a1. NomNomNotifier shows error message
- Use Case ends
- 1b. Order does not exist in NomNomNotifier
- 1b1. NomNomNotifier shows error message
- Use Case ends
Use Case: Viewing all order(s) stored in the application
MSS
- Manager requests to view all order(s)
- NomNomNotifier shows all order(s) stored in the UniqueOrderList
- Use Case ends
Use Case: Viewing the order history of a customer
MSS
- Manager requests to view order history of a customer
- NomNomNotifier attempts to get the order history associated with the customer
- NomNomNotifier display the order history of the customer
- Use Case ends
Extension
- 2a. Customer not found
- 2a1. NomNomNotifier shows error message
- Use Case ends
Planned Enhancements
Team Size: 5
-
Enforce Case-Insensitivity for Tags to Promote Consistency
The current behavior for tag handling creates a discrepancy between shortcut creation (case-insensitive) and tag usage (
t/
command is case-sensitive), which can lead to user confusion. We plan to align the behavior for consistency and improve user experience. We plan to make tags case-insensitive to promote consistency between shortcut handling and custom tagging. -
Enforce Prefix Usage (
t/
) on Filter Command:The current behaviour regarding the filter feature, involves the multiple keywords being parsed into
TagsContainKeywordPredicate
which uses thecontain
method against all current tags and filtering the people based on the keywords. As Tag Names are allowed to have spaces, possible tags could be “No Pork” and “Pork Lover”. When using the filter feature for “Pork Lover” as input, the result would show people containing both tags as both contain the keyword: “Pork”. This may limit the effectiveness of this feature. As such, we plan to use prefixt/
within the filter command so that the entire keyword “Pork Lover” can be parsed, with relevant customer details being listed. -
Enhance the unique identifier of a
Person
to be both name and phone numberThe current behaviour includes the unique identifier of a Person to the name attribute. However, we acknowledge that people can have the same name. As such, this enhancement would entail the name and phone number to be unique identifier for a
Person
. We understand that this may interfere with order put command. This would also entail the inclusion of a phone number field when using theput
command. -
Being able to edit/delete order history of a customer
Add
editHistory
anddeleteHistory
command that allows order history of a customer to be modified, to enhance flexibility of the application. To do that, we can addeditHistory
/deleteHistory
method insideOrderTracker
, and call it from theexecute
ineditHistory
/deleteHistory
. -
Being able to export customer order history into csv file
Add
downloadOrderHistory
command to export all customer data alongside the order history, so that the manager can mass process all the customer data for other purpose such as targeted advertising using machine learning -
Add support for reading long command
Currently, the application may crash if the user inputs a very large string into the text box, as the entire content is loaded into memory at once. This can cause issues with memory management and lead to application instability or crashes, especially for files or inputs with significant text content. To prevent crashes and improve performance, we plan to implement a buffered reading approach using Java’s BufferedReader to handle large text inputs in manageable chunks
Non-Functional Requirements
- Should work on any mainstream OS as long as it has Java
17
or above installed. - Command Response Time: All operations (add, delete, search) should respond within 1 second
- Bulk Operations: Bulk actions (e.g., adding multiple customers) should handle up to 100 records and complete within 2 seconds
- 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.
- Error Handling: Meaningful error messages should be provided for invalid inputs (e.g., invalid email or phone number).
- Command Documentation: Provide help text for each command and clear usage instructions.
Glossary
- Mainstream OS: Windows, Linux, Unix, MacOS
- Private contact detail: A contact detail that is not meant to be shared with others
- CLI (Command-Line Interface): A text-based interface where users can type commands to interact with the system
- Customer: An individual whose details (e.g., name, phone number, email) are stored in the system for tracking purposes
- Tag: A label or keyword that can be associated with a customer to categorize or describe them (e.g., “vegetarian”, “loyalty-programme”)
- Parameter: Information provided by the user as part of a command, such as name, email, or phone number
- CSV (Comma-Separated Values): A common format for storing and exchanging tabular data, where each row represents a record, and each field is separated by a comma
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
-
cd into that empty folder and run the command :
java -jar NomNomNotifier.jar
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 running the command:
java -jar NomNomNotifier.jar
.
Expected: The most recent window size and location is retained.
-
Deleting a person
-
Deleting 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.
-
Saving data
-
Dealing with corrupted data files
- Within the same directory of NomNomNotifier.jar, if the data file with
addressbook.json
is corrupted (eg. input restrictions for fields is breached), the next time NomNomNotifier is relaunched, an empty list will be displayed on the GUI. - If customers are added to that empty list, changes made to the empty list will overwrite the corrupted
addressbook.json
file
- Within the same directory of NomNomNotifier.jar, if the data file with
-
Dealing with missing data file
- Within the same directory of NomNomNotifier.jar, if the data file with
addressbook.json
is missing or deleted, the next time NomNomNotifier is relaunched, a sample list of customers will be showned. - When new data is stored or existing data is changed through existing commands (eg. add, edit, add shortcut, add order), a new
addressbook.json
file will be created
- Within the same directory of NomNomNotifier.jar, if the data file with