Refer to the guide Setting up and getting started.
Note: The lifeline for Commands in all Sequence Diagrams should end at the destroy marker (X) if they exist, but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main
(consisting of classes Main
and MainApp
) is in charge of the app launch and shut down.
The bulk of the app's work is done by the following four components:
UI
: The UI of the App.Logic
: The command executor.Model
: Holds the data of the App in memory.Storage
: Reads data from, and writes data to, the hard disk.Commons
represents a collection of classes used by multiple other components.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1
.
Each of the four main components (also shown in the diagram above),
interface
with the same name as the Component.{Component Name}Manager
class (which follows the corresponding API interface
mentioned in the previous point.For example, the Logic
component defines its API in the Logic.java
interface and implements its functionality using the LogicManager.java
class which follows the Logic
interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
The API of this component is specified in Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PersonListPanel
, StatusBarFooter
etc. All these, including the MainWindow
, inherit from the abstract UiPart
class which captures the commonalities between classes that represent parts of the visible GUI.
The UI
component uses the JavaFX UI framework. The layout of these UI parts are defined in matching .fxml
files that are in the src/main/resources/view
folder. For example, the layout of the MainWindow
is specified in MainWindow.fxml
The UI
component,
Logic
component.Model
data so that the UI can be updated with the modified data.Logic
component, because the UI
relies on the Logic
to execute commands.Model
component, as it displays Person
object residing in the Model
.API : Logic.java
Here's a (partial) class diagram of the Logic
component:
Note:
XYZCommand
refers to all Command
classes i.e. AddCommand
, FindCommand
, etc.Command
classes utilize ArgumentMultimap
, ArgumentTokenizer
or ParserUtil
classes.The sequence diagram below illustrates the interactions within the Logic
component, taking execute("delete 1")
API call as an example.
All Command
objects are created via the enum factory pattern. The following sequence diagram expands on the actual command
creation process encapsulated by createCommandUsingFactory()
in the above diagram.
Note that the leading whitespace in " 1"
is an implementation detail of AddressBookParser
.
How the Logic
component works:
Logic
is called upon to execute a command, it is passed to an AddressBookParser
object which in turn uses an enum factory CommandType
to create the relevant enum (e.g. CommandType.DELETE
) from the parsed command word.CommandType
enum calls the relevant command's factory method to create a command object (e.g. DeleteCommand
) with the parsed arguments.Model
when it is executed (e.g. to delete a person).Model
) to achieve.Logic
as a String
.How the parsing works:
AddressBookParser
passes the first token to CommandType
which dispatches the matching type of command.AddressBookParser
instantiates the command with the user input arguments.DeleteCommand
) uses ArgumentTokenizer
, ArgumentMultimap
and possibly ParserUtil
to break down and interpret the user input arguments.API : Model.java
The Model
component,
Person
objects (which are contained in a UniquePersonList
object).Person
objects (e.g. results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiable ObservableList<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.UniquePersonList
in a private undoStack
.UniquePersonList
in a private redoStack
.UserPref
object that represents the user’s preferences. This is exposed to the outside as a ReadOnlyUserPref
objects.Model
represents data entities of the domain, they should make sense on their own without depending on other components)Note:
More details on the implementation of undo
and redo
can be found here.
An alternative (arguably, more OOP) model is given below. It has a Tag
list and an Asset
list in the AddressBook
, which Person
references. This allows AddressBook
to only require one Tag
object per unique tag, and one Asset
object per unique asset, instead of each Person
needing their own Tag
and Asset
objects.
API : Storage.java
The Storage
component,
AddressBookStorage
and UserPrefStorage
, which means it can be treated as either one (if only the functionality of only one is needed).Model
component (because the Storage
component's job is to save/retrieve objects that belong to the Model
)Some classes used by multiple components are in the seedu.addressbook.commons
package.
This section describes some noteworthy details on how certain features are implemented.
Asset related functionality is supported by Asset
and Assets
objects. Assets
are the actual field belonging to
a Person
object, and may contain zero or more Asset
objects.
The following class diagram details the relationship between classes involved under the model component.
Assets
and Asset
objects are immutable and expose a static factory of
method which parses String
objects to
return the respective class. To support editing a Person
object's Assets
, the Assets
class has a static edit
method which facilitates creating a new Assets
object with a specified Asset
object replaced.
Assets are created when the user invokes the add
, edit
or asset
commands specifying an asset is to be
associated with person.
The following sequence diagram shows how an add
command specifying creating a contact responsible for a 'pen'
asset interacts with the classes in Logic
and Model
.
The follow sequence diagram expands on the process of creating a person with assets referenced in the above diagram.
The edit asset mechanism is implemented by the following operations:
persons
list.assets
list of each person
.assetName
is equivalent to the name from the o\
prefix, rename it to the assetName
specified by the n\
prefix. If the assetName
does not match, do nothing.Note: If the assetName
specified by the o\
prefix does not exist in the user input, the application will throw an error to inform the user that the command is invalid.
Aspect: How users create and update assets
add
.
The user uses the asset
command to add or edit details to each asset.
add
. Assets only have a name.
Aspect: How updating of assets is implemented
Assets
and Asset
objects are immutable; a linear search and replace
is performed to update the UniquePersonList
whenever a change to any is required.
Asset
has a static hash table with some primary key.
The PersonMatchesQueryPredicate
class defines the algorithm that determines whether a Person
matches the user's query
string.
The following sequence diagram shows how the command
object is created when the user executes find David
. Note that the leading whitespace in " David"
is an implementation detail of AddressBookParser
.
The filtered list of Person
objects in model
is then updated as such:
The undo/redo mechanism is implemented within AddressBook.java
by saving the entire persons
list. It uses an undo and a redo stack to maintain the history. Additionally, it implements the following operations:
AddressBook#save()
— Copies the current persons
list into the undoStack
.AddressBook#undo()
— Restores the previous persons
list state from the undoStack
.AddressBook#redo()
— Restores a previously undone persons
list state from the redoStack
.save()
is used within the AddressBook
class methods, saving only when the persons list is about to be modified. save()
is set to be private to prevent potential misuse from other classes, and Law of Demeter violations.
undo
and redo
are exposed in the Model
interface as Model#undo()
, Model#redo()
respectively.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
Step 1. The user launches the application for the first time. The persons
list will be initialized with the initial address book state (State 0), with the undoStack
and redoStack
empty.
Step 2. The user executes the command delete 5
to delete the 5th person in the address book. The delete
command eventually calls save()
, which causes the state of the persons
list before delete 5
is executed (State 0) to be saved into the undoStack
. The 5th person is then removed from the persons
list (State 1).
Step 3. The user executes add n/David ...
to add a new person. The add
command eventually calls save()
, causing another persons
list state (State 1) to be saved into the undoStack
, before adding the person (State 2).
Note: If a command fails its execution, or if the command does not modify the persons
list, it will not call save()
. Hence, this persons
list state will not be saved into the undoStack
.
Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the undo
command. The undo
command will call Model#undo()
, which will:
persons
list (State 2) into the redoStack
.persons
list state (State 1) from the undoStack
.persons
list.Notice that the states are copied into the persons
list instead of replacing it, resulting in the exact same object being used. This is to prevent synchronization issues and to reduce coupling with the GUI, allowing the GUI to use this same list object throughout the program's life.
Note: If the undoStack
is empty, then there are no previous persons
list states to restore. The undo
command uses Model#canUndo()
to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo.
The following sequence diagram shows how an undo operation goes through the Logic
component:
Similarly, how an undo operation goes through the Model
component is shown below:
The redo
command does the opposite — it calls Model#redo()
, which will:
persons
list into the undoStack
.persons
list state from the redoStack
.persons
list.Note: If the redoStack
is empty, then there are no previously undone persons
list states to restore. The redo
command uses Model#canRedo()
to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
Step 5. The user then decides to execute the command list
. Commands that do not modify the persons
list, such as list
, will usually not call AddressBook#save()
, Model#undo()
or Model#redo()
. Thus, the undoStack
and redoStack
remains unchanged.
Step 6. The user executes clear
, which eventually calls save()
.
Since there are still states in the redoStack
, all states in the redoStack
(State 2) will be removed.
Reason: It no longer makes sense to redo the add n/David ...
command (State 2), and ignore the clear
command (State 3). This is the behavior that most modern desktop applications follow.
The following activity diagram summarizes what happens when a user executes a new command (excluding undo & redo):
Aspect: How undo & redo executes:
Model
will now need a reference to Storage
.String
in RAM.
undo()
and redo()
methods
delete
, just save the person being deleted).undo
command is correct.
This would be especially difficult for commands that modify multiple people at once (e.g. asset
command)Aspect: Data structure used to store undo & redo states:
add()
and set()
have to be used appropriately to prevent synchronization issues.The CommandHistory
class is an abstraction for a command history data structure.
It stores all command strings that were successfully executed by the user.
When the user executes a command successfully, LogicManager
calls the add()
method in CommandHistory
to store the command string.
CommandExecutor
is an interface that has the methods execute()
, getPreviousCommandText()
and getNextCommandText()
.
A private nested class RecordedCommandExecutor
in MainWindow
implements this interface.
When the user presses the UP
key, a KeyEvent
is generated by JavaFX, which is then handled by the CommandBox
class through the handleKeyPressed()
method.
The following sequence diagram shows the interaction between the classes when the user presses the UP
key.
Note that TextField
is a class in JavaFX. CommandBox
has a textField
attribute.
A similar interaction occurs when the user presses the DOWN
key.
Target user profile:
Value proposition:
Logistics managers often have to keep track of many contacts such as
This is usually done with general purpose software like Microsoft Excel, which may be cumbersome to use as they
Therefore, the application aims to deliver the following:
Priorities: High (must have): * * *
, Medium (nice to have): * *
, Low (unlikely to have): *
Priority | As a ... | I want to ... | So that I can ... |
---|---|---|---|
* * * | user | add new contacts and assets | keep track of these details |
* * * | user | delete contacts | update the list if a contact is no longer needed |
* * * | user | edit contacts and assets | change details without recreating contacts, as there are too many details to re-add |
* * * | user | easily view my existing contacts from the GUI | visually find the contacts I'm looking for |
* * * | user | list all contacts | view all contacts at a glance |
* * * | user | search contacts by any category (e.g. name, asset, etc.) | easily find the relevant contact |
* * * | user | see usage instructions | refer to instructions and examples when I forget how to use certain commands |
* * | user | add tags to contacts | categorize them according to my preferences and workflow |
* * | user | quickly copy contact information (e.g. email) onto the clipboard | use the contact information immediately after finding it |
* * | user | see no advertisements | not be distracted from my tasks |
* * | user | add secondary personnel associated with an asset | have a backup contact if the main person is unreachable |
* * | user | toggle between light/dark theme | customize the app to my preferences |
* * | user | resize the app’s window | easily use multiple apps at once |
* * | user | add a profile picture to each contact | easily identify them |
* * | user | easily search within the system even if I mistype a few words | more easily locate relevant information |
* * | new user | view a drop-down suggestion of commands | efficiently navigate and utilize the app without extensive prior knowledge |
* * | hurried user | have commands even with extra whitespaces accepted | not waste time retyping commands |
* * | advanced user | type shorter commands | manage my contacts and assets quicker |
* * | advanced user | use keyboard shortcuts | use the app more efficiently |
* | advanced user | add custom fields | add more information to contacts |
(For all use cases below, the System is AssetBook (AB)
and the Actor is the user
, unless specified otherwise)
Use case: UC1 - Add a contact MSS
Extensions
Use case: UC2 - List contacts MSS
Use case: UC3 - Search contacts MSS
Extensions
Use case: UC4 - Edit contact MSS
Extensions
Use case: UC5 - Edit assets MSS
Extensions
Use case: UC6 - Delete contact MSS
Extensions
Use case: UC7 - Undo command MSS
Extensions
Use case: UC8 - Redo command MSS
Extensions
Use case: UC9 - Clear all contacts MSS
Use case: UC10 - Add person to JSON file directly MSS
Extensions
11
or above installed.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.
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 contact
Test case: add n\John Doe p\98765432 e\johnd@example.com a\311, Clementi Ave 2, #02-25 t\friends t\owesMoney A\screwdriver
Expected: AssetBook displays a success message, and the newly added contact's details are shown.
Test case: add n\John Doe
Expected: AssetBook displays an error stating invalid command format, and no contact is added.
Other incorrect add
commands to try: add n\ p\ e\ a\311, Clementi Ave 2, #02-25 t\friends t\owesMoney A\screwdriver
Expected: Similar to previous.
Deleting a contact while all contacts are being shown
Prerequisites: List all persons using the list
command. Multiple persons in the list.
Test case: delete 1
Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message.
Test case: delete 0
Expected: No person is deleted. Error details shown in the status message.
Other incorrect delete
commands to try: delete
, delete 0
, delete x
, ...
(where x is larger than the list size)
Expected: Similar to previous.
Dealing with corrupted data file
Modify data file into an invalid format. Eg. Removing fields of a contact, changing non-list fields into lists, etc.
Open AssetBook. If AssetBook was already opened, close and reopen it.
Expected: AssetBook will detect that the data file is corrupted, warn the user that the data file could not be loaded and that entering a command would reset it, and start with an empty list.
Team size: 5
undo
and redo
messages more descriptive: Currently, undo
and redo
commands only display a success message without any additional information. In future versions, we will display the specific commands undone or redone, along with the changes.copy
command more user-friendly: Currently, using the copy
command on assets and tags will include the square brackets associated with the list data structure, which are unnecessary for the users. In future versions, we will remove these square brackets when copying tags and assets.find
command searches across all names, tags, and assets indiscriminately (e.g. searching bell
may return someone who owns a bell asset and someone else who has the name Bell). We plan to allow users the options to limit the range of their searching either through new commands or specifying prefixes.add
command or edit
command on names is not allowed if another contact has the same name, but there may exist multiple people with the same name that one needs to keep track of. In future versions, we aim to improve duplicate person detection and potentially allow multiple contacts with the same name as long as other fields are different.dave tan
and Dave Tan
). We plan to make duplicate checking more robust and potentially warn the user first for such cases.remark
command: At present users can only add additional information to contacts using tags. We plan to allow the user to add more detailed information like notes using a remark
command.tab
to quickly complete the command word, and what fields they are missing.2555, 232
for the user's convenience. This however means that inputs such as +++
would be accepted, which is unlikely to be the user's intention. We plan to improve phone number validation without restricting the user in the future.