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
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.
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
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
andTransaction
object residing in theModel
.
Logic component
API : Logic.java
Figure: Partial class diagram of the Logic
component.
How the Logic
component works:
- When
Logic
is called upon to execute a command, it uses theAddressBookParser
class to parse the user command. - This results in a
Command
object (more precisely, an object of one of its subclasses e.g.,AddCommand
) which is executed by theLogicManager
. - The command can communicate with the
Model
when it is executed (e.g. to add a client). - The result of the command execution is encapsulated as a
CommandResult
object 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
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
Figure: Class diagram of Model
component.
The Model
component,
- stores the client data i.e., all
Person
objects (which are contained in aUniquePersonList
object). - stores the currently ‘selected’
Person
objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiableObservableList<Person>
that can be ‘observed’ e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. - stores the transaction data i.e., all
Transaction
objects (which are contained in aTransactionList
object). - stores the currently ‘selected’
Transaction
objects (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
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).
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
,TempAddressBookStorage
andUserPrefStorage
, which means it can be treated as either one (if only the functionality of only one is needed). - depends on some classes in the
Model
component (because theStorage
component’s job is to save/retrieve objects that belong to theModel
)
Common classes
Classes used by multiple components are in the 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
,RemoveCommand
work 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
Person
modular 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
Person
implementation 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 TransactionField
s 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, theFilteredList
should be updated to only containTransaction
with a reference to the client’s id.- Pros: Consistent design with the
Person
class. - 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
Person
object 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:
Transaction
class would be coupled to thePerson
class. In addition, inconsistent design in comparison to thePerson
class.
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
. -
LogicManager
execute the command which will be parsed usingAddressBookParser
andAddTransactionParser
if the input is valid. -
AddTransactionParser
will returnAddTransactionCommand
which containsTransactionBuilder
. ThisTransactionBuilder
will be used later byAddTransactionCommand#execute
to generate the appropriateTransaction
. -
AddTransactionCommand
will then update theTransaction List
by callingModel#addTransaction
. -
CommandResult
will be returned back to theLogicManager
if 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
TransactionBuilder
to generate theTransaction
later.- Pros: No need to pass the
ModelManager
to 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
. -
LogicManager
execute the command which will be parsed usingAddressBookParser
andFindTransactionCommandParser
if the input is valid. -
FindTransactionCommandParser
will returnFindTransactionCommand
which containsTransactionPredicateBuilder
. This will be used later byFindTransactionCommand#execute
to generate the appropriateTransactionPredicate
. -
AddTransactionCommand
will then update theTransaction List
by callingModel#updateFilteredTransactionList
usingTransactionPredicate
. -
CommandResult
will be returned back to theLogicManager
if 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
TransactionPredicateBuilder
to generate theTransactionPredicate
later.- Pros: No need to pass the
ModelManager
to 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
. -
LogicManager
execute the command which will be parsed usingAddressBookParser
andPayCommandParser
if the input is valid. -
PayCommand
will returnPayCommand
which contains the index of the transaction. - In
PayCommand#execute
, thetransactionToUpdate
will be replaced byupdatedTransaction
by callingModel#setTransaction
. -
CommandResult
will be returned back to theLogicManager
if 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
Membership
object 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
Field
class will handle how to sort its own data,SortCommand
will 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:
SortCommand
will determine how the fields are to be sorted.- Pros: Allows
SortCommand
to have full flexibility in deciding how the fields are to be sorted and may allow for more complicated chaining of fields. - Cons: Will clutter
SortCommand
and 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
,exit
orundo
or 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 firstlist
is 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
11
or 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 Gura
should 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’svendor
tag 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 withAmount
12 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 withAmount
12 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 x
where 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 x
where 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 topaid
as the provided index is invalid. Error message is shown. - Test case:
pay x
where x is larger than the transaction list size
Expected: No transaction status is changed topaid
as the provided index is invalid. Error message is shown. - Test case:
pay
Expected: No transaction status is changed topaid
as 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 tounpaid
as the provided index is invalid. Error message is shown. - Test case:
unpay x
where x is larger than the transaction list size
Expected: No transaction status is changed tounpaid
as the provided index is invalid. Error message is shown. - Test case:
unpay
Expected: No transaction status is changed tounpaid
as 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:
undo
11 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\.tempdata
are 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
undo
again, 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.save
file 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.