Developer Guide
- About
- Acknowledgements
- Getting started
- Design
- Feature Implementations
- Documentation, logging, testing, configuration, dev-ops
- Appendix: Requirements
- Appendix: Instructions for manual testing
- Appendix: Planned Enhancements
About
Fish Ahoy! is a desktop CLI-focused application, designed to help users take better care of their aquatic pets. It allows fish keepers to:
- Keep track of their tanks and fishes in a hierarchical view, sorted by tanks.
- Keep track of their fishes’ attributes such as the last time it was fed and how often it needs to be fed
- Consolidate fish-up-keeping tasks and automatically remind users to feed their fish according to information given by the user
This developer guide aims to provide instructions and guidelines for developers to understand how to effectively use and contribute to this project by explaining design considerations for certain key features. Moreover, new developers can use this guide as an entry point for navigating this extensive code base.
Acknowledgements
- This project is based on the AddressBook-Level3 project created by the SE-EDU initiative.
- Task implementation was inspired by SoConnect and fxml file was reused. However, our implementation is entirely new.
- Help implementation (including the code) was reused with minimal changes from the CLIMods project. Html files were not reused.
Getting started
Refer to the guide Setting up and getting started.
Design
.puml
files used to create diagrams in this document can be found in the diagrams folder. Refer to the PlantUML Tutorial at se-edu/guides to learn how to create and edit diagrams.
Architecture
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main
has two classes called Main
and MainApp
. It is responsible for,
- At app launch: Initializes the components in the correct sequence, and connects them up with each other.
- At shut down: Shuts down the components and invokes cleanup methods where necessary.
Commons
represents a collection of classes used by multiple other components.
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.
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 fish delete 1
.
Each of the four main components (also shown in the diagram above),
- defines its API in an
interface
with the same name as the Component. - implements its functionality using a concrete
{Component Name}Manager
class (which follows the corresponding APIinterface
mentioned in the previous point.
For example, the Logic
component defines its API in the Logic.java
interface and implements its functionality using the LogicManager.java
class which follows the Logic
interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component’s being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
UI component
The API of this component is specified in Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, MainContent
, StatusBarFooter
etc. All these, including the MainWindow
, inherit from a generic abstract UiPart
class (not shown above) which captures the commonalities between classes that represent parts of the visible GUI.
The UI
component uses the JavaFx UI framework. The layout of these UI parts are defined in matching .fxml
files that are in the src/main/resources/view
folder. For example, the layout of the MainWindow
is specified in MainWindow.fxml
The UI
component,
- executes user commands using the
Logic
component. - listens for changes to
Model
data so that the UI can be updated with the modified data. - keeps a reference to the
Logic
component, because theUI
relies on theLogic
to execute commands. - depends on some classes in the
Model
component, as it displaysFish
,Tank
, andTask
objects residing in theModel
. - does not depend on the
Storage
component, as it does not read or save data to files.
Displaying Panels
All information about the fish, tanks, and tasks are displayed in their respective panels.
These panels have a ListView
that displays the information in a scrollable list.
The ListView
is populated by an ObservableList
that is stored in the Model
component.
The ListView
is automatically notified when the ObservableList
is updated, so that the ListView
can be updated with the new information.
This is not triggered if any items inside the ObservableList
are modified, so the Logic
component calls the refresh
functions of each pane when a command is executed.
Shown below is the class diagram for all the relevant classes for a TankListPanel
. The other panels are similar.
TankListPanel
has a ListView
that displays the TankCard
objects. Since these are custom objects, they require a custom cell factory to be created.
TankListViewCell
is the custom cell factory that creates the TankCard
objects.
All the required JavaFX components are described in the corresponding TankListCard.fxml
file.
Displaying Tank Readings
Unlike the rest of the UI, the charts used to display the tank readings are not described in any .fxml
file.
Instead, they are created in the TankCard
class. This was done to allow for greater flexibility in terms of which chart should be displayed for any given tank.
While this version of the app does not have this functionality, future versions of the app could allow the user to choose which chart to display for each tank.
It may also allow the user to add custom tank readings based on the equipment that they have.
To populate the charts, the TankCard
class uses the Tank
object stored in the Model
component passed to it by the TankListPanel
.
Each of the three types of readings has their own ObservableList
which is then looped through to create the data points for the chart.
A possible improvement to the way that readings are currently stored is to use a HashMap
instead of three separate ObservableList
s.
This could allow the user to supply their own types of readings (which would be the key of the HashMap
) and the corresponding values.
Combining this with the ability to choose which chart to display for each tank would allow for a lot of flexibility in terms of how the user can view their tank readings.
However, this would requite significant changes to the way readings are stored. It might require a rework of the entire readings
package to make it more flexible.
Logic component
API : Logic.java
Here’s a (partial) class diagram of the Logic
component:
How the Logic
component works:
- When
Logic
is called upon to execute a command, it uses theAddressBookParser
class to parse the user command. - This results in a
Command
object (more precisely, an object of one of its subclasses e.g.,AddCommand
) which is executed by theLogicManager
. - The command can communicate with the
Model
when it is executed (e.g. to add a fish). - The result of the command execution is encapsulated as a
CommandResult
object which is returned back fromLogic
.
The Sequence Diagram below illustrates the interactions within the Logic
component for the execute("fish delete 1")
API call.
FishDeleteCommandParser
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Here are the other classes in Logic
(omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
- When called upon to parse a user command, the
AddressBookParser
class creates anXYZCommandParser
(XYZ
is a placeholder for the specific command name e.g.,FishAddCommandParser
) which uses the other classes shown above to parse the user command and create aXYZCommand
object (e.g.,FishAddCommand
) which theAddressBookParser
returns back as aCommand
object. - All
XYZCommandParser
classes (e.g.,FishAddCommandParser
,TaskDeleteCommandParser
, …) inherit from theParser
interface so that they can be treated similarly where possible e.g, during testing. - The 3 main types of
XYZCommandParser
are divided into command parsers forFish
,Tank
andTask
.
Model component
API : Model.java
The Model
component,
- stores the address book data i.e., all
Fish
objects (which are contained in aUniqueFishList
object). - stores the currently ‘selected’
Fish
objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiableObservableList<Fish>
that can be ‘observed’ e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. - stores a
UserPref
object that represents the user’s preferences. This is exposed to the outside as aReadOnlyUserPref
objects. - does not depend on any of the other three components (as the
Model
represents data entities of the domain, they should make sense on their own without depending on other components) - the
Task
andTank
model is implemented similarly to theFish
part of the model.
Tag
list in the AddressBook
, which Fish
references. This allows AddressBook
to only require one Tag
object per unique tag, instead of each Fish
needing their own Tag
objects.Storage component
API : Storage.java
The Storage
component,
- can save both address book data and user preference data in json format, and read them back into corresponding objects.
- inherits from both
AddressBookStorage
andUserPrefStorage
, which means it can be treated as either one (if only the functionality of only one is needed). - depends on some classes in the
Model
component (because theStorage
component’s job is to save/retrieve objects that belong to theModel
)
Common classes
Classes used by multiple components are in the seedu.addressbook.commons
package.
Feature Implementations
This section describes some noteworthy details on how certain features are implemented.
Automatic Feeding Reminders
Implementation
The entrypoint of this feature is in the start()
method of MainApp, which is automatically called when the user opens
Fish Ahoy!. We then access the Logic
component to access the Model
component to find out which Tank
have unfed
Fish
. For each tank with unfed Fish
, we create a TaskFeedingReminder
. We then return an ArrayList
of
TaskFeedingReminders
as feedingReminders
. In the Logic
component, we create a TaskFeedingReminderCommand
for
each TaskFeedingReminder
, then execute these commands, updating the Model
component before saving the states
if the various lists.
Design considerations:
- Alternative 1: Create a command parser and other relevant files to allow the user to execute this command
- Pros: user can update Reminders with commands like other features
- Cons: will be redundant most of the times as Fish feeding intervals are not that short. Even if user calls this command, the reminders likely do not need to be updated.
FishSortCommand feature
Implementation
The fish sorting feature leverages SortedList
functionality of Javafx. By creating custom comparators to compare fish
attributes, we are able to use a SortedList
to sort its list by the specified order.
Specifically, it currently sorts by the five compulsory fields of a fish:
- Name
- Last Fed Interval
- Species
- Feeding Interval
- Tank
Currently, upon instantiation of ModelManager
, it creates a SortedList
from a AddressBook
.
A Filteredlist
is created based off the same SortedList
. Hence, when we perform sorting operations, we are able to
manipulate and display the filtered list. As a result, we are able to sort the fish list and filter at the same time.
Given below is an example usage scenario and how the sort mechanism behaves at each step.
Step 1. The user is currently using the application, and there are three entries currently existing in the
AddressBook
, Marlin, Nemo, Dory
, added in that order to a tank, named Ocean tank
.
Step 2. The user executes fish sort by/n tk/1
. FishParser
receives the sort
keyword and calls FishSortCommandParser#parse()
,
in which the keyword n
is used to select a Comparator. In this case, the NameComparator
, which compares the names between fish,
is passed to FishSortCommand
and returned.
Step 3. FishSortCommand#execute()
first calls Model#sortFilteredFishList()
, which in turn calls SortedList#setComparator()
.
This call triggers the SortedList to sort the current list using the given comparator. In this case, Marlin, Nemo, Dory
sorts into Dory, Marlin, Nemo
.
Step 4. FishSortCommand#execute()
then retrieves the associated tank if tk/
is present. In this case, a tank index of 1
is received. The Ocean tank
is then retrieved through Model#getFilteredTankList()
and List<Tank>#get()
.
Step 5. FishSortCommand#execute()
then calls Model#updateFilteredFishList()
, which sets a predicate to only display fish in Ocean tank
.
Step 6. FishSortCommand#execute()
then finally returns and triggers a GUI change in MainWindow
to display the sorted FilteredFish
of Dory, Marlin, Nemo
.
Design considerations:
Aspect: Where Sorting takes place :
-
Alternative 1 (current choice): Use a SortedList and comparators to sort within the list.
- Pros: Easy to implement.
- Cons: Requires a separate list or wrapping.
-
Alternative 2: Sorts a list externally before replacing the
AddressBook
list.- Pros: More customization and control over sorting.
- Cons: Requires a duplicate list to be made each time.
TaskAddCommand feature
Motivation
As a FishTracker application, on top of tracking fishes, we provide a way for fishkeepers to keep track of their tasks such as changing of tank water or repairing certain tank equipments
Implementation
Adding a task can be done via the command “task add”. You are able to specify paramters like:
- Description
- Tank Index (to relate task to a tank)
- Priority Level (Low / Medium / High)
How TaskAddCommand
is executed
When the command task add <description>
is invoked it first goes through the main parser AddressBookParser
, after
which it is delegated to command-specific parsers, namely TaskCommandParser -> TaskAddCommandParser.
Thus, LogicManager
will invoke execute
on TaskAddCommand
with the following code
command.execute(model);
, where model.addTask(taskToAdd)
will be called.
Design Considerations
- Alternative 1 (current choice): Create a parser for task commands and a taskAdd parser for add-specific task commands
- Pros: Easy to manage TaskList functionalities.
- Cons: May have performance issues due to numerous calls down command-specific parsers
- Alternative 2 : Do not abstract task command parsing to a
TaskParser
- Pros: Easy to implement
- Cons: Not well abstracted. Difficult to implement other task commands in the future like marking / deleting of tasks. Will also result in an extremely long parser of all types of commands which may be difficult to maintain.
TankFeedCommand feature
Motivation
As a FishTracker application, we provide a way for fishkeepers to track the LastFedDate
of all their fishes,
as having multiple tanks and fishes makes feeding difficult to keep track of without a tracking system.
Implementation summary
As such, every Fish
contains a LastFedDateTime
object, which contains a date field which records their lastFedDate
.
When the fishkeeper decides to feed a particular tank by invoking the command tank feed <index>
,
the program will feed all fishes in that tank, changing LastFedDate
of all fishes in that tank.
How TankFeedCommand
is executed
When command tank feed <index>
is invoked, it first passes through the main parser AddressBookParser
,
before being delegated to command-specific parsers, namely TankParser
-> TankFeedCommandParser
,
which returns a TankFeedCommand
to LogicManager
.
With this, LogicManager
will invoke execute
on TankFeedCommand
with the following code command.execute(model);
,
where model.setLastFedDateFishes(tankToFeed, formattedDate);
will be called.
The setLastFedDateTimeFishes(tankToFeed, formattedDate)
function will be called down the chain of classes
[ModelManager
-> Tank
-> AddressBook
-> UniqueFishList
].
UniqueFishList#setLastFedDateFishes(String newDate)
will then call
internalList.stream().forEach(fish -> fish.setLastFedDateTime(newDate));
,
creating a new stream from internalList
containing references to all Fish objects in internalList
.
Every Fish
object will call fish.setLastFedDateTime(newDate)
, where a new LastFedDateTime
object with the updated date
will be created and replace the Fish
’s lastFedDateTime
field.
Design Considerations
- Alternative 1 (current choice): Create a setLastFed(tankToFeed, formattedDate) which allows for flexible date
- Pros: Allows for flexible setting of
LastFedDateTime
for future implementations. - Cons: Higher chance of bugs as function allows for variability of date-time.
- Pros: Allows for flexible setting of
- Alternative 2: Create a setLastFedToNow(tankToFeed) which hard codes
LastFedDateTime
to current date time- Pros: Straightforward behaviour of function, since
TankFeedCommand
sets theLastFedDateTime
to the current date-time. - Cons: Not flexible, may not be useful for future implementations which requires variable settings of date-time.
- Pros: Straightforward behaviour of function, since
Documentation, logging, testing, configuration, dev-ops
Appendix: Requirements
Product scope
Target user profile:
This product is for fish enthusiasts who wants to keep track of their numerous fishes’ details, such as their health, tank status, feeding requirements etc.
Value proposition:
This product makes it easier for fish keepers to keep track of numerous tasks to maintain the health of the fish and fish tanks. There are many fine details that go into maintaining an environment for live aquatic animals, and this product will help track them.
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 | be able to create weekly tasks | have a record of my tasks throughout the week |
* * * |
user | keep track of the individual tanks | so that I can manage them in the future |
* * * |
user | access a local fish database | know more about my fish |
* * * |
user | delete my fish from the system easily | get rid of dead fish |
* * * |
user | delete a task | check off completed tasks |
* * * |
user | delete a tank | keep my tank status updated if I retire a tank |
* * * |
user | view all my fishes at a glance | keep track of their numbers |
* * * |
user | view my weekly tasks I need to fulfill for each of my fish tanks | not miss any tasks throughout the week |
* * * |
user | transfer my data from one system to another | change my systems |
* * * |
fish keeper | delete fish from a tank | move them to different tanks |
* * * |
fish keeper | know which food can be fed to each fish | keep them well fed |
* * * |
owner of multiple fish tanks | know the number and type of fish in each tank | monitor their condition |
* * |
fish keeper | know the number of fish for each species | know if any species requires help |
* * |
fish keeper | know the health conditions of my fish | be updated on their conditions |
* * |
fish keeper | attach a picture to the fish’s entry | identify it and track it’s growth |
* |
expert user | customize my entries fields | suit the app to my use |
* |
inexperienced fish keeper | know what are the required equipment for keeping fish | my fish won’t die |
* |
inexperienced fish keeper | know what equipment is compatible with my tanks | |
* |
fish keeper | know the conditions of my equipment | maintain them |
* |
fish keeper | know ammonia levels in fish tanks | adjust the amount at the end of every week |
* |
fish keeper | know nitrate levels in fish tanks | adjust the amount at the end of every week |
Use cases
(For all use cases below, the System is *Fish Ahoy!*
and the Actor is the user
, unless specified otherwise)
Use case: Add a fish
MSS
- User adds a fish
-
Fish is added to Fish Ahoy!
Use case ends.
Extensions
-
1a. User specifies a tank.
- 1a1. Fish is added to the tank.
Use case ends.
Use case: Add a tank
MSS
- User adds a tank
- Tank is added to Fish Ahoy!
Use case ends.
Use case: Add a task
MSS
- User adds a task
-
Task is added to Fish Ahoy!
Use case ends.
Extensions
-
1a. User specifies if the task is a
todo
.- 1a1.
todo
task is created.
Use case ends.
- 1a1.
-
1b. User specifies if the task is a
deadline
.-
1b1.
deadline
task is created.Use case ends.
-
-
1c. User specifies if the task includes a
fish
.-
1b1. task is created and
fish
is tagged to it.Use case ends.
-
Non-Functional Requirements
- Should work on any mainstream OS as long as it has Java
11
or above installed. - Should be able to hold up to 1000 fish, tanks and tasks without a noticeable sluggishness in performance for typical usage.
- 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.
- A user with average experience of CLI should be able to use the system.
Glossary
- Mainstream OS: Windows, Linux, Unix, OS-X
- Private contact detail: A contact detail that is not meant to be shared with others
- Fish: A fish owned by the user to be added to Fish Ahoy!
- Tank: A fish tank owned by the user to house fish to be added to Fish Ahoy!
- Task: A weekly task of the user regarding fish-keeping to be added to Fish Ahoy!
Appendix: Instructions for manual testing
Given below are instructions to test the app manually.
Launch and shutdown
-
Initial launch
-
Download the jar file and copy into an empty folder
-
Double-click the jar file Expected: Shows the GUI with a set of sample data. The window size may not be optimum.
-
-
Saving window preferences
-
Resize the window to an optimum size. Move the window to a different location. Close the window.
-
Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-
Deleting a fish
-
Deleting a fish while all fish are being shown
-
Prerequisites: List all fish using the
list fishes
command. Multiple fish in the list. -
Test case:
fish delete 1
Expected: First fish deleted from the list. Details of deleted fish shown in status message. -
Test case:
fish delete 0
Expected: No fish deleted. Error details shown in status message. Fish panels remains the same. -
Other incorrect delete commands to try:
fish delete
,fish delete x
,...
(where x is larger than the list size)
Expected: Similar to previous.
-
Readings graph feature
- Checking that graphs of each tank can display readings correctly
- Prerequisites: The tanks are the default sample tanks, “freshwater tank” and “saltwater tank”
- Test case: paste the following test values into
readings.json
:
{
"readingLevels" : [ {
"tankName" : "freshwater tank",
"commaSeperatedValuesAmmonia" : "0.2,0.3,0.0",
"commaSeperatedDatesAmmonia":"03/04/2023 14:00,04/04/2023 20:01,05/04/2023 14:02",
"commaSeperatedValuesPH": "7.1,6.8,7.0",
"commaSeperatedDatesPH" : "03/04/2023 14:00,04/04/2023 20:01,05/04/2023 14:02",
"commaSeperatedValuesTemp" : "26.0,25.0,26.2",
"commaSeperatedDatesTemp":"03/04/2023 14:00,04/04/2023 20:01,05/04/2023 14:02"
}, {
"tankName" : "saltwater tank",
"commaSeperatedValuesAmmonia" : "",
"commaSeperatedDatesAmmonia" : "",
"commaSeperatedValuesPH" : "",
"commaSeperatedDatesPH" : "",
"commaSeperatedValuesTemp" : "",
"commaSeperatedDatesTemp" : ""
} ]
}
Expected:
Warning: If you want to test with more of your own values, you should ensure all 3 types of readings come as a set e.g. if any readings were to be made, it would be all 3 and they are made at the same time as the app ensures this
Saving data
-
Dealing with missing/corrupted data files
- Missing data files are automatically created. If data files are corrupted, delete all data files so new ones will be created, containing our sample data.
Appendix: Planned Enhancements
- The current error message for an invalid index is too general. We plan to make the error message also more accurate
by filtering by why the index is invalid.
- [-MAX_INT….0]: Index must be a positive non-zero integer
- [4..MAX_INT]: Index is out-of-bounds. Index must correspond to a valid tank.
- The current parameter parsing does not check for nonsensical or invalid values. We plan do execute sanity checks to
protect the user from receiving unintentional results for the following parameters:
- Last fed dates: Check for invalid dates like 30th February and reject them
- Feeding interval: While it makes sense that a feeding interval can be 0 days and 25 hours, it
makes our
fish sort by/fi
less intuitive as ours sorts by days then hours.0d25h
will appear before1d0h
. We can automatically convert feeding intervals such that hours is less than 24 in future iterations
-
Alphanumeric fields may be truncated if they are too long as we do not limit their length. They may also become truncated if the user’s app window size is too small. A side effect of this can be for example, fish with long names, which are supposed to be distinct, will appear as the same after truncation. We will limit the length of such parameters in future enhancements to avoid this.
-
Logging messages on the terminal are not optimized for Fish Ahoy! We plan to improve this by simple changes from “Addressbook” to “Fish Ahoy”, as well as fixing the fxml versions in fxml files. Furthermore, we can add more useful logging messages related to Fish Ahoy commands such as information on newly added commands, so the user may gain more information. For instance, Tank readings could display the current list of values, so the user can understand the graph.
-
Make the offline version of the User Guide more user-friendly by adding a css file for style. Also fix links so that they work for navigation.
- Current command semantic is not flexible. For example, when adding a fish, 5 values have to be filled by the user. We can improve this by making certain fields optional, such as Last Fed Date (assumes last fed date is the date time of addition).