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
andedit
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:
-
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.
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:
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
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
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 |
---|---|
|
Holds data about the dives. Provides a few getters and setters. |
|
Encapsulates simple properties of the dives |
|
Stores a list of |
|
Handles updating the DiveSessionList. |
|
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.
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:
-
Create a temporary copy of the
DiveSessionList
. -
Perform the update/addition on this list and sort it to make calculations easier.
-
Check if there are any overlapping dives
-
If there are overlapping dives throw
DiveOverlapsException
.
-
-
Recalculate pressure groups and check for consistency
-
If not consistent throw
LimitExceededException
error.
-
-
Commit new
DiveSessionList
to divelog. UpdateVersionedDivelog
's current pointer.
The flow can be seen in the sequence diagram below:
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
andEditCommand
. 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.
-