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
interface
with the same name as the Component. - implements its functionality using a concrete
{Component Name}Manager
class (which follows the corresponding APIinterface
mentioned in the previous point.
For example, the Logic
component defines its API in the Logic.java
interface and implements its functionality using the LogicManager.java
class which follows the Logic
interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component’s being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
UI component
The API of this component is specified in Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PersonListPanel
, 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
Logic
component. - listens for changes to
Model
data so that the UI can be updated with the modified data. - keeps a reference to the
Logic
component, because theUI
relies on theLogic
to execute commands. - depends on some classes in the
Model
component, as it displaysPerson
object residing in theModel
.
Logic component
API : Logic.java
Here’s a (partial) class diagram of the Logic
component:
How the Logic
component works:
- When
Logic
is called upon to execute a command, it is passed to aNetworkBookParser
object which in turn creates a parser that matches the command (e.g.,DeleteCommandParser
) and uses it to parse the command. - This results in a
Command
object (more precisely, an object of one of its subclasses e.g.,DeletePersonCommand
) which is executed by theLogicManager
. - The command can communicate with the
Model
when it is executed (e.g. to delete a person). - The result of the command execution is encapsulated as a
CommandResult
object which is returned back fromLogic
.
Here are the other classes in Logic
(omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
- When called upon to parse a user command, the
NetworkBookParser
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 theNetworkBookParser
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.
Here is an overview of what the other classes in Logic
do:
-
ArgumentMultiMap
andArgumentTokeniser
are used to map the parameters of the user’s input into key-value pairs, where the keys are specified usingArgumentTokeniser
-
CliSyntax
is where command-specific keywords are stored. It is used as the arguments forArgumentTokeniser
to process the user input into:{keyword : parameter}
pairs.- Example usage: The text
1 /name John Doe /phone 98765432
when mapped usingArgumentTokeniser
with the keywords/name
and/phone
produces:{1}, {/name : John Doe}, {/phone : 98765432}
- Any text that appears before the first possible keyword is stored in its own entry.
-
ArgumentMultiMap
can 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
-
ParserUtil
contains 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
Person
objects (which are contained in aUniqueList<Person>
object). - stores a
UserPref
object that represents the user’s preferences. This is exposed to the outside as aReadOnlyUserPref
objects. - 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
Model
represents data entities of the domain, they should make sense on their own without depending on other components)
The VersionedNetworkBook
component,
- stores the currently filtered
Person
objects (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
Person
objects (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>
enforcesT
to implementIdentifiable<T>
, which has the methodisSame(T)
to check for identity against another object. - The identity is determined by the class that
T
binds 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 anAssertionError
if 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 atindex
and 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 applyingfunction
on 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
NetworkBookStorage
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 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
create
keyword and the prefixes. - One and only one of the prefix
/name
is present. - Each of
/grad
and/priority
prefixes appears at most once. - All values corresponding to the prefixes
/name
,/phone
,/email
,/link
,/grad
,/course
,/spec
,/priority
and/tag
are 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
CreateCommandParser
to 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:
-
/name
is not provided, since all contacts already have a name, andedit
command 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
/grad
or/priority
, the original person should not contain the corresponding field, asedit
command 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
AddCommandParser
generateEditPersonDescriptor
, 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
EditPersonDescriptor
contains optional indices to specify the entry of a multi-valued field to edit, which is much different fromAddPersonDescriptor
and 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
,/tag
is present. - If
/name
,/priority
or/grad
is present, then/index
is not present. - If
/phone
,/email
,/link
,/course
,/spec
or/tag
is present, then at most one prefix/index
is 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,
-
EditPersonDescriptor
constructor copies the details of the person. -
EditPersonDescriptor
has setter methods to allow changing the details. -
EditPersonDescriptor
has atoPerson
method that returns a new instance ofPerson
that 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
EditCommand
with onlyEditPersonDescriptor
and withoutEditAction
, andEditCommandParser
generates the instance ofEditPersonDescriptor
directly.EditCommandParser
then 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 keepCommand
classes consistent in design, we decide to only have oneEditCommand
class and practice inheritance withEditAction
. - Implement
EditCommand
such thatEditAction
edits thePerson
object directly. This means thatPerson
class must be mutable, which breaks the defensiveness of the current code and has the potential of introducing more bug. Moreover, thePerson
class being immutable also accommodates for theundo
andredo
command, in which theVersionedNetworkBook
only creates a shallow copy of the current list ofPerson
objects and hence any mutation of thePerson
object 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:
-
DeleteCommandParser
checks if the/name
prefix is specified. If yes, it throws aParseException
as the name of a contact cannot be deleted. - The public setter methods in
DeletePersonDescriptor
simply remove the corresponding field entry from the descriptor.
We have considered the following alternative implementation:
-
Implement two different command preambles for
DeletePersonCommand
andDeleteFieldCommand
, such asdelete
andremove
, ordeletePerson
anddeleteField
.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 (
delete
andremove
) 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
/index
prefixes,OpenLinkCommandParser
throws aParseException
. - If there is no
/index
prefix, 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
Person
executesDesktop::browse(URI)
, whereDesktop
andURI
are java classes from default packages. - On Mac OS, the
Person
executes theopen
command in the terminal through theRuntime
class, which is a built-in class in java language. Theopen
command in the terminal opens the computer’s default browser if theURI
supplied is correctly formatted to be a web link. - On Ubuntu, the
Person
executes thexdg-open
command in the terminal through theRuntime
class.
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::undo
is called inModel::undoNetworkBook
. -
VersionedNetworkBook::redo
is called inModel::redoNetworkBook
. -
VersionedNetworkBook::commit
is called inModel::setPerson
,Model::addPerson
,Model::deletePerson
andModel::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 theSortField
enumeration. This value is then used later to determine the predicate implementation. -
parseSortOrder()
parses a given string into a value of theSortOrder
enumeration. 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 aSortField
andSortOrder
and 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
CommandResult
upon 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
MainWindow
already 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
NetworkBook
instance. - 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
NetworkBook
instance. - 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
filter
command where thename
field is passed to the/by
prefix instead as the way to find contacts by their name.- This is not optimal for our use case as the
find
command 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
11
or 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
create
command).- 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 12345678
gives 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 true
means filtering in all contacts that have courseCS2103T
and are taking the course (i.e. current date is between the start and end date), whilefilter /by course /with CS2103T /taken false
means filtering in all contacts that have courseCS2103T
regardless of whether the contact is taking the course. The latter is equivalent tofilter /by course /with CS2103T
, without the/taken
tag. - Enhanced behaviour: The
/taken
prefix can be changed to/taking
, to clearly suggest filtering in contacts that are taking the course. Furthermore, the presence of the prefix/taking
should indicate filtering in contacts taking the course without thetrue
value 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 /taking
means filtering in all contacts that are takingCS2103T
, whilefilter /by course /with CS2103T
(without the/taking
prefix) means filtering in all contacts that have courseCS2103T
regardless 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 witha
are 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 12345678
and+6512345678
are 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 12345678
and+6512345678
should 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.com
andgoogle.com
are 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.com
andgoogle.com
should 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
,medium
andhigh
. - 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 of10
levels 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
CS2103T
starting 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
,start
andend
fields. 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 Test
Expected: A new contact with name
Test
is 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 12345678
Expected: A new contact with name
Test
and phone12345678
is created, with no other field specified. -
Test case:
create /name Test /phone 12345678 /phone 87654321
Expected: A new contact with name
Test
and phones12345678
and87654321
is 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 1234
Expected: A new phone number of
1234
is 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 1234
Expected: 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 Test
Expected: 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 Test
Expected: 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 1
Expected: First phone number of first contact should change to
12345678
. NetworkBook shows the updated details. -
Test case:
edit 1 /phone 12345678 /index 0
Expected: 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 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. 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 1
Expected: The first phone number of the first contact is deleted.
-
Test case:
delete 1 /name
Expected: No detail is deleted. NetworkBook shows an error message.
-
Test case:
delete 1 /grad
Expected: Graduation year of the first contact is deleted.
-
Filtering contact list
-
Filter contact by name
-
Test case:
find
Expected: No filtering is done. NetworkBook shows an error message.
-
Test case:
find test
Expected: All contacts with
test
as substring (case-insensitive) are filtered in.
-
-
Filter contact by
course
,tag
,spec
,grad
-
Test case:
filter /by course /with CS2103T
Expected: All contacts with courses with
CS2103T
being a substring are filtered in. -
{Likewise for
specialisation
,tag
} -
Test case:
filter /by grad /with 2021
Expected: All contacts with graduation either AY2021-S2 or AY2122-S1 are filtered in.
-
Test case:
filter /by course
Expected: No filtering is done. NetworkBook shows an error message.
-
Sorting contact list
-
Sort contact list
-
Test case:
sort /by name
, orsort /by name /order asc
Expected: Contacts are sorted by name in ascending ASCII order.
-
Test case:
sort /by name /order desc
Expected: 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
test
as substring (case-insensitive). -
Test case:
undo
Expected: NetworkBook reverts to the previous state before
find test
command was executed. -
Test case:
redo
Expected: NetworkBook reapplies the changes done by
find test
, which means only contacts withtest
as 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 test
Expected: All contacts with name containing
test
as 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 Test
if 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:
save
Expected: 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%. |