CinnamonBun Developer Guide
- Acknowledgements
- Setting up, getting started
- Design
- Implementations
- Documentation, logging, testing, configuration, dev-ops
-
Appendix: Requirements
- Product scope
- User stories
-
Use cases
- Use case: Get client list
- Use case: Add a client
- Use case: Edit a client’s information
- Use case: Append fields and tags to a client
- Use case: Remove fields and tags from a client
- Use case: Add remark to a client
- Use case: Delete a client
- Use case: Find a client
- Use case: Sort customer list
- Use case: Add a transaction
- Use case: Delete a transaction
- Use case: List all transactions
- Use case: Find transactions from a client
- Use case: Pay a transaction
- Use case: Unpay a transaction
- Use case: Undo modifications
- Use case: Add membership to a client
- Non-functional requirements
- Glossary
-
Appendix: Instructions for manual testing
- Launch and shutdown
- Adding a client
- Editing a client
- Append fields to a client
- Remove fields from a client
- Add or remove remark from a client
- Deleting a client
- Sorting the client list
- Adding a Transaction
- Deleting a Transaction
- List a Transaction
- Find Transactions
- Pay
- Unpay
- Undo data modifications
- Adding membership
- Removing membership
- Listing all members
- Saving data
- Appendix: Effort
Acknowledgements
List of sources of all code and third-party libraries:
- This project is based on the AddressBook-Level3 projected created by the SE-EDU initiative.
- Libraries used: JavaFX
- Edit distance algorithm adapted from geeksforgeeks.
- ListView selection adapted from StackOverflow.
Setting up, getting started
Refer to the guide Setting up and getting started.
Design
Note
The .puml files used to create diagrams in this document can be found in the diagrams folder. Refer to the PlantUML Tutorial at se-edu/guides to learn how to create and edit diagrams.
Architecture

Figure: High-level architecture diagram of CinnamonBun.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main has two classes called Main
and MainApp.
It is responsible for,
- At app launch: Initializes the components in the correct sequence, and connects them up with each other.
- At shut down: Shuts down the components and invokes cleanup methods where necessary.
Commons represents a collection of classes used by multiple other components.
The rest of the App consists of 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.
How the architecture components interact with each other

Figure: Sequence diagram of main components’ interaction when use 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.

Figure: Partial class diagram of interaction of the main components.
The sections below give more details of each component.
UI component

Figure: Overview of CinnamonBun’s GUI.
The API of this component is specified in Ui.java

Figure: Class diagram of CinnamonBun’s GUI using JavaFX framework.
The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, PersonListPanel, TransactionListPanel, 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. - keeps a reference to the
Logiccomponent, because theUIrelies on theLogicto execute commands. - depends on some classes in the
Modelcomponent, as it displaysPersonandTransactionobject residing in theModel.
Logic component
API : Logic.java

Figure: Partial class diagram of the Logic component.
How the Logic component works:
- When
Logicis called upon to execute a command, it uses theAddressBookParserclass to parse the user command. - This results in a
Commandobject (more precisely, an object of one of its subclasses e.g.,AddCommand) which is executed by theLogicManager. - The command can communicate with the
Modelwhen it is executed (e.g. to add a client). - The result of the command execution is encapsulated as a
CommandResultobject which is returned back fromLogic.

Figure: Interactions inside the logic component for the delete 1 command.
Info
The lifeline for DeleteCommandParser and DeleteCommand should end at their destroy markers (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Here are the other classes in Logic (omitted from the class diagram above) that are used for parsing a user command:

How the parsing works:
- When called upon to parse a user command, the
AddressBookParserclass creates anXYZCommandParser(XYZis a placeholder for the specific command name e.g.,AddCommandParser) which uses the other classes shown above to parse the user command and create aXYZCommandobject (e.g.,AddCommand) which theAddressBookParserreturns back as aCommandobject. - All
XYZCommandParserclasses (e.g.,AddCommandParser,DeleteCommandParser, …) inherit from theParserinterface so that they can be treated similarly where possible e.g, during testing.
Model component
API : Model.java

Figure: Class diagram of Model component.
The Model component,
- stores the client data i.e., all
Personobjects (which are contained in aUniquePersonListobject). - stores the currently ‘selected’
Personobjects (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 the transaction data i.e., all
Transactionobjects (which are contained in aTransactionListobject). - stores the currently ‘selected’
Transactionobjects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiableObservableList<Transaction>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
UserPrefobject that represents the user’s preferences. This is exposed to the outside as aReadOnlyUserPrefobjects. - does not depend on any of the other three components (as the
Modelrepresents data entities of the domain, they should make sense on their own without depending on other components).
Storage component
API : Storage.java

Figure: Class diagram of Storage component.
The Storage component,
- stores user preference data in json format, and reads it back into corresponding objects.
- stores client and transaction data in binary format, and reads it back into corresponding objects.
- inherits from
AddressBookStorage,TempAddressBookStorageandUserPrefStorage, which means it can be treated as either one (if only the functionality of only one is needed). - depends on some classes in the
Modelcomponent (because theStoragecomponent’s job is to save/retrieve objects that belong to theModel)
Common classes
Classes used by multiple components are in the seedu.address.commons package.
Implementations
This section describes some noteworthy details on how certain features are implemented.
Person, Field and Tag
Implementation
The Person class is used to represent a client. The abstract Field class represents the client data, and the Tag class represents the tags of the Person.
Person contains an immutable HashMap<Prefix, Field> of fields, and an immutable HashSet<Tag> of tags. Since Person is immutable, any getters and setters
will return a new instance of Person. By using a HashMap<Prefix, Field> and HashSet<Tag>, Person can be made modular, as it is easy to add any new data to Person
without needing to edit Person.java.
By adding the getter and setter functions, we were able to remove unnecessary classes like EditPersonDescriptor & PersonBuilder from the original AB-3 base code.

Figure: Simplified class diagram of showing the association between Person, Field and Tag.
AddCommand, EditCommand, RemarkCommand, AppendCommand and RemoveCommand adds and removes fields and tags from a Person.

Figure: Simplified sequence diagram of AppendCommand.
Info
The lifeline for AppendCommandParser and AppendCommand should end at their destroy markers (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Note
- The above sequence diagram has been simplified to provide a more comprehensible overview. It is not completely accurate as some unnecessary details have been omitted.
- While not shown here,
AddCommand,EditCommand,RemarkCommand,RemoveCommandwork very similarly toAppendCommand, and their sequence diagram will be almost identical toAppendCommand’s.
Design considerations
Aspect: Structure for storing fields
-
Alternative 1 (original AB-3 method):
Hard-code every field directly into
Person.- Pros: None.
- Cons: Extremely highly coupled code that requires many changes to be done everytime a new field is added. Prone to bugs.
-
Alternative 2 (current choice)
Make
Personmodular by storing aHashMap<Prefix, Field>of fields instead.- Pros: Code is much less coupled. Much easier to maintain.
- Cons: A lot of work was required to decouple the existing
Personimplementation from AB-3.
Transaction
Implementation
The Transaction class is used to represent a client’s transaction. The abstract TransactionField class represents the transaction data.
Transaction contains an immutable HashMap<Prefix, TransactionField> of transactionFields as well as identifier of the Person
who owns the Transaction. This person identifier will be stored as a long inside the Transaction class.

Figure: Simplified class diagram of showing the association between Transaction and other related classes.
Transaction class has 6 commands: AddTransaction, listTransaction, findTransaction, deleteTransaction,
pay, and unpay. All commands have similar implementation, user input will be parsed to
return a Command object. Model will execute this object to do the desired behavior.
Info
Note that there is a dependencies from class Transaction to class Person. Transaction does not
directly store the reference to the Owner (Person) of the Transaction, instead Transaction
stores an identifier (personIdentifier) to identify the owner (Person) of the Transaction.
Design considerations
The current implementation of Transaction class is similar to Person class. Every field/attribute of transaction needs to
extend from the TransactionField class. The Transaction class will have a list of TransactionFields in which all of it’s
fields must be registered in the TransactionFieldRegistry. Each field is either a required field or an optional field.
Transaction class consists of fields Amount, TransactionDate, DueDate, Note, and Status.
Aspect: Structure for storing fields
-
Alternative 1 (current choice): Create a list (
FilteredList) of Transactions, controlled byModelManager. Everytime a user create a transaction, a new instance of transaction will be added to the list and a client specified by its unique identifier (personId) will be referenced by this transaction. To list all the transactions of a particular client, theFilteredListshould be updated to only containTransactionwith a reference to the client’s id.- Pros: Consistent design with the
Personclass. - Cons: Have to handle cases when a user is updated/removed. The input specified by the users corresponds to the index of the displayed clients/users. Hence we need to retrieve the client’s attributes before initializing the Transaction object thus harder to maintain a good design.
- Pros: Consistent design with the
-
Alternative 2: Every
Personobject has a list of transactions which will be initialized with an empty list. Each time a user add a transaction, the object will be added into the specifiedPerson’s Transaction List.- Pros: Easier to implement compared to the other alternative.
- Cons:
Transactionclass would be coupled to thePersonclass. In addition, inconsistent design in comparison to thePersonclass.
Add Transaction
Implementation
The Add Transaction implementation is similar to adding a Person with
an extra step. When adding Transaction, user will have to specify
the owner of the Transaction using the index displayed.
However, Transaction stores personIdentifier as the reference to the Person class. Hence there
must be a mechanism to convert the index provided by the user to the correct
personIdentifier.
This issue is addressed by using the class TransactionBuilder that takes a personIdentifier
and returns the desired Transaction.

Figure: Simplified activity diagram showing the sequence of action of Add Transaction command.
Given below is an example usage scenario and how Add Transaction behaves at each step:
- The user input
addTransaction 1 a/12.5 td/2020-11-11 dd/2020-12-12 n/This is a note --paid. -
LogicManagerexecute the command which will be parsed usingAddressBookParserandAddTransactionParserif the input is valid. -
AddTransactionParserwill returnAddTransactionCommandwhich containsTransactionBuilder. ThisTransactionBuilderwill be used later byAddTransactionCommand#executeto generate the appropriateTransaction. -
AddTransactionCommandwill then update theTransaction Listby callingModel#addTransaction. -
CommandResultwill be returned back to theLogicManagerif there is no failure in execution.
The above steps are illustrated by the AddTransaction Sequence Diagram below. In the given diagram,
addTransactionEx represents addTransaction 1 a/12.5 td/2020-11-11 dd/2020-12-12 n/This is a note --paid
and transaction represents 1 a/12.5 td/2020-11-11 dd/2020-12-12 n/This is a note --paid for brevity.
Figure: Simplified sequence diagram showing the sequence of action of Add Transaction command.
Design considerations
Aspect: How add transaction executes
-
Alternative 1 (current choice): Store
TransactionBuilderto generate theTransactionlater.- Pros: No need to pass the
ModelManagerto the command parser, makes a better separation of concern. - Cons: Harder to do unit tests on this implementation since it uses functinal interface.
- Pros: No need to pass the
-
Alternative 2: Pass the ModelManager to
AddTransactionCommandParser.- Pros: easier to implement.
- Cons: Not a good design as model should only resides inside
Command#execute.
Find Transaction
Implementation
In findTransaction implementation, user will have to specify
the Person using the index displayed.
However, we have a similar issue as AddTransaction. To get all transactions
that the owner has, we need to convert the index to the correct personIdentifier.
This personIdentifier will be used as the TransactionPredicate to filter the Transaction
List.
This issue is addressed by using the class TransactionPredicateBuilder that takes a personIdentifier
and returns the desired TransactionPredicate.

Figure: Simplified activity diagram showing the sequence of action of Find Transaction command.
Given below is an example usage scenario and how Find Transaction behaves at each step:
- The user input
findTransaction 1. -
LogicManagerexecute the command which will be parsed usingAddressBookParserandFindTransactionCommandParserif the input is valid. -
FindTransactionCommandParserwill returnFindTransactionCommandwhich containsTransactionPredicateBuilder. This will be used later byFindTransactionCommand#executeto generate the appropriateTransactionPredicate. -
AddTransactionCommandwill then update theTransaction Listby callingModel#updateFilteredTransactionListusingTransactionPredicate. -
CommandResultwill be returned back to theLogicManagerif there is no failure in execution.
Figure: Simplified sequence diagram showing the sequence of action of Find Transaction command.
Design considerations
Aspect: How find transaction executes
-
Alternative 1 (current choice): Store
TransactionPredicateBuilderto generate theTransactionPredicatelater.- Pros: No need to pass the
ModelManagerto the command parser, makes a better separation of concern. - Cons: Harder to do unit tests on this implementation since it uses functinal interface.
- Pros: No need to pass the
-
Alternative 2: Pass the ModelManager to
FindTransactionCommandParser.- Pros: easier to implement.
- Cons: Not a good design as model should only resides inside
Command#execute.
Pay
Implementation
This command will update the Status of the Transaction to paid using
the specified transaction index by the user.

Figure: Simplified activity diagram showing the sequence of action of Pay command.
Given below is an example usage scenario and how Pay behaves at each step:
- The user input
pay 1. -
LogicManagerexecute the command which will be parsed usingAddressBookParserandPayCommandParserif the input is valid. -
PayCommandwill returnPayCommandwhich contains the index of the transaction. - In
PayCommand#execute, thetransactionToUpdatewill be replaced byupdatedTransactionby callingModel#setTransaction. -
CommandResultwill be returned back to theLogicManagerif there is no failure in execution.
Figure: Simplified sequence diagram showing the sequence of action of Pay command.
Info
Although listTransaction, deleteTransaction, and unpay are not shown here,
the implementation of these commands are very similar to the other transaction
commands given.
Undo
Implementation
The undo mechanism is facilitated by SerializableTempAddressBookStorage.
It extends the TempAddressBookStorage interface and is stored and managed at StorageManager as tempAddressBookStorage.
This mechanism will store the previous states of CinnamonBun in temporary files when a modification or change is made to the data.
When users type the undo command, the latest stored state will be restored.
Additionally, it implements the follow operations which are exposed in the Storage interface:
-
SerializableTempAddressBookStorage#getTempAddressBookFilepath()— Gets the file path of where the temporary files are stored. -
SerializableTempAddressBookStorage#addNewTempAddressBookFile()— Saves the current state of CinnamonBun and keep track of the version. -
SerializableTempAddressBookStorage#popTempAddressFileData()— Gets the latest state of CinnamonBun stored inSerializableTempAddressBookStorage.

Given below is an example usage scenario and how the undo mechanism behaves at each step.
Step 1. The user launches the application for the first time. The SerializableTempAddressBookStorage will be initialized
with the file path of where the temporary files will be saved, and an empty ArrayList<Path> named tempFiles is created to store the file paths of the
temporary files that will be created.

Step 2. The user makes a modification to the client list such as executing the command delete 5 to delete the 5th client.
In LogicManager, before executing the command, it will locally store the current client list’s state.
After executing the command, it will compare the previous state with the current state of the client list. If it senses they are different, such as in this
case since user has deleted the 5th client, it will call LogicManager#savePrevAddressBookDataInTemp().
From there, it will call Storage#addNewTempAddressBookFile() where the previous state of the client list, before the delete 5 was executed, will be saved as a temporary file.
The new temporary file will then be added into tempFiles list in SerializableTempAddressBookStorage.
Info
Only modifications made to the client list or transaction list will be saved. See the user guide for more info.
SerializableTempAddressBookStorage also will only store the 10 latest modifications. When the 11th modification is made, it will remove
the earliest modification saved and delete the temporary file for it.
If there are any issues with creating the temporary file and saving the data, an exception will be thrown and the modification will not be saved in a temporary file, thus, the modification cannot be undone.

Step 3. The user executed add n/David... to add a new client. The steps mentioned in step 2 would be repeated. Thus, tempFiles will now store
2 file paths to the 2 temporary files created.

Step 4. The user now decides that adding the client was a mistake and decides to undo that action by executing the undo command.
The undo command will call LogicManager#undoPrevModification(), which calls Storage#popTempAddressFileData(). This will obtain
the latest temporary file added and restore the client list to the state saved in the temporary file (the state of the client list before add the new client, so before step 3).
Info
If there are no previous modifications to be restored (no modifications in the first place, or the user has undone the last 10 modifications and reached the limit) an error message will be shown.
If the temporary file for the modification user is trying to undo is corrupted or deleted, then for that particular modification it can no longer be undone and an error message will be shown there was issue reading the file.

The following sequence diagram shows how the undo operation works:

Step 5. The user executed the command filter Daniel to find all Daniels. This command does not modify the client list.
Hence, Storage#addNewTempAddressBookFile() will not be called and no new temporary files will be added.

Step 6. The user closes the CinnamonBun application. All temporary files created will be deleted.
Design considerations
Aspect: How undo executes
-
Alternative 1 (current choice): Saves the entire application data in temporary files.
- Pros: Easy to implement.
- Cons: May have performance issues in terms of storage and memory usage.
-
Alternative 2: Individual command knows how to undo by itself.
- Pros: Will use less memory (e.g. for
delete, just save the client being deleted). - Cons: We must ensure that the implementation of each individual command are correct.
- Pros: Will use less memory (e.g. for
Clear Filtered functionality
The clearFiltered commands works in tandem with the find or listMembers command. After the find/listMembers function populates
ModelManager’s filteredPersons, the clearFiltered commands retrieves the filteredPersons from the Model and
deletes all people in filteredPersons using the deletePerson function from Model.
If the find or listMembers command had not been executed beforehand, the clearFiltered command will still function
as if clearing the entire address book, as by default, the filteredPersons will be the whole address book.
If there are no people in the filteredPersons, the CommandResult returned has a message “No clients to delete.”.
Below is a sequence diagram to show how the ClearFilteredCommand executes.

Info
The lifeline for ClearFilteredCommand should end at its destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Membership functionality
Implementation
The membership functionality allows users to be assigned a membership tier, either ‘Bronze’, ‘Silver’ or ‘Gold’.
It inherits from the Field abstract class and is stored as a Field in fields HashMap of the Person class.
Below is a figure of the class diagram of the Membership class.

The
ALL tier in the Tier enum is used for listMembers functionality.
When the user calls the addMembership functionality along with a specified client and membership tier, a Membership
object is created and stored in the fields HashMap of the specified client with the key being the Membership prefix and functions like any other Field.
The removeMembership functions in a similar manner to other fields in that the Field with the Membership prefix is removed
from the fields HashMap.
The listMembers command functions in a similar fashion to the Find command in that it uses a Predicate in order to filter
clients with a Membership. It utilizes the PersonContainsMembershipPredicate which checks if a user has a Membership stored
in its fields HashMap.
When a client’s membership is displayed on the screen, the colour of the membership Label changes according to the membership
tier. This is done by having a FlowPane stored in the PersonCard class. When a membership is displayed, a Label with
a different tier is created with a different id. The CSS files (Cinnamon.css or Caramel.css) then decides what colour to render the label background accordingly.
Design considerations
Aspect: How it executes
-
Alternative 1 (current choice): Store a
Membershipobject in each user.- Pros: Easy to implement.
- Cons: Membership functionality is limited (only able to have predefined membership tiers).
-
Alternative 2: Create a list of memberships and assign users a membership index.
- Pros: Allows for more flexible memberships (more than just gold,silver,bronze) with extra details such as descriptions.
- Cons: Harder to implement.
Sort functionality
Implementation
The sort mechanism is facilitated by SortCommand. It extends Command and the main logic of sort is in it’s
execute function which returns a CommandResult object.
The SortCommand#execute() function would first parse the user’s inputs. For every field parsed, the function would create a
Comparator for that field using either of the functions:
-
SortCommand#getComparatorDefault()— Creates a comparator with the field specified in ascending order. -
SortCommand#getComparatorDescending()— Creates a comparator with the field specified in descending order.
One sort command allows for sorting multiple fields at a time in the order specified. Stating sort n/ a/ means
sort the list by name in ascending order followed by the client’s addresses. Clients with same name would be then
sorted based on their addresses.
Thus, after creating the Comparator for a particular field, it’ll be added upon a previously created Comparator using Comparator#thenComparing().
To be able to sort the client list, we expose an operation in the Model interface as Model#sortPersonList().
We then passed the Comparator created to Model#sortPersonList() in SortCommand#execute().
Java’s List library will then handle the sorting based on the Comparator.
Design considerations
Aspect: How it executes
-
Alternative 1 (current choice): Each
Fieldclass will handle how to sort its own data,SortCommandwill then wrap it into a comparator and pass toModel#sortPersonList().- Pros: Easy to implement, each field class can handle their own sorting of its data. Will not clutter
SortCommand. - Cons: Does not allow for more complicated chaining of fields since the way each field is being sorted is independent of others.
- Pros: Easy to implement, each field class can handle their own sorting of its data. Will not clutter
-
Alternative 2:
SortCommandwill determine how the fields are to be sorted.- Pros: Allows
SortCommandto have full flexibility in deciding how the fields are to be sorted and may allow for more complicated chaining of fields. - Cons: Will clutter
SortCommandand may not be manageable once there are a lot of fields.
- Pros: Allows
Command chaining
Implementation
The command chaining mechanism is handled in the execute() function of the LogicManager class which is where the user’s input is parsed, executed and then returned as a CommandResult.
To handle multiple commands, the program splits the given user input using the | delimiter used to separate the multiple commands. Once the input has been split, the program can then evaluate each command sequentially by iterating through the individual commands.
While iterating through the individual commands, the program checks if any of the commands are a special command - help, exit and undo - or if it is invalid. If any of these conditions are met, the program will then set the isValid boolean to false, break out of the loop and set the addressBook of the model to one that was taken before the start of command execution, essentially reverting all the changes.
Design considerations
Aspect: Behaviour of command chains with special commands and errors
-
Alternative 1: The valid commands before a special command
help,exitorundoor command error will still be executed- Pros: Easier to implement as there is no need to check the validity of each command and reset the model.
- Cons: Will only execute certain commands instead of all when a chain is broken which may be confusing. The undo feature may be harder to implement since a command can be partially run.
-
Alternative 2 (current choice): Disregard all commands in a chain whenever a special command or error is found.
- Pros: Intuitive and plays well with other features such as
undo. - Cons: Command validity has to be caught and handled in execution() which may slow down performance.
- Pros: Intuitive and plays well with other features such as
Command completion/correction
Implementation
First, the program checks if the given input or the last command is blank. It is hardcoded to complete such cases with an add command as I thought that was more fitting and also because it would most probably get completed with add anyway after running through the completion algorithm.
If the input is not blank, it will get the last word in the user’s input and first try to complete it by checking if it is a substring of any command. If it is a substring of a command, it will be completed with that command. If the word is a substring of multiple commands, the shortest command will be used.
Once the program determines that the last word cannot be completed, it will try to correct it by finding the Levenshtein distance between the word and all commands. The Levenshtein distance between two words is the minimum number of single-character edits (insertions, deletions or substitutions) required to change one word into the other. The command with the smallest distance from the word will be used as the correction. In the event of a tie, the first evaluated command will be used.
The Levenshtein distance is calculated using the editDistance(String str1, String str2) method which uses a typical dynamic programming algorithm with a runtime of O(m * n). Unlike standard algorithms, however, this method uses a fixed height matrix with only two rows that the program alternates between during computation. This means the size is bounded by the length of str2 which in this case will be one of the fixed commands.
Design considerations
Aspect: Behaviour of command completion/correction
-
Alternative 1: Suggestions will be shown to the user in realtime while they are typing.
- Pros: The user will be able to view each suggestion and will have much more information and freedom to decide whether to take up a suggestion.
- Cons: A lot harder to implement in terms of logic, storage and UI.
-
Alternative 2 (current choice): Complete/correct on demand. Take away user choice and provide what the program thinks is the most accurate replacement.
- Pros: Less computationally intensive and a lot easier to implement.
- Cons: The user will not have a choice of suggestions and will not know what they’ll get (blackbox).
Command history
Implementation
The command history is handled by CommandBox, which also handles the user input.
This mechanism aims to be as close to the history mechanism in the Linux Bash terminal as possible.
Here is a brief example of how the command history works:
- The command history only begins when the application is launched, and is cleared when the application closes.
- Each time the user presses the up arrow key, the command box will be replaced with the command they previously executed, until they reach the start of their history.
- Each time the user presses the down arrow key, the command box will be replaced with the command they next executed, until they reach the latest command (the current one they have not executed).
- If the user executes duplicate commands one after another, only the first will be added to history.
- For example, if the user executes
list,list,list, only the firstlistis added to history.
- For example, if the user executes
The command history works with the use of two ArrayList<String> named historyBuffer and activeBuffer.
historyBuffer contains the commands that was actually executed by the user (i.e., the user pressed enter).
Whenever the user executes a command, regardless whether it is valid or not, the command is added to a historyBuffer.
Commands in historyBuffer, once added, will never change.
activeBuffer is a duplicate of historyBuffer that allows editing. Whenever the user cycles through their history and edits the command, they are editing the commands of activeBuffer.
When the user edits a previous command and executes, the content of that command in activeBuffer is restored with the original in historyBuffer.
Example usage (red arrow is what the command box is displaying):
Step 1. 
Figure: User starts with a blank command box.
Step 2. 
Figure: User types append 1 t/vendor.
Step 3. 
Figure: User executes append 1 t/vendor. append 1 t/vendor is appended to historyBuffer and activeBuffer. Command box is blank again.
Step 4. 
Figure: User types edit 2 e/ but does not execute. User then presses the up arrow key to cycle to addMembership 1 m/silver.
Step 5. 
Figure: User changes addMembership 1 m/silver to addMembership 3 m/bronze.
Step 6. 
Figure: User executes addMembership 3 m/bronze. addMembership 3 m/bronze is appended to historyBuffer and activeBuffer. Un-executed command edit 2 e/ is erased. Command box is blank again. addMembership 1 m/silver in activeBuffer is restored.
Step 7. 
Figure: User cycles to listTransaction and changes it to random invalid string.
Step 8. 
Figure: User cycles to delete 5 and changes it to delete 7.
Step 9. 
Figure: User executes delete 7. delete 7 is appended to historyBuffer and activeBuffer. Command box is blank again. delete 5 in activeBuffer is restored. listTransaction is not restored.
Design considerations
Aspect: Functionality
-
Alternative 1:
Have a very simple implementation with only one history list.
Do not store edits for every single entry, and simply replace the command box with the previous command.
If the user presses up or down, any edits are lost.
- Pros: Very simple to implement.
- Cons: Less feature rich.
-
Alternative 2 (current choice)
Store the history using two lists, and store edits for every command. If the user presses up or down, edits are not lost.
- Pros: Very similar to Linux Bash terminal, intuitive for experienced command line users.
- Cons: More complicated to implement.
Documentation, logging, testing, configuration, dev-ops
Appendix: Requirements
Product scope
Target user profile:
A small business owner or freelancer that:
- has a need to manage not more than 1000 clients.
- has a need to keep track of transactions with clients.
- prefers desktop applications over other types.
- can type fast.
- prefers keyboard interactions to mouse interactions.
- is reasonably comfortable using CLI apps.
Value proposition:
CinnamonBun is a free, open source, purpose built application that handles client and transaction information easily. CinnamonBun helps small businesses and freelancers on a budget to get up and running quickly.
User stories
Priorities: High (must have), Medium (nice to have), Low (unlikely to have)
| Priority | As a … | I want to … | So that I can… |
|---|---|---|---|
HIGH |
user | add new clients | keep my client list updated. |
HIGH |
user | delete clients | remove those who are no longer my clients. |
HIGH |
user | edit client information | keep my clients’ information updated. |
HIGH |
user | list all my clients | see all my client’s information. |
HIGH |
user | find client via keywords | easily find specific clients. |
MEDIUM |
user | delete clients via filtered keywords | easily manipulate and delete people from CinnamonBun. |
MEDIUM |
user | add membership to clients | have a rewards program for my clients. |
MEDIUM |
user | sort my clients | sort the clients based on fields I want. |
LOW |
user | store my client’s birthday | give them discounts on their birthday. |
LOW |
user | store my client’s age | know if I can sell them age restricted items. |
HIGH |
user | add client transaction | store the transaction history of my clients. |
HIGH |
user | delete a transaction | delete the transaction from the transaction list. |
MEDIUM |
user | chain multiple commands | execute multiple commands at once. |
LOW |
user | list all clients who are members. | |
LOW |
user | assign different tiers of memberships to my clients. | |
MEDIUM |
user | undo previous modifications made to the data | undo mistakes. |
MEDIUM |
user | have to ability to auto-complete/auto-correct the commands | type faster. |
MEDIUM |
user | append data fields and tags to clients | append fields and tags to a client without editing the entire client. |
MEDIUM |
user | remove data fields and tags from clients | remove fields and tags from a client without editing the entire client. |
HIGH |
user | be able to re-input my previous commands | execute similar commands without having to retype the whole command. |
HIGH |
user | switch between light and dark themes. | |
HIGH |
user | list all transactions | |
HIGH |
user | find a client’s transactions | see all the transactions associated with a particular client. |
MEDIUM |
user | pay a transaction | change the status of the transaction to be paid. |
MEDIUM |
user | unpay a transaction | change the status of the transaction to be unpaid. |
MEDIUM |
user | flag a transaction as paid or unpaid | know if a transaction has been paid. |
LOW |
user | click on a client to see the list of their transactions | see a client’s transactions without typing. |
HIGH |
user | remove memberships from clients | remove a client from my rewards program. |
Use cases
For all use cases below, the System is CinnamonBun and the Actor is the user, unless specified otherwise.
Use case: Get client list
MSS
- User requests to list all clients.
- CinnamonBun displays a list of all clients.
Use case ends.
Use case: Add a client
MSS
- User requests to add a new client with specified fields.
- CinnamonBun adds the client with the specified fields.
Use case ends.
Extension
- 1a. CinnamonBun detects that the specified fields do not satisfy requirements.
- 1a1. CinnamonBun displays error message.
Use case ends.
- 1a1. CinnamonBun displays error message.
- 1b. CinnamonBun detects a duplicate client.
- 1a1. CinnamonBun displays error message.
Use case ends.
- 1a1. CinnamonBun displays error message.
Use case: Edit a client’s information
MSS
- User specify which client to be edited
- User inputs the values to be edited
- CinnamonBun edits the value
Use case ends.
Extensions
- 1a. No client index specified
- 1a1. CinnamonBun shows an error message.
Use case resumes at step 1.
- 1a1. CinnamonBun shows an error message.
- 2a. No fields are provided
- 2a1. CinnamonBun shows an error message.
Use case resumes at step 2.
- 2a1. CinnamonBun shows an error message.
- 2b. Some fields are inputted wrongly
- 2b1. CinnamonBun shows the appropriate error message.
Use case resumes at step 2.
- 2b1. CinnamonBun shows the appropriate error message.
- 2c. Value edited is email and there is already an existing email by another client in CinnamonBun
- 2c1. CinnamonBun shows an error message.
Use case resumes at step 2.
- 2c1. CinnamonBun shows an error message.
Use case: Append fields and tags to a client
MSS
- User specify which client to be edited.
- User input the fields and tags to be appended.
- CinnamonBun appends the fields and tags to the client.
Use case ends.
Extensions
- 1a. CinnamonBun detects that the specified fields do not satisfy requirements.
- 1a1. CinnamonBun displays error message.
Use case ends.
- 1a1. CinnamonBun displays error message.
- 1b. CinnamonBun detects a duplicate client.
- 1a1. CinnamonBun displays error message.
Use case ends.
- 1a1. CinnamonBun displays error message.
Use case: Remove fields and tags from a client
MSS
- User specify which client to be edited.
- User input the fields and tags to be removed.
- CinnamonBun removes the fields and tags from the client.
Use case ends.
Extensions
- 1a. CinnamonBun detects that the specified fields do not satisfy requirements.
- 1a1. CinnamonBun displays error message.
Use case ends.
- 1a1. CinnamonBun displays error message.
Use case: Add remark to a client
MSS
- User specify which client to be edited.
- User input the remark to be added.
- CinnamonBun adds the remark to the client.
Use case ends.
Use case: Delete a client
MSS
- User requests to list clients.
- CinnamonBun shows a list of clients.
- User specify which client to be deleted.
- CinnamonBun deletes the specified client.
Use case ends.
Extensions
- 2a. The list is empty.
Use case ends. - 3a. The given index is invalid.
- 3a1. CinnamonBun shows an error message.
Use case resumes at step 2.
- 3a1. CinnamonBun shows an error message.
Use case: Find a client
MSS
- User requests to find clients with the specified keyword(s)
- CinnamonBun shows a list of clients with an attribute containing at least one keyword
Use case ends.
Extensions
- 1a. No keyword is specified.
- 1a1. CinnamonBun shows an error message.
Use case resumes at step 2.
- 1a1. CinnamonBun shows an error message.
- 2a. The list is empty.
Use case ends.
Use case: Sort customer list
MSS
- User inputs the fields the list is to be sorted on.
- CinnamonBun sorts the client list accordingly in order of the fields specified.
- The sorted list is displayed.
Use case ends.
Extensions
- 1a. User inputs no fields
- 1a1. An error message is shown.
Use case resumes at step 1.
- 1a1. An error message is shown.
- 1b. User inputs non-existent/not supported fields
- 1b1. An error message is shown
Use case resumes at step 1
- 1b1. An error message is shown
Use case: Add a transaction
MSS
- User specify which client to add the transaction to.
- User input the parameters of the transactions (Amount, Transaction Date, Due Date, Note, and Status).
- CinnamonBun add the transaction to the transaction list.
Use case ends.
Extensions
- 1a. CinnamonBun detects that the specified fields do not satisfy requirements.
- 1a1. CinnamonBun displays error message.
Use case ends.
- 1a1. CinnamonBun displays error message.
Use case: Delete a transaction
MSS
- User specify which transaction to be deleted.
- CinnamonBun removes the transaction from the transaction list.
Use case ends.
Extensions
- 1a. CinnamonBun detects that the specified index does not satisfy requirements.
- 1a1. CinnamonBun displays error message.
Use case ends.
- 1a1. CinnamonBun displays error message.
Use case: List all transactions
MSS
- User specify the command keyword.
- CinnamonBun displays all transactions in the application.
Use case ends.
Use case: Find transactions from a client
MSS
- User specify which client’s transactions be displayed.
- CinnamonBun displays all transactions from the client.
Use case ends.
Extensions
- 1a. CinnamonBun detects that the specified index does not satisfy requirements.
- 1a1. CinnamonBun displays error message.
Use case ends.
- 1a1. CinnamonBun displays error message.
Use case: Pay a transaction
MSS
- User specify which transaction to be paid.
- CinnamonBun changes the status of the transaction to be paid.
Use case ends.
Extensions
- 1a. CinnamonBun detects that the specified index does not satisfy requirements.
- 1a1. CinnamonBun displays error message.
Use case ends.
- 1a1. CinnamonBun displays error message.
Use case: Unpay a transaction
MSS
- User specify which transaction to be unpaid.
- CinnamonBun changes the status of the transaction to be unpaid.
Use case ends.
Extensions
- 1a. CinnamonBun detects that the specified index does not satisfy requirements.
- 1a1. CinnamonBun displays error message.
Use case ends.
- 1a1. CinnamonBun displays error message.
Use case: Undo modifications
MSS
- User undos the latest modification made to the Clients’ list in the CinnamonBun.
- The modifications have been undone.
- CinnamonBun shows the earlier client list without the modifications.
Use case ends.
Extensions
- 1a. There have been no modification made prior to calling undo.
- 1a1. CinnamonBun shows an error message.
Use case ends.
- 1a1. CinnamonBun shows an error message.
- 1b. The previous data cannot be read due to file reading issues/corruption
- 1b1. CinnamonBun shows an error message.
Use case ends.
- 1b1. CinnamonBun shows an error message.
Use case: Add membership to a client
MSS
- User specifies which client to add membership to and what membership tier to assign to the client
- CinnamonBun assigned the membership to the specified client
- CinnamonBun displays the list of clients with updated membership
Use case ends.
Extensions
- 1a. The given index is invalid.
- 1a1. CinnamonBun shows an error message.
Use case ends.
- 1a1. CinnamonBun shows an error message.
- 1b. The given membership tier is invalid.
- 1b1. CinnamonBun shows an error message.
Use case ends.
- 1b1. CinnamonBun shows an error message.
Non-functional requirements
- Should work on any mainstream OS as long as it has Java
11or above installed. - Should be able to hold up to 1000 clients without a noticeable sluggishness in performance for typical usage.
- A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
- A user should be able to easily find a client.
- A user should be able to easily navigate the interface.
- Commands should be able to execute without any noticeable latency.
- Error messages when inputting invalid commands will be displayed in the output box.
Glossary
- Mainstream OS: Windows, Linux, Unix, OS-X
Appendix: Instructions for manual testing
Given below are instructions to test the app manually.
Note
These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.
Launch and shutdown
- Initial launch
- Download the jar file and copy into an empty folder
- Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
- Saving window preferences
- Resize the window to an optimum size. Move the window to a different location. Close the window.
- Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
Adding a client
- Adding a new client while all clients are being shown
- Test case:
add n/Gawr Gura p/12345678 e/gura@hololive.com a/123 Atlantis
Expected: A new client namedGawr Gurashould appear at the bottom of the client list. - Test case:
add n/Ceres Fauna p/12345678
Expected: No new client is added as not all required fields are provided. Error message is shown.
- Test case:
Editing a client
- Editing a client while all clients are being shown
- Prerequisites: There needs to be existing client data in the client list.
- Test case:
edit 1 e/client_name@example.com
Expected: First client’s email is changed toclient_name@example.com. - Test case:
edit 2 e/client_name@example.com
Expected: Second client’s email is not changed as the email is already in use. Error message is shown.
Append fields to a client
- Append fields to a client.
- Prerequisites: There needs to be existing client data in the client list.
- Test case:
append 1 b/1999-02-17
Expected: First client’s birthday is set to1999-02-17. - Test case:
append 1 n/Ookami Mio
Expected: First client’s name is set toOokami Mio.
Remove fields from a client
- Remove fields from a client.
- Prerequisites: There needs to be existing client data in the client list.
- Append a tag and birthday to the first client using the command
append 1 t/vendor b/1999-02-17. - Test case:
remove 1 b/
Expected: First client’s birthday is removed. - Test case:
remove 1 t/vendor
Expected: First client’svendortag is removed. - Test case:
remove 1 n/
Expected: First client’s name is not removed as every client must have a name. Error message is shown.
Add or remove remark from a client
- Add or remove remark from a client.
- Prerequisites: There needs to be existing client data in the client list.
- Test case:
remark 1 r/High shipping cost.
Expected: First client’s remark is set toHigh shipping cost.. - Test case:
remark 1 r/
Expected: First client’s remark removed.
Deleting a client
- Deleting a client while all clients are being shown
- Prerequisites: There needs to be existing client data in the client list.
- Test case:
delete 1
Expected: First client is deleted from the list. - Test case:
delete 0
Expected: No client is deleted. Error message is shown. - Other incorrect delete commands to try:
delete,delete x,...(where x is larger than the list size)
Expected: No client is deleted. Error message is shown.
Sorting the client list
- Sorting the client list based on certain fields
- Prerequisites: There needs to be existing client data in the client list.
- Test case:
sort n/
Expected: The client list will display the clients in ascending alphabetical order. - Test case:
sort n/ a/ p/ desc
Expected: The client list will display the clients in ascending alphabetical order. Clients with the same name will then be displayed according to their addresses in ascending order. And if they also have the same address, they’ll be displayed based on their phone number in descending order. - Test case:
sort l:)/ djewijw p/
Expected: An error would be thrown as the fields specified do not exist.
Adding a Transaction
- Adding a new transaction while all transactions are being shown
- Test case:
addTransaction 1 a/12 td/2020-11-11 dd/2020-12-12 n/this is a note --paid
Expected: A new paid transaction withAmount12 should appear at the bottom of the transaction list. - Test case:
addTransaction 1 a/12 td/2020-11-11 dd/2020-12-12 n/this is a note
Expected: A new unpaid transaction withAmount12 should appear at the bottom of the transaction list. - Test case:
addTransaction 1 td/2020-11-11 dd/2020-12-12 n/this is a note
Expected: No new transaction is added as not all required fields are provided. Error message is shown. - Test case:
addTransaction 1 a/12 td/2020-11-11 dd/2002-12-12 n/this is a note
Expected: No new transaction is added as not the due date is before the transaction date. Error message is shown.
- Test case:
Deleting a Transaction
- Deleting a transaction while all transactions are being shown
- Test case:
deleteTransaction 1
Expected: The first transaction is deleted. - Test case:
deleteTransaction 0
Expected: No transaction is deleted as the provided index is invalid. Error message is shown. - Test case:
deleteTransaction xwhere x is larger than the transaction list size
Expected: No transaction is deleted as the provided index is invalid. Error message is shown. - Test case:
deleteTransaction
Expected: No transaction is deleted as the required field is not provided. Error message is shown.
- Test case:
List a Transaction
- list all transactions
- Test case:
listTransaction
Expected: All transactions will be listed. - Test case:
listTransaction abcd
Expected: All transactions will be listed.
- Test case:
Find Transactions
- Find transactions while all clients are being shown
- Test case:
findTransaction 1
Expected: All transactions from the first client are listed. - Test case:
findTransaction 0
Expected: No transaction is shown as the provided index is invalid. Error message is shown. - Test case:
findTransaction xwhere x is larger than the client list size
Expected: No transaction is listed as the provided index is invalid. Error message is shown. - Test case:
findTransaction
Expected: No transaction is listed as the required field is not provided. Error message is shown.
- Test case:
Pay
- Pay a transaction while all transactions are being shown
- Test case:
pay 1
Expected: Status of the first transaction of the transaction list is changed topaid. - Test case:
pay 0
Expected: No transaction status is changed topaidas the provided index is invalid. Error message is shown. - Test case:
pay xwhere x is larger than the transaction list size
Expected: No transaction status is changed topaidas the provided index is invalid. Error message is shown. - Test case:
pay
Expected: No transaction status is changed topaidas the required field is not provided. Error message is shown.
- Test case:
Unpay
- Unpay a transaction while all transactions are being shown
- Test case:
unpay 1
Expected: Status of the first transaction of the transaction list is changed tounpaid. - Test case:
unpay 0
Expected: No transaction status is changed tounpaidas the provided index is invalid. Error message is shown. - Test case:
unpay xwhere x is larger than the transaction list size
Expected: No transaction status is changed tounpaidas the provided index is invalid. Error message is shown. - Test case:
unpay
Expected: No transaction status is changed tounpaidas the required field is not provided. Error message is shown.
- Test case:
Undo data modifications
- Undo a modification that was previously made
- Prerequisites: There needs to be modifications made to the client list.
- Test case:
undo
Expected: The previous modification done will be reverted and the application will display the previous version of the client list.
- Undo a modification when there are none
- Prerequisites: No modifications were made since the start of the application or all modifications have been reverted.
- Test case:
undo
Expected: An error message will be shown stating that there is nothing to undo since there are no modifications.
- Only able to undo the 10 latest modifications.
- Prerequisites: More than 10 modifications were made without reverting any of them.
- Test case:
undo11 times
Expected: Notice that it can only revert the client list by the latest 10 modifications made and not the modifications before those. At the 11thundo, will show an error message stating that there is nothing to undo since there are no modifications.
- Handling temporary file corruption.
- Prerequisites: Some modifications were made, but the latest temporary files in the
data\.tempdataare either corrupted or deleted by the user and not the application. -
Test case:
undo
Expected: An error message would be shown stating it cannot read the temporary file. The temporary file if it’s not already deleted by the user, will then be deleted by the application. The client list will not be able to revert to before the modification stored in the corrupted temporary file.However, if the user were to call
undoagain, and if the second-latest temporary file data is not corrupted or deleted by user, the application will be able to revert the client list to the state stored in the temporary file. Thus, effectively undoing the latest 2 modifications.
- Prerequisites: Some modifications were made, but the latest temporary files in the
Adding membership
- Adding a membership to a user
- Prerequisites: There needs to be existing client data in the client list.
- Test case:
addMembership 1 m/gold
Expected: The client at index 1 will have a gold membership assigned to him. - Test case:
addMembership 3 m/bronze
Expected: The client at index 3 will have a bronze membership assigned to him. - Test case:
addMembership 1 m/platinum
Expected: An error will be displayed as there is no tier called ‘platinum’. - Test case:
addMembership -1 m/silver
Expected: An error will be displayed as -1 is an invalid index.
Removing membership
- Removing a membership from a user
- Prerequisites: There needs to be existing client data in the client list with a membership.
- Test case:
removeMembership 1(User at index 1 has a membership)
Expected: The client at index 1 will have his membership removed. - Test case:
removeMembership 1(User at index 1 has no membership)
Expected: Error message showing that user at that index has no membership. - Test case:
removeMembership -1
Expected: An error will be displayed as -1 is an invalid index.
Listing all members
- Listing members
- Prerequisites: There needs to be existing client data in the client list with memberships.
- Test case:
listMembers(Some users have memberships)
Expected: List of all members will be displayed. - Test case:
listMembers(No users have memberships)
Expected: No clients displayed. - Test case:
listMembers gold(Some users have gold memberships)
Expected: All members with gold membership will be displayed. - Test case:
listMembers hello
Expected: Error message shown as hello is not a valid membership tier.
Saving data
- Dealing with missing/corrupted data files
- Test case: Delete the
CinnamonBun.savefile in the data folder which should be in the same folder as the jar file. This will simulate a missing save file.
Expected: A data set with some default users will be used and a new save file will be created after running any command.
- Test case: Delete the
Appendix: Effort
This appendix will highlight the difficulty level, challenges faced, effort required, and achievements of CinnamonBun.
Time constraint and high workload
This semester, all of our team members had high workload from multiple modules. The amount of effort that TP required was very taxing on all members of the team. We felt that the requirements of TP was too much (application, UG, DG, weekly quizzes).
Despite that, the team still managed to deliver a high quality product at the end.
Person, Field and Tag
The Person and Tag classes were modified from AB-3, while Field is an abstract class representing existing classes such as Name, Email etc. as well as new classes such as Birthday, Remark etc.
Originally, all of Person’s fields were hard-coded into the Person class, resulting in extremely high coupling, and it was very difficult to add new fields to Person.
A lot of effort had to be put into fixing this issue. Due to the extremely high coupling, this changed affected multiple systems across the entire project, including the saving system, edit command, and many more.
It took almost two weeks to rewrite multiple systems while ensuring compatibility with other existing systems and test cases.
Fortunately, these changes allowed some features we added later on to be done much more easily compared to when the project first started.