VBook Developer Guide


Acknowledgements


Setting up, getting started

Refer to the guide Setting up and getting started.


Design

Architecture

The Architecture Diagram given above explains the high-level design of the App.

Given below is a quick overview of the 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 :rm -i 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 API interface mentioned in the previous point.

For example, the Logic component defines its API in the Logic.java interface and implements its functionality using the LogicManager.java class which follows the Logic interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.

The sections below give more details of each component.



UI component

The API of this component is specified in Ui.java

Structure of the UI Component

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. Other than the MainWindow, we have created a PasswordPromptDialog that is used to prompt the user for a password when they try to access the app.

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 the UI relies on the Logic to execute commands.
  • depends on some classes in the Model component, as it displays Person object residing in the Model.


Logic component

API : Logic.java

Here's a (partial) class diagram of the Logic component:

The sequence diagram below illustrates the interactions within the Logic component, taking execute(":rm -i 1") API call as an example.

Interactions Inside the Logic Component for the `:remove -i 1` Command

Note: The lifeline for DeleteCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.

How the Logic component works:

  1. When Logic is called upon to execute a command, it is passed to an AddressBookParser object which in turn creates a parser that matches the command (e.g., DeleteCommandParser) and uses it to parse the command.
  2. This results in a Command object (more precisely, an object of one of its subclasses e.g., DeleteCommand) which is executed by the LogicManager.
  3. The command can communicate with the Model when it is executed (e.g. to remove a person).
    It can take several interactions (between the command object and the Model) to achieve the result.
  4. The result of the command execution is encapsulated as a CommandResult object which is returned back from Logic.

Here are the other classes in Logic (omitted from the class diagram above) that are used for parsing a user command:

How the parsing works:

  • When called upon to parse a user command, the AddressBookParser class creates an XYZCommandParser (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 a XYZCommand object (e.g., AddCommand) which the AddressBookParser returns back as a Command object.
  • All XYZCommandParser classes (e.g., AddCommandParser, DeleteCommandParser, ...) inherit from the Parser interface so that they can be treated similarly where possible e.g, during testing.


Model component

API : Model.java

The Model component,

  • stores the address book data i.e., all Person objects (which are contained in a UniquePersonList object).
  • stores the currently 'selected' Person objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiable ObservableList<Person> that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
  • stores a UserPref object that represents the user’s preferences. This is exposed to the outside as a ReadOnlyUserPref objects.
  • does not depend on any of the other three components (as the Model represents data entities of the domain, they should make sense on their own without depending on other components)

Note: An alternative (arguably, a more OOP) model is given below. It has a Tag list in the AddressBook, which Person references. This allows AddressBook to only require one Tag object per unique tag, instead of each Person needing their own Tag objects.



Storage component

API : Storage.java

The Storage component

  • can save both address book data and user preference data in JSON format, and read them back into corresponding objects.
  • inherits from both AddressBookStorage and UserPrefStorage, 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 the Storage component's job is to save/retrieve objects that belong to the Model)


Common classes

Classes used by multiple components are in the seedu.address.commons package.


Implementation

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

Undo/redo feature

Implementation

The undo/redo mechanism is facilitated by VersionedAddressBook. It extends AddressBook with an undo/redo history, stored internally as an addressBookStateList and currentStatePointer. Additionally, it implements the following operations:

  • VersionedAddressBook#commit() — Saves the current address book state in its history.
  • VersionedAddressBook#undo() — Restores the previous address book state from its history.
  • VersionedAddressBook#redo() — Restores a previously undone address book state from its history.

These operations are exposed in the Model interface as Model#commitAddressBook(), Model#undoAddressBook() and Model#redoAddressBook() respectively.

Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.

Step 1. The user launches the application for the first time. The VersionedAddressBook will be initialized with the initial address book state, and the currentStatePointer pointing to that single address book state.

UndoRedoState0

Step 2. The user executes :rm -i 5 command to remove the 5th person in the address book. The :rm command calls Model#commitAddressBook(), causing the modified state of the address book after the :rm -i 5 command executes to be saved in the addressBookStateList, and the currentStatePointer is shifted to the newly inserted address book state.

UndoRedoState1

Step 3. The user executes :add -n David to add a new person. The :add command also calls Model#commitAddressBook(), causing another modified address book state to be saved into the addressBookStateList.

UndoRedoState2

Note: If a command fails its execution, it will not call Model#commitAddressBook(), so the address book state will not be saved into the addressBookStateList.

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#undoAddressBook(), which will shift the currentStatePointer once to the left, pointing it to the previous address book state, and restores the address book to that state.

UndoRedoState3

Note: If the currentStatePointer is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The :undo command uses Model#canUndoAddressBook() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo.

The following sequence diagram shows how an undo operation goes through the Logic component:

UndoSequenceDiagram-Logic

Note: The lifeline for UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

Similarly, how an undo operation goes through the Model component is shown below:

UndoSequenceDiagram-Model

The :redo command does the opposite — it calls Model#redoAddressBook(), which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the address book to that state.

Note: If the currentStatePointer is at index addressBookStateList.size() - 1, pointing to the latest address book state, then there are no undone AddressBook states to restore. The :redo command uses Model#canRedoAddressBook() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.

Step 5. The user then decides to execute the command :list. Commands that do not modify the address book, such as :list, will usually not call Model#commitAddressBook(), Model#undoAddressBook() or Model#redoAddressBook(). Thus, the addressBookStateList remains unchanged.

UndoRedoState4

Step 6. The user executes :clear, which calls Model#commitAddressBook(). Since the currentStatePointer is not pointing at the end of the addressBookStateList, all address book 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.

UndoRedoState5

The following activity diagram summarizes what happens when a user executes a new command:

Design considerations:

Aspect: How undo & redo executes:

  • Alternative 1 (current choice): Saves the entire address book.

    • 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 :remove, just save the person being removed).
    • Cons: We must ensure that the implementation of each individual command is correct.


Remove Feature

Implementation

The remove feature allows removal of a person from the address book. The user can remove a person by specifying the index of the person to remove. The user can also remove multiple persons by specifying multiple indexes of the persons to remove.

How the remove feature works: Format: :remove -i INDEX1, INDEX2...

The DeleteCommand class has a method DeleteCommand#execute(Model model) that calls the ModelManager.
The ModelManager class has a method ModelManager#deletePerson(Person target) that calls the AddressBook class.
The AddressBook class has a method AddressBook#removePerson(Person key) that removes a person from the UniquePersonList field persons in the AddressBook class.

The following class diagram shows the relationships between the classes involved in the remove feature:

The following sequence diagram shows how a remove operation goes through the Logic component:

Interactions Inside the Logic Component for the `:remove -i 1, 2, 3` Command

Similarly, how a remove operation goes through the Model component is shown below:

DeleteSequenceDiagram-Model

The following activity diagram summarizes what happens when a user executes a new command:

Design considerations:

Aspect: How remove executes:

  • Alternative 1 (current choice): Removes the person by index.

    • Pros: Easy to implement.
    • Cons: Requires the user to know the index of the person to remove.
  • Alternative 2: Removes the person by name.

    • Pros: More user-friendly as the user can specify the name.
    • Cons: Requires additional logic to handle duplicate names.


Add Feature

Implementation

The add feature allows a person to be added to the address book. It accepts the parameters name, phone, location, email, remark and tag. The name parameter is compulsory, while the rest are optional. Multiple tags are accepted for one person.

The add feature follows the remove feature in that AddCommand calls ModelManager, which calls the AddressBook class, which adds a person to the UniquePersonList class. Therefore, the class and activity diagram will be omitted for conciseness.

The following sequence diagram shows how an add operation goes through the Logic component:

Interactions Inside the Logic Component for the `:add` Command

The parsing process is described in detail in this sequence diagram:

Interactions Inside the Logic Component for parsing

Similar to the remove feature, how an add operation goes through the Model component is shown below:

AddSequenceDiagram-Model


Export Feature

Implementation

The ExportCommand class is responsible for exporting address book data to a user-specified location in JSON format. It provides flexibility in its usage by allowing a destination to be selected via a file chooser or by setting a predetermined destination file, which is particularly useful for testing purposes. The data to be exported is encrypted, and the ExportCommand handles decryption, export location selection, and file I/O operations. The following outlines its components and workflow.

The ExportCommand class facilitates this export functionality and manages file I/O operations in a structured, asynchronous workflow.

Constructor Variants:

  • ExportCommand(): The default constructor for regular use, opening a file chooser dialog to select the export destination.
  • ExportCommand(File destinationFile, File sourceFile, String keyPath): An overloaded constructor that allows specifying a destination file and encryption key path directly, which is particularly useful for testing.

Attributes:

  • destinationFile: The file chosen or set as the target for the export.
  • sourceFile: A temporary file that holds the JSON data to be exported.
  • keyPath: The path to the decryption key required for decrypting the address book data.

Given below is an example usage scenario and how the export process behaves at each step.

Step 1. The user initiates an export by executing :export. The ExportCommand will attempt to decrypt the data before exporting it.

Step 2. The execute(Model model) method reads encrypted data from the sourceFile, decrypting it with EncryptionManager.decrypt() using the provided keyPath. The decrypted data is written to a temporary file vbook.json.

Step 3. If destinationFile is not set, ExportCommand invokes chooseExportLocation(Stage stage), which displays a file chooser dialog for the user to select an export location. If the user cancels this dialog, the export process is aborted with an error message.

Step 4. The performExport(File sourceFile, File destinationFile) method copies the decrypted data to the specified destinationFile, using Files.copy() with StandardCopyOption.REPLACE_EXISTING to overwrite any existing file. The temporary file is then deleted.

Note: The performExport method is asynchronous, leveraging CompletableFuture to manage successful completion or error handling, ensuring smooth performance without blocking the main application thread.

The following sequence diagram explains how the export operation works:

ExportSequenceDiagram

Design Considerations:

Aspect: Export Execution and Destination Selection

Alternative 1 (current choice): Use a file chooser dialog to allow the user to select the export location.

  • Pros: User-friendly, provides flexibility in specifying the export location.
  • Cons: Requires user interaction, which may be cumbersome for repeated exports.

Alternative 2: Set a default export location without user input.

  • Pros: Streamlined and faster for frequent exports.
  • Cons: Less flexible, as it may overwrite existing files without warning.


Encryption Feature

Implementation

The encryption mechanism is managed by the EncryptionManager class. This component is responsible for securely encrypting and decrypting sensitive data using the AES (Advanced Encryption Standard) algorithm. The EncryptionManager performs encryption and decryption with a secret key, which is securely loaded and stored using Java's Key Store API. The implementation details are as follows:

Methods Overview

  1. encrypt(String data, String keyPath):
    • Encrypts plain text data using the AES algorithm.
    • Takes the path to the key store as an argument (defaulting to vbook.jks if not provided).
    • Returns a byte array containing the encrypted data.
  2. decrypt(byte[] data, String keyPath):
    • Decrypts the given encrypted byte array back into plain text.
    • Also takes the path to the key store as an argument (defaulting to vbook.jks if not provided).
    • Returns the decrypted string.
  3. generateKey(String keyPath):
    • Generates a new AES secret key and stores it in a local key store file.
    • If the key store already exists, it does not overwrite it but notifies that the alias already exists.
    • Saves the generated key under the alias vbook-encryption.
  4. getKey(String keyPath):
    • Retrieves the AES secret key from the specified key store.
    • If the key store does not exist, it calls generateKey() to create one.
    • Returns the retrieved secret key.

Usage in Application

  • The EncryptionManager is used in the ExportCommand to decrypt data before exporting it and in JsonAddressBookStorage to encrypt data before writing it to a file.

Usage

// Encryption
String jsonData = JsonUtil.toJsonString(new JsonSerializableAddressBook(addressBook));
byte[] encryptedData = EncryptionManager.encrypt(jsonData, this.keyPath);

// Decryption
jsonData = EncryptionManager.decrypt(encryptedData, this.keyPath);

Example Usage Scenario

Step 1. The user initially adds a new contact in the address book. The EncryptionManager uses the AES algorithm and the secret key to encrypt the information before saving it.

Step 2. The encrypted data is stored securely. When needed, the user can request to decrypt the information.

Step 3. The EncryptionManager decrypts the data using the same AES algorithm and the secret key, ensuring that the information is securely handled at all times.

Note: If an error occurs during encryption or decryption (e.g., if the secret key is invalid or corrupted), the EncryptionManager will handle the error gracefully and return an appropriate error message.

The following sequence diagram shows how the encryption process works:

EncryptionSequenceDiagram

Note: The sequence diagram simplifies the encryption and decryption processes to focus on the main interactions between components.

Design Considerations for Encryption Feature

Core Limitation
  1. Risk of Local KeyStore Exposure:

    • If a hacker gains access to the JKS file containing the encryption keys, they could decrypt sensitive data. This represents a fundamental limitation of local storage, as the security of the keys relies on the local file system's security.
  2. Alternative Storage Locations:

    • Storing the JKS file in the JAVA_HOME/lib/security/cacerts directory is an option, but this depends on the user’s configuration and permissions. Users might not have their JAVA_HOME path set correctly, which can lead to access issues.
  3. Security Through Obscurity:

    • While relying on obscurity—such as using less common paths for the JKS file—can add a layer of security, it should not be the sole defense mechanism. Obscurity alone does not adequately protect against determined attacks.
Compromise Between Security and User Experience
  • User Experience Considerations:
    • As a local application, VBook prioritizes convenience, which may lead users to prefer simpler access to their data over maximum security. Finding a balance between security and usability is crucial.
    • Given that VBook handles contact data, adequate security measures must be in place while ensuring users are not overwhelmed by complex key management.


Password Management Feature

Implementation

The password management mechanism is handled by the PasswordManager class. This component is responsible for securely hashing and verifying user passwords using the PBKDF2 (Password-Based Key Derivation Function 2) algorithm with HMAC-SHA1. The PasswordManager ensures that passwords are safely stored in a local text file, employing a salting strategy to enhance security. The implementation details are as follows:

Methods Overview

  1. readPassword(String path):
    • Reads the stored hashed password from the specified file (defaulting to password.txt).
    • Returns the hashed password as a string or null if the file does not exist.
  2. savePassword(String password, String path):
    • Accepts a plaintext password, generates a salt, hashes the password using PBKDF2, and saves the resulting hash and salt to the specified file (default: password.txt).
    • Creates the file if it does not already exist.
  3. isPasswordCorrect(String inputPassword, String path):
    • Compares the input plaintext password against the stored hashed password.
    • Reads the stored hash and salt, hashes the input password, and returns true if they match or false otherwise.
  4. hashPassword(String password, byte[] salt):
    • Hashes the provided password using the specified salt with PBKDF2 and returns a string containing both the salt and hash encoded in Base64.
  5. generateSalt():
    • Generates a secure random salt using SecureRandom for use in password hashing.

Usage in Application

  • The PasswordManager is invoked during application startup to check for an existing password file.
    • If the file is absent, the user is prompted to create a new password.
    • On subsequent starts, the user must input their original password for verification before proceeding.

Usage

// Saving a new password on initial startup
if (PasswordManager.readPassword(null) == null) {
        String newPassword = scanner.nextLine();
        PasswordManager.savePassword(newPassword, null);
}

// Verifying the password on subsequent starts
String inputPassword = scanner.nextLine();
if (PasswordManager.isPasswordCorrect(inputPassword, null)) {
        // Handle correct password
} else {
        // Handle wrong password
}

Example Usage Scenario

Step 1. On the initial startup of the application, the PasswordManager checks for the existence of the password.txt file. If the file is not found, the user is prompted to enter a new password.

Step 2. The entered password is hashed and saved securely, along with a generated salt.

Step 3. On subsequent startups, the user is prompted to input their original password. The PasswordManager verifies the input against the stored hash and salt, granting access if the password matches.

Note: If an error occurs during password hashing or verification (e.g., if the stored format is incorrect), the PasswordManager will handle the error gracefully and provide an appropriate error message.

The following activity diagram illustrates how the password management process operates:

PasswordManagementActivityDiagram

Note: The activity diagram simplifies the password management processes to highlight the flow for user authentication.

Design Considerations for Password Management Feature

Core Limitations

  • Risk of Local File Exposure:
    • If an attacker gains access to the password.txt file, they could potentially compromise user accounts. This poses a fundamental risk of local file storage, as the security of the password relies on the protection of the local file system.
    • For future implementation, we may consider setting a retry limit.

Documentation, logging, testing, configuration, dev-ops


Appendix: Requirements

Product scope

Target user profile:

  • has a need to manage a significant number of contacts
  • prefer desktop apps over other types
  • can type fast
  • value privacy and self-hosting
  • prefers typing to mouse interactions
  • is reasonably comfortable using CLI apps

Value proposition: manage contacts faster than a typical mouse/GUI driven app



User stories

Implemented user stories

Listed below are user stories for features that have already been implemented in the current version of the application.

Priority As a …​ I want to …​ So that I can…​
* * * first-time user add contacts to my contact book store my contacts
* * * user add contacts to my contact book using only partial details store contacts that I may not have full information about
* * * user see all my contacts see and manage my contacts
* * * user remove contacts remove contacts I do not need anymore
* * user edit contact details correct errors I made when adding a contact
* * first-time user see sample contacts explore the app's features without adding real data
* * first-time user clear sample data and start fresh input my real contacts securely
* * first-time user view a tutorial on the app learn how to use the app quickly
* * first-time user quickly access a CLI command cheat sheet learn essential commands without slowing down
* * new user secure my contact data with a password feel confident that my client information is protected
* * new user choose to encrypt the contact data that is stored ensure my client information cannot be accessed from the storage location
* * new and inexperienced user undo actions like deletions CTRL+Z recover data quickly if I make a mistake
* * new and inexperienced user redo an action that was undone with undo CTRL+SHIFT+Z reapply an action if I realise I need it after undoing it
* * new and inexperienced user be prompted with why an invalid command is invalid receive immediate and specific feedback if I type an invalid command
* * returning user search contacts using partial details (name, email) find relevant contacts faster
* * user whose contacts span multiple projects tag contacts with a project or organisation name organise my contacts better
* * user filter contacts by project or organisation quickly locate clients related to specific tasks
* * experienced user use keyboard shortcuts to bring up the CLI execute commands faster
* * experienced user use keyboard shortcuts to manage contacts manage my contacts faster
* user multi-select contacts for deletion manage my list more efficiently
* power user export my contact list to JSON format use it in other tools or projects

Future user stories

Listed below are user stories that represent features that we have not implemented yet, but plan to in the future.

Priority As a …​ I want to …​ So that I can…​
* * new user import contacts from a CSV or another format (e.g. Apple's .vcf) use more data formats to quickly populate my address book
* * new user open up a settings menu configure keyboard shortcuts
* returning user customise the app's theme make my user experience more personalised as I use the app more
* frequent user navigate command history with arrow keys quickly fill the search field and modify and execute previous commands
* programmer configure my shortcuts to be similar to my IDE shortcuts switch between my IDE and VBook more effectively
* frequent user pin important contacts have them appear at the top of my list for easy access
* long time user archive old contacts clean up my address book without having to remove contacts


Use cases

(For all use cases below, the System is the VBook and the Actor is the user, unless specified otherwise)

Use case: UC01 - Add a person

MSS

  1. User requests to add a specific person to VBook.

  2. VBook adds the person.

    Use case ends.

Extensions

  • 1a. One or more of the inputted parameters, or the command, is invalid.

    • 1a1. VBook shows an error message.

      Use case ends.

  • 1b. The name of the requested person is the same as an existing person in the address book.

    • 1b1. VBook shows an error message.

      Use case ends.

Use case: UC02 - Edit a person's details

MSS

  1. User requests to list persons.

  2. VBook shows a list of persons.

  3. User requests to edit a specific person's details in the list.

  4. VBook edits the person's details.

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. VBook shows an error message.

      Use case resumes at step 2.

  • 3b. One or more of the inputted parameters, or the command, is invalid.

    • 3b1. VBook shows an error message.

      Use case resumes at step 2.

  • 3c. The name of the requested person is the same as an existing person in the address book.

    • 3c1. VBook shows an error message.

      Use case resumes at step 2.

Use case: UC03 - Remove a person

MSS

  1. User requests to list persons.

  2. VBook shows a list of persons.

  3. User requests to remove a specific person in the list.

  4. VBook removes the person.

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. VBook shows an error message.

      Use case resumes at step 2.

Use case: UC04 - Find persons matching criteria

MSS

  1. User requests to find persons in the list that match provided search criteria.

  2. VBook displays a list of persons that match the criteria.

    Use case ends.

Extensions

  • 1a. No persons match the search criteria.

    • 1a1. VBook displays a message indicating that no persons were found.

      Use case ends.

  • 1b. The command entered is invalid.

    • 1b1. VBook shows an error message.

      Use case ends.

Use case: UC05 - Export data

MSS

  1. User requests to export data.

  2. VBook opens the file explorer window.

  3. User chooses the destination and name of the exported data.

  4. VBook exports the data to the destination folder in JSON format.

    Use case ends.

Extensions

  • 2a. User closes the file explorer window without selecting a destination.

    Use case ends.

  • 3a. The name of the exported data clashes with an existing name in the same file destination.

    • 3a1. The file explorer displays an error message.

      Use case returns to step 2.

  • 3b. The user enters an invalid name.

    • 3b1. The file explorer displays an error message.

      Use case returns to step 2.

Use case: UC06 - Enter password

Preconditions: User has already set a password previously.

MSS

  1. User starts the app.

  2. VBook displays a window prompting the user to enter a password.

  3. User enters the correct password.

  4. VBook closes the password prompt window and opens the main app window.

    Use case ends.

Extensions

  • 2a. User enters the wrong password.
    • 2a1. VBook displays an error message.

      Use case returns to step 2.

Use case: UC07 - Undo command

Preconditions: At least one command that changes the address book has been entered.

MSS

  1. User requests to undo the last command.

  2. VBook undoes the last change to the data.

    Use case ends.

Extensions

  • 1a. There is no previously done add/edit/remove command found.

    • 1a1. VBook displays a message that there are no more commands to undo.

      Use case ends.

  • 1b. User has undone more commands than the maximum amount allowed.

    • 1b1. VBook displays a message that there are no more commands to undo.

      Use case ends.

Use case: UC08 - Redo command

MSS

  1. User requests to redo the last undone command.

  2. VBook redoes the last undone command.

    Use case ends.

Extensions

  • 1a. There is no previously undone command.

    • 1a1. VBook displays a message that there are no more commands to redo.

      Use case ends.

  • 1b. User has redone more commands than the maximum amount allowed.

    • 1b1. VBook displays a message that there are no more commands to redo.

      Use case ends.



Non-Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 17 or above installed.
  2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
  3. A user with above average typing speed of 60 words per minute for regular English text should be able to accomplish most of the tasks faster using commands than using the mouse.
  4. Should work without any Internet connection.
  5. Data is stored in an encrypted file that can be edited by exporting a decrypted version of the file from the GUI.
  6. Commands should execute within 0.5 seconds under normal usage conditions.
  7. Should use strong encryption standards for data storage and secure export.
  8. Should support keyboard-only navigation for users with limited mouse access.


Glossary

  • Mainstream OS: Windows, Linux, Unix, MacOS
  • Private contact detail: A contact detail that is not meant to be shared with others

Appendix: Known Issues

Failing tests on Windows when run more than once

  1. In EncryptionManagerTest.java, temporary files are created before and deleted after each test for the Encryption and Export tests. Without this cleanup, subsequent runs of ./gradlew test will fail.
  2. However, on Windows machine, the test will throw a java.nio.file.FileSystemException exception when attempting to delete the files due to the difference in how Windows processes manage files. (StackOverflow issue)
  3. A current workaround is to check if the OS is Windows, and skip the file deletion on cleanup. This has been implemented in our tests.
  4. However, before starting subsequent tests, you will need to manually delete the temporary *test.key files and test folder created, both in the root directory of VBook.jar.
  5. This issue does not exist on Mac and Linux machines.

Appendix: Planned Enhancements

Team Size: 4

  1. Make result display text selectable: The current result display window can display text but users cannot select text to copy and paste into the command box. We plan to make the window selectable so users can copy and paste in example commands to try out.
  2. Expanded contact information: The current contact list wraps around long text so the user can see the information. However, this makes the list uneven and very long remarks can make one contact unreasonably long. We plan to create a contact page per contact that contains full information about every contact, while keeping a truncated view for the main window.
  3. Improved input validation for tags: Currently, our tags have no restriction on size, which cause them to exceed the UI space. We plan to add a maximum length for the tags to be 50 characters, as the longest English word is 45 characters.
  4. Add input validation for find command: Currently, the find command does not check if parameters like name / phone number etc. are valid before executing the find command. We plan to add input validation for the find command so that searching with an invalid parameter will fail with an error.
  5. Add input validation for location: Currently, the location field takes in any values. However, this is not specific and the user can enter in values that are clearly not locations, such as 0000000. Hence, we plan to split the location field into three distinct fields: postal code, street name, and block number. Input validation will ensure specificity, restrict ambiguity, and allow only valid special characters relevant to location data.
  6. Add input validation for phone numbers: The current phone number field does not have a strict limit on the kind of values it accepts. We plan to use regular expressions to validate phone number inputs.
  7. Address integer overflow issues for index: Currently, when a number larger than the maximum value for the data type integer is entered into our index field, the error message displays: Invalid command format! instead of The person index provided is invalid. This is likely due to an integer overflow error throwing a different exception than expected. We plan to fix this by adding a check for overflow and returning the appropriate error message afterwards.
  8. Tampering with encrypted vbook.json data manually: Doing so would result in an exception if the program attempts to decrypt the data (for example, on :export command). However, this exception is not handled gracefully on the Command box and instead, only shown in the console. We plan to fix this by adding a Command Exception on decryption error to inform the user that the data has been tampered with.

Appendix: Instructions for manual testing

Given below are instructions to test the app manually.

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

Launch

  1. Initial launch:

    1. Instructions:

      • Download the .jar file and copy it into an empty folder.
      • Open a command terminal, change the directory (cd) to the folder where you saved the VBook.jar file, and use the following command to run the application:
      cd path/to/vbook
      java -jar VBook.jar
      
  2. Saving window preferences:

    1. Instructions:
      • Resize the window to an optimum size and move it to a different location on the screen.
      • Close the window.
    2. Re-launch the app:
      • Re-open the application by using the same command above.
    3. Expected outcome: The most recent window size and location should be retained.

Opening the Help Window

  1. Basic help command:

    1. Test case: :help
    2. Expected outcome: A help window should open, displaying a list of available commands with their syntax and descriptions.

Adding a person

  1. Adding a new person with all details:

    1. Test case: :add -n John Doe -p 91234567 -e johndoe@example.com -l 123, Clementi Rd, #01-01, S123456 -t Friend -r Met at a wedding
    2. Expected outcome: The new contact "John Doe" with all specified details should be added to the list, with a confirmation message displaying the details of the added contact.
  2. Adding a person with only mandatory fields:

    1. Test case: :add -n Jane Smith
    2. Expected outcome: "Jane Smith" should be added to the list with only the name field populated, and all other optional fields should be left blank.

Listing all persons

  1. Basic list command to show all contacts:
    1. Prerequisites: Ensure there are multiple contacts in the list.
    2. Test case: :list
    3. Expected outcome: The app should display all contacts in the list, ordered by the date that each contact was added (older contacts on top).

Editing a person

  1. Editing a person’s details with valid inputs:

    1. Prerequisites: Ensure a contact list with multiple persons, and select a person to edit.
    2. Test case: :edit 1 -n Jonathan Doe -p 92345678 -e jonathan.doe@example.com -l 456, Orchard Rd, #02-02, S654321 -t Family
    3. Expected outcome: The details of the first person in the list should be updated with the provided name, phone, email, address, and tag. A confirmation message should display the updated details.
  2. Editing only a specific field:

    1. Test case: :edit 1 -p 91234567
    2. Expected outcome: The phone number of the first person should be updated to "91234567," with all other fields remaining unchanged. A confirmation message should display the new contact information.

Export

  1. Successful export to a chosen location:

    1. Prerequisites: Ensure the contact list has multiple persons added.
    2. Test case: :export
    3. Steps: Trigger the export command. When the file explorer opens, navigate to the desired location, set a file name of "contacts.json", and confirm.
    4. Expected outcome: A JSON file named contacts.json is created in the selected location, containing the contact data. A confirmation message should display the successful export.
  2. Export with existing file name in destination:

    1. Prerequisites: Ensure an existing file named contacts.json is present in the selected export location.
    2. Test case: :export
    3. Steps: Trigger the export command, select the same destination and enter the name contacts.json for the export.
    4. Expected outcome: A prompt should appear asking for confirmation to overwrite the existing file. Confirming should replace the existing file with the new export, while canceling should leave the file unchanged.

Undo

  1. Undo a recent add command:

    1. Test case: :add -n John Doe -p 91234567 -e johndoe@example.com followed by :undo
    2. Expected outcome: The new contact "John Doe" should be removed from the list, and a confirmation message should display indicating the undo operation was successful.
  2. Undo a recent edit command:

    1. Prerequisites: Ensure at least one contact is in the list.
    2. Test case: :edit 1 -p 92345678 followed by :undo
    3. Expected outcome: The phone number of the contact at index 1 should revert to its previous value, with a message confirming the undo operation.

Redo

  1. Redo a recent undo of an add command:

    1. Prerequisites: Add a new contact and then undo the action.
    2. Test case: :add -n John Doe -p 91234567 -e johndoe@example.com, then :undo, followed by :redo
    3. Expected outcome: The contact "John Doe" should reappear in the list after the :redo command, with a confirmation message indicating the redo was successful.
  2. Redo a recent undo of an edit command:

    1. Prerequisites: Edit a contact’s details and then undo the edit.
    2. Test case: :edit 1 -p 92345678, then :undo, followed by :redo
    3. Expected outcome: The phone number of the contact at index 1 should be updated to "92345678" again after the :redo command, with a confirmation message.

Find

  1. Find by full name:

    1. Prerequisites: Ensure the contact list contains a person named "John Doe."
    2. Test case: :find -n John Doe
    3. Expected outcome: The contact "John Doe" should be displayed in the search results, with a confirmation message indicating the number of matches found.
  2. Find by partial name:

    1. Prerequisites: Ensure the contact list has multiple entries with names containing "John" (e.g., "John Doe," "Johnny Appleseed").
    2. Test case: :find -n John
    3. Expected outcome: All contacts with "John" in their names should be displayed in the search results, showing all relevant entries.

Removing a person

  1. Removing the first person in the list:

    1. Prerequisites: List all persons using the :list command. Multiple persons are in the list.
    2. Test case: :remove -i 1
    3. Expected outcome: The first contact is removed from the list. A confirmation message displays the details of the removed contact.
  2. Attempting to remove a person with an invalid index (zero):

    1. Prerequisites: Ensure the contact list has entries.
    2. Test case: :remove -i 0
    3. Expected outcome: No person is removed. An error message displays, indicating an invalid command format.

Clearing all Persons

  1. Basic clear command to remove all contacts:

    1. Prerequisites: Ensure there are multiple contacts in the list.
    2. Test case: :clear
    3. Expected outcome: All contacts should be removed from the list, and a confirmation message should appear, indicating that the contact list has been cleared.
  2. Clear command when the contact list is already empty:

    1. Prerequisites: Ensure the contact list is empty.
    2. Test case: :clear
    3. Expected outcome: No errors should occur, the display should remain unchanged, and a confirmation message should appear, indicating that the contact list has been cleared.

Quit Command

  1. Basic quit command to exit the application:

    1. Prerequisites: Ensure the application is running.
    2. Test case: :quit
    3. Expected outcome: The application should close.
  2. Quit command immediately after startup:

    1. Prerequisites: Open the application with no modifications made to the contact list.
    2. Test case: :quit
    3. Expected outcome: The application should close immediately without any prompt.

Saving data

  1. Dealing with missing data files

    1. Prerequisites: Ensure there is an existing data/vbook.json file in the home folder of the .jar file.
    2. Test case: Delete the data/vbook.json file, close the address book, and then open it again.
    3. Expected outcome: The data should be replaced with the sample data that appears when the app is first opened.

Password Prompt

  1. Initial fresh startup - Cancelling prompt:

    1. Prerequisites: Ensure there is no password.txt file in the root directory of the project.
    2. Test case: Start the program without a password.txt file. Press "Cancel" or close the password prompt using the cross button.
    3. Expected outcome: The program exits gracefully. No password.txt file is created.
  2. Initial fresh startup - Creating a new password

    1. Prerequisites: Ensure there is no password.txt file in the root directory of the project.
    2. Action: Type a new password in the password prompt and confirm.
    3. Expected outcome: The main GUI is displayed. A new password.txt file is created in the root directory containing the salted hash of the entered password.

Encryption Manager

  1. Initial fresh startup

    1. Prerequisites: Ensure there is no data/vbook.json file or vbook.jks file in the root directory.

    2. Test case: Start the program and invoke a GUI command (e.g., :add).

    3. Action: Add a new entry using the GUI command.

    4. Expected outcome:

      • A new vbook.jks file is created in the root directory, containing the encryption key.
      • A data/vbook.json file is created. Attempting to open the file from the file explorer shows unreadable binary data.
      • The GUI displays the data correctly.
  2. Subsequent loads with existing vbook.jks and encrypted vbook.json

    1. Prerequisites: Ensure a valid vbook.jks file exists in the root directory and an encrypted data/vbook.json file is present.

    2. Test case: Start the program.

    3. Action: Launch the program.

    4. Expected outcome:

      • The program reads the data/vbook.json file using the vbook.jks encryption key.
      • The GUI displays the data correctly.
      • Any GUI commands modify the encrypted data/vbook.json file, ensuring the file remains binary and unreadable outside the program.

Appendix: Effort

This project was challenging to implement, especially with our ambition of making it as keyboard-friendly as possible. Implementing keyboard shortcuts, as well as keyboard-friendly UI was not easy.

While we didn't expand the fields much from AB3, certain features like the Export feature took us a lot of time to debug, especially given known bugs with the Windows system (see failing tests on Windows when run more than once). The password prompt with proper salted hashing and encryption features with standard encryption algorithms also add an additional layer of security.