Developer Guide
- Acknowledgements
- Setting up, getting started
- Design
- Implementation
- Documentation, logging, testing, configuration, dev-ops
- Appendix: Requirements
- Appendix: Planned enhancements
- Appendix: Instructions for manual testing
- Appendix: Effort
Acknowledgements
- JavaFX was used to develop a Graphical User Interface (GUI).
- Jackson was used for JSON local storage of data.
- JUnit5 was used for automated unit and integration testing.
- TestFx was used for automated JavaFX GUI testing.
Setting up, getting started
Refer to the guide Setting up and getting started.
Design
.puml files used to create diagrams in this document docs/diagrams folder. Refer to the PlantUML Tutorial at se-edu/guides to learn how to create and edit diagrams.
Architecture
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main (consisting of classes Main and MainApp) is in charge of the app launch and shut down.
- At app launch, it initializes the other components in the correct sequence, and connects them up with each other.
- At shut down, it shuts down the other components and invokes cleanup methods where necessary.
The bulk of the app’s work is done by the following four components:
-
UI: The UI of the App. -
Logic: The command executor. -
Model: Holds the data of the App in memory. -
Storage: Reads data from, and writes data to, the hard disk.
Commons represents a collection of classes used by multiple other components.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1.
Each of the four main components (also shown in the diagram above),
- defines its API in an
interfacewith the same name as the Component. - implements its functionality using a concrete
{Component Name}Managerclass (which follows the corresponding APIinterfacementioned in the previous point.
For example, the Logic component defines its API in the Logic.java interface and implements its functionality using the LogicManager.java class which follows the Logic interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component’s being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
UI component
The API of this component is specified in Ui.java
The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, PersonListPanel, StatusBarFooter etc. All these, including the MainWindow, inherit from the abstract UiPart class which captures the commonalities between classes that represent parts of the visible GUI.
These parts may use custom component classes such as FieldLabel and FieldHyperlink that
inherit from default JavaFX components. These subclasses can provide
reasonable defaults or part-specific behavior to simplify code.
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 displaysPersonobject residing in theModel.
Logic component
API : Logic.java
Here’s a (partial) class diagram of the Logic component:
How the Logic component works:
- When
Logicis called upon to execute a command, it is passed to aNetworkBookParserobject which in turn creates a parser that matches the command (e.g.,DeleteCommandParser) and uses it to parse the command. - This results in a
Commandobject (more precisely, an object of one of its subclasses e.g.,DeletePersonCommand) which is executed by theLogicManager. - The command can communicate with the
Modelwhen it is executed (e.g. to delete a person). - The result of the command execution is encapsulated as a
CommandResultobject which is returned back fromLogic.
Here are the other classes in Logic (omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
- When called upon to parse a user command, the
NetworkBookParserclass 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 theNetworkBookParserreturns 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.
Here is an overview of what the other classes in Logic do:
-
ArgumentMultiMapandArgumentTokeniserare used to map the parameters of the user’s input into key-value pairs, where the keys are specified usingArgumentTokeniser -
CliSyntaxis where command-specific keywords are stored. It is used as the arguments forArgumentTokeniserto process the user input into:{keyword : parameter}pairs.- Example usage: The text
1 /name John Doe /phone 98765432when mapped usingArgumentTokeniserwith the keywords/nameand/phoneproduces:{1}, {/name : John Doe}, {/phone : 98765432}- Any text that appears before the first possible keyword is stored in its own entry.
-
ArgumentMultiMapcan then perform specific operations, including but not limited to:- Retrieve all values/only a specific value from the set.
- Check that a certain key only appears once, or exactly once.
- Example usage: The text
-
ParserUtilcontains useful commands for parsing text such as removing leading/trailing whitespace from text, verifying that there are no duplicate entries in the text, and so on.
Type Inference Within NetworkBookParser
The activity diagram below describes the workflow of NetworkBookParser
when determining which Parser to use:
Type Inference Within FilterCommandParser
The sequence diagram illustrates the interactions within FilterCommandParser to infer
which type of FilterCommand to return, using ArgumentMultiMap to extract the field the user
wishes to filter, based on input.
The full implementation of FilterCommandParser can be found in the Filter Command Implementation.
Model component
API : Model.java
The Model component,
- stores the networkbook data, which in turn is all
Personobjects (which are contained in aUniqueList<Person>object). - stores a
UserPrefobject that represents the user’s preferences. This is exposed to the outside as aReadOnlyUserPrefobjects. - exposes the current list of displayed person as a
Observable<Person>obtained from theVersionedNetworkBook. - 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)
The VersionedNetworkBook component,
- stores the currently filtered
Personobjects (the result of a filter) as a separate filtered list. This list is used as the intermediate list between the originalUniqueList<Person>and the sorted list. - stores the sorted list
Personobjects (the result of a filter and a sort) as a separate sorted list which is exposed to outsiders as an unmodifiableObservableList<Person>that can be ‘observed’ e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. - stores a list of states that can be backtracked/forwarded to. This list is extensively used in undo and redo command.
The UserPrefs component,
- stores the GUI settings of window size and position.
- stores the path of the data file.
The UniqueList<T> class,
- is a generic class that ensures that items within the list conforms to the unique constraint. In other words, each pair of objects within the list must have a different identity.
UniqueList<T>enforcesTto implementIdentifiable<T>, which has the methodisSame(T)to check for identity against another object. - The identity is determined by the class that
Tbinds to.- For
Person, the identity is the name. Two names are equal if there string values are equal. - For
Phone, the identity is the literal string value of the phone. - For
Email, the identity is the literal string value of the email. - For
Link, the identity is the literal string value of the link. - For
Course, the identity is the literal string value of the course name. If two courses are of the same name but of different start and end dates, they are considered having the same identity. - For
Specialisation, the identity is the literal string value of the specialisation. - For
Tag, the identity is the literal string value of the tag.
- For
-
UniqueList<T>does an identity check upon adding every object to the list. It throws anAssertionErrorif duplicates are found.- Any actor that wants to add an object to the list must ensure that an identity check has been done before the add method is called.
-
UniqueList<T>supports supplying the object at the specified index to a consumer through the methodconsumeItem(int index, ThrowingIoExceptionConsumer<T> consumer). The method takes the item atindexand passes it into theconsumer. -
UniqueList<T>supports supplying the object at the specified index to a consumer, at the same time applying a function on the same object to produce a value through the methodconsumerAndComputeItem(int index, ThrowingIoExceptionConsumer<T> consumer, Function<T, U> function). The method works the same as above, and does an extra step of applyingfunctionon the object and return the computed value of typeU.
Storage component
API : Storage.java
The Storage component,
- can save both NetworkBook data and user preference data in JSON format, and read them back into corresponding objects.
- inherits from both
NetworkBookStorageandUserPrefStorage, 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 networkbook.commons package.
Implementation
This section describes some noteworthy details on how certain features are implemented.
Create new contact
The implementation of the create command follows the convention of a normal command,
where CreateCommandParser is responsible for parsing the user input string
into an executable command.
CreateCommandParser first obtains the values corresponding to the prefixes
/name, /phone, /email, /link, /grad, /course, /spec, /priority and /tag.
CreateCommandParser ensures that:
- There is no preamble text between the
createkeyword and the prefixes. - One and only one of the prefix
/nameis present. - Each of
/gradand/priorityprefixes appears at most once. - All values corresponding to the prefixes
/name,/phone,/email,/link,/grad,/course,/spec,/priorityand/tagare valid.
If any of the above constraints are violated, CreateCommandParser throws a ParseException.
Otherwise, it creates a new instance of CreateCommand that corresponds to the user input.
CreateCommand comprises of the person to be added, which is an instance of Person.
Upon execution, CreateCommand first queries the supplied model if it contains a person with an identical name.
If no such person exists, CreateCommand then calls on model::addPerson to add the person into the networkBook data.
We have considered the following alternative implementations:
- Implement
CreateCommandParserto parse the arguments using regular expressions. This is not optimal for our use case as having a regex expression to parse the field values would be more complicated to scale and debug.
Add details
The implementation of the add command follows the convention of a normal command, where AddCommandParser is responsible for parsing the user input string into an executable command.
AddCommandParser first obtains the values corresponding to the prefixes
/name, /phone, /email, /link, /grad, /course, /spec, /priority and /tag.
It ensures that each of /name, /grad and /priority prefixes appears at most once. If not, it throws a ParseException.
Otherwise, it attempts to generate an AddPersonDescriptor with the details provided. It checks the following:
-
/nameis not provided, since all contacts already have a name, andeditcommand should be used to update a contact’s name - Apart from
/name, at least one field to add information has been specified
If the details do not fulfil these requirements, it throws a ParseException. Otherwise, it produces an AddCommand with the index of contact and the descriptor.
Upon execution, the AddCommand will first obtain the Person at the specified index in the displayed contact list, and then attempts to add the information in the descriptor to the Person. In the process, it checks that:
- There is a corresponding person at the specified index
- If the descriptor contains
/grador/priority, the original person should not contain the corresponding field, aseditcommand should have been used
These incorrect usages will throw a CommandException. Otherwise, the AddCommand will proceed to replace the original person with the new person after adding information.
We have considered the following alternative implementation:
-
Let
AddCommandParsergenerateEditPersonDescriptor, and when executingAddCommand, treat the information in the edit descriptor as information to add.This implementation was not chosen because it creates unnecessary coupling between add and edit commands. Our final implementation of
EditPersonDescriptorcontains optional indices to specify the entry of a multi-valued field to edit, which is much different fromAddPersonDescriptorand thus they are implemented separately.
Edit details
The implementation of the edit command follows the convention of a normal command,
where EditCommandParser is responsible for parsing the user input string
into an executable command.
EditCommandParser first obtains the values corresponding to the prefixes
/name, /phone, /email, /link, /grad, /course, /spec, /priority, /tag and /index.
EditCommandParser ensures that:
- One and only one of
/name,/phone,/email,/link,/grad,/course,/spec,/priority,/tagis present. - If
/name,/priorityor/gradis present, then/indexis not present. - If
/phone,/email,/link,/course,/specor/tagis present, then at most one prefix/indexis present.
If any of the above constraints are violated, EditCommandParser throws a ParseException.
Otherwise, it creates a new instance of EditCommand that corresponds to the user input.
EditCommand makes use of EditPersonDescriptor, which is an editable version of Person class.
Most importantly,
-
EditPersonDescriptorconstructor copies the details of the person. -
EditPersonDescriptorhas setter methods to allow changing the details. -
EditPersonDescriptorhas atoPersonmethod that returns a new instance ofPersonthat matches the current details.
EditCommand comprises of the index of the person to edit, and editAction as an instance of EditAction.
Each EditAction implements edit(EditPersonDescriptor),
which mutates the input instance of EditPersonDescriptor.
EditAction is an interface that has implementing concrete classes corresponding to each type of action
(i.e. EditPhoneAction for editing phone, EditEmailAction for editing email, etc).
Upon execution, EditCommand first obtains the Person at the index index in the model.
EditCommand then creates a new instance of EditPersonDescriptor that matches the details of the Person.
EditCommand then calls on editAction::edit to mutate the created EditPersonDescriptor.
EditCommand then converts the current EditPersonDescriptor into a new Person.
EditCommand then asks the model to update the original Person with the edited Person.
We have considered the following alternative implementations:
- Implement
EditCommandwith onlyEditPersonDescriptorand withoutEditAction, andEditCommandParsergenerates the instance ofEditPersonDescriptordirectly.EditCommandParserthen must know the details of the person editing in order to generate the correct instance ofEditPersonDescriptor. This is not optimal for object-oriented programming, as the parser should not need to know how the current model looks like. - Use a different class for each type of edit command
(i.e. editing phone with
EditPhoneCommand, editing email withEditEmailCommand, etc). This design has the advantage that the parser does not need to know how the current model looks like. However, to keepCommandclasses consistent in design, we decide to only have oneEditCommandclass and practice inheritance withEditAction. - Implement
EditCommandsuch thatEditActionedits thePersonobject directly. This means thatPersonclass must be mutable, which breaks the defensiveness of the current code and has the potential of introducing more bug. Moreover, thePersonclass being immutable also accommodates for theundoandredocommand, in which theVersionedNetworkBookonly creates a shallow copy of the current list ofPersonobjects and hence any mutation of thePersonobject might introduce bugs.
Filter contact list
The implementation of the filter command follows the convention of a normal command, where FilterCommandParser is responsible for parsing the user input string into an executable FilterCommand.
FilterCommandParser first obtains the values corresponding to the prefixes /by and /with, and ensures that each prefix is indicated once and only once.
If the value corresponding to /by is course, the value of the tag /taken is obtained from the value of the prefix /with.
A new filter command is then created with the Predicate<Person> that corresponds to the values of /by and /with, and /taken if any.
Upon execution, FilterCommand passes the instance of Predicate<Person> to the model through the method model::updateDisplayedPersonList. The model then uses the predicate internally to update the displayed list of contacts.
The details of how the parser infers which type of FilterCommand is to be returned can be found in ref
Delete contact
DeleteCommandParser parses user input string into either a DeletePersonCommand, or a DeleteFieldCommand.
The command responsible for removing an entire contact is DeletePersonCommand. Below illustrates the process of parsing and executing a DeletePersonCommand.
NetworkBookParser will instantiate a DeleteCommandParser when it detects the delete preamble. The DeleteCommandParser then checks if any field prefix is present in the command string (including /phone, /email, /link, /grad, /course, /spec, /priority and /tag).
If none of the field prefix is present, it is expected to be a DeletePersonCommand. The parser then checks if the /index prefix has been specified by mistake (as it is used to specify which entry of a multi-valued field to delete). If yes, it throws a ParseException. Otherwise, it proceeds to generate a DeletePersonCommand with the specified index of contact.
Upon execution, the DeletePersonCommand will first obtain the person at the corresponding index in the displayed contact list. It throws a CommandException if there’s no person at the specified index. Else, it removes all information about the person from the model.
Delete details
The command responsible for removing some details of a contact is DeleteFieldCommand. This command is generated by DeleteCommandParser if a field prefix is present in the delete command string (including /phone, /email, /link, /grad, /course, /spec, /priority and /tag).
The process of parsing the field to delete and execution is the same as edit command above. The sequence diagram is the same after replacing original class names with DeleteCommandParser, DeleteAction, DeleteFieldCommand andDeletePersonDescriptor. Some noteworthy differences are listed below:
-
DeleteCommandParserchecks if the/nameprefix is specified. If yes, it throws aParseExceptionas the name of a contact cannot be deleted. - The public setter methods in
DeletePersonDescriptorsimply remove the corresponding field entry from the descriptor.
We have considered the following alternative implementation:
-
Implement two different command preambles for
DeletePersonCommandandDeleteFieldCommand, such asdeleteandremove, ordeletePersonanddeleteField.The intention of this alternative is to differentiate the two commands better, so that users would not be confused about their usages. After discussion, it has been noticed that the first suggestion (
deleteandremove) does not provide more clarity, and the second suggestion creates unnecessary trouble for advanced users. The current way to differentiate these commands (by the presence or absence of field prefix) is sufficiently intuitive for new users, and can be easily remembered and applied by advanced users.
Open link/email
The implementation of the opening link/email command follows the convention of normal command, where OpenEmailCommandParser/OpenLinkCommandParser is responsible for parsing the user input string into an executable command. Below illustrates the process for open link command. The process of opening email is similar, where the reader can simply replace link with email to get the process for opening email.
OpenLinkCommandParser first obtains the values corresponding to the preamble and the prefix /index, and return an object of class OpenLinkCommand.
- If there are multiple
/indexprefixes,OpenLinkCommandParserthrows aParseException. - If there is no
/indexprefix, the link index takes the default value of1.
OpenLinkCommand then executes on the Model to open the link at personIndex (index of contact) and linkIndex (index of contact). The Model calls on the NetworkBook, which then calls on the Person at the correct index to open the link at linkIndex.
The Person opens the link by first detects which OS the application is running on.
- On Windows, the
PersonexecutesDesktop::browse(URI), whereDesktopandURIare java classes from default packages. - On Mac OS, the
Personexecutes theopencommand in the terminal through theRuntimeclass, which is a built-in class in java language. Theopencommand in the terminal opens the computer’s default browser if theURIsupplied is correctly formatted to be a web link. - On Ubuntu, the
Personexecutes thexdg-opencommand in the terminal through theRuntimeclass.
Undo/redo
The undo/redo mechanism is facilitated by VersionedNetworkBook. It extends NetworkBook with an undo/redo history of its state (encompassing list of all contacts and displayed list of contacts), stored internally as an networkBookStateList and currentStatePointer. Additionally, it implements the following operations:
-
VersionedNetworkBook::commit— Saves the current NetworkBook state in its history. -
VersionedNetworkBook::undo— Restores the previous NetworkBook state from its history. -
VersionedNetworkBook::redo— Restores a previously undone NetworkBook state from its history.
These operations are called in functions of the Model interface:
-
VersionedNetworkBook::undois called inModel::undoNetworkBook. -
VersionedNetworkBook::redois called inModel::redoNetworkBook. -
VersionedNetworkBook::commitis called inModel::setPerson,Model::addPerson,Model::deletePersonandModel::updateDisplayedPersonList.
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 VersionedNetworkBook will be initialized with the initial NetworkBook state, and the currentStatePointer pointing to that single state.
Step 2. The user executes delete 5 command to delete the 5th person displayed in NetworkBook. The delete command calls Model::deletePerson which in turn calls VersionedNetworkBook::commit, causing the modified state of the NetworkBook after the delete 5 command executes to be saved in the networkBookStateList, and the currentStatePointer is shifted to the newly inserted NetworkBook state.
Step 3. The user executes create /name David … to add a new person. The create command also calls Model::addPerson which also in turn calls VersionedNetworkBook::commit, causing another modified NetworkBook state to be saved into the networkBookStateList.
VersionedNetworkBook::commit, so the NetworkBook state will not be saved into the networkBookStateList.
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::undoNetworkBook, which will shift the currentStatePointer once to the left, pointing it to the previous NetworkBook state, and restores the NetworkBook to that state.
currentStatePointer is at index 0, pointing to the initial state of NetworkBook when the user began the current session, then there are no previous NetworkBook states to restore. The undo command uses Model::canUndoNetworkBook 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 the undo operation works:
UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
The redo command does the opposite — it calls Model::redoNetworkBook, which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the NetworkBook to that state.
currentStatePointer is at index networkBookStateList.size() - 1, pointing to the latest NetworkBook state, then there are no undone NetworkBook states to restore. The redo command uses Model::canRedoNetworkBook 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 open 1 /index 1. Commands that do not modify NetworkBook’s list of all contacts or displayed contacts, such as open 1 /index 1, will usually not call Model::undoNetworkBook, Model::redoNetworkBook, or any Model commands that call VersionedNetworkBook::commit. Thus, the networkBookStateList remains unchanged.
Step 6. The user executes clear, which calls Model::setNetworkBook which in turn calls VersionedNetworkBook::commit. Since the currentStatePointer is not pointing at the end of the networkBookStateList, all NetworkBook states after the currentStatePointer will be purged. Reason: It no longer makes sense to redo the add n/David … command. This is the behavior that most modern desktop applications follow.
The following activity diagram summarizes what happens when a user executes a new command:
Some design considerations:
-
Alternative 1 (current choice): Saves the entire NetworkBook state.
- Pros: Easy to implement.
- Cons: May have performance issues in terms of memory usage.
-
Alternative 2: Individual command knows how to undo/redo by itself.
- Pros: Will use less memory (e.g. for
delete, just save the person being deleted). - Cons: Additional implementation of each individual command requires more time and effort spent on achieving undo/redo behaviour for said command.
- Pros: Will use less memory (e.g. for
Sorting displayed contacts
The NetworkBook class wraps around the data displayed to the user.
The sorting feature builds on the filter feature present in NetworkBook, which uses JavaFX’s FilteredList, an implementation of the ObservableList interface.
The sort feature makes use of JavaFX’s SortedList, another implementation of the ObservableList interface.
SortedList takes a predicate which it then uses to sort the list.
To implement the sort feature, a new SortedList was added to NetworkBook, wrapping around the existing FilteredList.
This sorted list is the list that is displayed to the user.
The sort command updates the predicate of the SortedList to a PersonSortComparator.
PersonSortComparator extends Comparator<Person>, adding in a few extra methods specific to sorting persons:
-
parseSortField()parses a given string into a value of theSortFieldenumeration. This value is then used later to determine the predicate implementation. -
parseSortOrder()parses a given string into a value of theSortOrderenumeration. This value is then used later to determine the predicate implementation. -
generateXXComparator()methods. These methods return comparators which compare based on XX field of Person in either ascending or descending order. -
generateComparator()takes in aSortFieldandSortOrderand calls the relevantgenerateXXComparator()based on the given sort order and field.
Given below is an example usage scenario and how the sorting mechanism behaves at each step.
Step 1. The user launches the app. The rendered list is sorted by name in ascending order by default.
Current displayed contacts:
| Sorting | Filter |
|---|---|
| name, ascending | none |
Step 2. The user executes find al command to filter contacts by name. This updates the predicate of the FilteredList to only show contacts with names matching “al”.
The SortedList predicate remains unchanged.
Current displayed contacts:
| Sorting | Filter |
|---|---|
| name, ascending | name containing “al” |
Step 3. The user enters sort /by name /order desc to sort the filtered list by name in descending order.
The NetworkBook parser parses this into a sort command using a sort command parser.
The sort command parser constructs a new PersonSortComparator, which uses the static method generateComparator() to generate the appropriate comparator.
SortCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Step 4. The sort command is executed. It calls updateDisplayedPersonList() of the model, updating the predicate of the SortedList.
This newly sorted list is then rendered in the main UI upon updating of the model.
Current displayed contacts:
| Sorting | Filter |
|---|---|
| name, descending | name containing “al” |
Step 5. A SortCommandResult is also returned by the command, which is then passed to MainWindow.
The main window then updates the sorting status displayed in the status bar.
Step 6. The user uses filter to filter by tag “friends”. The list of contacts is filtered and the sorting remains the same.
Current displayed contacts:
| Sorting | Filter |
|---|---|
| name, descending | tag containing “friend” |
The following sequence diagram shows how the sort operation works:
SortCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Sort and filter status bar display
The existing implementation has a status bar displaying the save file path. To display the current sort and filter status, another label was added to the status bar.
When a command that changes sorting (e.g. sort) is run, it returns a SortCommandResult which is used to update the status bar.
Likewise, when a command that changes filtering (e.g. filter, find, list) is run, it returns a FilterCommandResult which is used to update the status bar.
The following activity diagram summarizes what happens when a command is executed:
Some design considerations:
-
Alternative 1 (current choice): Return subclass of
CommandResultupon execution.- After command execution, the command result is returned to
MainWindow, which has access to the status bar element. - By using polymorphism, additional information can be added to the command results of relevant commands.
- As
MainWindowalready receives the command results in order to update the text result box, the simplest way of implementing the status bar update would be to use the command results to update the status bar as well.
- After command execution, the command result is returned to
-
Alternative 2: Directly read status of model.
- The sorting and filtering is controlled by a
NetworkBookinstance. - The status bar could read the sorting and filtering predicate/comparator directly.
- However, this increases coupling between model and UI which is undesirable. Hence, this alternative was not chosen.
- The sorting and filtering is controlled by a
-
Alternative 3: Implement observer pattern to allow UI updates on model status change.
- The sorting and filtering is controlled by a
NetworkBookinstance. - The observer design pattern could be implemented to update the status bar when the filtering predicate/comparator updates, without increasing coupling.
- However, this introduces some complexity as there needs to be new observer and observable interfaces, as well as their corresponding method implementations.
- Since commands are currently the only way to change the sort or filter status, alternative 1 is a simpler way to implement the feature.
- The sorting and filtering is controlled by a
Find contacts by name
The implementation of the command to find contacts by their names follows the convention of a normal command, where FindCommandParser class is responsible for parsing the user input string into an executable command.
FindCommandParser first obtains the values input by the user after the keyword find.
FindCommandParser ensures that there is at least one key term value after trimming the user input for space characters.
If the above constraint is violated, FindCommandParser throws a ParseException.
Otherwise, it creates a new instance of FindCommand that corresponds to the user input.
FindCommand stores an instance of NameContainsKeyTermsPredicate, which represents a predicate that checks if a person’s name contains at least one of the space-separated key terms the user has input. (Note: contains is defined here as a case-insensitive, antisymmetric relation; that is, A may contain B even if B does not contain A).
Examples:
- “Ben” is contained in “Ben Leong”.
- “al” is contained in both “Alex Yeoh” and “Jiale”.
- “BenL” is not contained in “Ben Leong” because the ‘space’ character between the words in the name invalidates the contains relationship.
Upon execution, FindCommand passes the instance of NameContainsKeyTermsPredicate to the model through the method model::updateDisplayedPersonList. The model then uses the predicate internally to update the displayed list of contacts.
We have considered the following alternative implementations:
- Do not implement a separate command to find contact by name i.e. use the
filtercommand where thenamefield is passed to the/byprefix instead as the way to find contacts by their name.- This is not optimal for our use case as the
findcommand is a command we expect users to use often to search for contacts via their name. Hence simplifying the command call usage would make the experience more efficient for users.
- This is not optimal for our use case as the
Documentation, logging, testing, configuration, dev-ops
Appendix: Requirements
Product scope
Target user profile:
- is an NUS computing student or computing professional
- has a need to manage a significant number of contacts
- has a need to manage a lot of details for each contact
- is looking into networking with other computing students and professionals
- prefers desktop apps over other types
- can type fast
- prefers typing to mouse interactions
- is reasonably comfortable using CLI apps
Value proposition: As computing students and professionals network with alumni to expand their career prospects, our app keeps a list of contacts of people that each user networks with.
- Offline, with a static online page that contains user manual and download link
- Sort contacts by name, priority, graduation year
- Filter contacts by details, e.g. courses taken, graduation year, specialisations, tags.
User stories
Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *
| Priority | As a … | I want to … | So that I can… |
|---|---|---|---|
* * * |
user | see usage instructions in the app | refer to instructions when I forget how to use the app |
* * * |
user | create a new contact | keep a record of individuals in my network |
* * * |
user | add more details about an existing contact | store information about my contacts for future reference |
* * * |
user | edit or delete details of a contact | replace outdated details with more accurate information |
* * * |
user | delete a contact | remove individuals I no longer keep contact with |
* * |
user | find a contact by name | locate details of contacts without having to go through the entire list |
* * |
user with many contacts | sort contacts by their details | locate contacts with special characteristics that I am looking for |
* |
user with many contacts | filter contacts based on their details | locate contacts who fulfil certain conditions that I am looking for |
* * |
new user | use commonly-available keyboard shortcuts (e.g. ctrl-c for copy, ctrl-v for paste) | provide input more efficiently with shortcuts I am accustomed to |
* * |
user | use simple and easy-to-press shortcuts | remember and execute the shortcuts more easily |
* * |
user | open my email app by clicking on my contact’s email | send emails to my contacts more efficiently |
* * |
user | open the relevant website by clicking on my contact’s social link | conveniently access their social links when needed |
* * |
user | undo and redo my commands | revert my changes when I make a mistake |
* * |
new user | have a quick-start guide | start using the basic functionality of the app as soon as possible |
* * |
user | visit an online page containing the complete user manual | refer to the full set of instructions when needed |
* |
user | navigate to the relevant section of the online manual directly from the catalogue | quickly find instructions on the feature I want to use |
* |
user | export my contacts in the form of readable text | easily share my contacts with others |
* |
user with many devices | import data from my exported contacts | sync my contact details across different devices |
Use cases
Use case: Create a new contact
MSS
-
User requests to create a new contact with a name.
-
NetworkBook creates a new contact with the name.
Use case ends.
Extensions
-
1a. User does not include the contact’s name in the request.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
-
1b. User includes more details about the contact in the request.
-
1ba. All the details provided are in the correct format.
-
1ba1. NetworkBook creates a contact with all included details.
Use case ends.
-
-
1bb. Some of the details provided are not correctly formatted.
-
1bb1. NetworkBook shows an error message.
Use case ends.
-
-
1bc. Some of the details are provided twice.
-
1bc1. NetworkBook shows an error message.
Use case ends.
-
-
-
1c. The name is not unique.
-
1c1. NetworkBook shows an error message.
Use case ends.
-
Use case: Add phone numbers to contact
This use case is also applicable to adding email, link, course, specialisation, tag to a contact. For each contact, each of these fields is recorded by a list, and new entries for a field will be appended to the field’s list.
MSS
-
User requests to add phone numbers to a specific contact in the displayed list.
-
NetworkBook adds the new phone numbers to the contact’s list of phone numbers.
-
NetworkBook updates the displayed contact card with the new phone numbers.
Use case ends.
Extensions
-
1a. The given index is invalid.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
-
1b. User does not input any field.
-
1b1. NetworkBook shows an error message.
Use case ends.
-
-
1c. One of the given phone numbers is in an invalid format.
-
1c1. NetworkBook shows an error message.
Use case ends.
-
-
1d. One of the given phone numbers is already present in the contact’s list of phone numbers.
-
1d1. NetworkBook ignores the present phone number, and adds the other phone numbers to the contact’s list of phone numbers.
Use case resumes at step 3.
-
-
1e. User provides the same phone number more than once.
-
1e1. NetworkBook shows an error message.
Use case ends.
-
Use case: Add graduation year to a contact
This use case is also applicable to adding priority to a contact. For each contact, each of these fields is a single value instead of a list. They cannot be added if the value is already present.
MSS
-
User requests to add graduation year to a specific contact in the list.
-
NetworkBook adds the graduation year to the contact.
-
NetworkBook informs user of the contact’s new graduation year.
Use case ends.
Extensions
-
1a. The given index is invalid.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
-
1b. User does not input any field.
-
1b1. NetworkBook shows an error message.
Use case ends.
-
-
1c. The given graduation year is in an invalid format.
-
1c1. NetworkBook shows an error message.
Use case ends.
-
-
1d. The contact already has a graduation year.
-
1d1. NetworkBook shows an error message.
Use case ends.
-
Use case: Edit the name of a contact
This use case is also applicable to editing graduation, priority of a contact, except for extension 1e. which is specific to the name field.
MSS
-
User requests to edit the name of a specific contact in the list.
-
NetworkBook updates the contact.
-
NetworkBook updates the displayed contact card of the person.
Use case ends.
Extensions
-
1a. The given index is invalid.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
-
1b. The name provided is not correctly formatted.
-
1b1. NetworkBook shows an error message.
Use case ends.
-
-
1c. The user does not provide any field to edit.
-
1c1. NetworkBook shows an error message.
Use case ends.
-
-
1d. The user provides two name fields.
-
1d1. NetworkBook shows an error message.
Use case ends.
-
-
1e. The name provided is taken by another contact of the user
-
1e1. NetworkBook shows an error message.
Use case ends.
-
Use case: Edit a phone number of a contact
This use case is also applicable to editing email, link, course, specialisation, tag of a contact.
MSS
-
User requests to edit a phone of a specific contact in the list at the specific index in the contact’s phone number list.
-
NetworkBook updates the contact.
-
NetworkBook updates the displayed contact card of the person.
Use case ends.
Extensions
-
1a. The given contact index is invalid.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
-
1b. The phone number provided is not correctly formatted.
-
1b1. NetworkBook shows an error message.
Use case ends.
-
-
1c. The user does not provide any field to edit.
-
1c1. NetworkBook shows an error message.
Use case ends.
-
-
1d. The user provides two phone fields.
-
1d1. NetworkBook shows an error message.
Use case ends.
-
-
1e. The user provides an invalid phone number index.
-
1e1. NetworkBook shows an error message.
Use case ends.
-
-
1f. The user provides the phone number index twice.
-
1f1. NetworkBook shows an error message.
Use case ends.
-
-
1g. The user does not provide the phone number index.
-
1g1. The phone number index takes the default value of 1.
-
1g1a. The phone number index of 1 is invalid.
Use case resumes at step 1e1.
-
1g1b. The phone number index of 1 is valid.
Use case resumes at step 2.
-
-
Use case: Delete a contact
MSS
-
User requests to delete a specific contact in the list.
-
NetworkBook deletes the contact.
-
NetworkBook updates the displayed list of contact.
Use case ends.
Extensions
-
1a. The given index is invalid.
-
1a1. NetworkBook shows an error message that the index is invalid.
Use case ends.
-
Use case: Delete a single-valued field of a contact
This use case is applicable to deleting graduation, priority of a contact.
MSS
-
User specifies index of contact and a single-valued field to delete.
-
NetworkBook updates the contact by deleting the field.
-
NetworkBook updates the displayed contact card of the person.
Use case ends.
Extensions
-
1a. The given index of contact is invalid.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
-
1b. User provides multiple fields to delete.
-
1b1. NetworkBook shows an error message.
Use case ends.
-
-
1c. User provides an index field after the single-valued field to delete.
-
1c1. NetworkBook shows an error message.
Use case ends.
-
-
1d. The single-valued field of the contact is empty.
-
1d1. NetworkBook shows an error message.
Use case ends.
-
Use case: Delete a multi-valued field of a contact
This use case is applicable to deleting a phone, email, link, course, specialisation, tag of a contact.
MSS
-
User specifies index of contact, a multi-valued field, and the index of entry to delete.
-
NetworkBook updates the contact by deleting the entry from the field’s list.
-
NetworkBook updates the displayed contact card of the person.
Use case ends.
Extensions
-
1a. The given index of contact is invalid.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
-
1b. User provides multiple fields to delete.
-
1b1. NetworkBook shows an error message.
Use case ends.
-
-
1c. The given index of entry is invalid.
-
1c1. NetworkBook shows an error message.
Use case ends.
-
-
1d. User provides multiple indexes of entry to delete.
-
1d1. NetworkBook shows an error message.
Use case ends.
-
-
1e. User does not provide an index of entry.
-
1e1. The index of entry is default to 1.
-
1e1a. The contact’s field does not have an entry at index 1.
Use case resumes at step 1c1.
-
1e1b. The contact’s field has an entry at index 1.
Use case resumes at step 2.
-
-
Use case: Filter contacts
This user story applies to filtering contacts by course, specialisation, graduation year, tag.
MSS
-
User requests to filter by a field with a specified value.
-
NetworkBook displays the contacts with the field that matches the specified value.
Use case ends.
Extensions
-
1a. Field is not specified or value is not specified.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
-
1b. Field specified is invalid.
-
1b1. NetworkBook shows an error message.
Use case ends.
-
-
1c. Value specified is invalid.
-
1c1. NetworkBook shows an error message.
Use case ends.
-
Use case: Sort contacts
MSS
-
User chooses to sort based on a field (e.g. name, graduation year, priority) and an order (ascending or descending).
-
NetworkBook shows list of user’s contacts, sorted by the specified field and in the specified order.
Use case ends.
Extensions
-
1a. User has no contacts to sort.
-
1a1. NetworkBook shows an empty contact list.
Use case ends.
-
-
1b. User does not specify a field.
-
1b1. NetworkBook shows an error message.
Use case ends.
-
-
1c. The specified field is invalid.
-
1c1. NetworkBook shows an error message.
Use case ends.
-
-
1d. User does not specify an order.
-
1d1. Networkbook shows list of user’s contacts, sorted by the specified field and ascending order by default.
Use case ends.
-
-
1e. The specified sorting order is invalid.
-
1e1. NetworkBook shows an error message.
Use case ends.
-
-
1f. Certain contacts do not have any data in the specified field (e.g. no email address stored)
-
1f1. NetworkBook shows sorted list of user’s contacts who have data in the specified field. Any contacts without that field specified are put at the end of the list.
Use case ends.
-
Use case: Search contacts by name
MSS
-
User specifies the text they would like to search.
-
NetworkBook shows list of user’s contacts with names containing the searched text.
Use case ends.
Extensions
-
1a. The search text is not specified.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
Use case: Open email app from NetworkBook
MSS
-
User requests to email a specific contact in the list to the specific email address of the contact.
-
NetworkBook loads the default email app of the user.
-
NetworkBook pre-fills the contact’s email in the recipient field.
Use case ends.
Extensions
-
1a. The contact index is invalid.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
-
1b. The email index is invalid.
-
1b1. NetworkBook shows an error message.
Use case ends.
-
-
1c. The email index is not provided.
-
1c1. NetworkBook takes the default value of email index of 1.
-
1c1a. The email index of 1 is invalid.
Use case resumes at step 1b1.
-
1c1b. The email index of 1 is valid.
Use case resumes at step 2.
-
-
-
2a. The user has not logged in to his default email app.
-
2a1. User will be taken to the sign-in page of his default email app.
Use case ends.
-
Use case: Open link from NetworkBook
MSS
-
User requests to open a link of a specific contact.
-
NetworkBook loads the default browser app of the user with the link opened.
Use case ends.
Extensions
-
1a. The contact index is invalid.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
-
1b. The link index is invalid.
-
1b1. NetworkBook shows an error message.
Use case ends.
-
-
1c. The link index is not provided.
-
1c1. NetworkBook takes the default value of link index of 1.
-
1c1a. The link index of 1 is invalid.
Use case resumes at step 1b1.
-
1c1b. The link index of 1 is valid.
Use case resumes at step 2.
-
-
-
2a. The domain of the link is not a registered domain.
-
2a1. The user will see the error page displayed by the browser used to load the page link.
Use case ends.
-
-
2b. The link is valid but the page fails to load.
-
2b1. The user will see the error page displayed by the browser used to load the page link.
Use case ends.
-
Use case: Undo
MSS
-
User requests to undo the previous command.
-
NetworkBook reverts back to the previous state.
Use case ends.
Extensions
-
1a. User has not keyed in any command previously.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
Use case: Redo
MSS
-
User requests to redo the latest undo command.
-
NetworkBook reapplies the changes recalled by the undo command.
Use case ends.
Extensions
-
1a. There are no more changes to redo.
-
1a1. NetworkBook shows an error message.
Use case ends.
-
Use case: Access user manual on online page
MSS
- User requests to visit the online page.
- The online page renders.
- User selects link to visit the user manual.
-
The user manual page renders.
Use case ends.
Extensions
-
1a. The online page fails to load.
-
1a1. The user will see the error page displayed by the browser used to load the page link.
Use case ends.
-
-
4a. The user manual page fails to load.
-
4a1. The user will see the error page displayed by the browser used to load the page link.
Use case ends.
-
Use case: Navigate from catalogue to relevant section of online manual
Preconditions: User is on user manual page.
MSS
- User chooses a manual section title within the catalogue.
-
Browser navigates to display relevant section of online manual.
Use case ends.
Use case: Export contact in readable text
MSS
-
User requests to export a specific contact in the list.
-
NetworkBook exports a text file storing user details in a readable format.
Use case ends.
Extensions
-
1a. The contact index is invalid.
- 1a1. NetworkBook shows an error message.
Use case ends.
Use case: Import data from exported contacts
MSS
-
User requests to import contacts data.
-
NetworkBook loads the user’s file explorer.
-
User selects a file containing exported contacts.
-
NetworkBook creates new contacts with details specified in the export file.
-
NetworkBook saves the path to the data file.
Use case ends.
Extensions
-
2a. User exits the file explorer without selecting any file.
-
2a1. NetworkBook shows an error message.
Use case ends.
-
-
3a. The file chosen is not in the correct format.
-
3a1. NetworkBook shows an error message.
Use case ends.
-
-
3b. NetworkBook does not have permission to read the file.
-
3b1. NetworkBook shows an error message.
Use case ends.
-
-
3c. The file contains contacts with same names as some existing contacts.
-
3c1. NetworkBook skips these contacts and only creates new contacts whose names are not present yet.
-
3c2. NetworkBook shows the skipped contacts.
Use case ends.
-
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 contacts 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 new user should be able to familiarise him/herself with most of the basic features of the app upon finishing going through the quick-start guide.
- A user should be able to use commonly available and easy-to-remember keyboard shorcuts
- Common shortcuts to edit text, including Ctrl+C to copy, Ctrl+V to paste, Ctrl+A to select all, Ctrl+Z to undo text change, Ctrl+Y to redo text change, etc.
- Ctrl+F: find a contact
- Ctrl+N: create a new contact
- Ctrl+G: edit a contact
- Ctrl+Z: undo last command (when not editing text)
- Ctrl+Y: redo last command undone (when not editing text)
- Ctrl+W: exit the app
- Ctrl+S: manually save data
- Up/down arrow keys: navigate command history
- A new user should be able to understand the meaning of a command just by looking at the keywords used in the command.
Glossary
- Mainstream OS: Windows, Linux, Unix, OS-X.
- Command: a string keyed in by the user in the GUI text input that signifies an action to be done by the app.
- Contact: a contact of the user whose information is stored in the app, which includes name, phone numbers, emails, links, graduation year, courses taken, specialisations, priority level and tags of/associated with the person.
- Field: an attribute of a contact that describes information about the contact. Possible fields of a contact are elaborated in the contact term above.
- Single-valued field: a field that cannot hold many values, so that each contact can only have one value. These fields include name, graduation year and priority level.
- Multi-valued field: a field that can possibly hold many values, so that each contact has a list of values. These fields include phone numbers, emails, links, courses, specialisations and tags.
- Course taken: a module that a person has taken in university or outside (for e.g. CS2103T module in NUS).
- Specialisation: the specialisation a person can take in their computing degree in NUS (e.g. Software Engineering, Artificial Intelligence).
- Graduation year: the year and semester that a person will graduate / has graduated from NUS (e.g. AY2526-S2, meaning the second semester of the academic year spanning from 2025 to 2026).
- Link: a web link which directs to a contact’s profile page on a social platform (e.g. LinkedIn, GitHub).
- Tag: an annotation to a person. This can be anything memorable of the person.
- Priority: the priority level of a contact set by the user. Its value can be either high, medium or low.
Appendix: Planned enhancements
Given below are the planned enhancements. The current behaviour specifies how the application behaves as of v1.4, and the enhanced behaviour specifies how the application should behave in a future milestone.
Logic
- Better error handling on creating a duplicate contact (for
createcommand).- Current behaviour: When the user attempts to create a new contact of the same name as an existing contact, the app displays an error message and prevents the user from creating that new contact.
- Enhanced behaviour: When the user attempts to create a new contact of the same name as an existing contact, the app creates a new contact, but gives a warning message that another contact with the same name already exists. A sample warning message can be:
Noted, created new contact: Nguyen. Another contact with the name "Nguyen" already exists. If you created the new contact by accident, type "undo" to undo creating the contact.This also means that person’s uniqueness constraint should be enforced through a hidden ID and not the name. - Justification: This allows user to add two persons of exactly the same name to the contact list. The error message warns the user of a possible mistake.
- Handling duplicates of phone, email and link across different contacts.
- Current behaviour: When the user attempts to add the same phone, email or link as another contact’s phone, email or link respectively, the software adds the detail without any warning.
- Enhanced behaviour: When the user attempts to add the same phone, email or link as nother contact’s phone, email or link respectively, the software should still add the detail, but with a warning message. For example, suppose that the contact at index 2 has a phone number of
12345678, the commandadd 1 /phone 12345678gives a warning message:Added phone "12345678" to the contact at index 1. Phone number "12345678" already exists in the contact at index 2. If you added the phone number by accident, type "undo" to undo adding the phone number. - Justification: Generally, phone numbers, emails and links are not shared by multiple contacts, hence adding the warning message warns the user of a possible mistake. However, the software should not prevent the user totally from doing so to accommodate the rare case that a phone number, email or link is shared by multiple contacts.
- Better command format for filtering course.
- Current behaviour: The command
filter /by course /with CS2103T /taken truemeans filtering in all contacts that have courseCS2103Tand are taking the course (i.e. current date is between the start and end date), whilefilter /by course /with CS2103T /taken falsemeans filtering in all contacts that have courseCS2103Tregardless of whether the contact is taking the course. The latter is equivalent tofilter /by course /with CS2103T, without the/takentag. - Enhanced behaviour: The
/takenprefix can be changed to/taking, to clearly suggest filtering in contacts that are taking the course. Furthermore, the presence of the prefix/takingshould indicate filtering in contacts taking the course without thetruevalue following the tag, while omitting the prefix means no filtering based on whether the contacts are taking the course. This means thatfilter /by course /with CS2103T /takingmeans filtering in all contacts that are takingCS2103T, whilefilter /by course /with CS2103T(without the/takingprefix) means filtering in all contacts that have courseCS2103Tregardless of whether they are taking the course.
- Current behaviour: The command
- More user-friendly info message.
- Current behaviour: Upon a successful command dealing with one contact, the info message shows all the information of the contact, which is rather verbose. For example, with command
create /name Nguyen /phone 12345678 /phone 87654321, the info message is:Noted, created new contact: Nguyen; Phones: [12345678, 87654321]; Emails: []; Links: []; Courses: []; Specialisations: []; Tags:. - Enhanced behaviour: The info message can be shortened to show only information being add/edited/deleted. For example, with command
create /name Nguyen /phone 12345678 /phone, the info message can be:Noted, created new contact: Nguyen; Phones: [12345678, 87654321].
- Current behaviour: Upon a successful command dealing with one contact, the info message shows all the information of the contact, which is rather verbose. For example, with command
Model
- Better name handling.
- Current behaviour: Name entered by user is kept as it is.
- Enhanced behaviour: Name entered by user is standardised so that for each word, the first character is capitalised and the rest of the characters are not capitalised (non-alphabetical characters are kept as they are).
- Justification: This standardises the display of contact names. Furthermore, this allows better sorting. As of
v1.4, when sorting by name in ascending order, names starting withaare put behind names starting withZ. With the standardisation, there would be no name starting witha.
- Stricter phone uniqueness constraint.
- Current behaviour: Two phone numbers are identified as having the same identity if the user input of the two phones are the same. This means that
+65 12345678and+6512345678are identified as two different phone numbers. - Enhanced behaviour: Two phone numbers are identified as having the same identity if they have the same country code and number part. This means that
+65 12345678and+6512345678should be identified as the same phone number.
- Current behaviour: Two phone numbers are identified as having the same identity if the user input of the two phones are the same. This means that
- Stricter link uniqueness constraint.
- Current behaviour: Two links are identified as having the same identity if the user input of the two links are the same. This means that
https://google.com,www.google.com,http://google.comandgoogle.comare identified as 4 different links. - Enhanced behaviour: Two links are identified as having the same identity if the link excluding the protocol are the same. This means that
https://google.com,www.google.com,http://google.comandgoogle.comshould be identified as the same link, as without the protocol, they are all reduced togoogle.com.
- Current behaviour: Two links are identified as having the same identity if the user input of the two links are the same. This means that
- Better range of priority.
- Current behaviour: Priority only has 3 possible values:
low,mediumandhigh. - Enhanced behaviour: Number of priority values can be specified by the user and saved to
preference.json, or whichever JSON file that the user indicates as preference JSON file inconfig.json. Priority then can be represented by a number. There should be a cap of10levels of priority level as well, to prevent GUI display problems. - Justification: With the current range of priority, it may cause inconvenience for users that want to differentiate their contact in terms of priority in greater number of levels. Hence the app should allow the user to specify the number of priority levels.
- Current behaviour: Priority only has 3 possible values:
Storage
- Better course storage.
- Current behaviour: Courses are stored as a command entered by the user. For example, the course of
CS2103Tstarting on 10 Aug 2023 ending on 10 Dec 2023 is stored as a JSON string"CS2103T /start 10-08-2023 /end 10-12-2023". - Enhanced behaviour: Courses can be stored as a JSON object with
name,startandendfields. For example, the same course can be stored as
- Current behaviour: Courses are stored as a command entered by the user. For example, the course of
{
"name": "CS2103T",
"start": "10-08-2023",
"end": "10-12-2023"
}
while a course of CS2103T without a starting and ending date can be stored as
{
"name": "CS2103T",
"start": null,
"end": null
}
UI
- Display course details in greater details.
- Current behaviour: Each course tile only displays the title of the course, omitting the information on course start and end dates.
- Enhanced behaviour: Each course tile can display 3 pieces of information: name, start date and end date. This can be done by expanding each course tile vertically to have 3 lines, with first line displaying name, second line displaying start date, third line displaying end date.
Appendix: Instructions for manual testing
Given below are instructions to test the app manually.
Launch and shutdown
-
Initial launch.
-
Download the jar file and copy into an empty folder.
-
Run the jar file with the command in the terminal
java -jar networkbook.jar.Expected: Shows the GUI with a set of sample contacts. The window size may not be optimal.
-
-
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 person or details
-
Create a new contact
-
Prerequisite: Before executing each of the following test cases, your contact list should not have contact with name
Test. If there is, either edit/delete the contact, or conduct each test cases with another name not already present in your contact list. -
Test case:
create /name TestExpected: A new contact with name
Testis created, with no other field specified. If the command is run the second time, no new person is added, and NetworkBook shows an error message. -
Test case:
create /name Test /phone 12345678Expected: A new contact with name
Testand phone12345678is created, with no other field specified. -
Test case:
create /name Test /phone 12345678 /phone 87654321Expected: A new contact with name
Testand phones12345678and87654321is created, with no other field is specified.
-
-
Add details to a contact
-
Prerequisite: your contact list has at least 1 contact, and your first contact does not have a phone number of
1234. If there is, either edit/delete the phone number of the contact, or conduct the following test case with another phone that the first contact does not have. -
Test case:
add 1 /phone 1234Expected: A new phone number of
1234is added to the first contact. If the command is run the second time, no new details are added, and NetworkBook shows an error message. -
Test case:
add 0 /phone 1234Expected: No new details are added. NetworkBook shows an error message.
-
Editing a person
-
Editing a person’s single-value field.
-
Test case:
edit 1 /name TestExpected: If there is another contact with name
Test, NetworkBook shows an error message. Otherwise, name of the first contact should change toTest. NetworkBook shows the updated details. -
Test case:
edit 0 /name TestExpected: No person is updated. NetworkBook shows an error message.
-
{ Likewise for
graduation,priority}
-
-
Editing a person’s multi-value field.
-
Prerequisite: Your contact at index 1 has at least 1 phone number.
-
Test case:
edit 1 /phone 12345678 /index 1Expected: First phone number of first contact should change to
12345678. NetworkBook shows the updated details. -
Test case:
edit 1 /phone 12345678 /index 0Expected: No person is updated. NetworkBook shows an error message.
-
{ Likewise for
email,course,specialisation,link,tag}
-
Deleting a person
-
Deleting a person.
-
Prerequisite: Your contact list has at least 1 contact.
-
Test case:
delete 1Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message.
-
Test case:
delete 0Expected: No person is deleted. NetworkBook shows an error message.
-
-
Deleting a field from a person
-
Prerequisite: Your first contact in the list has at least 1 phone number and a graduation year.
-
Test case:
delete 1 /phone /index 1Expected: The first phone number of the first contact is deleted.
-
Test case:
delete 1 /nameExpected: No detail is deleted. NetworkBook shows an error message.
-
Test case:
delete 1 /gradExpected: Graduation year of the first contact is deleted.
-
Filtering contact list
-
Filter contact by name
-
Test case:
findExpected: No filtering is done. NetworkBook shows an error message.
-
Test case:
find testExpected: All contacts with
testas substring (case-insensitive) are filtered in.
-
-
Filter contact by
course,tag,spec,grad-
Test case:
filter /by course /with CS2103TExpected: All contacts with courses with
CS2103Tbeing a substring are filtered in. -
{Likewise for
specialisation,tag} -
Test case:
filter /by grad /with 2021Expected: All contacts with graduation either AY2021-S2 or AY2122-S1 are filtered in.
-
Test case:
filter /by courseExpected: No filtering is done. NetworkBook shows an error message.
-
Sorting contact list
-
Sort contact list
-
Test case:
sort /by name, orsort /by name /order ascExpected: Contacts are sorted by name in ascending ASCII order.
-
Test case:
sort /by name /order descExpected: Contacts are sorted by name in descending ASCII order.
-
{Likewise for
grad,priority}
-
Undo/redo
-
Undo and redo for data-changing command
-
Key in a successful command, e.g.
find test, assuming that your contact list has at least another contact with name with no substringtest.Expected: NetworkBook filters in only contacts with
testas substring (case-insensitive). -
Test case:
undoExpected: NetworkBook reverts to the previous state before
find testcommand was executed. -
Test case:
redoExpected: NetworkBook reapplies the changes done by
find test, which means only contacts withtestas substring (case-insensitive) is filtered in.
-
Saving data
-
Dealing with missing data file
-
Delete the data file. If you have not modified
preferences.json, your data file should be located in/data/networkbook.json. -
Launch the app.
Expected: The app populated with default sample data.
-
-
Dealing with corrupted data file
-
Open the data file.
-
Change an email to
"test@gmail"ornull. -
Launch the app.
Expected: The app starts with an empty list of contacts.
-
-
Dealing with data file without write permission
-
Make the data file read-only.
-
Launch the app.
-
Test case:
find testExpected: All contacts with name containing
testas substring (case-insensitive) are filtered in. -
{Likewise for non-data-changing commands:
filter,sort,list,help,exit} -
Test case:
create /name test(prerequisite: your contact list does not include any contact with nametest)Expected: NetworkBook shows an error message.
-
{Likewise for data-changing commands:
add,edit,delete,undo,redo}
-
-
Save command
-
Make the data file read-only.
-
Launch the app.
-
Key in a successful data-changing command, e.g.
create /name Testif your contact list does not a contact with nameTest.Expected: NetworkBook shows an error message, but a new contact card is still created in the GUI. However, data is not saved to the data file.
-
Go to your OS’ file system and add write permission to the data file.
-
Key in command:
saveExpected: Nothing is changed in the GUI, but a new person object is created in the data file.
-
Appendix: Effort
The table below summarises the functional codes we have contributed to evolve the original AB3 project:
| Category of achievement | Efforts |
|---|---|
| More fields that are useful in networking | Added grad, priority, link, spec and course Adapted phone and email to hold multiple values |
| More intuitive command words | Adapted create and add commands Changed field prefix format to be separate from field value |
| More powerful commands for efficient networking |
edit and delete can be applied to a single item in a list find command can search by fragment of name sort and filter commands for efficient searching undo and redo commands to help user recover mistakes open and email commands to integrate with other apps |
| More comfortable UI/UX | New colour schemes, more spacious organisation, display of item indices in multi-valued fields Status bar to inform user of current filter and sort status Keyboard shortcuts and mouse interactions with GUI items |
Apart from these new or adapted features, other note-worthy efforts are listed below:
| Challenge | Efforts |
|---|---|
AB3 only had 1 multi-valued field, tag, implemented using Set. The Json-friendly version was JsonAdaptedTag. We wanted to have many of such fields. |
We implemented new generic UniqueList and JsonAdaptedProperty classes for these fields. UniqueList stores values in an internal ArrayList, and supports easier interaction with values in the list. JsonAdaptedProperty is similar to JsonAdaptedTag except that it works for any identifiable type. |
AB3’s use of requireAllNonNull may throw run-time NullPointerException. |
We adapted this design by replacing all run-time exceptions with assertion statements. |
| AB3 does not have GUI testing, which affects code coverage for new UI codes. | We set up headless testing using TestFX library, using FxRobot to simulate user actions. We maintained our code coverage above 87%. |