Developer Guide
Table of Contents
- Acknowledgements
- Setting up, getting started
- Design
- Implementation
- Documentation, logging, testing, configuration, dev-ops
- Appendix: Requirements
- Appendix: Instructions for manual testing
Acknowledgements
- Gim is adapted from the AddressBook-Level3 project created by the SE-EDU initiative.
- Libraries used: JavaFX, Jackson, JUnit5
Setting up, 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 :del 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
, ExerciseListPanel
, StatusBarFooter
etc. All these, including the MainWindow
, inherit from the abstract UiPart
class which captures the commonalities between classes that represent parts of the visible GUI.
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 displaysExercise
object residing in theModel
.
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 theExerciseTrackerParser
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 an exercise). - 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(":del 1")
API call.
DeleteCommandParser
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
ExerciseTrackerParser
class creates anXYZCommandParser
(XYZ
is a placeholder for the specific command name e.g.,AddCommandParser
) which uses the other classes shown above to parse the user command and create aXYZCommand
object (e.g.,AddCommand
) which theExerciseTrackerParser
returns back as aCommand
object. - All
XYZCommandParser
classes (e.g.,AddCommandParser
,DeleteCommandParser
, …) inherit from theParser
interface so that they can be treated similarly where possible e.g, during testing.
Model component
API : Model.java
The Model
component,
- stores the exercise tracker data i.e., all
Exercise
objects (which are contained in aExerciseList
andExerciseHashMap
object). - stores the currently ‘selected’
Exercise
objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiableObservableList<Exercise>
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)
Storage component
API : Storage.java
The Storage
component,
- can save both exercise tracker data and user preference data in json format, and read them back into corresponding objects.
- inherits from both
ExerciseTrackerStorage
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 gim.commons
package.
Implementation
This section describes some noteworthy details on how certain features are implemented.
Exercise
Implementation
An Exercise
is stored in ExerciseList
and ExerciseHashmap
of Model
An Exercise
contains the following attributes:
- a
Name
, which represents the name of the Exercise - a
Weight
, which represents the total weight used for a certain Exercise - a
Reps
, which represents the number of times a specific exercise was performed - a
Sets
, which represents the number of cycles of reps that was completed - a
Date
, which represents the date an exercise was performed
Date Implementation
The default format for date follows dd/MM/uuuu
. uuuu
is chosen over yyyy
because this avoids unexpected exceptions
under strict parsing by the Java API DateTimeFormatter
, such as those exceptions related to year-of-era.
The validity of a given date string depends on two factors: (1) Regex formatting and (2) Non-existent date checking.
-
For the regex formatting, the Singleton class
RegexList
contains aList<String>
calledregexList
that contains the accepted regex strings. The methodisValidDateByRegex
iterates throughregexList
to check whether a given date string follows any of the accepted regex strings. -
For the non-existent date checking, the Singleton class
FormatterList
contains aList<String>
calledformatterList
that contains the accepted date patterns. The private constructor of this class uses stream mapping to obtain aList<DateTimeFormatter>
calledformatterList
containingDateTimeFormatter
objects that follows strict date validity check usingResolverStyle.STRICT
.
The Singleton pattern is used here to prevent multiple instantiation of this class.
- The drawbacks of using this pattern is not really pronounced as there are not many classes having them as dependencies (only the
Date
class). Therefore, coupling within the code base will not increase much. - Testing will not be affected by the fact that singleton objects carry data from one test to another because there is no mutation
of data inside the singleton objects
RegexList
andFormatterList
. All tests will have the same singleton objects used.
Design Considerations
Aspect: Fields of Exercise are Final
-
Current choice: The aforementioned fields in
Exercise
are final, effectively making our Exercise class immutable.- Rationale: Code written with immutable objects is easier to reason with and easier to understand, facilitating a smoother process when it comes to debugging and testing any code related to
Exercise
.
- Rationale: Code written with immutable objects is easier to reason with and easier to understand, facilitating a smoother process when it comes to debugging and testing any code related to
Exercise Hashmap
Implementation
The Exercise Hashmap stores data in the form of a hashmap, where the key of the hashmap is the Name
of an Exercise
and its associated value is an Exercise
ArrayList, containing a list of exercises (with the same name).
Design Considerations
Aspect: Choosing the Data Structure
-
Current choice: We decided to use a hashmap data structure.
- Rationale: We wanted to create associations between exercises with the same name. Utilising a hashmap structure, we can easily identify and retrieve exercises with the same exercise name (by their unique key identifier). Hence, this facilitates commands that rely on this retrieval to be implemented, such as Listing of Personal Records and Generating a suggested workout routine.
Sorting Exercise List
Implementation
The sorting of exercise list is facilitated by ModelManager
which implements Model
. ModelManager
contains a filteredExercises
list which is the list of exercises in a FilteredList
‘wrapper’ from javafc.collections.transformation
. filteredExercises
gets the list of exercises to be displayed from method getExerciseList()
in ExerciseTracker
.
ExerciseTracker
has method sortDisplayedList()
which calls sortDisplayedList()
in ExerciseList
.
ExerciseList
contains a displayedList
of type ObservableList<Exercise>
and is the list that will be displayed by the Ui
.
It is a duplicated copy of the internalUnmodifiableList
of type unmodifiableObservableList
. ExerciseList
has method
sortDisplayedList()
which sorts the displayedList
by order of date using the sort()
method in java.util.Collections
with a Comparator<Exercise>
.
Execution
When the command :sort
is entered, the Ui
sends the command to Logic
. Logic
parses and identifies the :sort
command that was entered, and creates
an instance of it. Logic
then executes the command. Model
will have the displayed list sorted and the sorted list will be displayed by Ui
.
Example Usage
Given below is an example usage scenario and how the sorting mechanism behaves at each step.
Step 1: The user launches the application which loads the set of exercises previously keyed. displayedList
will be initialised
to be the same as the internalUnmodifiableList
in ExerciseList
where the exercises are sorted by the date of input.
Step 2: The user executes :sort
command to sort the exercises based on date of exercises done. The ExerciseTrackerParser
identifies that the command is a SortCommand
. The command calls Model
to sortDisplayedList
and the Ui
displays the
displayedList
which has the exercises sorted by their respective dates.
The following sequence diagram shows how the sort command is executed.
Design considerations
Aspect: Displayed List structure
-
Current choice:
displayedList
is a duplicated copy of the list of exercises ininternalUnmodifiableList
of typeUnmodifiableObservableList
inExerciseList
class- Rationale: The sort command will sort the
diplayedList
, not affecting theinternalUnmodifiableList
. This allows users to view the sorted list of exercises while maintaining a defensive copy of exercises keyed by user.
- Rationale: The sort command will sort the
Aspect: Open-Closed Principle
-
Current choice:
sortDisplayedList()
inExerciseList
sorts thedisplayedList
using thesort()
method injava.util.Collections
and aComparator<Exercise>
.- Rationale: Using a
Comparator<Exercise>
insort()
allows one to extend thesortDisplayedList
method to accommodate other sorting orders by simply changing theComparator<Exercise>
used should the sorting criteria change in the future.
- Rationale: Using a
Viewing exercises within a date range
Implementation
-
ExerciseTrackerParser
callsRangeCommandParser#parse
. -
RangeCommandParser#parseArguments
will return an enum typeVariation
according to the arguments in theArgumentMultimap
created. - When we obtain
Variation.ONE
,RangeCommandParser#getVariationOne
will be called.- In this case, the arguments expected to be inside the
ArgumentMultimap
will be the start date and end date.
- In this case, the arguments expected to be inside the
- When we obtain
Variation.TWO
,RangeCommandParser#getVariationTwo
will be called.- In this case, we expect the number of days to be the only argument inside
ArgumentMultimap
.
- In this case, we expect the number of days to be the only argument inside
-
ParserUtil#parseDate
will be called to obtain theDate
object(s). -
RangeCommand
is returned. - Then, the
execute()
method of the resultingRangeCommand
object will be called, returning aCommandResult
object with the appropriate message.
Execution
When the command :range start/START_DATE end/END_DATE
or :range last/NUMBER_OF_DAYS
is entered, the Ui
sends
the command to Logic
. LogicManager
parses and identifies the :range
command that was entered, and creates an instance of it.
LogicManager
then executes the command by calling execute()
in RangeCommand
.
RangeCommand
will call the method sortFilteredExerciseList(predicate)
defined in Model
. This will cause
filteredExercises
in ModelManager
to be filtered according to the predicate DateWithinRangePredicate
.
The resulting list will only include exercise entries that have dates between the start date (inclusive) and
end date (inclusive), arranged from the most recent first. If two exercise entries have the same date,
they will be sorted in alphabetical order.
Model
will have the list sorted and the sorted list will be displayed by Ui
.
Example Usage
Given below is an example usage scenario and how the date range view mechanism behaves at each step.
Step 1: The user launches the application which loads the set of exercises previously keyed.
Step 2: The user executes :range start/10/10/2022 end/15/10/2022
command to view all the exercises done between
10 October 2022 and 15 October 2022. The ExerciseTrackerParser
identifies that the command is a RangeCommand
.
The command calls Model
to sortFilteredExerciseList(predicate)
and the Ui
displays the filteredExercises
list which has all the exercises between the specified dates.
The following sequence diagram shows how the date range process is executed.
Design considerations
Aspect: Simplicity of command design
-
Current choice: The two variations of the
:range
command uses the same keyword but with different expected arguments. This reduces the number of command keywords the user needs to remember when using the app. -
Alternative choice: Instead of using
:range
for both variations, another keyword such as:rangeLast
could be used in addition to:range
. The drawback for this design choice is that it goes against the simplicity of command design where the set of command keywords should be minimised where possible.
Aspect: Enum types are used to indicate the variations
-
Current choice: Within
RangeCommandParser
, the enumVariation
has only two values:ONE
andTWO
to represent:range
command variation one and two respectively. This ensures that the value representing the command variation cannot be anything not defined in the enum type. Adding new variations is also easily done by adding another value inside the enum type.
Listing of Personal Records
Implementation
The mechanism for listing Exercise personal record(s) is facilitated by PrCommand
, which extends from Command
.
It implements the following operations:
-
PrCommand#execute()
Executes and coordinates the necessary objects and methods to list personal record(s). -
PrCommandParser#parse()
Parses user input from UI and initialises a PrCommand object.
The user can choose to view personal record(s) for specific exercises with the ‘n/’ prefix:
:pr n/NAME1 [n/NAME2 n/NAME3 ...]
The user can also choose to view personal records for ALL exercises with the ‘all/ prefix’:
:pr all/
Example Usage
Given below is an example usage scenario for how the mechanism for listing Exercise personal record(s) behaves at each step.
Step 1. The user launches the application and already has 4 Exercise instances, with two unique Exercises (Squat and Deadlift), in the exercise tracker.
Step 2: The user enters the command :pr n/Squat
to view their personal record for the exercise ‘Squat’.
The following sequence diagram shows how the PrCommand
works.
Design considerations
Aspect: Type of arguments to accept
-
Alternative 1 (current choice): Accept exercise names.
- Pros: Being able to view and list personal records by Exercise name is more intuitive and convenient, especially since all unique Exercises are listed in the UI (bottom right).
- Cons: Would require users to type more characters; also require users to enter exercise names accurately.
-
Alternative 2: Accept index as arguments.
- Pros: Suggestions are generated based on PR recorded by the app. As such, the input exercise(s) must already exist in the app. Accepting indexes would guarantee this condition.
- Cons: May require users to scroll to locate index of desired exercise, when the number of exercises grow.
Generating a suggested workout routine
Implementation
Workout suggestions are suggested by Generator
objects. The suggestion mechanism follows the command pattern. The GeneratorFactory
creates a concrete Generator
object, and passes it to the GenerateCommand
object, which treats all generators as a general Generator
type. GenerateCommand
is able to get a workout suggestion without knowledge of the type of generator. The following class diagram illustrates this.
The mechanism for generating a suggested workout routine is facilitated by GenerateCommand
, which extends from Command
.
It implements the following operations:
-
GenerateCommand#execute()
— Executes and coordinates the necessary objects and methods to generate a suggested workout routine. -
GenerateCommandParser#parse()
— Parses user input from UI and initializes a GenerateCommand object.
Cases such as where the index from the user input is out of bounds, are handled by the methods.
Example Usage
Given below is an example usage scenario for how the mechanism for generating a workout routine behaves at each step.
Step 1. The user launches the application, and already has 2 exercises, squat and deadlift, at index 1 and 2, in the exercise tracker.
Step 2: The user enters the command :gen 1,2 level/easy
to generate an easy workout routine consisting of the exercises squat and deadlift.
The following sequence diagram shows how the GenerateCommand
works.
A Name
object exerciseName
is returned to g:GenerateCommand
by calling a method in :Model
.
For the sake of brevity, this interaction is omitted from the diagram.
The diagram below illustrates the interaction between g:GenerateCommand
and GeneratorFactory
class.
The static method GeneratorFactory#getGenerator()
creates a Generator
of the correct difficulty level, such as EasyGenerator
.
The number of Generator
objects created is equal to the number of unique exercise names. They are s:EasyGenerator
and d:EasyGenerator
for squat and deadlift respectively.
Design considerations
Aspect: Number of Generator
objects:
-
Current choice: Pairing each unique exercise to one
Generator
.- Rationale: The current
:gen
command specifies a single difficulty level for all exercises listed in the command. A possible extension in the future would be to allow each exercise to be linked to its own difficulty level, for example,:gen deadlift/easy squat/hard
. This design would make such an implementation possible.
- Rationale: The current
Listing of unique stored Exercises in a graphical UI
Implementation
The display window is located in the bottom right of the application. The display mechanism has been implemented with the Observer design pattern in mind.
It is primarily driven by SavedExerciseListWindow
(which holds the UI for the display). The logic is
handled by ExerciseKeys
and ExerciseHashMap
.
General class diagram
The SavedExerciseListWindow
class implements the Observer
interface as it is the observer. The ExerciseHashMap
class maintains an internal ArrayList of type Observer
, which can be modified through the addUI function. As the UI elements are usually initialized later than the data on loading of the application, the SavedExerciseListWindow
UI object is only added as an observer after its constructor is called. This guards against any null-pointer exceptions which may occur when preloading data from a hashmap in storage.
Subscribing to updates
Once the SavedExerciseListWindow
object has been added to the arraylist of Observer
in the ExerciseHashMap
, it ‘subscribes’ to notifications whenever the ExerciseHashMap changes. Based on the functionality of the Hashmap as well as the application, this can be generalised into two distinct scenarios.
- Adding an exercise - Whenever a new exercise has been added, there is a possibility of a new key being added.
- Removing an exercise - Whenever a new exercise has been removed, there is a possibility of a key being removed permanently.
Updating
Whenever there is a state changing operation, the ExerciseHashMap
object will notify all observers through the notifyObservers method. All Observers in the list will run the update method that is individually specified in their class. As such , all Observers of ExerciseHashMap are required to override the update method as shown below.
@Override
public void update() {
.
unique implementation detail here...
.
}
Below is a sample sequence diagram for the current implementation of how notifyObservers work.
The logic behind the calculations and formatting of the display message is handled by the ExerciseKeys
class.
Through this pattern, each observer gets to define exactly what the required display/result should be.
Design considerations
Aspect: Polymorphism
- The immediately apparent benefit of this design would be the Polymorphism that it capitalises on. In particular, the notifyObservers function in
ExerciseHashMap
.
public void notifyObservers() {
for (Observer o: observerArrayList) {
o.update();
}
}
-
Notice that
ExerciseHashMap
does not know the nature of the observers and how they interact with it.ExerciseHashMap
only stores a list of the objects observing it. It does not have to define what they should do to update, instead, the responsibility of deciding what to do is passed on to the Observers themselves. -
This allows for flexibility in having different types of objects having different forms of updating. This keeps the code in
ExerciseHashMap
short and hides the implementation of the Observers behind theObserver
interface which acts as an intermediary to help the UI communicate withExerciseHashMap
.
Documentation, logging, testing, configuration, dev-ops
Appendix: Requirements
Product scope
Target user profile:
- Programmers who love vim and want to hit the gym for some exercise. However, they are too occupied with work to recall their progressions and don’t know what to do next
- They may also find it hard to remember their statistics on each exercise
Value proposition:
- Leverage on their blazing speed on vim to save, write and view gym data in a familiar fashion
- Provides a fast platform for users to track their gym progress or workout routine
- Has vim-like commands to make things more efficient for vim lovers
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 exercises | keep track of my exercises |
* * * |
user | delete exercises | see what exercises I have done |
* * * |
user | look for all entries of a specific exercise | track my progression for that particular exercise |
* * * |
user | view exercises in chronological order | keep track of my gym progress |
* * * |
user | want to track my personal records | keep track of my progress for each exercise (and show off to my friends) |
* * * |
user | generate workouts of different difficulty | customise my workout based on how I’m feeling that day |
* * * |
user | view my recent exercises | plan for my next gym session |
* * * |
user | view my exercises done within a date range | track my overall progress over a period of time (eg. weekly, monthly, etc) |
* * * |
user | see what names the system has registered | add exercises correctly and quickly |
* * |
new user | remove all sample data | input my own data |
* * |
advanced user | have a quick summary of all the commands I can do in the application | save time |
* * |
clumsy user | have a safeguard against accidentally clearing all data | preserve my exercise |
* |
user | track my calories intake | attain my fitness goals |
* |
user | track my calories burnt during the gym session | attain my fitness goals |
* |
user | have a tailored workout program | target my specific strengths and weaknesses for outstanding results |
* |
user | have motivation to go to the gym | stay motivated to attain my fitness goals |
* |
user | track my Rate of Perceived Exertion (RPE) of previous workout | better plan for my next workout |
* |
user | view my run timings | track my running progression |
* |
user | share my workout plan with my friends | progress together with them |
* |
user | access a workout plan done by my friends | learn from them |
Use cases
(For all use cases below, the System is Gim
and the Actor is the user
, unless specified otherwise)
Use case 1: Help
System: Gim
Use case: UC01 - Request for help
Actor: User
MSS
- User requests for help.
- Gim displays help message.
Use case ends.
Use case 2: Add an exercise
System: Gim
Use case: UC02 - Add an exercise
Actor: User
MSS
- User requests to add an exercise.
- Gim adds the exercise into storage.
Use case ends.
Extensions
- 1a. User enters the command wrongly.
- 1a1. Gim shows an error message.
Use case resumes at step 1.
- 1a1. Gim shows an error message.
Use case 3: Delete an exercise
System: Gim
Use case: UC03 - Delete an exercise
Actor: User
MSS
- User requests to delete an exercise.
- Gim deletes the exercise.
Use case ends.
Extensions
- 1a. User enters the command wrongly.
- 1a1. Gim displays the error message.
Use case resumes at step 1.
- 1a1. Gim displays the error message.
- 1b. User enters an exercise that does not exist in the app.
- 1b1. Gim displays that the exercise does not exist.
Use case ends.
- 1b1. Gim displays that the exercise does not exist.
Use case 4: Clear all exercise entries in the system
System: Gim
Use case: UC04 - Clear all exercise entries in the system
Actor: User
MSS
- User requests to clear all exercise entries in the system.
- Gim clears all exercise entries in the system.
Use case ends.
Extensions
- 1a. User enters the command wrongly.
- 1a1. Gim shows an error message.
Use case resumes at step 1.
- 1a1. Gim shows an error message.
Use case 5: List exercises
System: Gim
Use case: UC05 - List exercises
Actor: User
MSS
- User requests to list all stored exercises.
- Gim lists the stored exercises.
Use case ends.
Use case 6: Sort exercises
System: Gim
Use case: UC06 - Sort exercises
Actor: User
MSS
- User requests to sort the displayed list of exercises.
- Gim sorts the displayed list of exercises by date.
Use case ends.
Use case 7: View exercises within a time period
System: Gim
Use case: UC07- View exercises within a time period
Actor: User
MSS
- User requests to view displayed exercises within a time period.
- Gim displays exercises completed within the specified time period in an order sorted by date.
Use case ends.
Extensions
- 1a. User enters the command wrongly.
- 1a1. Gim displays the error message.
Use case resumes at step 1.
- 1a1. Gim displays the error message.
- 1b. User enters an invalid date.
- 1b1. Gim displays the invalid date error message.
Use case resumes at step 1.
- 1b1. Gim displays the invalid date error message.
Use case 8: Filter exercises by keyword(s)
System: Gim
Use case: UC08 - Filter exercises by keyword(s)
Actor: User
MSS
- User requests to filter the displayed list of exercises by keyword(s).
- Gim displays the filtered list of exercises.
Use case ends.
Extensions
- 1a. User enters the command wrongly.
- 1a1. Gim displays the error message.
Use case resumes at step 1.
- 1a1. Gim displays the error message.
- 2a. Filtered list of exercises is empty.
- 2a1. Gim displays a reminder message.
Use case ends.
- 2a1. Gim displays a reminder message.
Use case 9: View Personal Record (PR) for exercise(s)
System: Gim
Use case: UC09 - View Personal Record (PR) for exercise(s)
Actor: User
MSS
- User requests to view Personal Record (PR) for exercise(s).
- Gim calculates and displays the Personal Record (PR) for exercise(s).
Use case ends.
Extensions
- 1a. User enters the command wrongly.
- 1a1. Gim displays the error message.
Use case resumes at step 1.
- 1a1. Gim displays the error message.
- 1b. User enters the name of exercise(s) wrongly.
- 1b1. Gim displays exercise(s) not registered in system message.
Use case resumes at step 1.
- 1b1. Gim displays exercise(s) not registered in system message.
Use case 10: Generate workout suggestion for exercise(s)
System: Gim
Use case: UC10 - Generate workout suggestion for exercise(s)
Actor: User
MSS
- User requests to generate a workout suggestion.
- Gim computes a sample workout for the user.
Use case ends.
Extensions
- 1a. User enters the command wrongly.
- 1a1. Gim displays the error message.
Use case resumes at step 1.
- 1a1. Gim displays the error message.
- 1b. User enters an invalid index.
- 1b1. Gim displays the invalid index error message.
Use case resumes at step 1.
- 1b1. Gim displays the invalid index error message.
- 1c. User enters an incorrect format for index(es).
- 1c1. Gim displays the incorrect index format message.
Use case resumes at step 1.
- 1c1. Gim displays the incorrect index format message.
- 1d. User enters the name of exercise(s) wrongly.
- 1d1. Gim displays exercise(s) not registered in system message.
Use case resumes at step 1.
- 1d1. Gim displays exercise(s) not registered in system message.
- 1e. User enters an invalid difficulty level.
- 1e1. Gim displays the invalid difficulty level message.
Use case resumes at step 1.
- 1e1. Gim displays the invalid difficulty level message.
Use case 11: Exit Gim
System: Gim
Use case: UC11 - Exit Gim
Actor: User
MSS
- User requests to exit Gim.
- Gim exits.
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 exercises without a noticeable sluggishness in performance for typical usage.
- Should work without an internet connection.
- Should be able to support frequent updating of data.
- 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.
- Should not be able to verify if the user actually perform the exercises they input.
- Users with no coding background should be able to use Gim.
- Should be optimised for a single user.
- The product file size should not exceed 100MB.
- Data should be persisted locally and be in a human-readable format, e.g. JSON.
- Should be delivered to a user as a single JAR file.
- Should not lose any data if application is closed through external means (i.e. not using exit command).
Glossary
- Exercise: Physical activity done in a regular gym that is structured and repetitive, usually involving some weights.
- Mainstream OS: Windows, Linux, Unix, OS-X.
- Personal Record (PR): Heaviest weight recorded in the exercise tracker for a specific exercise.
- Rate of Perceived Exertion (RPE): A measure of a physical activity intensity level.
- Reps: Number of times you perform a specific exercise.
- Sets: Number of cycles of reps that you complete.
- Vim: A Unix text editor, known for being lightweight, fast and efficient. It can be controlled entirely with the keyboard with no need for menus or a mouse.
- Weight: Total weight of equipment (in kg).
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 exercises. 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.
-
Adding an exercise
-
Adding an exercise to the system.
-
Test case:
:add n/Squat w/60 s/1 r/5 d/25/01/2022
.
Expected: A new exercise with the nameSquat
, with weight60kg
, with set1
, with reps5
and with date25/01/2022
is added at the bottom of the Exercise List in Gim. The Result Display Window indicates that an exercise namedSquat
has been successfully added. -
Test case:
:add n/Squat w/60 s/1 r/5
.
Expected: A new exercise with the nameSquat
, with weight60kg
, with set1
, with reps5
and with today’s date is added at the bottom of the Exercise List in Gim. The Result Display Window indicates that an exercise namedSquat
has been successfully added. -
Test case:
:add n/Squat
.
Expected: No exercise is added. The Result Display Window indicates that the command is of an invalid format. -
Other incorrect
:add
commands to try::add
,:add x/invalid
,...
(where x is any invalid prefix).
Expected: Similar to previous.
-
Deleting an exercise
-
Deleting an exercise while all exercises are being shown
-
Prerequisite: List all exercises using the
:list
command. Multiple exercises in the list. -
Test case:
:del 1
Expected: First exercise is deleted from the list. Details of the deleted exercise shown in the Result Display Window. Timestamp in the status bar is updated. -
Test case:
:del 0
Expected: No exercise is deleted. Error details shown in the Result Display Window. Status bar remains the same. -
Other incorrect delete commands to try:
:del
,:del x
,...
(where x is larger than the list size)
Expected: Similar to previous.
-
Clearing all exercises in the system
-
Clearing all exercises in the system
-
Prerequisite: There is at least one exercise in the Exercise List.
-
Test case:
:clear
Expected: The Result Display Window will indicate that the command is invalid. -
Test case:
:clear confirnm/
Expected: All exercises in the system is cleared. The Result Display Window will indicate that the exercise tracker has been cleared.
-
Filtering exercises by keyword(s)
-
Filtering displayed list of exercises using keyword(s).
-
Prerequisite: There are multiple exercises in the Exercise List.
-
Prerequisite: There exists an exercise with exercise name
Deadlift
.
Test case::filter Deadlift
.
Expected: The exercise with exercise nameDeadlift
appears in the Exercise List after executing the command. -
Prerequisite: There exists an exercise with exercise name
Deadlift
and an exercise with exercise nameSquat
.
Test case::filter Deadlift Squat
.
Expected: The exercise with exercise nameDeadlift
and exercise with exercise namedSquat
appear in the Exercise List after executing the command. -
Other incorrect
:filter
commands to try::filter
(no keywords provided).
Expected: The Result Display Window will indicate that the command is invalid.
-
Sorting exercises
-
Sorting displayed list of exercises by order of date.
-
Prerequisite: There are multiple exercises in the Exercise List.
-
Test case:
:sort
.
Expected: The exercises displayed in the Exercise List will be sorted by date after executing the command.
-
Viewing exercises within a time period
-
Viewing exercises within a time period.
-
Prerequisite: There is at least one exercise in the Exercise List.
-
Test case:
:range last/5
.
Expected: List exercises completed in the last 5 days. -
Test case:
:range start/01/01/2022 end/31/01/2022
.
Expected: List exercises completed between 01/01/2022 and 31/01/2022. -
Test case:
:range start/01/01/202222 end/31/01/202222
.
Expected: The Result Display Window will indicate that the date input format is invalid. -
Other incorrect
:range
commands to try::range
,range last/abc
,range start/01/01/2022
, …
Expected: The Result Display Window will indicate that the command is invalid.
-
Generating workout suggestion for exercise(s)
-
Generating workout suggestion using index(es).
-
Prerequisite: There is at least one exercise in the Exercise List.
-
Test case:
:gen 1 level/easy
.
Expected: Generate a workout with difficulty level easy for the exercise at index 1 of the Exercise List. -
Prerequisite: There is only one exercise in the Exercise List.
Test case::gen 2 level/easy
.
Expected: The Result Display Window will indicate that the index is invalid.
-
-
Generating workout suggestion using exercise name(s).
-
Prerequisite: There is at least one exercise in the Exercise List.
-
Prerequisite: There exists an exercise with exercise name
Deadlift
.
Test case::gen n/Deadlift level/easy
.
Expected: Generate a workout with difficulty level easy for exercise with exercise nameDeadlift
. -
Prerequisite: There does not exist an exercise with exercise name
Squat
.
Test case::gen n/Squat level/easy
.
Expected: The Result Display Window will indicate that the exercise is not registered in the system. -
Test case:
:gen n/Deadlift level/easyyyyyy
.
Expected: The Result Display Window will indicate that the level is not supported. -
Other incorrect
:gen
commands to try::gen
(no keywords provided).
Expected: The Result Display Window will indicate that the command is invalid.
-
Listing Personal Records (PR)
-
Listing Personal Record(s) of exercise(s).
-
Prerequisite: There is at least one exercise in the Exercise List.
-
Prerequisite: There exists at least one exercise with exercise name
Deadlift
.
Test case::pr n/Deadlift
.
Expected: List the Personal Record for exercise with exercise nameDeadlift
. -
Prerequisite: There exists only exercises with exercise name
Deadlift
and exercise nameSquat
.
Test case::pr all/
.
Expected: List the Personal Records for exercises with exercise nameDeadlift
and exercise nameSquat
. -
Prerequisite: There does not exist an exercise with exercise name
Bench press
.
Test case::pr n/Bench press
.
Expected: The Result Display Window will indicate that the exercise is not registered in the system. -
Other incorrect
:pr
commands to try::pr
(no keywords provided).
Expected: The Result Display Window will indicate that the command is invalid.
-
Saving data
-
Dealing with missing/corrupted data files
-
Prerequisites: Able to access the
/data
folder (created in the same folder containing theGim.jar
file). -
Test Case: Navigate to the
/data
folder and delete theexercisetracker.json
file. Then reopen the application.
Expected: The application will be repopulated with the initial starting data.
-