PROJECT: DiveLog

Overview

DiveLog is a command line driven desktop dive log application for divers to calculate their blood nitrogen levels prior to conducting dives. It was built to help divers plan so as to minimise the risk of decompression sickness. The app was originally based on Addressbook level 4 and written as part of the requirements for NUS CS2113T in semester 1 of AY2018/2019.

Summary of contributions

  • Major enhancement: added consistency check between dives

    • What it does: When a dive is added or edited, one must always check whether the dive is safe to attempt or not given previous diving history.

    • Justification: If a user enters a dive that is too long, deep or makes subsequent dives unsafe he or she must be warned.

    • Highlights: This enhancement affects both the add and edit command.

  • Minor enhancements: Added a set_units command which makes the app switch between feet and meters, integrated timezones into calculations, morphed the model.

  • Code contributed: RepoSense Dashboard

  • Other contributions:

    • Project management:

      • Managed releases v1.1 - v1.4 (4 releases) on GitHub

    • Enhancements to existing features:

      • Performed initial morph of model and UI. Added the DiveSession object. (Pull requests #1, #2, #3, #6)

      • Adapted old tests to make them work for the divelog. Including rewriting handles for GUI tests. (Pull request #28)

      • Initial implementation of look up tables for pressuregroup calculations (Pull request #38)

    • Community:

      • PRs reviewed (with non-trivial review comments): #119, #66, #125, #68,

      • Contributed to forum discussions (examples: #58, #62, #29)

      • Reported bugs and suggestions for other teams in the class (examples: 1 , 2, 3)

    • Documentation:

      • Documented a product survey in the appendix of the developer guide.

Contributions to the User Guide

Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project.

Adding a dive session: add

Use this command to adds your new dive session to the Dive Log.

Format: add ds/DATE_STARTED ts/TIME_DIVE_STARTED de/DATE_ENDED te/TIME_DIVE_ENDED ss/SAFETY_STOP d/DEPTH l/LOCATION

Words in UPPER_CASE are the parameters, items in SQUARE_BRACKETS are optional. Order of parameters are fixed as per the format above, meaning if you entered the ts/TIME_DIVE_STARTED before the ds/DATE_STARTED, the program will not be able to add your dive.

Put a ts/ before the time started(24 hours format - HHMM), te/time ended (24 hours format - HHMM), d/depth (in metres), l/ location (location name).

Example: add ds/04082018 ts/0900 de/04082018 te/1020 ss/1015 d/15 l/Bukit Lagoon tz/+8

A list of possible command flags is shown below.

Table 1. Table of Prefix and Data
Data Prefix Example

Date started / Ended (DDMMYYYY)

ds or de

ds/04082018 or de/05082018

Time Started / Ended (HHMM)

ts or te

ts/0800 or te/0900

Location

l

l/Sentosa

Depth (integer value). Depending on whether the app is set to use meters or feet, the value used will change.

d

d/18

Timezone (in UTC)

tz

tz/+12 or tz/-5

Adding a command may fail for a variety of reasons. This is to ensure your safety during dives. As a rule of thumb some reasons why a dive may not be added include:

  • The dive puts you outside the safety guidelines given by padi (i.e. its too deep or long).

  • The dive makes future dives too risky.

  • The dive timing overlaps with another dive.

  • Issues with the way you entered the command.

An important thing to note is that your safety stop time must be between your start and end time.

If for instance you enter an invalid dive, the system will tell you that you have dived to deep. Some common error messages are shown below:

Table 2. Common error messages (non-exhaustive list)
Error Messages Cause

The dive overlaps with another dive. Not updating the divelog.

There is another dive which is occuring at the same time.

Dive is too deep and too long!!

The dive you added is too long/deep given your current plans. This message is often accompanied by advice about how you can stay safe. Please follow it.

Invalid date format! (Not DDMMYYYY)

Date you entered is invalid. Make sure the date is in the form of DDMMYYYY

Invalid time format! (Not HHMM)

Time entered is in invalid format.

Start and End date or time or Safety Stop Time are not in chronological order! Start Date should be earlier than End Date! Safety Stop should be between Start and End time"

You need to make sure that your start time is before your end time and safety stop comes inbetween.

Setting display units set_units

If you’re not used to the metric or imperial system, set_units switches between meters and feet. (App defaults to meters) All data displayed will be automatically and accurately converted
Format: set_units meters|feet

  • As of v1.4 it updates both the DISPLAY units and the actual entered units.

  • The settings are not saved upon application restart.

  • Internally, we store everything in meters, as a result the xml file produced will always be in meters.

Example usage: set_units feet

Contributions to the Developer Guide

Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project.

Model component

ModelClassDiagram
Figure 1. Structure of the Model Component

API : Model.java

The Model,

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

  • stores the DiveLog data.

  • enforces relational rules between dive sessions (i.e Dives may not overlap). For more information on this take a look at the section on [CRUD].

  • exposes an unmodifiable ObservableList<DiveSession> 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.

Some choices made here include the encapsulation of even simple values such as depth which is stored as a DepthProfile object. By doing so suppose we want to extend Depth to support complex dives with multiple depths or different units, we can simply update DepthProfile without breaking existing code. Similarly, we also decided to have DiveSession implement java’s Comparable interface as it makes sorting the dives easier. Sorting the dives is an essential part of making sure that the dives retain their integrity.

A rough summary of what each class does is as follows:

Class(es) Role

DiveSession

Holds data about the dives. Provides a few getters and setters.

Time, OurDate, PressureGroup, DepthProfile, Location

Encapsulates simple properties of the dives

DiveSessionList

Stores a list of DiveSessions and provides properties of the list.

DiveLog

Handles updating the DiveSessionList.

VersionedDiveLog

Handles undo/redo feature and versioning.

Looking Up Dive Tables For Pressure Groups

At the heart of the divelog app is the ability to automatically calculate nitrogen pressure groups. To do so we adhere to PADI’s dive charts, available here https://elearning.padi.com/company0/tools/RDP_Table%20Met.pdf. These charts provide a lookup table for divers through which they can determine their current pressure group. The underlying implementation for these charts can be found in the PadiDiveTable class. A such the dive tales are stored in the resources folder as JSON files. They are loaded using the DiveTableUtil class via the FASTXML Jackson library. The PadiDiveTable itself is a singleton class. This choice was made as it makes sense to load the Dive Tables only once at the stat into memory (although in its current form it is not the case).

Design Considerations

Aspect: Retrieving Dive Tables
  • Alternative 1 (current choice): Use JSON files

    • Pros: Easy to change data within tables.

    • Cons: May have performance issues if the tables are too large as it reads from disk.

  • Alternative 2: Hard code the tables

    • Pros: Have mildly smaller impact on performance as data is loaded into memory at start up.

    • Cons: Very tedious to implement, results in unreadable code.

Aspect: Minimising File Reads And Data Duplication
  • Alternative 1 (current choice): Use a singleton class

    • Pros: It is possible to read the file only once and the memory used by the objects will only exist in one place. This saves both memory and disc writes.

    • Cons: Can be troublesome to implement.

  • Alternative 2: Just use PADIDiveTable standard classes

    • Pros: Easy to implement

    • Cons: Wastes memory and performs unnecessary IO.

Localized Units

As of v1.3, a set_units command was added. This allows the user to switch between meters and feet. As shown in the diagram below, the implementation of this command involves altering the ApplicationState singleton. Once the ApplicationState is updated a UnitsChangedEvent is posted to the EventsCenter. The EventsCenter issues a call to the UI. The relevant parts of the UI will be forced to re-render themselves to match the new units.

setunitsSequenceDiagram

As of v1.4 the ParserUtil#parseDepthProfile(String depth) also reads this state, and converts the units to meters. Internally all the units are stored as meters. This choice was largely made to keep things simple. The ApplicationState also exposes a function to get the current units setting.

Consistency checks for DiveSession objects during Creation/Update

At its heart, the application is a dive log. The source code for this application was adopted from an addressbook application. The addressbook has a relatively simple set of rules, DiveLog on the other hand has a more complicated set of rules which have to be followed. This is because the starting pressure group on each dive is dependent on the previous dive’s end time and ending pressure group. In turn, the ending pressure group is dependent on the starting pressure group and depth of a dive. This poses a lot of problems as a single update to the system requires recalculating all subsequent dive’s pressure groups. Furthermore, it is only after all calculations are complete that the system can make sure that the update is valid. Some possible sources of inconsistency include:

  • Dives with overlapping time periods - these cause problems in the chain as the PADI tables are only meant to be used by a single diver doing one dive at a time.

  • Dives that are deemed too deep and too long by PADI - we have no way of determining their nitrogen level apart from the tables.

  • Dives which cause other dives to become too dangerous. Lets say you add a dive in between two dives. This may cause the later dive to be deemed as too dangerous.

At the end we have incorporated most of the code into our model. For add and edit we required atomicity thus the general flow is as follows:

  1. Create a temporary copy of the DiveSessionList.

  2. Perform the update/addition on this list and sort it to make calculations easier.

  3. Check if there are any overlapping dives

    1. If there are overlapping dives throw DiveOverlapsException.

  4. Recalculate pressure groups and check for consistency

    1. If not consistent throw LimitExceededException error.

  5. Commit new DiveSessionList to divelog. Update VersionedDivelog 's current pointer.

The flow can be seen in the sequence diagram below:

width: 650px

In terms of calculations, the pressure group properties of the DiveSession objects themselves will mutate.

Design Considerations

Upon failure of calculations it is important that the divelog remains in a consistent state. For instance, when updating the dive log if after adding a dive we see that it is causing issues in the dive log, or if we edit a dive to have overlapping time periods with another dive, the divelog should roll back the latest transaction and throw an error. This calls for atomicity in our Create, Update and Delete. Fortunately, in our case, deleting the dive cannot actually cause a violation as it only makes the dives safer. Thus our design considerations could be summed up as follows:

  • Ensure consistency in the divelog

  • Ensure creating and Updating the divelog are atomic

Aspect: Ensure consistency in the divelog
  • Alternative 1: implementing rules for consistency in logic module.

    • Pros: Stronger Separation of Concerns.

    • Cons: Will require duplicating code accross AddCommand and EditCommand. This will lead to inconsistent behaviour between the two and more lines of code to debug.

  • Alternative 2: implementing rules for consistency in model module.

    • Pros: Only one code base to debug. System will remain consistent.

    • Cons: Weaker separation of concerns than if implemented separately. Can also mean a bug effects both add and edit making the system unreliable.

Aspect: Ensure creating and updating the divelog are atomic
  • Alternative 1: Creating a temporary copy of the list and mutating the copy

    • Pros: Simple to implement and fast.

    • Cons: Inefficient

  • Alternative 2: Having a separate data structure that only handles mutation after the edited dive.

    • Pros: Will be more efficient.

    • Cons: More code will need to be written. Since VersionedDiveLog already maintains copies, it is just a matter of passing the reference of the temporary dive list to the system. Furthermore, most divers will not have more than 100 dives.