By: CS2103T-F12-03 Since: Oct 2019 Licence: MIT

1. Setting up

Refer to the guide here.

2. Design

2.1. Architecture

ArchitectureDiagram
Figure 1. Architecture Diagram

The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.

The .puml files used to create diagrams in this document can be found in the diagrams folder. Refer to the Using PlantUML guide to learn how to create and edit diagrams.

Main has two classes called Main and MainApp. It is responsible for,

  • At app launch: Initializes the components in the correct sequence, and connects them up with each other.

  • At shut down: Shuts down the components and invokes cleanup method where necessary.

Commons represents a collection of classes used by multiple other components. The following class plays an important role at the architecture level:

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of the App consists of four components.

  • UI: The UI of the App.

  • Logic: The command executor.

  • Model: Holds the data of the App in-memory.

  • Storage: Reads data from, and writes data to, the hard disk.

Each of the four components

  • Defines its API in an interface with the same name as the Component.

  • Exposes its functionality using a {Component Name}Manager class.

For example, the Logic component (see the class diagram given below) defines it’s API in the Logic.java interface and exposes its functionality using the LogicManager.java class.

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

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.

ArchitectureSequenceDiagram
Figure 3. Component interactions for contact delete 1 command

The sections below give more details of each component.

2.2. UI component

UiClassDiagram
Figure 4. Structure of the UI Component

API : Ui.java

The UI consists of a MainWindow which is made up of four parts, i.e. StatusBarFooter, HelpWindow, ChatPane and ResultPane. MainWindow may also have a PromptHandler which contains a list of Prompt objects (see here for more information). In particular,

  • The ChatPane manages text interaction with the user. It uses CommandBox to read commands and DialogBox to display commands and feedback. To handle auto-completion, CommandBox uses an AutoCompleteNode to provide suggestions. The class diagram for the sub-component is shown below.

UiChatPaneClassDiagram
Figure 5. Structure of the Chat Pane sub-component
  • The ResultPane displays a relevant ResultView based on the command entered. The following class diagram shows a partial view of the component with only the NoteListResultView and ContactListResultView.

UiResultPaneClassDiagram
Figure 6. Structure of the Result Pane sub-component

Most of these classes, including the MainWindow itself, inherit from the abstract UiPart class.

The UI component uses 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.

  • Displays feedback and updates the ResultPane using CommandResult in the Logic component.

  • Listens for changes to Model data so that the UI can be updated with the modified data.

2.3. Logic component

LogicClassDiagram
Figure 7. Class diagram of overall Logic Component

API : Logic.java

  1. Logic uses the TaglineParser class to parse the user command.

  2. The user command is passed to different command parser based on the command type. E.g. "note delete 1" will be passed to NoteCommandParser

  3. This results in a Command object which is executed by the LogicManager.

  4. The command execution can affect the Model (e.g. adding a note).

  5. The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui.

  6. In addition, the CommandResult object can also instruct the Ui to perform certain actions, such as displaying help to the user.

2.3.1. Contact Logic component

ContactLogicClassDiagram
Figure 8. Class diagram of the Contact Logic Component
  1. Contact Logic is a sub-component of Logic.

  2. TaglineParser will pass a user input that can be classified as a contact command (i.e. has "contact " prefix), to the ContactCommandParser without including the "contact" keyword, e.g. TaglineParser will only pass "create --n Bob" instead of "contact create --n Bob".

  3. ContactCommandParser identifies the type of contact command and passes the argument string to the respective command parser. For example, ContactCommandParser will pass "--n Bob" to CreateContactParser if it receives "create --n Bob" as an input.

  4. This results in a ContactCommand object which is returned to the LogicManager.

  5. The command execution can affect the ContactModel.

Given below is the Sequence Diagram for interactions within the Logic component for the execute("contact create --n Bob") API call.

ContactCreateSequenceDiagram
Figure 9. Interactions Inside the Logic Component for the contact create --n Bob Command

2.3.2. Note Logic component

NoteLogicClassDiagram
Figure 10. Class diagram of the Note Logic Component
  1. Note Logic is a sub-component of Logic.

  2. It obtains the user command parsed by TaglineParser through the NoteCommandParser class.

  3. The user command is passed to the respective command parser. E.g. "note delete 1" will be passed to DeleteNoteParser.

  4. This results in a NoteCommand object which is returned to the LogicManager.

  5. The command execution can affect the NoteModel (e.g. adding a note).

Given below is the Sequence Diagram for interactions within the Logic component for the execute("note delete 1") API call.

NoteDeleteSequenceDiagram
Figure 11. Interactions Inside the Logic Component for the note delete 1 Command

2.3.3. Group Logic component

GroupLogicClassDiagram
Figure 12. Class diagram of the Group Logic Component
  1. Group Logic is a sub-component of Logic.

  2. It obtains the user command parsed by TaglineParser through the GroupCommandParser class.

  3. The user command is passed to the respective command parser. E.g. "group delete x1" will be passed to DeleteGroupParser.

  4. This results in a GroupCommand object which is returned to the LogicManager.

  5. The command execution can affect the GroupModel (e.g. adding a group).

  6. The command execution can affect the ContactModel (e.g. displaying contacts in a group).

Given below is the Sequence Diagram for interactions within the Logic component for the execute("group delete x1") API call.

2.4. Model component

ModelClassDiagram
Figure 13. Class diagram of the overall Model Component

API : Model.java

The Model,

  • stores a UserPref object that represents the user’s preferences.

  • manages Address Book data through ContactModel sub-component.

  • manages Note Book data through NoteModel sub-component.

  • manages Group Book data through GroupModel sub-component.

  • manages Tag Book data through TagModel sub-component.

2.4.1. Contact Model component

Contact Model Diagram
Figure 14. Class diagram of the Contact Model Component

The ContactModel,

  • stores the Address Book data.

  • exposes an unmodifiable ObservableList<Contact> which can be accessed from Model 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.

  • does not depend on any of the other three components.

2.4.2. Note Model component

NoteModelClassDiagram
Figure 15. Class diagram of the Note Model Component

The NoteModel,

  • stores the Note Book data.

  • exposes an unmodifiable ObservableList<Note> which can be accessed from Model 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.

  • does not depend on any of the other three components.

As an additional feature to be implemented in the future, we can store a Tag list in Note. This would allow Note to be able to be better categorized.

2.4.3. Group Model component

GroupModelClassDiagram
Figure 16. Class diagram of the Group Model Component

The GroupModel,

  • stores the Group Book data.

  • exposes an unmodifiable ObservableList<Group> which can be accessed from Model 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.

  • does not depend on any of the other three components.

2.5. Storage component

StorageClassDiagram
Figure 17. Class diagram of the overall Storage Component

API : Storage.java

The Storage component,

  • can save UserPref objects in json format and read it back.

2.5.1. Contact Storage component

ContactStorageClassDiagram
Figure 18. Class diagram of the Contact Storage Component

The ContactStorage component,

  • can save the Address Book data in json format and read it back.

2.5.2. Note Storage component

NoteStorageClassDiagram
Figure 19. Class diagram of the Note Storage Component

The NoteStorage component,

  • can save Note objects in json format and read it back.

  • can save NoteIdCounter state in json format and read it back.

  • can save the Note Book data in json format and read it back.

2.5.3. Group Storage component

GroupStorageClassDiagram
Figure 20. Class diagram of the Group Storage Component

The GroupStorage component,

  • can save Group objects in json format and read it back.

  • can save the Group Book data in json format and read it back.

2.5.4. Tag Storage component

TagStorageClassDiagram
Figure 21. Class diagram of the Tag Storage Component

The TagStorage component,

  • can save Tag objects in json format and read it back.

  • can save the Tag Book data in json format and read it back.

2.6. Common classes

Classes used by multiple components are in the tagline.commons package.

3. Implementation

This section describes some noteworthy details on how certain features are implemented.

3.1. User Prompting

3.1.1. Description

When the user enters an incomplete command, the command could be missing only a few compulsory fields. Instead of forcing the user to edit the command entirely, TagLine will prompt the user for further details instead.

At this point, the user may abort the command or provide the requested details. When all details are provided, the command is executed.

3.1.2. Implementation

Representing a prompt

The prompting mechanism uses Prompt objects to represent individual queries for additional information. A list of Prompt objects is used to pass information between the Logic and Ui components. Prompt contains the following fields:

  • prefix: The prefix of the missing field (e.g. for a contact create command, the name field has prefix --n)

  • question: A question to ask the user for details regarding the missing information

  • response: The response from the user

These fields are accessible through getters and setters in the Prompt class.

Passing the prompts

Given below is an example scenario where the user command has missing compulsory fields.

Step 1: The Ui passes the user’s command to Logic, which finds one or more missing compulsory fields. For each missing field, it creates a new Prompt object with a question. Then it throws a ParseException containing the list of Prompt objects.

UserPromptSequenceDiagramStep1

Step 2: The Ui receives the list of Prompt objects. For each Prompt, it retrieves the question and obtains the corresponding user feedback using the mechanism here.

UserPromptSequenceDiagramStep2

Step 3: The Ui passes the original command, together with the processed Prompt objects, back to Logic. Logic then executes the corrected command.

UserPromptSequenceDiagramStep3

The full sequence diagram is shown below:

UserPromptSequenceDiagramFull

The user can also abort the command by pressing the Escape button. In this case, the Ui will discard the original command and continue to receive further user commands.

Getting responses from the user

To obtain responses to a list of prompts, the UI uses a PromptHandler to indicate the incomplete command that it is currently working on. PromptHandler uses the Iterator design pattern to fill a list of prompts. It implements the following operations:

  • PromptHandler#getPendingCommand: Returns the incomplete command

  • PromptHandler#fillNextPrompt: Fills the next unfilled prompt in the list

  • PromptHandler#getNextPrompt: Gets the question of the next unfilled prompt in the list

  • PromptHandler#isComplete: Returns true if all prompts have been filled

  • PromptHandler#getFilledPromptList: Gets the filled prompt list

To allow the Ui to handle user prompts, the sequence of steps taken to handle user input has been modified. To illustrate the program flow, three possible scenarios of user input will be discussed.

  1. The user enters some input with missing compulsory fields.

    1. The input is passed to Logic, where a PromptRequestException is thrown.

    2. MainWindow takes the list of prompts in the PromptRequestException, and creates a new PromptHandler in the private field promptHandler.

    3. MainWindow gets the first prompt question from promptHandler and displays it.

  2. The user is currently being prompted, and enters some input to answer a prompt. There are more prompts remaining.

    1. MainWindow has a promptHandler which is incomplete. It calls fillNextPrompt with the user input.

    2. MainWindow checks that promptHandler is still incomplete.

    3. MainWindow gets the next prompt question from promptHandler and displays it.

  3. The user is currently being prompted, and enters some input to answer a prompt. There are no more prompts remaining.

    1. MainWindow has a promptHandler which is incomplete. It calls fillNextPrompt with the user input.

    2. MainWindow checks that promptHandler is now complete.

    3. MainWindow calls getPendingCommand and getFilledPromptList of promptHandler.

    4. The incomplete command and the filled prompt list are passed to Logic to execute the command.

The cases above are labelled and summarized in the full activity diagram below. The mechanism for aborting commands is done using listeners and not shown below.

UserPromptActivityDiagram
Figure 22. Overall activity diagram for handling user input

3.1.3. Design Considerations

Aspect: Prompt handling method
  • Alternative 1: The Ui functions as per before and is unaware of prompting. The Logic keeps track of the incomplete command and sends prompts back as CommandResult objects.

    • Pros: Decreases coupling between Ui and Logic components

    • Cons: Violates the Single Responsibility Principle for CommandResult, i.e. CommandResult may now have to change because of changes to the prompting feature

      Ui has no way to know if it is currently handling prompting, so it cannot abort prompts, disable/enable autocomplete or display special messages.

  • Alternative 2: The Logic component keeps track of the incomplete command and throws an exception containing prompts to the Ui.

    • Pros: Greater flexibility for Ui to handle prompts, e.g. aborting

    • Cons: LogicManager has to keep track of the command entered, rather than simply acting as a bridge between the Ui and the Parser sub-component. Increases number of potential points of failure and decreases maintainability.

  • Alternative 3: The Logic component requests prompts from the Ui. The Ui component keeps track of the incomplete command.

    • Pros: Greater flexibility for Ui to handle prompts, e.g. aborting

Alternative 3 was chosen as it allows for flexibility in prompt handling while having Ui be the sole component responsible for collecting prompt responses.

Aspect: Command correction method
  • Alternative 1: The Ui updates the command with the user’s responses by adding the new data to the command string.

    • Pros: No need to overload Logic#execute() and Parser#parse() methods

    • Cons: Requires Ui to know where to insert preambles, and increases coupling between Ui and Logic components (as Ui now needs to know and follow the command format)

  • Alternative 2: The LogicManager updates the command with the user’s responses by adding the new data to the command string.

    • Pros: No need to overload Parser#parse() method

    • Cons: Requires LogicManager to know where to insert preambles, and reduces flexibility of prompting

  • Alternative 3: TaglineParser and the individual parser classes handle the list of Prompt objects when parsing the command

    • Pros: Easily handles preambles, and also allows greater extensibility of the prompt feature, e.g. can have the user fix incorrect commands or confirm actions

    • Cons: Requires changing multiple Parser classes, may increase code duplication

Alternative 3 is chosen as it allows the confirmation messages for the clear commands to be implemented easily.

For Alternative 1 and 2, implementing confirmation would inadvertently add an alternative command to directly perform the action. To illustrate, suppose we check for confirmation for the contact clear command by having the user type YES. Then due to the mechanism of the prompting feature, we will inadvertently include a new command like contact clear <prefix> YES. Since this is unintuitive, alternative 3 was chosen instead.

3.2. Group Contacts feature

3.2.1. Description

Groups allows users to better organize contacts into relevant social circles (represented as Group) to better express relationships much like how they exists as in real life. This feature would provide the foundation for further more advanced features such as tagging of notes with group tags.

The user can work with groups by using the commands as detailed in the group section.

Commands currently available:

  • group create - creates a new group

  • group remove - removes a contact from a group

  • group add - adds an existing contact to the group

  • group list - list all available groups

  • group find - searches for group by exact name and displays contacts in the group

  • group delete - disbands a group (contacts in group are not deleted)

3.2.2. Implementation

The grouping feature is facilitated by GroupBook, an additional Model component in addition to the current AddressBook. It extends the functionality of AddressBook by providing a way to group contacts together into unique Group classes identified by their GroupName. This allows users to form more natural associations of contacts such as "BTS-members". Identifying which contacts are group members of a Group is done by storing a record of their ContactId in the Group. Additionally, GroupManager extends Tagline with the following operations to support commands dealing with groups:

The above operations are exposed in the Model interface by their respective method names.

These above are static utility functions which form the underlying structure of how a GroupCommand works.

Given below is an example usage scenario on how a typical lifecycle of a Group behaves at each step. With emphasis on showing the effects of DeleteCommand as an example of a command from ContactCommand would interact with GroupCommand and GroupModel state.

Step 1. The user initially has several contacts in AddressBook.

GroupContactsState0
Figure 23. Simplified state of relevant Model components initially

The AddressBook model state contains all the Contact class that exists in the App. Since no Group has been created yet, GroupBook model state is currently empty. All of the contacts found in AddressBook are displayed on the UI by default.

Step 2. Wishing to better organize her contacts into groups, the user executes group create BTS calling CreateGroupCommand. to create a new Group instance with no members.

GroupContactsState1
Figure 24. State after Group "BTS" is created

The GroupBook model state now contains a Group instance for "BTS" with no members recorded as memberIds. Any command regarding Group would prompt the UI to display the contacts in the group. A group with no members would cause the UI to be empty. As there are no contacts in the group. While a group with members in it would cause UI to display all the contacts belonging to that group.

Step 3. The user then executes group add BTS --i 00001 --i 00002 --i 0013 --i 0004 calling the addMembersToGroupCommand to add several contacts to the group. Only the String representation of the ContactId will be stored in the Group.

GroupContactsState2
Figure 25. State after four contacts are added into Group "BTS"

Group "BTS" now has members in it and the UI would display all the contacts found in the group.

Step 4. The user realizes she has made a mistake adding a wrong contact and in a fit of rage chooses to delete the contact instead of merely removing the contact from the Group. Executing contact delete 00013 which then deletes the Contact with contactId of 00013. However, this does not remove the contact’s id from the memberId attribute in the Group the contact was in. This step does not involve GroupModel in any way.

GroupContactsState3
Figure 26. State after contact with contactId = 00013 is deleted, UI for groups is not active at this point

Deleting a Contact would cause it to be removed from AddressBook model state and the Contact no longer exists. Due to the contact command, the active UI shifts to displaying a list of contacts (not illustrated here for simplicity) and the groups as shown in the image are actually not visible to the user. However behind the scenes, while the UI no longer has contact of 00013, it is still recorded as a member in GroupBook model state. The updating of GroupBook model state is deferred.

Step 5. The user then executes group add BTS --i 00003 to add the correct contact as a member on the Group and view the Contact profiles. This calls AddMemberToGroupCommand which then updates the Group ensuring that all memberIds correspond to an existing ContactId found in AddressBook. The contacts of the group are also displayed to the user.

GroupContactsState4
Figure 27. State after user views contacts of Group "BTS", UI displaying the group of contacts is now visible

Here, the GroupBook model state is updated and memberId of 00013 from the previous step is removed while Contact with contactId of 00003 is added into the Group. This change is also reflected in the UI which changes back to group display now that a group command is issued. Now all is as it should be in Group "BTS".

The following sequence diagram summarizes what happens when a user executes a FindGroupCommand which which updates the Group similar to how AddMemberToGroupCommand does in the above example:

GroupSequenceDiagram
Figure 28. Sequence diagram of executing FindGroupCommand to view contacts in a Group

3.2.3. Design Considerations

Aspect: How groups stores contacts
  • Alternative 1: Stores ContactId class in a Collection in Group

    • Pros: Easy to get ContactId from Group to retrieve Contact classes from Addressbook.

    • Cons: Increases coupling to implementation of Contact. Storage and retrieval after reloading the app would also cause new instances of ContactId to be created when loading Group or would require more complicated loading of Group from storage having to happen after AddressBook is loaded and having to reference Contact classes to ensure the same ContactId class is referenced by both Contact and Group it is in.

  • Alternative 2 (current choice): Stores Collection of Strings which are able to uniquely identify Contact.

    • Pros: Group classes are less coupled to implementation of Contact. Simpler to load Group classes from storage. due to not needing to check and obtain a reference to ContactId. User input is also parsed as Strings.

    • Cons: Deciding when to check if members are still part of a Group since it need not be done at loading time. While it is more flexible, can be a potential source of confusion as it may be possible to forget to update the members in Group.

3.3. Tagging feature

3.3.1. Description

The user can tag a note with many tags by using note tag command.

3.3.2. Implementation

In order to add tagging feature we will need to take a look at two processes, which are the tag command creation and the execution of the command.

Creating Tag Command

We will use a TagParserUtil to create a tag from user input.

Given below is an example scenario when a user tag a note with 2 tags.

Step 1: The user command will be passed to TaglineParser, all the way to the TagNoteParser.

CreatingTagNoteCommand

Step 2: NoteParserUtil will be used to create a noteId object.

Step 3: Finally, TagParserUtil will be used to create tag objects. All of them will be aggregated inside a tagList before being passed to the TagNoteCommand Constructor.

This whole process has created a TagNoteCommand object from user input.

Executing Tag Command

Now, we will take a look on how we are executing the tagging command.

Given below is an example scenario when the tagging command gets executed.

Step 1: The TagNoteCommand interact with NoteManager through model to find the note to be tagged.

ExecutingTagNoteCommand

Step 2: The TagNoteCommand then exchange each tag with another tag which is registered inside the model. Internally, model will have to interact with TagManager which will find the registered tag or register one if it does not exist inside model.

Step 3: Finally, the note will be tagged with the registered tag using model.

This whole process has successfully executed the TagNoteCommand.

3.4. Note filtering feature

3.4.1. Description

The user can filter notes by providing a filter in the note list command.

Types of filter:

  • No prefix - filter by String keyword

  • Prefix # - filter by hashtag

  • Prefix @ - filter by contact

  • Prefix % - filter by group

3.4.2. Implementation

The note filter mechanism is facilitated by the NoteFilter class. It contains the filter value and the enum FilterType.

A NoteFilter is generated by the NoteFilterUtil inner class in ListNoteParser and passed into ListNoteCommand.

ListNoteCommand then creates a Predicate based on the filter and updates the list of notes in the UI via Model.

Filter by String keyword

Filter by keyword is facilitated by the following classes:

  • KeywordFilter - implementation of NoteFilter that is passed into ListNoteCommand

  • NoteContainsKeywordsPredicate - Predicate passed into Model#updateFilteredNoteList() to list only notes that contain the keywords.

Given below is an example scenario where the user enters a command to filter notes by keywords.

Step 1: The user command is passed through the LogicManager to ListNoteParser. ListNoteParser checks the input arguments and identify the String keywords.

The keywords are passed into NoteFilterUtil#generateKeywordFilter() which returns a KeywordFilter containing the keywords and FilterType.KEYWORD.

FilterKeywordSequenceDiagram1
Figure 29. Sequence diagram of parsing note list user command to obtain a ListNoteCommand

Step 2: The ListNoteCommand returned will be executed by the LogicManager. If a NoteFilter exists and is of FilterType.KEYWORD, ListNoteCommand#filterAndListByKeyword() will be called.

FilterKeywordSequenceDiagram2
Figure 30. Sequence diagram of executing ListNoteCommand to update filtered note list by keyword in Model

The method will create a NoteContainsKeywordsPredicate and update the list of notes to be displayed via Model#updateFilteredNoteList().

FilterKeywordExample
Filter by Tag

Filter by Tag is facilitated by the following classes/methods:

  • TagParserUtil#parseTag() - to obtain the Tag objects from the user input tag strings

  • TagFilter - implementation of NoteFilter that is passed into ListNoteCommand

  • NoteContainsTagsPredicate - Predicate passed into Model#updateFilteredNoteList() to list only notes that is tagged by specified Tag

Given below is an example scenario where the user enters a command to filter notes by tag.

Step 1: Similar to filtering by keyword, the user command is passed to the ListNoteParser. The ListNoteParser checks the input arguments and identify the tag strings.

The tag strings are passed into NoteFilterUtil#generateTagFilter(). TagParserUtil#parseTag() is called to get Tag from the tag string. TagFilter containing the list of tags and FilterType.TAG is returned.

FilterTagSequenceDiagram1
Figure 31. Sequence diagram of parsing user input tag strings to obtain a ListNoteCommand

Step 2: The ListNoteCommand returned will be executed by the LogicManager. If a NoteFilter exists and is of FilterType.TAG, ListNoteCommand#filterAndListByTag() will be called.

FilterTagSequenceDiagram2
Figure 32. Sequence diagram of executing ListNoteCommand to update filtered note list by Tag in Model

The method will check if the tags in the NoteFilter exists via Model#findTag(). If a Tag does not exist, an error message will be displayed.

If all tags exist, the tags will be passed into the NoteContainsTagsPredicate and update the list of notes to be displayed via Model#updateFilteredNoteList().

FilterTagExample

3.5. Contact Profile feature

3.5.1. Description

The user can view all notes that are associated with a certain contact with contact profile feature. A note is considered to be associated with a contact X if it satisfies one of the following criteria:

  • The note is tagged with a contact tag that represents X.

  • The note is tagged with a group tag where the represented group has X as one of its members.

This feature takes advantage of the existing note tag command by having these two types of tags:

  • Contact tag:

    • Use @ symbol as the tag prefix.

    • Represents a contact using its id.

    • Usage example: @12345. This represents a contact with id 12345.

  • Group tag:

    • Use % symbol as the tag prefix.

    • Represents a group using its name.

    • Usage example: %cs2103t. This represents a group with name cs2103t.

The user can use contact show command to display the profile of a contact. A contact profile contains all the stored information (e.g. email and address) and notes that are associated with that contact.

3.5.2. Implementation

The tags that represent contact and group are facilitated by the ContactTag and GroupTag classes.

Whenever a user uses note tag command to tag a note with any type of tag, part of the input that represents the value of tag (i.e. follows --t TAG format) will be passed into TagParserUtil. The parser will handle the translation based on the first non-whitespace character into a Tag object, which is possible to be either ContactTag or GroupTag.

Meanwhile, the mechanism to display a contact profile is facilitated by the ShowContactCommand class. This command takes a contact id as parameter and stores it inside the object. Upon execution, the command will ask Model for all groups that contain the given contact as one of their members. Afterward, it will filter notes based on contact id and all the group names that are converted into Tag objects.

Parsing for contact and group tags

Parsing for contact and group tags are handled by TagParserUtil as described in the tagging feature section. Differences between input format with other type of tag (i.e. hash tag) is only the first character where contact tag uses @ symbol and group tag uses % symbol.

However, the main difference is in the implementation of Tag#isValidInModel, which is an abstract method that needs to be implemented by every concrete subclass of Tag. The following are how this method is implemented in contact and note tags:

  • Contact tag determines its validity in the model by calling Model#findContact, which takes a ContactId as parameter and returns an optional object of Contact that has the specified contact id. If such contact object exists, a contact tag is considered valid.

  • Group tag determines its validity in the model by calling Model#hasGroupName, which takes a GroupName as parameter and returns a boolean whether such group name exists in the data stored by Tagline or not.

Creating the command

Given below is an example scenario where the user enters a command to show a contact profile.

Step 1: Similar to other contact commands, the user command is first passed to TaglineParser to identify its type until it finally arrives at ShowContactParser which will create the command object. The ShowContactParser also checks the input arguments to make sure it follows the basic format.

ContactShowParserDiagram
Figure 33. Sequence diagram of parsing user input string to obtain a ShowContactCommand
Executing the command

When executing of ShowContactCommand, there will be an initial check whether the provided contact id is valid or not by calling Model#findContact similar with checking validity for contact tag. If the provided contact id is invalid (e.g. non existing contact), the execution will stop and directly return a CommandResult which informs about the error.

Suppose now we only consider the contact show command that is provided with valid contact id. During execution, it will translate the contact id into ContactIdEqualsSearchIdPredicate and NoteContainsTagsPredicate objects. Afterwards, those predicate will be used to update the observable contact and note list, by calling Model#updateFilteredContactList and Model#updateFilteredNoteList.

ContactShowExecutionDiagram
Figure 34. Sequence diagram of executing contact show command.

During translation from contact id to multiple tags, the command objects will call Model#findGroupsWithMember to get all groups that contain the specified contact id as one of its members. Afterwards, it creates a single array of tags that represent all tags that are associated with the contact id.

RetrieveAssociatedTagsDiagram
Figure 35. Sequence diagram of translation from contact id into associated tags.

A successful execution of ShowContactCommand will return a CommandResult that indicates a view change into ContactProfileResultView. The result view for contact profile observes the changes on observable contact and note lists to update accordingly.

3.6. Logging

We are using java.util.logging package for logging. The LogsCenter class is used to manage the logging levels and logging destinations.

  • The logging level can be controlled using the logLevel setting in the configuration file (See Section 3.7, “Configuration”)

  • The Logger for a class can be obtained using LogsCenter.getLogger(Class) which will log messages according to the specified logging level

  • Currently log messages are output through: Console and to a .log file.

Logging Levels

  • SEVERE : Critical problem detected which may possibly cause the termination of the application

  • WARNING : Can continue, but with caution

  • INFO : Information showing the noteworthy actions by the App

  • FINE : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size

3.7. Configuration

Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json).

4. Documentation

Refer to the guide here.

5. Testing

Refer to the guide here.

6. Dev Ops

Refer to the guide here.

Appendix A: Product Scope

Our product is targeted at users who:

  • Need to manage a large variety of notes related to multiple categories

  • Need to manage large numbers of team projects or relationships

  • Want to keep their notes organized

  • Prefer desktop applications over mobile applications

  • Prefer typing commands over using graphical interfaces

Value proposition: TagLine manages notes faster than a typical mouse/GUI driven app

Appendix B: 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

add a new contact

* * *

user

edit a contact

update outdated information

* * *

user

delete a contact

remove entries that I no longer need

* * *

user

find a contact by name

locate details of contacts without having to go through the entire list

* * *

user

view all contacts in a group

* * *

user

add new notes

* * *

user

edit a note

fix typos or incorrect details

* * *

user

delete a note

clean up my app

* * *

user

tag my notes

group related notes together

* * *

user

view all notes according to tags

view only notes related to an issue

* * *

user

view all notes related to a contact

discuss these notes with them when I meet them

* *

user

group my contacts

manage contacts for different occasions better

* *

user

view all notes related to a group

* *

user

view all notes related to groups as well when querying for a person

view all information associated with that person at a glance

* *

user with many friends with the same name

be able to differentiate them easily

locate a specific person

* *

user

archive old notes

keep them while not cluttering my app page

* *

user

export all my data and create a backup

keep my data somewhere safe

* *

new user

get suggestions when typing commands

do not need to memorize commands

*

user

embed links in my notes

directly access relevant webpages

*

user

associate photos with notes

store and view related photos and notes together

*

user

add text styles

personalize my entries

*

user

colour entries with the same tag

organize my notes better

*

user

prompted for correction when I make typos

fix my command without re-typing it entirely

*

user

prompted for confirmation when I delete or edit notes or contacts

avoid making irreversible mistakes

*

user

list all notes by chronological order

view most relevant notes first

*

user

lock notes with authentication

keep my notes secure

Appendix C: Use Cases

(For each of the use cases below, the System is TagLine and the Actor is the user, unless specified otherwise)

The use cases are divided into categories using the following naming convention:

  • UCC for contact-related use cases

  • UCN for note-related use cases

  • UCE for error handling use cases.

UCC01 Add person

MSS

  1. User requests to add a contact.

  2. TagLine adds the contact to the contact list.

    Use case ends.

Extensions

  • 1a. UCE01 Invalid command syntax

  • 1b. UCE02 Missing compulsory fields

UCC02 Add group

MSS

  1. User requests to create a new group.

  2. TagLine creates the group.

    Use case ends.

Extensions

  • 1a. UCE01 Invalid command syntax

  • 1b. UCE02 Command with missing compulsory fields

    Use case ends.

UCN01 Add note

MSS

  1. User requests to add a new note.

  2. TagLine creates the note.

  3. TagLine displays the newly created note.

    Use case ends.

Extensions

  • 1a. UCE01 Invalid command syntax

  • 1b. User does not include a tag for the note

    • 1b1. TagLine prompts user if the user wants to add a tag.

    • 1b2. User either adds a tag or declines.

      Use case resumes at step 2.

UCN02 Add tag to note

MSS

  1. User requests to tag a currently existing note

  2. TagLine adds the tag to the note.

  3. TagLine displays the edited note.

    Use case ends.

Extensions

  • 1a. UCE01 Invalid command syntax

  • 1b. UCE03 Command with ambiguous field

UCE01 Invalid command syntax

MSS

  1. User inputs an invalid command.

  2. TagLine requests correction from the user.

  3. User corrects the command.

  4. TagLine executes the command.

    Use case ends.

UCE02 Command with missing compulsory fields

MSS

  1. User inputs a command with missing compulsory fields.

  2. TagLine prompts user for a missing field value.

  3. User inputs the field value.

    Until all missing field values are inputted.

  4. TagLine executes the command.

    Use case ends.

Extensions

  • 2a. User aborts the command.

    • 2a1. TagLine confirms the abort.

      Use case ends.

UCE03 Command with ambiguous field

MSS

  1. User inputs a command with an ambiguous field value (e.g. name).

  2. TagLine prompts user with a list of suggested values and their unique IDs.

  3. User inputs the ID.

  4. TagLine executes the command.

    Use case ends.

Extensions

  • 2a. User aborts the command.

    • 2a1. TagLine confirms the abort.

      Use case ends.

Appendix D: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 11 or above installed.

  2. Should be able to hold up to 1000 contacts without a noticeable sluggishness in performance for typical usage.

  3. Should be able to display large amounts of text quickly, i.e. up to 10MB of text data within 2 seconds

  4. Command syntax should not exceed 10 distinct terms, in order to avoid user confusion.

Appendix E: Glossary

Ambiguous field

A field for a command that is not unique, e.g. many users can have the name John Doe

Mainstream OS

Windows, Linux, Unix, OS-X

Private contact detail

A contact detail that is not meant to be shared with others

Appendix F: Instructions for Manual Testing

Given below are instructions to test the app manually.

These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.

F.1. Autocomplete

  1. Looking at autocomplete options

    1. Test case: Type a valid partial command, e.g. c, co or contact del.
      Expected: The autocomplete popup will suggest the next word. So contact will be suggested for c and co, and contact delete will be suggested for contact del.

    2. Test case: Type an invalid partial command, e.g. d or contact qqq
      Expected: The autocomplete popup disappears.

    3. Test case: Use the arrow keys to navigate down to an option in the autocomplete popup. Press Enter or use the mouse to select it.
      Expected: The command box displays the auto-completed string.

Note

The autocomplete menu will only pop up after the user types in the command box. It will disappear after clicking elsewhere on the screen or selecting an autocomplete option.

F.2. Prompting

  1. Requesting additional information from the user

    1. Test case: contact create
      Expected: The chat pane will show two dialogs from TagLine. The first dialog will indicate that the user is being prompted, and the second will ask for the contact name.
      Continuation: Bob
      Expected: A contact named 'Bob' will be created.

    2. Test case: note tag
      Expected: The chat pane will show two dialogs from TagLine. The first dialog will indicate that the user is being prompted, and the second will ask for the note ID.
      Continuation: 1 (or any valid note ID)
      Expected: The chat pane will display another dialog from TagLine, asking for a tag.
      Continuation: #TEST (or any valid tag)
      Expected: The note with ID 1 will be tagged with the tag #TEST.

    3. Test case: contact delete
      Expected: TagLine will ask for the contact ID.
      Continuation: Enter any invalid ID.
      Expected: An error message will be shown.

    4. Test case: contact delete
      Expected: TagLine will ask for the contact ID.
      Continuation: Press the escape key.
      Expected: A special message will be shown indicating that the command has been aborted.

F.3. Creating a note

  1. Creating a new note

    1. Test case: note create --T TagLine Testing
      Expected: Message on successful creation of note displayed with details of the note. The note created appears in the list of notes in the list pane on the right.

    2. Test case: note create --c This is a creation of a note
      Expected: Similar to previous.

    3. Test case: note create --T TagLine Testing --c This is a creation of a note
      Expected: Similar to previous.

    4. Test case: note create --T TagLine Testing --c This is a creation of a note --t #testing
      Expected: Similar to previous. The note created shown in the note list reflects the tag "#testing".

    5. Test case: note create
      Expected: No note is created. A message asking for the note content is displayed.

      1. Subsequent action: Enter This is a creation of a note. Expected: Message on successful creation of note displayed with details of the note. The note created appears in the list of notes in the list pane on the right.

    6. Test case: note create
      Expected: No note is created. A message asking for the note content is displayed.

      1. Subsequent action: Press enter without any text. Expected: No note is created. An error message is returned with the error details. Command is highlighted red in the command box.

    7. Test case: note create --T This is a title of too many characters, more than 50
      Expected: No note is created. An error message is returned with the error details. Command is highlighted red in the command box.

F.4. Deleting a note

  1. Deleting a note

    1. Test case: note delete 1
      Expected: Note with the id of "1" is deleted from the note list. A message indicating successful note deletion is displayed with the details of deleted note.
      *An assigned note id is static, deleted note id are not reassigned.

    2. Test case: note delete 1 (Ensure note with ID '1' is has been removed)
      Expected: No note is deleted. An error message is displayed with the error details. Command is highlighted red in the command box.

    3. Test case: note delete a
      Expected: Similar to previous.

    4. Test case: note delete X (Where X is an is a number not used as a note id i.e -1)
      Expected: Similar to previous.

F.5. Editing a note

  1. Editing the sample note with id 2 upon launch

    1. Prerequisite: Ensure the sample notes appear in the application and note #2 is an untitled note.

    2. Test case: note edit
      Expected: No note is edited. An error message is displayed with the error details. Command is highlighted red in the command box.

    3. Test case: note edit 2
      Expected: Similar to previous.

    4. Test case: note edit 2 --c (With a space after '--c' as the prefix is '--c ')
      Expected: Similar to previous.

    5. Test case: note edit 2 --c Team Meeting - Wed 4pm
      Expected: Message on successful edit of note displayed with details of the note. The note edited is reflected in the list of notes in the list pane on the right.

    6. Test case: note edit 2 --T CS2103T Meeting
      Expected: Similar to previous.

F.6. Listing and filtering notes

  1. Listing all notes

    1. Prerequisite: Change list pane to list other information. E.g. contact list

    2. Test case: note list
      Expected: Message on successful listing is displayed. All notes are listed in the list pane on the right pane.

  2. Filtering notes by keywords in content

    1. Test case: note list cs
      Expected: Message on successful listing is displayed. Notes containing keyword are listed in the list pane on the right pane.

    2. Test case: note list team bug
      Expected: Message on successful listing is displayed. Notes containing any of the keywords are listed in the list pane on the right pane.

    3. Test case: note list notfound
      Expected: No notes are listed. Message is displayed stating that no notes matching keyword is found.

  3. Filtering notes by tag

    1. Prerequisite: Ensure the sample tags are in the application with tag list

    2. Test case: note list #songs
      Expected: Message on successful listing is displayed. Notes tagged by specified tag are listed in the list pane on the right pane.

    3. Test case: note list @1
      Expected: Similar to previous.

    4. Test case: note list %cs2103t
      Expected: Similar to previous.

    5. Test case: note list @1 %cs2103t
      Expected: Similar to previous. Notes tagged by any of the tags specified are listed in the list pane on the right pane.

    6. Test case: note list #notfound
      Expected: No notes are listed. Message is displayed stating that tag cannot be found.

    7. Test case: note list #songs #notfound
      Expected: Similar to previous.

F.7. Creating a group

  1. Creating a group with no members with all groups listed

    1. Prerequisites: List all groups using the group list command. Multiple groups in the list. Must have Contact of ID 1 in Addressbook (the display showing all available contacts, use contact list), cannot have Contact of ID 0 in Addressbook. Be sure to delete group ao3 before the start of every test case with group delete ao3.

    2. Test case: group create ao3
      Expected: Group ao3 is created with no members. Status message indicates "New group successfully added".

    3. Test case: group create ao3 --i 1
      Expected: Group ao3 is created with with ContactID of 1 listed in the group. Status message indicates "New group successfully added.".

    4. Test case: group create ao3 --i 0 --i 1
      Expected: Group ao3 is created with with ContactID of 1 listed in the group. Status message indicates "New group successfully added. The following members were not found [00000]".

    5. Test case: group create ao3 --i holland
      Expected: Group ao3 is not created. Status message indicates "MemberIds should be numeric and only up to 5 characters long".

    6. Test case: group create archive of our own
      Expected: UI is unchanged. Status message indicates "GroupNames should not contain whitespace, and it should not be blank. Alphanumeric, dash, and underscores are acceptable".

F.8. Adding members to a group

  1. Adding members to a group with the group currently being displayed

    1. Prerequisites: Display contacts in group BTS with group find BTS, if not found create with the group. Must have Contact of ID 1 in Addressbook (the display showing all available contacts, use contact list), cannot have Contact of ID 0 in Addressbook. Group BTS should not contain members with Contact of ID 1 or 0.

    2. Test case: group add BTS --i 1
      Expected: Group BTS is displayed with members including Contact with ContactID of 1. Status message indicates "Attempting to add contact(s) to group".

    3. Test case: group add BTS --i 1
      Note: The purpose of this test is cummulative with the previous to add a contact to a group that already exists in the group
      Expected: Group BTS is displayed with members including Contact with ContactID of 1. Status message indicates "Attempting to add contact(s) to group".

    4. Test case: group add BTS --i 0
      Expected: Group BTS is unchanged. Status message indicates "Attempting to add contact(s) to group. The following members were not found [00000]".

    5. Test case: group add BTS --i holland
      Expected: Group BTS is unchanged. Status message indicates "MemberIds should be numeric and only up to 5 characters long".

    6. Test case: group add archive of our own --i 1
      Expected: UI is unchanged. Status message indicates "GroupNames should not contain whitespace, and it should not be blank. Alphanumeric, dash, and underscores are acceptable".

F.9. Removing members from a group

  1. Removing members from a group with the group currently being displayed

    1. Prerequisites: Display contacts in group BTS with group find BTS, if not found create with the group. Must have Contact of ID 1 in the group (if not there add it in using group add BTS --i 1), cannot have Contact of ID 0 in the group.

    2. Test case: group remove BTS --i 1
      *Expected: Group BTS is displayed with members excluding Contact with ContactID of 1. Status message indicates "Attempting to remove contact(s) from group.".

    3. Test case: group remove BTS --i 0
      *Expected: Group BTS is unchanged. Status message indicates "Attempting to remove contact(s) from group. The following members were not found [00000]".

    4. Test case: group remove BTS --i holland
      Expected: Group BTS is unchanged. Status message indicates "MemberIds should be numeric and only up to 5 characters long".

    5. Test case: group remove bangtan sonyeondan --i 1
      Expected: UI is unchanged. Status message indicates "GroupNames should not contain whitespace, and it should not be blank. Alphanumeric, dash, and underscores are acceptable".

F.10. List all available groups

  1. List all groups.

    1. Prerequisites: Must contain at least one Group

    2. Test case: group list
      Expected: All groups are listed. Status message indicates "Success! Listing groups found."

F.11. Find a specific group

  1. Display all members in a group with group currently not being dislayed.

    1. Prerequisites: Group BTS must exist in the list of group, check with group list, if not found create with the group. Group fanficdotnet does not exist in list of groups. Ensure that group is currently not being displayed using contact list before every test case.

    2. Test case: group find BTS
      *Expected: Group BTS is displayed with members. Status message indicates "Success! Di*splaying the group".

    3. Test case: group find fanficdotnet
      Expected: UI is unchanged. Status message indicates "The group name provided could not be found.".

    4. Test case: group find archive of our own
      Expected: UI is unchanged. Status message indicates "GroupNames should not contain whitespace, and it should not be blank. Alphanumeric, dash, and underscores are acceptable".

F.12. Deleting a group

  1. Deleting a group while viewing members in the group

    1. Prerequisites: View group ao3 (which must already exist otherwise create it) with group find ao3, group ao3 must have Contact of ContactID 1 in it and exist in Addressbook (check with contact list).

    2. Test case: group delete ao3
      Expected: Group ao3 is deleted and all groups remaining groups (if any) are being displayed, contact of contactID 1 still exists in Addressbook. Status message indicates "Success! now showing all groups.".

    3. Test case: group delete archive of our own
      Expected: UI is unchanged. Status message indicates "GroupNames should not contain whitespace, and it should not be blank. Alphanumeric, dash, and underscores are acceptable".

F.13. Tagging a note

  1. Prerequisites: Enter note list command to view all notes. Ensure that note with id 1 exists. Otherwise, take any other note or create one and replace all id 1 with the chosen note’s id in each of the following test cases.

  2. Tagging a note with hash tag.

    1. Prerequisites: Note with id 1 is not tagged with #any topic, #topic1, #topic2, and #topic3.

    2. Test case: note tag 1 --t #any topic
      Expected: Note successfully tagged with #any topic which should be reflected in the response message and in the note view.

    3. Test case: Enter note tag 1 --t #any topic twice
      Expected: The first time we enter the command, it should succeed normally as described in the previous test case whereas the second time we enter the same command, we would get an error message saying that the tag has been tagged to the note.

    4. Test case: note tag 1 --t #topic1 --t #topic2 --t #topic3
      Expected: Note successfully tagged with the three hash tag which should be reflected in the response message and in the note view.

    5. Test case: Enter note tag 1 --t #topic1 --t #topic2 then enter note tag 1 --t #topic1 --t #topic3
      Expected: The first time we enter the command, it should succeed normally as described in the previous test case whereas the second time we enter the next command, we would get an error message saying that there are tags which have been tagged to the note. In this case, it is #topic1

    6. Test case: Enter note tag 1 --t #0123456789012345678901234567890
      Expected: We will get an error message saying that the maximum character for hash tag is 30.

  3. Tagging a note with contact tag

  4. Tagging a note with group tag

  1. Tagging a note with all types of tag

    1. Prerequisites: There is a contact with id 1, otherwise you may choose any other existing contact id. There is a group with name BTS, otherwise you may create one. Note with id 1 is not tagged with #topic, @00001 and %BTS.

    2. Test case: note tag 1 --t #topic --t @00001 --t %BTS
      Expected: Note successfully tagged with the three hash tag which should be reflected in the response message and in the note view.

F.14. Untagging a note

  1. Prerequisites: Enter note list command to view all notes. Ensure that note with id 1 exists. Otherwise, take any other note or create one and replace all id 1 with the chosen note’s id in each of the following test cases.

  2. Untagging a note with hash tag.

    1. Prerequisites: Note with id 1 is tagged with #any topic, #topic1, #topic2, and #topic3.

    2. Test case: note untag 1 --t #any topic
      Expected: Note successfully untagged with #any topic which should be reflected in the response message and in the note view.

    3. Test case: Enter note untag 1 --t #any topic twice
      Expected: The first time we enter the command, it should succeed normally as described in the previous test case whereas the second time we enter the same command, we would get an error message saying that the tag is not tagged to the note.

    4. Test case: note untag 1 --t #topic1 --t #topic2 --t #topic3
      Expected: Note successfully untagged with the three hash tag which should be reflected in the response message and in the note view.

    5. Test case: Enter note untag 1 --t #topic1 --t #topic2 then enter note tag 1 --t #topic1 --t #topic3
      Expected: The first time we enter the command, it should succeed normally as described in the previous test case whereas the second time we enter the next command, we would get an error message saying that there are tags which are not tagged to the note. In this case, it is #topic1

    6. Test case: Enter note untag 1 --t #0123456789012345678901234567890
      Expected: We will get an error message saying that the maximum character for hash tag is 30.

  3. Untagging a note with contact tag

  4. Untagging a note with group tag

  1. Untagging a note with all types of tag

    1. Prerequisites: There is a contact with id 1, otherwise you may choose any other existing contact id. There is a group with name BTS, otherwise you may create one. Note with id 1 is tagged with #topic, @00001 and %BTS.

    2. Test case: note untag 1 --t #topic --t @00001 --t %BTS
      Expected: Note successfully untagged with the three hash tag which should be reflected in the response message and in the note view.