Skip to content

Latest commit

 

History

History
1903 lines (1348 loc) · 112 KB

DeveloperGuide.adoc

File metadata and controls

1903 lines (1348 loc) · 112 KB

TeethHub - Developer Guide

By: Team W17-2      Since: 14 Mar 2019      Licence: MIT

1. Setting up

1.1. Prerequisites

  1. JDK 9 or later

    ⚠️
    JDK 10 on Windows will fail to run tests in headless mode due to a JavaFX bug. Windows developers are highly recommended to use JDK 9.
  2. IntelliJ IDE

    ℹ️
    IntelliJ by default has Gradle and JavaFx plugins installed.
    Do not disable them. If you have disabled them, go to File > Settings > Plugins to re-enable them.

1.2. Setting up the project in your computer

  1. Fork this repository, and clone the fork to your computer

  2. Open IntelliJ (if you are not in the welcome screen, click File > Close Project to close the existing project dialog first)

  3. Set up the correct JDK version for Gradle

    1. Click Configure > Project Defaults > Project Structure

    2. Click New…​ and find the directory of the JDK

  4. Click Import Project

  5. Locate the build.gradle file and select it. Click OK

  6. Click Open as Project

  7. Click OK to accept the default settings

  8. Open a console and run the command gradlew processResources (Mac/Linux: ./gradlew processResources). It should finish with the BUILD SUCCESSFUL message.
    This will generate all resources required by the application and tests.

  9. Open MainWindow.java and check for any code errors

    1. Due to an ongoing issue with some of the newer versions of IntelliJ, code errors may be detected even if the project can be built and run successfully

    2. To resolve this, place your cursor over any of the code section highlighted in red. Press ALT+ENTER, and select Add '--add-modules=…​' to module compiler options for each error

  10. Repeat this for the test folder as well (e.g. check HelpWindowTest.java for code errors, and if so, resolve it the same way)

1.3. Verifying the setup

  1. Run the seedu.address.MainApp and try a few commands

  2. Run the tests to ensure they all pass.

1.4. Configurations to do before writing code

1.4.1. Configuring the coding style

This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,

  1. Go to File > Settings…​ (Windows/Linux), or IntelliJ IDEA > Preferences…​ (macOS)

  2. Select Editor > Code Style > Java

  3. Click on the Imports tab to set the order

    • For Class count to use import with '*' and Names count to use static import with '*': Set to 999 to prevent IntelliJ from contracting the import statements

    • For Import Layout: The order is import static all other imports, import java.*, import javax.*, import org.*, import com.*, import all other imports. Add a <blank line> between each import

Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.

1.4.2. Updating documentation to match your fork

After forking the repository, the documentation will still have the SE-EDU branding and refer to the se-edu/addressbook-level4 repository.

If you plan to develop this fork as a separate product (i.e. instead of contributing to se-edu/addressbook-level4), you should do the following:

  1. Configure the site-wide documentation settings in build.gradle, such as the site-name, to suit your own project.

  2. Replace the URL in the attribute repoURL in DeveloperGuide.adoc and UserGuide.adoc with the URL of your fork.

1.4.3. Setting up CI

Set up Travis to perform Continuous Integration (CI) for your fork. See UsingTravis.adoc to learn how to set it up.

After setting up Travis, you can optionally set up coverage reporting for your team fork (see UsingCoveralls.adoc).

ℹ️
Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your personal fork.

Optionally, you can set up AppVeyor as a second CI (see UsingAppVeyor.adoc).

ℹ️
Having both Travis and AppVeyor ensures your App works on both Unix-based platforms and Windows-based platforms (Travis is Unix-based and AppVeyor is Windows-based)

1.4.4. Getting started with coding

When you are ready to start coding,

  1. Get some sense of the overall design by reading Section 2.1, “Architecture”.

  2. Take a look at Appendix A, Suggested Programming Tasks to Get Started.

2. Design

2.1. Architecture

Architecture
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 .pptx files used to create diagrams in this document can be found in the diagrams folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose Save as picture.

Main has only one class called 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.

SDforDeletePerson
Figure 3. Component interactions for 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 that is made up of parts e.g.CommandBox, ResultDisplay, PersonListPanel, StatusBarFooter, BrowserPanel etc. All these, including the MainWindow, 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.

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

2.3. Logic component

LogicClassDiagram
Figure 5. Structure of the Logic Component

API : Logic.java

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

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

  3. The command execution can affect the Model (e.g. adding a person).

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

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

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

DeletePersonSdForLogic
Figure 6. Interactions Inside the Logic Component for the delete 1 Command

2.4. Model component

ModelClassDiagram
Figure 7. Structure of the Model Component

API : Model.java

The Model,

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

  • stores the Address Book data.

  • exposes 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.

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

ℹ️
As a more OOP model, we can store a Tag list in Address Book, which Person can reference. This would allow Address Book to only require one Tag object per unique Tag, instead of each Person needing their own Tag object. An example of how such a model may look like is given below.

ModelClassBetterOopDiagram

2.5. Storage component

StorageClassDiagram
Figure 8. Structure of the Storage Component

API : Storage.java

The Storage component,

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

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

2.6. Common classes

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

3. Implementation

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

3.1. Patient Management

3.1.1. Patient Feature

The Patient class represents patients for our users on TeethHub. It extends Person with more patient-specific attributes, as well as methods. Various methods are also overridden in order for them to work appropriately with the new Patient class.

The following class diagram summarizes the new Patient class, which extends from Person:

PatientClassDiagram

3.1.2. Dental Records Feature

The Record class represents a dental record of a patient. Each Patient class has an list of Record as an attribute.

The Record class is purposely implemented to be similar to that of Person. Just like person, record has associate classes for specific operations, such as storage. This ensures that the processing of records is streamlined with Patient, which extends Person.

3.1.3. Records Mode Feature

The current implementation to view a specified patient’s dental records uses the goto command.

The GoToCommand extends the Command abstract class. The valid form of the command is goto INDEX. The INDEX of the command specifies the patient in the patient list, based to their denoted indexes.

On the other hand, the current implementation to go back to the patient list uses the back command.

It also extends the Command abstract class. Unlike the GoToCommand, the BackCommand does not take in any parameters. The valid form of the command is back.

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

Step 1. The user launches the application for the first time. All stored patients will be loaded and the user will be shown the patient list by default.

Step 2. The user executes goto 1 command to view the dental records of the first patient in the dental book. The goto command sets the specified patient in the MainWindow as the first patient. The patient list is now replaced by the dental record list of the specified patient.

Step 3. The user can now add, edit, or delete dental records, which are tied to the specified patient.

ℹ️
If the goto command is entered while the window is already showing dental records of a specified patient, an error message will be displayed on the window.

Step 4. The user now decides that he wants to view the patient list. He do so by executing the back command. After which, the record list is replaced by the patient list.

ℹ️
The back command will still work with parameters, but those parameters will be ignored.

Step 5. The user can now add, edit, or delete patients' personal information.

ℹ️
If the back command is entered while the window is already showing patients, an error message will be displayed on the window.

The following activity diagram summarizes what happens when a user executes the goto or back command:

GotoActivityDiagram

 

3.1.4. Patient’s Teeth Feature

The Teeth class represents patients' teeth for our users on TeethHub. It consist of an array of Tooth objects, which represents the individual tooth of patients.

ℹ️
When a patient is added by the user, TeethHub automatically creates a new set of all healthy and present teeth for the new patient. At this point of time, only permanent teeth is supported.

Each Tooth can be present or absent. If it is present, it can be on or off status. A tooth on status would mean that it is a problematic tooth (i.e. decaying tooth or dental prosthesis). A optional status message can be tied to each tooth on status, allowing our users to have a better overview of the tooth if necessary.

The command to edit a specific tooth of a patient is: teethedit INDEX.

ℹ️
The teethedit command can only run after a patient is specified via the goto command.

The following class diagram summarizes the Teeth class, which is a composition of the Tooth class:

 

TeethClassDiagram

 

3.1.5. Automatically Generated Tags Feature

There are only two types of tags that are valid in the Patient class. They are StatusTag and TeethTag. At any time, there can only be one of each kind of those tags. Unlike in Person, users cannot added their own tags to patients. We have implemented patient tags to be fully automated by our application.

StatusTag: Describes the condition of the patient’s teeth. The valid options are "Healthy Teeth", "Status Teeth", and "Absent Teeth". The worst tooth status found in the patient’s teeth will be reflected in the teeth status tag.

The activity diagram below demonstrates how TeethHub automatically update the patient’s status tag when his or her tooth is edited:

StatusTagActivityDiagram

TeethTag: Describes the teeth type of the patient. The valid options are "Primary Teeth" or "Permanent Teeth".

ℹ️
Since only permanent teeth is supported by TeethHub at the moment, TeethTag will always show "Permanent Teeth".

3.1.6. Dentist Feature

Following the single user policy, TeethHub only prompts the user once to acquire his or her name, which will then be used when creating new dental records for patients.

Currently, the application prompts the user for his or her name during his or her first attempt when adding a new dental record to a specified patient via the RecordAdd command.

ℹ️
Currently, the dentist’s name is stored in a .txt file in TeethHub. It is possible for users to change their name from the .txt file, although they are not encouraged to do so.

3.1.7. Other Relevant Features

We have also created new commands that will facilitate the Patient class.

ℹ️
The commands below can only be executed after a patient is specified via the GoTo command.

RecordAdd: Adds a new record to a specified patient.

RecordEdit: Edits an existing record of a specified patient.

RecordDelete: Deletes an existing record of a specified patient.

RecordClear: Clear all records of a specified patient.

3.1.8. Design Considerations

Aspect: Creating the Patient class
  • Current implementation: Create the Patient class by extending it from Person.

    • Alternative: Create the Patient class from the bottom-up.

      • Alternative Pros: As Patient will not be a subclass of any other class, it will be less affected by changes in other classes.

      • Alternative Cons: All existing classes and methods which currently work with Person needs to be re-written to work with the new Patient class. Attributes and methods cannot be reused, and must be re-implemented. Lastly, polymorphism cannot be applied in cases where there is a need to deal with both persons and patients.

    • Choice Justification: It is intuitive, as it is logical that all patients are persons as well. The code from Person can be reused in Patient through inheritance, and all existing classes and methods which work with Person will also work with Patient. Most importantly, it allows us to make use of the object-oriented programming principles we learnt in class. We assume that the Open-Closed Principle is applied on the Person class.

Aspect: Picking the appropriate data structure to store dental records
  • Current implementation: Store the records using a list.

    • Alternative: Store the records using a hash table.

      • Alternative Pros: It gives the fastest time complexity if a record search is required. It runs in O(1) time.

      • Alternative Cons: It can be challenging for collaborators to understand, which can be detrimental for collaborative programming. It also creates an extra layer of complexity in order to display the stored records in the order they were first stored in the application by our users.

    • Choice Justification: It is easy to understand, which is crucial for collaborative programming as other programmers may require accessing the records in the list. It can also save new records in the order of when they are added, from most recent to oldest, simply using List.add(0, Record). However, if the list gets long over time, it might cause additional waiting time for our users when they would to search for a specific record. This is because a linear search in a list is of O(n) time complexity.

Aspect: How the goto command executes
  • Current implementation: Use a static variable to store the specified patient, with a public getter method, and a static boolean that denotes the current list viewing mode.

    • Alternative: Save the specified patient and list viewing mode as an instance variable of MainWindow.

      • Alternative Pros: Will work properly even if MainWindow is no longer a singleton class.

      • Alternative Cons: Challenging to implement as major revamp is required to most existing classes and tests. All new classes which wish to access the specified patient or list viewing mode will need to take in a reference to the MainWindow instance.

    • Choice Justification: This is relatively easy to implement and understand. Furthermore, other classes can easily access the current specified patient, and the current list viewing mode. However, it may cause complications if MainWindow is no longer a singleton class.

Aspect: Data structure to support the goto command
  • Current implementation: Use a patient variable to store the patient specified by the command.

    • Alternative: Create a new immutable patient variable to store the specified patient.

      • Alternative Pros: Ensures the the specified patient cannot be edited by other classes or methods.

      • Alternative Cons: Major changes to the patient class would require the immutable patient class to be changed too. Furthermore, every time any record of the specified patient is modified, a new immutable patient needs to be created to update the currently stored immutable patient.

    • Choice Justification: An intuitive solution, as the specified patient is stored as an exact same class as a patient. Additionally, attributes of the specified patient can be accessed just like any other patient class. Changes to the patient class does not significantly affect the goto command. However, the patient class is mutable, and accidental changes to its attributes by other classes or methods can occur.

Aspect: Data structure for Teeth
  • Current implementation: Create a Tooth object representing a tooth, and use an array to store a list of tooth which will represent the teeth of patients.

    • Alternative: Create an integer array representing teeth. Each integer value in the array indicates the status of a tooth.

      • Alternative Pros: Simplest to implement.

      • Alternative Cons: Can be hard to understand by other programmers as integers are used to represent teeth statuses. Additionally, this is violating object-oriented principles.

    • Choice Justification: An straightforward object-oriented solution and easy to understand by other collaborating programmers who are familiar with object-oriented programming. However, Tooth and Teeth objects, as well as their relevant methods takes a significant amount of time to be created. They will also require proper test cases to be implemented.

Aspect: Not adding copies into local storage
  • Current implementation: Copies are not serialized into json file. It’s only inside the memory. When copies exist before exit, program will ask for user’s confirmation

    • Alternative: Serialize copies into json file. Make changes to comparators of patient/task such that it accepts same entries with copy tag.

      • Alternative Pros: Everything is on track. User does not need to make a confirmation before exiting with copies. And if the program exits accidentally, the copies are still there.

      • Alternative Cons: User may be able to generate too many unnecessary entries easily, whose serialization is costly in time and space. The change of comparator also violates the design of unique list, bringing potential security issues as well.

    • Choice Justification: Serialization may need less code in the beginning. The maintenacne is more difficult, and the robustness of the program is compromised. On the other hand, creating copies for the user is not a hard task. Thus, creating copies on demand should be a better idea.

Our user can also utilize TeethHub to store manage and interact with tasks aside from patients. Dentists would often have tasks that needs to be completed or performed and the implementation of the Task data object allows TeethHub to provide its user with the ability to manage these tasks and have them interact with their patient’s details when necessary as well. The main commands added with the data object serve to provide the user with a way to manage and view the tasks stored in the application.

This segment will explain the main features of the a new data type Task that TeethHub handle. Key implementation details and the design considerations when designing these features will also be covered.

3.2.1. Task storage and attributes

The Task class is a new type of object that is meant to represent any task a dentist would have to perform. Task objects are kept in a list and handled by the UniqueTaskList class. This follows the same process as the Patient class and by extension, the Person class which has a UniquePersonList handling a list of Person objects.

Storing information of all objects does not change between Task and Person classes and both are stored into a TeethHub.json file. Attributes for Task are instantiated as objects with their own methods to validate the user’s input much like the current implementation of Patient/Person .

ℹ️
Task has an attribute called LinkedPatient which stores the NAME and NRIC of a patient. This is to allow tasks to be bound to a patient and facilliate another implementation taskdone .

The following class diagram details the attributes Task and the multiplicity association between the classes:

TaskClassDiagramFinal

3.2.2. Task interaction feature (Basic)

Essential commands for basic interaction with the tasks such as, taskadd, taskedit and taskdelete are implemented in a similar fashion as compared to existing commands. All three commands extend from the abstract class Command and follow the same execution flow as patientadd, patientedit and patientdelete.

The generic execution flow of these commands mostly involve the Logic and Model component of the application and the order can be detailed as follows:

  1. User’s input command gets sent to LogicManager.

  2. LogicManager sends to AddressBookParser to check command validity.

  3. AddressBookParser creates a CommandParser(typically named commandclassnameParser like TaskAddCommandParser) to check the arguments.

  4. CommandParser creates an instance of the Command and sends it back to LogicManager.

  5. LogicManager calls the method to execute the Command.

  6. Command executes and calls methods from Model to interact with the Task objects.

  7. Result from the command’s execution gets sent back to the UI and eventually the user.

ℹ️
Adding and editing tasks may involved additional calls to Model if the user specifies a patient to link to the task as the application will have to extract unique information from the Patient to store in the task

The following sequence diagram illustrates the stated generic command execution flow and the components involved:

GenericSequenceDiagram

3.2.3. Task Autocomplete feature

The taskdone command is a feature implemented to allow the user to automatically complete a task of his/her choice, setting it to COMPLETED. The command also allows the user to automatically add a record to the task’s LinkedPatient upon completion of the task. This feature makes use of the Model by calling its methods setTask() and setPerson() to perform additional interactions with both task and patient respectively.

The general process followed during the command’s execution is as follows:

  1. User runs a valid taskdone command on a task with a linkedpatient

  2. Command gets parsed properly and reaches the execution stage within TaskDoneCommand

  3. New task gets created with the exact same attributes as the task to be completed.

  4. New task is set to COMPLETED

  5. setTask method is called to replace the task to be completed with the new task

  6. TeethHub’s stored list of patients is searched to look for a patient matching the task’s linked patient’s NRIC

  7. Retrieves the found patient and adds a record to the patient.

  8. setPerson method is called to replace the matching patient with the new patient who has the added record.

  9. Command execution ends and result is returned back to the user.

ℹ️
The current patientedit and patientdelete commands have been modified to update tasks linked to a patient when said patient gets modified or deleted. However, to keep in line with the concept of Defensive Programming, the execution still check and inform the user in an event where the LinkedPatient cannot be found.

The following sequence diagram illustrates an example where command executes on task with the index 1:

TaskDoneSequenceDiagram

3.2.4. Task Calendar feature

The taskcal command is a feature implemented to allow the user to view task related data in a more interactive and helpful manner. Executing the command creates a popup window with the list of tasks on the left side and a calendar on the right side. The popup window will also have a commandbox where the user is able to key in task related commands, dates in the format of dd-mm-yyyy to navigate the calendar or exit and help as miscelleanous commands.

This feature also enables the user to find tasks depending on the date they provide in the commandbox or the date cell they click on such that the task list will update to show users the tasks with start dates and/or end dates on the selected date cell.

ℹ️
Closing the Main Window whilst the Task Calendar popup is showing will also close the popup automatically. This is implemented intentionally as the popup is designed to be an extension of the Main Window, not a standalone window.

The calendar is generated with modifications to the date cells being done during the generation process. Modifications to the date cells (e.g. changing font colors, overlaying a circle) are done to highlight date cells with the color of task priority.

The class CalendarWindow uses a HashMap with the date, as the key and priority number, as a value,to store the start date and end date information from tasks stored in TeethHub, replacing the stored value with a highest priority value between the duplicates. This enables the date cells to be compared with the stored values amidst the generation process, choosing which date cells to highlight with the highest priority color.

The UI components of this feature are implemented using classes from JavaFX with the most important component being DatePicker which allows a calendar to be drawn up and displayed to the user. The implementation overrides the updateItem() method from DatePicker to enable modifications to be done to the date cells in the calendar.

ℹ️
Color scheme and certain font sizes of the Calendar are done using Cascading Style Sheets (CSS) in a Calendar.CSS file found under resources. Changes can be made to the CSS file to change color schemes of the calendar if needed

The following swimlane activity diagram illustrates the process in which the application goes through to create and display the interactive calendar for the user:

TaskCalendarSwimLaneDiagram

 

3.2.5. Design Considerations

Aspect: Storing the task class
  • Current implementation: Tasks are stored in the same json file as patients.

    • Alternative: Store the tasks in a seperate json file

      • Alternative Pros: Very unlikely to have a very large json file by saving different data types to a different file.The single json file that stores the data will not become too large if there are too many patients and/or tasks. Reduces the likelihood that the size of the json file will exceed the limit of certain file systems like FAT-32 with a limit of 4GB, which would result in the application being unable to save to the file.

      • Alternative Cons: Increases time spent opening a seperate json file. This results in more I/O work for application, decreasing application load time.Complicated to implement and affects future storage and I/O features. Implementation would affect I/O features that may need to read from the files to export both patients and tasks. Sets a precedent where new data types added in the future should be stored seperately for consistency, which would affect export and import commands as they have to be changed to read a new file to export/import.

    • Choice Justification: Most operating systems these days utilize a filesystem that allows for filesizes far beyond 4GB. The limit can go up to 2^64 bytes in NTFS for example and in almost all cases, it is unlikely for the a dentist to have such a large number of patients and tasks. Compared to the cons of the alternative where performance and increased likelihood of issues in the futre, the pros that the alternative offers do not seem valid enough to justify using it over the current choice.

Aspect: Representing the Priority class attribute for Task
  • Current implementation: Java’s special Enumeration (Enum) class is used for the Priority class

    • Alternative: Use a normal class type, with string as inner attribute for Priority.

      • Alternative Pros: Priorities can be named anything the user desires such as "code red", "code blue" for example. Easy to implement as it functions similarly to how name and title works.

      • Alternative Cons: Priority tracking depends on the user’s own memory of what he/she named them and sees as a higher priority, making it difficult for the program to display the appropriate color for priorities to benefit users. Goes against good programming practice which is to use enum for values that should not be changed and Enum is typically used to store priority types.

    • Choice Justification: Priorities being represented as "high", "med" and "low" is a common case and as such, most if not all people should be able to understand the naming convention. While giving freedom to the user is typically beneficial, in this case, it prevents the application from providing better more value to the user. The current choice uses terms that are understandable whilst providing benefits to the user by enabling a consistent colored display of priority tags.

3.2.6. Aspect: Modifying task and patient when executing taskdone command

  • Current implementation: Target task and patient are replaced with new instances with priority changed and record added respectively.

    • Alternative: Modify the target task and linked patient directly.

      • Alternative Pros: Easy to implement as they already have methods to perform the operations Requires less operations to execute the command, leading to performance increase.

      • Alternative Cons: Completing a task does not work with undo and redo due to the way the model is implemented.

    • Choice Justification: The current choice enables undo and redo to work with the task autocomplete feature, enabling tasks to be un-completed by using the undo command. This is important as the user could potentially key the wrong index when completing a task and wish to undo the command without having to manually edit the task. From tests done with the alternative, the performance increase is also negligible at best. The lack of a strong benefit does not make up for the inability to undo/redo making the alternative less desirable as compared to the current choice.

3.2.7. Aspect: Data strcuture to support Task Calendar generation

  • Current implementation: HashMap is used to store dates that have task’s starting and/or ending.

    • Alternative: Use an ArrayList to store the dates instead.

      • Alternative Pros: Does not require its stored values to have an equals() method. Lower memory consuption as there is no need to store both a key and a value. Retrieves elements with get() in O(1) time with a specified index.

      • Alternative Cons: Checking if an element exists within is O(n) time as the entire arraylist needs to be checked. Requires knowing the actual index in order to utilize the benefit of an O(1) retrieval.

    • Choice Justification: The generation of the calendar page requires a data structure that can check if a particular date is contained within itself in order to decide whether a date cell should be highlighted or not. Arraylist would be slower in this case as it would have to peform an O(n) every time a task gets iterated compared to the O(1) a HashMap offers. There is also no need to implement an equals() method for the DateCustom object to be stored as it is already implemented to ensure that there are no duplicate tasks. In this case, the current choices is a much better fit as compared to arraylist to support the calendar feature.

3.2.8. Aspect: Method of displaying Task Calendar

  • Current implementation: Task Calendar window is displayed in a seperate popup.

    • Alternative: Display the calendar in another pane in the Main Window.

      • Alternative Pros: Everything can be shown at one glance to the user. No need to shuffle between windows. No need for a command to make the calendar show.

      • Alternative Cons: Other elements displayed in the Main Window have to be shrunk along with the calendar’s contents to make space for the task calendar to be viewed. Main Window would be displaying too many things.

    • Choice Justification: Shrinking all elements in the Main Window would adversely affect the visibility of other important features the application provdies such as the patient’s information, the list of patients and list of tasks. This reduces the user experience as content cannot be clearly seen. Following the traditional usage of a calendar, most people typically look at a calendar when the need arises such as planning or scheduling. It would be better for a command to be provided and show the user a clear and distinct calendar only when he/she requires it, making the current choice ideal.

3.3. Undo/Redo feature

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.

UndoRedoStartingStateListDiagram

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

UndoRedoNewCommand1StateListDiagram

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.

UndoRedoNewCommand2StateListDiagram
ℹ️
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.

UndoRedoExecuteUndoStateListDiagram
ℹ️
If the currentStatePointer is at index 0, pointing to the initial address book state, then there are no previous address book 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 the undo operation works:

UndoRedoSequenceDiagram

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.

ℹ️
If the currentStatePointer is at index addressBookStateList.size() - 1, pointing to the latest address book state, then there are no undone address book 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.

UndoRedoNewCommand3StateListDiagram

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. We designed it this way because it no longer makes sense to redo the add n/David …​ command. This is the behavior that most modern desktop applications follow.

UndoRedoNewCommand4StateListDiagram

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

UndoRedoActivityDiagram

3.3.1. Design Considerations

Aspect: How undo & redo executes
  • Current implementation: Saves the entire address book.

    • Alternative: Individual command knows how to undo/redo by itself.

      • Alternative Pros: Will use less memory (e.g. for delete, just save the person being deleted).

      • Alternative Cons: We must ensure that the implementation of each individual command are correct.

    • Choice Justification: We decided to go with the current implementation as it is simple and easier to fix.

Aspect: Data structure to support the undo/redo commands
  • Current implementation: Use a list to store the history of address book states.

    • Alternative: Use HistoryManager for undo/redo

      • Alternative Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. Logic is not duplicated twice as a result.

      • Alternative Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as HistoryManager now needs to do two different things. It is also harder for the new incoming developers of our project, who are new Computer Science student undergraduates, to understand.

    • Choice Justification: We decided to keep to the Single Responsibility Principle and Separation of Concerns, thus we chose the current implementation.

3.4. [Proposed] Data Encryption

{Explain here how the data encryption feature will be implemented}

3.5. Viewing Information

3.5.1. Find feature

The Find mechanism is facilitated through the use of predicates in conjunction with the FilteredList within ModelManager

The below sequence diagram gives an overview of how a Find Command is created. When the proper string arguments for a Find Command is entered into the user interface, the arguments are passed to the Logic Manager and then the AddressBook parser to determine if the input is valid. If it valid, a new FindCommandParser object is then created and handles the remaining user input.

From FindCommandParser, it creates a MultipleContainsKeywordsPredicate before going into a loop that creates the respective parameter ContainsKeywordsPredicate if the parameter is present within the user input. The newly created ContainsKeywordsPredicate objects are passed back to the FindCommandParser and stored within a list. Once the loop has finished the list is passed over to the MultipleContainKeywordsPredicate object before it is used in the creation of a FindCommand object. On success, the FindCommand object is returned to the LogicManager as per the flow in the sequence diagram.

On execution of the Find Command, updateFilteredPersonList is called with the predicate stored within the program itself. It subsequently calls setPredicate(predicatte) and updates the displayed person list to only show patients that matches the given predicate.

FindCommandSequenceDiagram1

3.5.2. Design Considerations

Aspect: Creation of Predicate Classes
  • Current Implementation: Every associated parameter that is found within either a Patient or Record class has an associated parameter predicate classes extended from ContainsKeywordsPredicate class that handles the predicate testing when that parameter is specified to be searched.
    (E.g. The predicate for Name is handled in NameContainsKeywordsPredicate)

    • Alternative 1: Patient and Record would each have an individually associated PatientContainsKeywordsPredicate and RecordContainsKeywordsPredicate that contains each respective parameter predicate class within itself.

      • Alternative Pros: Collates all parameter predicates relevant to either Patient or Record within a singular class. This makes it easier for future developers to discover which file to change should they require to add or remove additional parameter predicates from TeethHub.

      • Alternative Cons: Violates Open-Closed Principle as every time a parameter predicate needs to be added or modified, we are forced to modify the code of either PatientContainsKeywordsPredicate or RecordContainsKeywordsPredicate in order to extend its behavior.

    • Choice Justification: Ultimately, while the current implementation can be considered more troublesome for future developers as they would need to create a new java file for each new parameter, we decided that it was more important for us to maintain proper software engineering principles.

3.5.3. Sort feature

The Sort mechanism is facilitated through the use of Comparators in conjunction with the Unique Lists for both record and patient depending on the programm’s mode when the command is called.

Upon parsing the arguments, SortCommandParser then checks which mode TeethHub currently is in. If it’s in record / goTo mode, it creates a RecordComparator and returns a SortRecordCommand. Otherwise, a PatientComparator is called instead and returns a SortPatientCommand instead.

When the comparator is retrieved, SortCommandParser then calls orderChecker from itself to determine the isReverse boolean. Finally, the respective parameters are used to create the neccessary SortCommand and returns it to AddressBookParser and then LogicManager.

On execution, there are two behaviours can occur depending on whether the SortCommand is a SortPatientCommand or SortRecordCommand. Should it be an instance of SortRecordCommand, sortRecordsBook(c,isReverse) is called from ModelManager, which in turns called sortRecords(c, isReverse) from VersionedAddressBook and finally calls sortStoredList (c, isReverse) from UniqueRecordList. This sorts the records currently stored according to the parsed comparator and hence changes the order records are displayed to the user.

Should it be a SortPatientCommand instead, the same logic flow occurs except it that calls the respective patient methods and classes instead.

SortCommandSequenceDiagram

3.5.4. Design Considerations

Aspect: Implementation of sort
  • Current Implementation: SortPatientCommand & SortRecordCommand extends from a super SortCommand class and share a common SortCommandParser. They hence share the same command words and allows the user to simply enter sort to sort either the displayed patients or records.

    • Alternative 1: Create separate parsers and commands for sorting patients and records respectively. PatientSortCommand and PatientSortParserCommand could sort patients while in patients mode while RecordSortCommand and RecordSortParserCommand could sort records while in records mode.

      • Alternative Pros: A clearer distinction of what mode the program is in would be made to the user. Additionally, it would be easier to change the parser behavior if necessary in the future of either sort command without affecting the other.

      • Alternative Cons: It would no longer be possible to change any shared behavior of both sort commands simply by modifying the SortCommand super class.

    • Choice Justification: Ultimately, we decided on having a parent SortCommand with its two SortPatientCommand and SortRecordCommand children share a common SortCommandParser. Aside from the fact that SortRecordCommand calls the Record equivalent methods and classes that SortPatientCommand does, the behavior of both sort commands are essentially the same. As such, it made more sense for us to go ahead with our current implementation.

3.5.5. Stat feature

The Stat mechanism is facilitated through the use of the inbuilt JavaFx framework in conjunction with the data stored within each Patient object.

Upon execution of the stat command, the patient to have statistics generated from is set to the StatWindow file which then uses it to create the report. As per the diagram below, StatWindow then obtains all attributes from the patient and sets the latest teeth image to itself.

The StatWindow then creates a Map<String, Integer> with all the valid procedure values found in Procedure as the keys and the values set to 0. StatWindow subsequently iterates through all the records linked to the set patient checking for the record’s procedure type, incrementing the value paired the respective procedure by 1 on a hit. It is from this Map that the records table is generated as well as the bar chart. The pie chart uses the Map as well while ignoring entries whose values have remained at 0.

It is at this point that all necessary data has been populated within the statistics report and it is displayed to the user.

StatActivityDiagram

3.5.6. Design Considerations

Aspect: Selecting the desired patient to stat
  • Current Implementation: The selected patient is determined by the Index number of the currently displayed patients within the PatientListPanel

    • Alternative 1: The selected patient is determined by a given keyword. Upon entering the keyword, the program then searches for the patients that match the keyword. Should multiple patients match the keyword, the command would fail and the user would be prompted to refine their search.

      • Alternative Pros: Combines patientfind and stat together, giving the user greater convenience should they know enough unique details of the patient they wish to stat.

      • Alternative Cons: Reduces the ease of use of the stat command. As the amount of patients stored within the program increases, so does the probability that the entered keywords would match multiple patients.

    • Choice Justification: Ultimately, we decided to stick with Index based selection as it increases the ease of use for the user when there’s a large number of patient entries. As the Index references the displayed patients, the user can hence perform a patientfind operation beforehand and locate the desired patient.

3.6. File Management

Although TeethHub already has a built-in auto-load and auto-save, implementing file management would give the user more flexibility with managing data.
PDF export is also implemented so that the user would have an easier time making sense of the data when offline.

The File Management features are: Open, Import, Save, Export. * If the file that is being opened/imported is corrupted, an error message is thrown and no change is made.
* If the user inputs an index range for import/export that does not exist, the current indexes that fall within the range are still imported/exported. This is because we want to make things easier on the user.

ℹ️

These following two keywords will be used by various File Management features.

FILE_PATH: The name and file type to be saved. FILE_PATH also allows the inclusion of folder names. If the indicated FILE_PATH does not exist, it will be created.
Any letters in the English alphabet and numbers are allowed.
Allowed special characters are:
! @ # $ % ^ & ( ) _ + - = { } [ ] ; ' , .
Special characters NOT allowed are:
< > : " | ? *

INDEX_RANGE:
Any positive integers (no decimals, must be greater than 0) are allowed.
Use commas (no space) to indicate a break. e.g. 1,3,5 for 1 and 3 and 5
Use dash (no space) to indicate a range. e.g. 3-5 for 3 to 5. 1-3-5 is not allowed, just use 1-5.
Use a combination of commas and dashes to indicate a range as well. e.g. 1-3,5 for 1 to 3 and 5.
all can be used instead to include everything. e.g. import test.json all or export test.json all

3.6.1. Open vs Import

Suppose you have a data.json file with the following contents:

OpenImportFeature1

The following image illustrates the difference when you open or import data.json.

OpenImportFeature2

3.6.2. Open feature

As TeethHub already has a built-in auto-load when starting the program, the implemented Open feature is simple.
The Open feature opens the specified file and overwrites the current TeethHub data with the file data.
The Open feature’s format is: open FILE_PATH

  1. AddressBookParser creates OpenCommandParser.

  2. The OpenCommandParser uses ParserUtil to parse the user input.

  3. If the input is valid, ParserUtil creates a ParsedInOut object and returns it to OpenCommandParser.

  4. OpenCommandParser creates OpenCommand initialized with the ParsedInOut object.

  5. OpenCommand checks for if the requested file is a ".json" file, if the file exists, if it is a file, or if it can be read.

  6. OpenCommand calls the existing readAddressBook().

OpenCommandSequenceDiagram

3.6.3. Import feature

As TeethHub already has a built-in auto-load when starting the program, the implemented Import feature makes use of it.
The Import feature opens the specified file and adds the file data to the current TeethHub data.
The Import feature’s format is: import FILE_PATH INDEX_RANGE

  1. AddressBookParser creates ImportCommandParser.

  2. The ImportCommandParser uses ParserUtil to parse the user input.

  3. If the input is valid, ParserUtil creates a ParsedInOut object and returns it to ImportCommandParser.

  4. ImportCommandParser creates ImportCommand initialized with the ParsedInOut object.

  5. ImportCommand checks for if the requested file is a ".json" file, if the file exists, if it is a file, or if it can be read.

  6. ImportCommand calls the existing readAddressBook() on a temporary storage.
    6a. ImportCommand adds contents from the temporary storage to the current storage based on the input INDEX_RANGE.
    6b. ImportCommand adds all contents from the temporary storage to the current storage if INDEX_RANGE is all.

3.6.4. Save feature

As TeethHub already has a built-in auto-save when exiting the program, the implemented Save feature makes use of it.
The Save feature saves all current TeethHub data to the specified file.
In addition to that, the Save can also save to PDF, using the Apache PDFBox.
In the Open Command Sequence Diagram above, you can see that OpenCommand creates an InOutAddressBookStorage. The InOutAddressBookStorage has the capability to call the existing saveAddressBook() and also a new saveAsPdf().
The Save feature’s format is: save FILE_PATH

  1. AddressBookParser creates SaveCommandParser.

  2. The SaveCommandParser uses ParserUtil to parse the user input.

  3. If the input is valid, ParserUtil creates a ParsedInOut object and returns it to SaveCommandParser.

  4. SaveCommandParser creates SaveCommand initialized with the ParsedInOut object.

  5. SaveCommand checks for if the requested file is a ".json" file or ".pdf" file. It also checks if the file is Read-only.
    5a. SaveCommand calls the existing saveAddressBook() if the requested file is a ".json" file.
    5b. SaveCommand calls the new saveAsPdf() if the requested file is a ".pdf" file.

3.6.5. Export feature

As TeethHub already has a built-in auto-save when starting the program, the implemented Export feature makes use of it.
The Export feature saves specified patients in the current TeethHub data to the specified file.
The Export feature’s format is: export FILE_PATH INDEX_RANGE

ExportCommandActivityDiagram
  1. AddressBookParser creates ExportCommandParser.

  2. The ExportCommandParser uses ParserUtil to parse the user input.

  3. If the input is valid, ParserUtil creates a ParsedInOut object and returns it to ExportCommandParser.

  4. ExportCommandParser creates ImportCommand initialized with the ParsedInOut object.

  5. ExportCommand checks if INDEX_RANGE is all.
    5a. ExportCommand calls SaveCommand if INDEX_RANGE is all. Refer to Save feature.
    5b. Otherwise, ExportCommand add contents from the current storage to the temporary storage based on the input INDEX_RANGE.

  6. ExportCommand checks for if the requested file is a ".json" file or ".pdf" file. It also checks if the file is Read-only.
    6a. ExportCommand calls the existing saveAddressBook() if the requested file is a ".json" file.
    6b. ExportCommand calls the new saveAsPdf() if the requested file is a ".pdf" file.

3.6.6. Design Considerations

Aspect: Reading or writing a file
  • Current implementation: (Open/Import/Save/Export)Command → InOutAddressBookStorage → JsonUtil → FileUtil

    • Alternative 1: (Open/Import/Save/Export)Command → Json Util → FileUtil

      • Alternative Pros: Less overhead and faster runtime as there are less classes to go through.

      • Alternative Cons: InOutAddressBookStorage does some file reading/writing error handling. Bypassing InOutAddressBookStorage would require the same error handling in (Open/Import/Save/Export)Command. Since (Open/Import/Save/Export)Command is not called when the program starts, we cannot move the error handling from InOutAddressBookStorage to (Open/Import/Save/Export)Command. In that case, we would have to copy the error handling instead, which means that we now have a duplicate logic, which is also not ideal.

    • Alternative 2: (Open/Import/Save/Export)Command → FileUtil

      • Alternative Pros: Same as Alternative 1.

      • Alternative Cons: Same as Alternative 1. In addition to that: The features of Json Util would need to be re-implemented in (Open/Import/Save/Export)Command, which would also lead to duplicate logic.

    • Choice Justification:
      Since:
      There already is a file reading/writing error handling implemented in InOutAddressBookStorage.
      There already is .json handling implemented in JsonUtil.
      It would be logical to make use of them instead of re-implementing them.

  • Current implementation: (Save/Export)Command → InOutAddressBookStorage → PdfUtil

    • Alternative 1: (Save/Export)Command → PdfUtil

      • Alternative Pros: Less overhead and faster runtime as there are less classes to go through.

      • Alternative Cons: InOutAddressBookStorage does some file reading/writing error handling. Bypassing InOutAddressBookStorage would require the same error handling in (Open/Import/Save/Export)Command. Since (Open/Import/Save/Export)Command is not called when the program starts, we cannot move the error handling from InOutAddressBookStorage to (Open/Import/Save/Export)Command. In that case, we would have to copy the error handling instead, which means that we now have a duplicate logic, which is also not ideal.

    • Alternative 2.1: (Save/Export)Command → PdfUtil → FileUtil

    • Alternative 2.2: (Save/Export)Command → FileUtil

      • Alternative Pros: Same as Alternative 1.

      • Alternative Cons: Same as Alternative 1. In addition to that: Passing the job to FileUtil would require implementing Pdf creation and saving that is already present in the third party library Apache PDFBox. Hence the job is passed to PdfUtil and stops there as it calls the already present writing methods of Apache PDFBox.

    • Choice Justification:
      Since:
      There already is a file reading/writing error handling implemented in InOutAddressBookStorage.
      There already is .pdf handling implemented in Apache PDFBox.
      It would be logical to make use of them instead of re-implementing them.

Aspect: Index ranges of Import and Export
  • Current implementation: The Import/Export features accept index ranges that are larger than the actual index range of the content to be imported/exported. Indexes out of range are simply ignored.
    E.g. There are patients from index 1 to index 30. User inputs export test.json 10-40. Patients with index 10 to 30 are exported, the requested 31 to 40 is ignored.

    • Alternative: Don’t allow indexes out of range for Import/Export.

      • Alternative Pros: User cannot input a very large index range. This prevents a scenario where a very large range causes slow runtime and increased memory due to the amount of indexes to process.

      • Alternative Cons: User may feel frustration of being denied due to minor mistakes. e.g. export data.json 1-31 being rejected when there are only 30 entries.

    • Choice Justification: As our goal when designing TeethHub was to make things easier for the user, we decided to allow the user to make some mistakes.

  • Current implementation: The Import/Export features accept the all keyword in place of an index range.

    • Alternative: Don’t parse "all" keyword for Import/Export.

      • Alternative Pros: Faster runtime as there are less characters in the regex to match.

      • Alternative Cons: User would need to know the total amount of patients in the external file if importing. Otherwise, the user might resort to inputting a very large index range, which would slow down runtime and increase memory needed due to the amount of indexes to process.

    • Choice Justification: We chose the current implementation to provide an alternative so that it would discourage users from inputting a very large index range.

  • Current implementation: The Export feature calls the Save feature when the all keyword is detected.

    • Alternative: Don’t parse all keyword for Export.

      • Alternative Pros: Less overhead and faster runtime as there the regex would not need to look for all

      • Alternative Cons: In the current implementation, Import and Export share the same parser as Import and Export share the same format of command FILE_PATH INDEX_RANGE. Since Import uses the all keyword, not parsing all would require an additional parser for Export.

    • Choice Justification: We chose the current implementation so as to reduce duplicate logic and improve user experience. As the accepted inputs of Open and Save are the same (except for .pdf), the user may expect the same accepted inputs for Import and Export as well.

3.7. Suggestion feature

As TeethHub contains commands that are similar, we decided to implement a Suggestion feature.
This feature was designed to help users who are familiar with older versions of TeethHub or Address Book 4, as they have the names of old commands.
When the user types a Common command, a suggestion will be displayed asking the user if they meant to type something else.
We define a Common command as a command whose name is used by the Patient commands, Record commands and/or Task commands.
For example, as there are patientadd, recordadd and taskadd, the Common command would be add.
As TeethHub has a Patient Mode and a Record Mode, only commands that can be used in the user’s current mode will be displayed.

SuggestionFeatureAddPatientMode

3.7.1. Design Considerations

  • Current implementation: When the user types a Common command, suggestions are displayed.

    • Alternative: Show Help window if the user inputs invalid commands N times in a row.

      • Alternative Pros: Only 1 implementation, as opposed to an implementation for each Common command.

      • Alternative Cons: Might be rude.

    • Choice Justification: We chose the current implementation as we occasionally found ourselves and other users typing add to add something, edit to edit something and so on. This implementation was designed to tackle this issue.

3.8. 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.9, “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.9. 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

We use asciidoc for writing documentation.

ℹ️
We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting.

4.1. Editing Documentation

See UsingGradle.adoc to learn how to render .adoc files locally to preview the end result of your edits. Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc files in real-time.

4.2. Publishing Documentation

See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.

4.3. Converting Documentation to PDF format

We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.

Here are the steps to convert the project documentation files to PDF format.

  1. Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the docs/ directory to HTML format.

  2. Go to your generated HTML files in the build/docs folder, right click on them and select Open withGoogle Chrome.

  3. Within Chrome, click on the Print option in Chrome’s menu.

  4. Set the destination to Save as PDF, then click Save to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.

chrome save as pdf
Figure 9. Saving documentation as PDF files in Chrome

4.4. Site-wide Documentation Settings

The build.gradle file specifies some project-specific asciidoc attributes which affects how all documentation files within this project are rendered.

💡
Attributes left unset in the build.gradle file will use their default value, if any.
Table 1. List of site-wide attributes
Attribute name Description Default value

site-name

The name of the website. If set, the name will be displayed near the top of the page.

not set

site-githuburl

URL to the site’s repository on GitHub. Setting this will add a "View on GitHub" link in the navigation bar.

not set

site-seedu

Define this attribute if the project is an official SE-EDU project. This will render the SE-EDU navigation bar at the top of the page, and add some SE-EDU-specific navigation items.

not set

4.5. Per-file Documentation Settings

Each .adoc file may also specify some file-specific asciidoc attributes which affects how the file is rendered.

Asciidoctor’s built-in attributes may be specified and used as well.

💡
Attributes left unset in .adoc files will use their default value, if any.
Table 2. List of per-file attributes, excluding Asciidoctor’s built-in attributes
Attribute name Description Default value

site-section

Site section that the document belongs to. This will cause the associated item in the navigation bar to be highlighted. One of: UserGuide, DeveloperGuide, LearningOutcomes*, AboutUs, ContactUs

* Official SE-EDU projects only

not set

no-site-header

Set this attribute to remove the site navigation bar.

not set

4.6. Site Template

The files in docs/stylesheets are the CSS stylesheets of the site. You can modify them to change some properties of the site’s design.

The files in docs/templates controls the rendering of .adoc files into HTML5. These template files are written in a mixture of Ruby and Slim.

⚠️

Modifying the template files in docs/templates requires some knowledge and experience with Ruby and Asciidoctor’s API. You should only modify them if you need greater control over the site’s layout than what stylesheets can provide. The SE-EDU team does not provide support for modified template files.

5. Testing

5.1. Running Tests

There are three ways to run tests.

💡
The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies.

Method 1: Using IntelliJ JUnit test runner

  • To run all tests, right-click on the src/test/java folder and choose Run 'All Tests'

  • To run a subset of tests, you can right-click on a test package, test class, or a test and choose Run 'ABC'

Method 2: Using Gradle

  • Open a console and run the command gradlew clean allTests (Mac/Linux: ./gradlew clean allTests)

ℹ️
See UsingGradle.adoc for more info on how to run tests using Gradle.

Method 3: Using Gradle (headless)

Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.

To run tests in headless mode, open a console and run the command gradlew clean headless allTests (Mac/Linux: ./gradlew clean headless allTests)

5.2. Types of tests

We have two types of tests:

  1. GUI Tests - These are tests involving the GUI. They include,

    1. System Tests that test the entire App by simulating user actions on the GUI. These are in the systemtests package.

    2. Unit tests that test the individual components. These are in seedu.address.ui package.

  2. Non-GUI Tests - These are tests not involving the GUI. They include,

    1. Unit tests targeting the lowest level methods/classes.
      e.g. seedu.address.commons.StringUtilTest

    2. Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
      e.g. seedu.address.storage.StorageManagerTest

    3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
      e.g. seedu.address.logic.LogicManagerTest

5.3. Troubleshooting Testing

Problem: HelpWindowTest fails with a NullPointerException.

  • Reason: One of its dependencies, HelpWindow.html in src/main/resources/docs is missing.

  • Solution: Execute Gradle task processResources.

6. Dev Ops

6.1. Build Automation

See UsingGradle.adoc to learn how to use Gradle for build automation.

6.2. Continuous Integration

We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.

6.3. Coverage Reporting

We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.

6.4. Documentation Previews

When a pull request has changes to asciidoc files, you can use Netlify to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See UsingNetlify.adoc for more details.

6.5. Making a Release

Here are the steps to create a new release.

  1. Update the version number in MainApp.java.

  2. Generate a JAR file using Gradle.

  3. Tag the repository with the version number. e.g. v0.1

  4. Create a new release using GitHub and upload the JAR file you created.

6.6. Managing Dependencies

A project often depends on third-party libraries. For example, Address Book depends on the Jackson library for JSON parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives:

  1. Include those libraries in the repository (this bloats the repository size)

  2. Require developers to download those libraries manually (this creates extra work for developers)

Appendix A: Suggested Programming Tasks to Get Started

Suggested path for new programmers:

  1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in Section A.1, “Improving each component”.

  2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. Section A.2, “Creating a new command: remark explains how to go about adding such a feature.

A.1. Improving each component

Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work).

Logic component

Scenario: You are in charge of logic. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases.

💡
Do take a look at Section 2.3, “Logic component” before attempting to modify the Logic component.
  1. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing clear, the user can also type c to remove all persons in the list.

    • Hints

    • Solution

      • Modify the switch statement in AddressBookParser#parseCommand(String) such that both the proper command word and alias can be used to execute the same intended command.

      • Add new tests for each of the aliases that you have added.

      • Update the user guide to document the new aliases.

      • See this PR for the full solution.

Model component

Scenario: You are in charge of model. One day, the logic-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command.

💡
Do take a look at Section 2.4, “Model component” before attempting to modify the Model component.
  1. Add a removeTag(Tag) method. The specified tag will be removed from everyone in the address book.

    • Hints

      • The Model and the AddressBook API need to be updated.

      • Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags?

      • Find out which of the existing API methods in AddressBook and Person classes can be used to implement the tag removal logic. AddressBook allows you to update a person, and Person allows you to update the tags.

    • Solution

      • Implement a removeTag(Tag) method in AddressBook. Loop through each person, and remove the tag from each person.

      • Add a new API method deleteTag(Tag) in ModelManager. Your ModelManager should call AddressBook#removeTag(Tag).

      • Add new tests for each of the new public methods that you have added.

      • See this PR for the full solution.

Ui component

Scenario: You are in charge of ui. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn’t prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems.

💡
Do take a look at Section 2.2, “UI component” before attempting to modify the UI component.
  1. Use different colors for different tags inside person cards. For example, friends tags can be all in brown, and colleagues tags can be all in yellow.

    Before

    getting started ui tag before

    After

    getting started ui tag after
    • Hints

      • The tag labels are created inside the PersonCard constructor (new Label(tag.tagName)). JavaFX’s Label class allows you to modify the style of each Label, such as changing its color.

      • Use the .css attribute -fx-background-color to add a color.

      • You may wish to modify DarkTheme.css to include some pre-defined colors using css, especially if you have experience with web-based css.

    • Solution

      • You can modify the existing test methods for PersonCard 's to include testing the tag’s color as well.

      • See this PR for the full solution.

        • The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes.

  2. Modify NewResultAvailableEvent such that ResultDisplay can show a different style on error (currently it shows the same regardless of errors).

    Before

    getting started ui result before

    After

    getting started ui result after
  3. Modify the StatusBarFooter to show the total number of people in the address book.

    Before

    getting started ui status before

    After

    getting started ui status after
    • Hints

      • StatusBarFooter.fxml will need a new StatusBar. Be sure to set the GridPane.columnIndex properly for each StatusBar to avoid misalignment!

      • StatusBarFooter needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated.

    • Solution

Storage component

Scenario: You are in charge of storage. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage.

💡
Do take a look at Section 2.5, “Storage component” before attempting to modify the Storage component.
  1. Add a new method backupAddressBook(ReadOnlyAddressBook), so that the address book can be saved in a fixed temporary location.

A.2. Creating a new command: remark

By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app.

Scenario: You are a software maintainer for addressbook, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible remark field for each contact, rather than relying on tags alone. After designing the specification for the remark command, you are convinced that this feature is worth implementing. Your job is to implement the remark command.

A.2.1. Description

Edits the remark for a person specified in the INDEX.
Format: remark INDEX r/[REMARK]

Examples:

  • remark 1 r/Likes to drink coffee.
    Edits the remark for the first person to Likes to drink coffee.

  • remark 1 r/
    Removes the remark for the first person.

A.2.2. Step-by-step Instructions

[Step 1] Logic: Teach the app to accept 'remark' which does nothing

Let’s start by teaching the application how to parse a remark command. We will add the logic of remark later.

Main:

  1. Add a RemarkCommand that extends Command. Upon execution, it should just throw an Exception.

  2. Modify AddressBookParser to accept a RemarkCommand.

Tests:

  1. Add RemarkCommandTest that tests that execute() throws an Exception.

  2. Add new test method to AddressBookParserTest, which tests that typing "remark" returns an instance of RemarkCommand.

[Step 2] Logic: Teach the app to accept 'remark' arguments

Let’s teach the application to parse arguments that our remark command will accept. E.g. 1 r/Likes to drink coffee.

Main:

  1. Modify RemarkCommand to take in an Index and String and print those two parameters as the error message.

  2. Add RemarkCommandParser that knows how to parse two arguments, one index and one with prefix 'r/'.

  3. Modify AddressBookParser to use the newly implemented RemarkCommandParser.

Tests:

  1. Modify RemarkCommandTest to test the RemarkCommand#equals() method.

  2. Add RemarkCommandParserTest that tests different boundary values for RemarkCommandParser.

  3. Modify AddressBookParserTest to test that the correct command is generated according to the user input.

[Step 3] Ui: Add a placeholder for remark in PersonCard

Let’s add a placeholder on all our PersonCard s to display a remark for each person later.

Main:

  1. Add a Label with any random text inside PersonListCard.fxml.

  2. Add FXML annotation in PersonCard to tie the variable to the actual label.

Tests:

  1. Modify PersonCardHandle so that future tests can read the contents of the remark label.

[Step 4] Model: Add Remark class

We have to properly encapsulate the remark in our Person class. Instead of just using a String, let’s follow the conventional class structure that the codebase already uses by adding a Remark class.

Main:

  1. Add Remark to model component (you can copy from Address, remove the regex and change the names accordingly).

  2. Modify RemarkCommand to now take in a Remark instead of a String.

Tests:

  1. Add test for Remark, to test the Remark#equals() method.

[Step 5] Model: Modify Person to support a Remark field

Now we have the Remark class, we need to actually use it inside Person.

Main:

  1. Add getRemark() in Person.

  2. You may assume that the user will not be able to use the add and edit commands to modify the remarks field (i.e. the person will be created without a remark).

  3. Modify SampleDataUtil to add remarks for the sample data (delete your data/TeethHub.json so that the application will load the sample data when you launch it.)

[Step 6] Storage: Add Remark field to JsonAdaptedPerson class

We now have Remark s for Person s, but they will be gone when we exit the application. Let’s modify JsonAdaptedPerson to include a Remark field so that it will be saved.

Main:

  1. Add a new JSON field for Remark.

Tests:

  1. Fix invalidAndValidPersonAddressBook.json, typicalPersonsAddressBook.json, validAddressBook.json etc., such that the JSON tests will not fail due to a missing remark field.

[Step 6b] Test: Add withRemark() for PersonBuilder

Since Person can now have a Remark, we should add a helper method to PersonBuilder, so that users are able to create remarks when building a Person.

Tests:

  1. Add a new method withRemark() for PersonBuilder. This method will create a new Remark for the person that it is currently building.

  2. Try and use the method on any sample Person in TypicalPersons.

[Step 7] Ui: Connect Remark field to PersonCard

Our remark label in PersonCard is still a placeholder. Let’s bring it to life by binding it with the actual remark field.

Main:

  1. Modify PersonCard's constructor to bind the Remark field to the Person 's remark.

Tests:

  1. Modify GuiTestAssert#assertCardDisplaysPerson(…​) so that it will compare the now-functioning remark label.

[Step 8] Logic: Implement RemarkCommand#execute() logic

We now have everything set up…​ but we still can’t modify the remarks. Let’s finish it up by adding in actual logic for our remark command.

Main:

  1. Replace the logic in RemarkCommand#execute() (that currently just throws an Exception), with the actual logic to modify the remarks of a person.

Tests:

  1. Update RemarkCommandTest to test that the execute() logic works.

A.2.3. Full Solution

See this PR for the step-by-step solution.

Appendix B: Product Scope

Target user profile:

  • is a dental practitioner

  • has a need to manage a significant number of patient information, records and tasks

  • prefer desktop apps over other types

  • can type fast

  • prefers typing over mouse input

  • is reasonably comfortable using CLI apps

  • prefers having graphical feedback from the application

  • has an occasional need to transfer chunks of information in and/or out of an app

Value proposition: manages data faster than a typical mouse/GUI drive app while keeping the graphic benefits of GUI

Appendix C: 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 patient’s particulars

know about their situation

* * *

user

edit my patient’s particulars

their personal information remains updated

* * *

user

store my patient’s teeth

track their teeth condition

* * *

user

modify my dental patients' teeth condition

understand and serve my dental patients'

* * *

user

isolate other patients' information

focus on the current patient in my clinic

* * *

user

view my patients' dental records

keep track of my patients' teeth health history

* * *

user

add a new dental record

store diagnosis and treatments of my patients

* * *

user

delete a dental record

remove records that have become redundant or irrelevant

* * *

user

edit a dental record

correct existing dental records

* * *

user

clear all dental records of a patient

protect the privacy of my patients

* * *

user

add a new task

keep track of what I need to do

* * *

user

delete a task

remove tasks that I have already completed or no longer need to do

* * *

user

edit a task

change details of certain tasks that I have already added

* * *

user

link a patient to a task

differentiate between tasks that are meant for a patient and normal tasks

* * *

user

set a task to complete

mark a task as done without needing to edit it again and add a record automatically if the task is linked to a patient

* * *

user

add a record automatically upon completing a task

save time after completing a task that relates a patient by having the record added automatically

* * *

user

view tasks in a calendar

see all the dates in which I have tasks starting or ending, keeping better track of urgency of tasks and deadlines

* * *

user

see a statistics report on each patient’s dental history

have an easier time understanding their potential problems

* * *

user

see a warning come up when I’m exiting the program if there exists duplicate entries

be reminded to edit them before exiting.

* *

user

see an overall statistics report on my patients

analyze potential trends

* *

user

copy a person

reduce the time needed to create a new person who has similar records to an existing person in the list

* *

user

copy a task

reduce the time needed to create a new task who has similar records to an existing task in the list

* *

user

create MC based on existing record

reduce the time needed to input existing data to another system.

* *

user

open an external file

work with patients and tasks saved in another file

* *

user

import specific patients from an external file

add patients and tasks from another file

* *

user

save to an external file

have multiple files for cataloguing

* *

user

export specific patients to an external file

have multiple files for cataloguing

* *

user

save/export to PDF

have a more presentable format for reading

* *

user

see suggestions when I type common commands

do not have to keep consulting the Help page

* *

user

add my own images into records

keep track and store relevant images

* *

user

see relevant dentistry tags on my patients' entries

have an overview of my patients' condition

* *

user with many persons in the address book

sort patients by desired parameter

locate a person easily

*

user with confidential patient information

log into the application with a password

prevent unauthorized access to the application when I am not around

Appendix D: Use Cases

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

Use case: Delete person

MSS

  1. User requests to list persons

  2. AddressBook shows a list of persons

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

  4. AddressBook deletes the person

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. AddressBook shows an error message.

      Use case resumes at step 2.

D.1. Use case: Open file

MSS

  1. User requests to open a .json file

  2. TeethHub opens the file and replaces the current contents with the file contents.

    Use case ends.

Extensions

  • 1a. The specified path file is not a .json file.

    • 1a1. TeethHub shows an error message.

      Use case resumes at step 0.

  • 1b. The specified .json file contents to not conform to TeethHub’s reading algorithm.

    • 1b1. TeethHub shows an error message.

      Use case resumes at step 0.

D.2. Use case: Save file

MSS

  1. User requests to save a .json file or a .pdf file.

  2. TeethHub saves the file, overwriting if the specified file already exists.

    Use case ends.

Extensions

  • 1a. The specified file path is not a .json file or a .pdf file.

    • 1a1. TeethHub shows an error message.

      Use case resumes at step 0.

  • 1b. The specified file path is read only.

    • 1b1. TeethHub shows an error message.

      Use case resumes at step 0.

D.3. Use case: Import file

MSS

  1. User requests to import a .json file.

  2. TeethHub imports the file, adding the file contents to the current TeethHub contents.

    Use case ends.

Extensions

  • 1a. The specified file path is not a .json file.

    • 1a1. TeethHub shows an error message.

      Use case resumes at step 0.

  • 1b. The specified file path is read only.

    • 1b1. TeethHub shows an error message.

      Use case resumes at step 0.

    • 1c. The given index is invalid.

    • 1c1. TeethHub shows an error message.

      Use case resumes at step 0.

D.4. Use case: Export file

MSS

  1. User requests to export a .json file or a .pdf file.

  2. TeethHub exports the specified contents to the file, overwriting if the specified file already exists.

    Use case ends.

Extensions

  • 1a. The specified file path is not a .json file or a .pdf file.

    • 1a1. TeethHub shows an error message.

      Use case resumes at step 0.

  • 1b. The specified file path is read only.

    • 1b1. TeethHub shows an error message.

      Use case resumes at step 0.

    • 1c. The given index is invalid.

    • 1c1. TeethHub shows an error message.

      Use case resumes at step 0.

{More to be added}

Appendix E: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 9 or higher installed.

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

  3. Should be able to provide all features even when working offline.

  4. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.

{More to be added}

Appendix F: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Private contact detail

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

Appendix G: 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.

G.1. Launch and Shutdown

  1. Initial launch

    1. Download the jar file and copy into an empty folder

    2. Double-click the jar file
      Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.

  2. Saving window preferences

    1. Resize the window to an optimum size. Move the window to a different location. Close the window.

    2. Re-launch the app by double-clicking the jar file.
      Expected: The most recent window size and location is retained.

{ more test cases …​ }

G.2. Patient and Record lists

  1. Switching of UI elements when goto command is run

    1. Prerequisites: List all patients using the list command. At least one patient should be displayed in the list.

      1. Test case: goto 1 (Patient of index 1)
        Expected: The patient list GUI is replaced by the record list GUI. Displays dental records of patient specified by index. The window size may not be optimum. Use the command: back to revert to the patient list.

      2. Test case: back
        Expected: No GUI elements changed. Error details shown in the status message. Status bar remains the same.

  2. Switching of UI elements when back command is run

    1. Prerequisites: Run the goto 1 command. GUI displays dental record list.

      1. Test case: back
        Expected: Shows the GUI with a set of sample patients. An alert box prompts for confirmation. The window size may not be optimum. Use the command: goto 1 to revert to the dental record list.

      2. Test case: goto
        Expected: No GUI elements changed. Error details shown in the status message. Status bar remains the same.

G.3. Deleting a patient

  1. Deleting a patient while all patients are listed

    1. Prerequisites: List all patients using the list command. Multiple patients should be displayed in the list.

      1. Test case: delete 1
        Expected: First patient is deleted from the list. Details of the deleted patient shown in the status message. Timestamp in the status bar is updated.

      2. Test case: delete 0
        Expected: No patient is deleted. Error details shown in the status message. Status bar remains the same.

      3. Other incorrect delete commands to try: delete, delete x (where x is larger than the list size), delete y (where y is a negative number), delete z (where z is not an integer)
        Expected: Similar to previous.

G.4. Deleting a dental record

  1. Deleting a record while all dental records of a patient are listed

    1. Prerequisites: List all dental records of a patient using the goto x command (x refers to the index of the patient to be tested). Multiple dental records should be displayed in the list.

      1. Test case: recorddelete 1
        Expected: First dental record is deleted from the list. Details of the deleted dental record is shown in the status message. Timestamp in the status bar is updated.

      2. Test case: recorddelete 0
        Expected: No record is deleted. Error details shown in the status message. Status bar remains the same.

      3. Other incorrect recorddelete commands to try: recorddelete, record delete, recorddelete x (where x is larger than the list size), recorddelete y (where y is a negative number), recorddelete z (where z is not an integer)
        Expected: Similar to previous.

G.5. Adding a task

  1. Adding a new task while all tasks are listed

    1. Prerequisites: List all tasks using the tasklist command Tasks should all be displayed in the task list. No tasks with the exact same attributes as the test cases are present in the list.

      1. Test case: taskadd ti/Test task sd/today st/1200 et/1300 pri/HIGH
        Expected: Task with the title "Test task" is added for with the current date as the start date and end date, a start time of 1200, end time of 1300 and a high priority tag displayed in red, should be added at the bottom of the list of all tasks.

      2. Test case: taskadd ti/Test task 2 sd/11-05-2019 ed/16-05-2019 ti/Test task 2 st/1200 et/1300
        Expected: Task with the title "Test task 2" is added with a start date of 11-05-2019, an end date of 16-05-2019, a start time of 1200, end time of 1300 and a high priority tag displayed in red, should be added at the bottom of the list of all tasks.

      3. Test case: taskadd ti/Test task 2 st/1200 et/1300
        Expected: No task is added. Error details shown in the status message showing proper command usage.

      4. Test case: taskaddti/Test task 2 st/1200 et/1300
        Expected: No task is added. Error details shown in the status message stating an unknown command.

      5. Other incorrect taskadd commands to try: task add, taskad d ti/Test task sd/today st/1200 et/1300 pri/HIGH
        Expected: Similar to previous.

G.6. Editing a task

  1. Editing a new task while all tasks are listed

    1. Prerequisites: List all tasks using the tasklist command. There are more than 3 tasks present. Tasks should all be displayed in the task list. No tasks with the exact same attributes as the test cases are present in the list.

      1. Test case: taskedit 1 ti/New Edited Title
        Expected: First task in the list of tasks has its title changed to "New Edited Title".

      2. Test case: taskedit 2 ti/New Edited Title 2 sd/today ed/today
        Expected: Second task in the list of tasks has its title changed to "New Edited Title 2", its start date and end date changed to the current date.

      3. Test case: taskedit 3 ti/New Edited Title 3 sd/today ed/today st/1300 et/1200
        Expected: No task is edited. Error details shown in the status message stating that start time cannot be before end time if start date is the same as the end date.

      4. Test case: taskedit x ti/New Edited Title (where x is larger than the number of tasks listed)
        Expected: No task is edited. Error details shown in the status message stating that the task index provided is invalid.

      5. Test case: task edit 1 ti/New Edited Title 4
        Expected: No task is edited. Error details shown in the status message stating an unknown command.

      6. Other incorrect taskadd commands to try: tasedit, taskedit/ti/Test task sd/today st/1200 et/1300 pri/HIGH
        Expected: Similar to previous.

G.7. Deleting a task

  1. Deleting a task while all tasks are listed

    1. Prerequisites: List all tasks using the tasklist command Tasks should all be displayed in the task list.

      1. Test case: taskdelete 1
        Expected: First task is deleted from the list. Details of the deleted task are shown in the result display box.

      2. Test case: taskdelete 0
        Expected: No record is deleted. Error details shown in the status message showing invalid command format.

      3. Test case: taskdelete0
        Expected: No record is deleted. Error details shown in the status message stating an unknown command.

      4. Other incorrect recorddelete commands to try: taskdel, task delete
        Expected: Similar to previous.

G.8. Exporting data

  1. Exporting specific patients to a .json or .pdf file.

    1. Prerequisites: List all patients using the list command. At least one patient should be displayed in the list.

      1. Test case: export test.json 1
        Expected: A .json file named test.json is created/overwritten in the "data" folder. When opened in text viewer (like Notepad), only the patient that corresponds to index 1 in TeethHub should be present, along with all tasks.

      2. Test case: export test ! @ # $ % ^ & ( ) _ + - = { } [ ] ; ' , .json 1
        Expected: Same as previous.

      3. Test case: export test < > : " | ? *.json 1
        Expected: Error message Special characters such as  < : | ? * are not allowed." is shown in message box. No file is created/overwritten.

      4. Test case: export test.pdf 1
        Expected: A .pdf file named test.pdf is created/overwritten in the "data" folder. When opened in PDF viewer (like Adobe Acrobat Reader DC), only the patient that corresponds to index 1 in TeethHub should be present, along with all tasks.

      5. Test case: export test.json 0 or export test.json x where x is an index not present in the patient list
        Expected: A .json file named test.json is created in the "data" folder. When opened in text viewer (like Notepad), no patient should be present, but all tasks are.

      6. Test case: export test.txt 1
        Expected: Error message "Input file type is not a .json or .pdf." is shown in message box. No file is created/overwritten.

      7. Test case: export testfolder/test.json 1
        Expected: A folder named "testfolder" is created in the "data" folder and a .json file named test.json is created/overwritten in the "testfolder" folder. When opened in text viewer (like Notepad), only the patient that corresponds to index 1 in TeethHub should be present, along with all tasks.

      8. Test case: export testfolder\test.json 1
        Expected: Same as previous.

      9. Test case: export \testfolder/test.json 1
        Expected: Same as previous.

      10. Test case: export testfolder\\\\\\\test.json 1
        Expected: Same as previous.

    2. Prerequisites: List all patients using the list command. At least 3 patients should be displayed in the list.

      1. Test case: export test.json 1,3
        Expected: A .json file named test.json is created/overwritten in the "data" folder. When opened in text viewer (like Notepad), only the patients that correspond to index 1 and index 3 in TeethHub should be present, along with all tasks.

      2. Test case: export test.json 1-3
        Expected: A .json file named test.json is created/overwritten in the "data" folder. When opened in text viewer (like Notepad), only the patient that corresponds to index 1 to index 3 in TeethHub should be present, along with all tasks.

    3. Prerequisites: List all patients using the list command. At least 5 patients should be displayed in the list.

      1. Test case: export test.json 1,3,5
        Expected: A .json file named test.json is created/overwritten in the "data" folder. When opened in text viewer (like Notepad), only the patients that correspond to index 1, index 3 and index 5 in TeethHub should be present, along with all tasks.

      2. Test case: export test.json 1,3-5
        Expected: A .json file named test.json is created/overwritten in the "data" folder. When opened in text viewer (like Notepad), only the patient that corresponds to index 1 and index 3 to index 5 in TeethHub should be present, along with all tasks.

      3. Test case: export test.json 1-3-5
        Expected: Error message "Invalid index range! Please input a positive unsigned index range." is shown in message box. No file is created/overwritten.

      4. Test case: export test.json 1,,5
        Expected: Error message "Invalid index range! Please input a positive unsigned index range." is shown in message box. No file is created/overwritten.

      5. Test case: export test.json all
        Expected: A .json file named test.json is created/overwritten in the "data" folder. When opened in text viewer (like Notepad), all in TeethHub should be present, along with all tasks. Result should be the same as using save test.json.

G.9. Opening data

  1. Trying to open missing/corrupted data files.

    1. Prerequisites: Save or Export a .json file with save test.json. Open the .json file in the "data" folder with a text editor (like Notepad). Delete the first line and save.

      1. Test case: open test.json
        Expected: Error message "Data file is not in the correct format." is shown in message box.

    2. Prerequisites: Make sure there is no file in the "data" folder named "test.json".

      1. Test case: open test.json
        Expected: Error message "File not found!" is shown in message box. { more test cases …​ }