By: Team F11-1 Since: March 2020 Licence: MIT

Table of Contents:

1. About this guide

This Developer Guide is a document to guide future software developers of the Calgo App by providing a sufficient and comprehensible overview of the project.

While we aim to provide a reasonable amount of depth, do keep in mind that the goal of this document is not to serve as a replacement for reading the actual code.

Welcome on-board the Software Development Team for Calgo! Together, we will inspire a healthier lifestyle!

2. Setting up

Refer to the guide here.

3. Design

3.1. Architecture

ArchitectureDiagram
Figure 1. Architecture Diagram
The .puml files used to create diagrams in this document can be found in the diagrams folder. Refer to the Using PlantUML guide to learn how to create and edit diagrams.

The Architecture Diagram given above describes the high-level design of the Calgo Application. From now on, all instances of Calgo Application will be referred to as App. Given below is a quick overview of each component.

The Main component comprises of two classes called Main and MainApp. This component is responsible for:

  • Launching App: Initializes the other components in the correct sequence, and connects them up with each other.

  • Exiting App: Shuts down the components and invokes cleanup method where necessary.

Commons represents a collection of classes used by multiple other components. In particular, the LogsCenter class plays an important role at the architecture level:

  • LogsCenter : Writes log messages to the App’s log file, for various classes.

The rest of the App comprises of four components.

  • UI: The User Interface (UI).

  • Logic: The command executor.

  • Model: The in-memory representation of the App data.

  • Storage: The file manager for reading from and writing to the hard disk.

Each of the four components:

  • Defines its Application Programming Interface (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 its 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 n/Apple.

ArchitectureSequenceDiagram
Figure 3. Component interactions for delete n/Apple command

The sections below give more details of each component.

3.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, FoodListPanel, DailyListPanel, StatusBarFooter 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:

  1. Executes user commands using the Logic component.

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

3.3. Logic component

LogicClassDiagram
Figure 5. Structure of the Logic Component

API : Logic.java

  1. Logic uses the CalgoParser 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 food).

  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 n/Apple") API call.

DeleteSequenceDiagram
Figure 6. Interactions Inside the Logic Component for the delete n/Apple Command
The lifeline for DeleteCommandParser and DeleteCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

3.4. Model component

ModelClassDiagram
Figure 7. Structure of the Model Component

API : Model.java

  1. Model stores user’s preferences in a UserPref object.

  2. Model also stores Food Record and Consumption Record data.

  3. This component exposes both ObservableList<Food> and ObservableList<DisplayFood>. The data stored in these two list objects is reflected in UI. Therefore, any changes made to the data in these lists are shown in the UI in real-time.

  4. To update the Model (and hence reflect the changes in the UI), Food attributes need to satisfy certain Predicates, which represent these changes.

  5. This component does not depend on any of the other three components.

To make Model follow the Object-Oriented Paradigm (OOP) more closely, we can store a Tag list in Food Record, which Food objects can reference. This would allow Food Record to only require one Tag object per unique Tag, instead of each Food needing their own Tag object. An example of how such a model may look like is given in the below diagram.

BetterModelClassDiagram
Figure 8. Structure of the Model Component

3.5. Storage component

StorageClassDiagram
Figure 9. Structure of the Storage Component

API : Storage.java

The Storage component allows us to save FoodRecord, UserPref, Goal, and ConsumptionRecord data in json format onto the disk, and read them back later on during the next session.

This would facilitate the following functions:

  1. Load past user App data and preferences.

  2. Generate and save insights reports based on previously and currently recorded user consumption.

  3. Generate and save a user-friendly version of the accumulated FoodRecord.

3.6. Common classes

Classes used by multiple components are in the life.calgo.commons package.

4. Implementation

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

4.1. Generating consumption statistics and insights report

(by Vineeth)

This feature allows a user to automatically generate a report that contains statistics and personalised insights based on his or her food consumption pattern on a given date. Do note that the generated report is a .txt file.

The user can invoke this functionality by entering the report command, which follows the following format: report d/DATE.

This section explains:

  • how the report command works and the crucial method it invokes during execution (In the Implementation subsection).

  • the various aspects that were deliberated over when coming up with the design of the statistics and insights report feature (In the Design Considerations subsection).

  • a summary that shows a simplified Activity Diagram that captures the essential logic in the execution of the report command (In the Summary subsection).

Before moving on to learn how the feature works, if you want to see what the report includes, you can refer to our User Guide.

4.1.1. Implementation

The specified feature is facilitated by a ReportGenerator object. If you are interested in how ReportGenerator fits into the architecture of Calgo, refer to this section.

To learn how the report command works, the most important method that you need to know is the generateReport() method from the ReportGenerator class.

Refer to the sequence diagram below to understand the top-level execution of the generateReport() operation after the user enters a valid report command.

ReportFeatureSequenceDiagram
Figure 10. Sequence Diagram for report command: generating 2020-03-27_report.txt
The lifelines for the ReportCommandParser object, ReportCommand object, ReportGenerator object should end at their destroy markers (X) but due to a limitation of PlantUML, the lifelines reach the end of diagram.

From the above diagram, creating a report for the consumption patterns on 27th of March 2020 involves the following steps:

Step 1: User inputs report d/2020-03-27 to generate the insights report based on food consumption of the abovementioned date.

Step 2: This input is saved as a String and passed into the LogicManager.

Step 3: The String input is parsed by CalgoParser, which removes the "d/" Prefix and sends the date input to ReportCommandParser.

Step 4: Once the ReportCommandParser checks that the given date is valid, it creates a ReportCommand object and returns it to LogicManager.

Step 5: LogicManager then executes the ReportCommand.

Step 6: From Model, ReportCommand retrieves the required objects to construct an instance of ReportGenerator.

Step 7: With the relevant objects retrieved from Steps 6, ReportCommand constructs a ReportGenerator object.

Step 8: Using the ReportGenerator object, ReportCommand invokes the crucial method generateInsights(), which prints neatly organised sections of analysed data based on the DailyFoodLog of the input date. For the section that gives insights related to the user’s favourite Food, the past seven days of DailyFoodLog objects are analysed.

Step 9: This newly generated report is saved in the data/reports folder. If the report is successfully generated, the CommandResult is true. Otherwise, it is false. This CommandResult object is finally returned to LogicManager, to signify the end of the command and GUI shows a result message to the user.

4.1.2. Design considerations

Many of the design considerations made for report command are similar to that of the export command. You can check out the similar design considerations over here.

In addition to those design considerations, the following consideration is specific to the functionality of the report command.

Aspect: How many days of past data should be used to analyse user’s favourite Food for the suggestions section of the report.
  • Alternative 1 (current choice): Analyse past seven days of consumption data

    • Pros: This implementation is more efficient as it does not have to churn out all existing consumption data produced by the user so far.

    • Cons: The reliability of the suggestions may be impacted due to certain special events such as buffet celebrations, in which the user may consume Food that he or she does not usually eat.

  • Alternative 2: Analyse all data

    • Pros: The insights will be much more reliable and more tolerant of outlier data points.

    • Cons: The efficiency of the report command gets worse over time and may cause dissatisfaction to the user.

4.1.3. Summary

The following activity diagram summarizes what happens when user executes a report d/DATE command with a correctly formatted date:

ReportActivityDiagram
Figure 11. Activity Diagram for Report command

You can check out this code snippet to see how the ReportCommand object determines if there are no Food entries present in the given date:

// if there is no food consumed on the given date, do not execute command
if (!model.hasLogWithSameDate(queryDate) || model.getLogByDate(queryDate).getFoods().size() == 0) {
   throw new CommandException(MESSAGE_REPORT_FAILURE + "\n" + String.format(NO_SUCH_DATE, queryDate))
}

4.2. Setting daily calorie goals for motivation

(by Vineeth)

This feature helps a user chunk his or her long term goal of developing a healthy lifestyle into smaller daily goals. Psychologically, this helps to motivate them as the perceived difficulty of achieving the long term goal reduces.

The user can set a daily calorie goal with the goal command, which follows the following format: goal GOAL.

This section addresses how the goal command works.

4.2.1. Implementation

In addition to the GoalCommandParser, the goal command relies heavily on the DailyGoal class, which is part of the Model component. Refer to the Model component diagram here.

To address the issue where a user does not want to set up a daily calorie goal, Calgo places a DUMMY_VALUE of 0 calories, as shown in the code snippet below, from DailyGoal class. To cater to a wide range of users, it also has a broad range of acceptable values, ranging from 1 to 99999. However, to guide users towards a healthy lifestyle, the App does display a warning message whenever a user sets a goal below the MINIMUM_HEALTHY_CALORIES.

    // Values used for GoalCommandParser when parsing user inputted goals.
    public static final int MINIMUM_HEALTHY_CALORIES = 1200;

    public static final int MINIMUM_ACCEPTABLE_CALORIES = 1;

    public static final int MAXIMUM_ACCEPTABLE_CALORIES = 99999;

    // Default value, when user does not input a goal.
    public static final int DUMMY_VALUE = 0;

Refer to the sequence diagram below to understand how a goal command is executed. .Sequence Diagram for goal command: updating daily calorie goal to 2000 calories.

GoalSequenceDiagram
The lifelines of the GoalCommandParser object, GoalCommand object and DailyGoal object should end at their destroy markers (X) but due to a limitation of PlantUML, the lifelines reach the end of diagram.

The following steps explain the sequence diagram:

Step 1: User inputs goal 2000 to update his or her goal to 2000 calories.

Step 2: This input is saved as a String and passed into the LogicManager.

Step 3: The String input is parsed by CalgoParser, which sends the goal value input to GoalCommandParser.

Step 4: Once the GoalCommandParser checks that the given value is valid, it converts the input to an Integer and creates a GoalCommand object and returns it to LogicManager.

Step 5: LogicManager then executes the GoalCommand, which in turn invokes updateDailyGoal method of Model.

Step 6: In Model, the updateDailyGoal method is a static method that generates a new DailyGoal object with the corresponding input. This DailyGoal object is returned to Model , which replaces the existing DailyGoal attribute of the ModelManager with the newly generated DailyGoal object.

4.2.2. Design Considerations

Aspect: Type of user input data that is required for goal command
  • Alternative 1 (current choice): Use a simple goal feature that accepts the user’s inputted value.

    • Pros:

      • The user is not daunted by the large amount of information he or she needs to provide to set a goal.

      • The user will not feel paranoid as Calgo does not ask for personal data such as height, weight, gender and age.

    • Cons:

      • The goal may not be effective unless the user diligently checks online for a appropriate goal and then enters it into Calgo.

  • Alternative 2: Use a scientific method to calculate the basal metabolic rate of the user.

    • Pros:

      • The goal is very effective because it matches their body type.

    • Cons:

      • A lot of data is required to be inputted by the user.

      • May cause users to avoid setting goals because of the large amount of personal data they need to store in Calgo.

      • User feeling uncomfortable about setting a goal will also affect effectiveness of report command.

4.2.3. Summary

In essence, the goal command is a fun feature that is used to motivate the user and generate specific insights if the user were to invoke the report command after setting a daily calorie goal.

Refer to the Activity Diagram below for a visual summary of the logic behind the execution of the goal command.

GoalActivityDiagram
Figure 12. Activity Diagram for goal command.

4.3. Searching for specific Food via categories and substrings

(by Eugene)

This section addresses how the find and list commands work. As they are complementary in their functions during the search process, both find and list commands will be explained together here for better coherence.

The find command allows us to search through the FoodRecord (via categorical or substring search) based on what the user enters for the Prefix. Users may enter one and only one Prefix. The search results can then be displayed in the GUI’s Food Record.

Meanwhile, the list command allows us to reset the GUI’s Food Record to once again show all entries in lexicographical order. This can be thought of as the reverse of a find command. However, unlike the find command, the list command does not use any Prefix, and ignores any input after its command word.

Prefix here indicates which Food attribute we are interested in. Categorical search finds Food objects with values that match the user-specified value representing one of the nutritional categories (Calorie, Protein, Carbohydrate, or Fat). Meanwhile, substring search finds matches for the user-entered substring in any part of the the Name or in any of the Tag objects belonging to the Food objects.
For more information on lexicographical ordering, please refer to its relevant section here.

The above commands rely on the FindCommand and ListCommand objects respectively. Objects of both classes use a Predicate<Food> object to filter through the current Food objects, where Food objects will be displayed in the GUI’s Food Record should they evaluate these predicates to be true.

4.3.1. Implementation

To search via a particular Food attribute, we use a FindCommandParser to create the corresponding Predicate<Food> based on which Food attribute the Prefix entered represents. This predicate is then used to construct a new FindCommand object, which changes the GUI display when executed.

The class diagram below shows the relevant Predicate<Food> classes used in the construction of FindCommand objects.

FindListCommandPredicateClassDiagram
Figure 13. Class Diagram showing the relevant predicates used in constructing FindCommand objects

As seen in the above class diagram, each Predicate<Food> is indeed representative of either Name, Calorie, Protein, Carbohydrate, Fat, or Tag. Moreover, it should be noted that each of these predicates test against a Food object, and therefore have a dependency on Food.

The sequence diagram below demonstrates how the find command works, for both categorical and substring search:

FindSequenceDiagram
Figure 14. Sequence Diagram for find command: Categorical Search and Substring Search
The lifeline for the both of the FindCommandParser objects, and both of the FindCommand objects should end at their destroy markers (X) but due to a limitation of PlantUML, the lifelines reach the end of diagram.

From the above, it is clear that both categorical search and substring search of the find command have similar steps:

Step 1: LogicManager executes the user input, using CalgoParser to realise this is a find command, and a new FindCommandParser object is then created.

Step 2: The FindCommandParser object parses the user-entered arguments that come with the Prefix, creating a Predicate<Food> object based on which Food attribute the Prefix represents.

  • In the above diagram examples, a ProteinContainsKeywordsPredicate object is created for the categorical search via Protein while a NameContainsKeywordsPredicate object is created for the substring search via Name.

Step 3: This Predicate<Food> object is then used to construct a new FindCommand object, returned to LogicManager.

Step 4: LogicManager calls the execute method of the FindCommand created, which filters for Food objects that evaluate the predicate previously created to be true. It then returns a new CommandResult object reflecting the status of the execution. These changes are eventually reflected in the GUI.

The find command therefore searches through the existing FoodRecord and then displays the relevant search results in the GUI’s Food Record. To once again show all Food entries in the display, we use the list command.

In constrast to FindCommand, the ListCommand constructor takes in no arguments and simply uses the predicate Model.PREDICATE_SHOW_ALL_FOODS to always show all Food entries in its execute method. This is described by the sequence diagram below:

ListSequenceDiagram
Figure 15. Sequence Diagram for list command
The lifeline for the ListCommand object should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

How the list command works:

Step 1: LogicManager executes the user input, using CalgoParser to realise this is a list command, and a new ListCommand object is created.

Step 2: LogicManager then calls the execute method of this ListCommand, which uses Model.PREDICATE_SHOW_ALL_FOODS to evaluate to true for all Food objects in the FoodRecord.

Step 3: LogicManager then returns a new CommandResult object to reflect the status of the execution, in the GUI. The GUI’s Food Record reflects the above changes to show all Food entries once again.

4.3.2. Design considerations

Aspect: Predicate construction source.
  • Alternative 1 (current choice): Each Predicate<Food> is constructed using a new object of type either Name, Calorie, Protein, Carbohydrate, Fat, Tag.

    • Pros:

      • Defensive programming by building new objects rather than relying on mutable sources.

      • Can reuse existing code and classes like ArgumentMultimap and their methods.

      • Models objects well to reflect the real-world.

    • Cons:

      • May be more resource-intensive than other alternatives.

      • New developers may not find this intuitive.

  • Alternative 2: Each Predicate<Food> is created using a String which represents the keywords.

    • Pros:

      • Easier to implement with fewer existing dependencies.

      • Less resource-intensive.

    • Cons:

      • More prone to bugs.

      • Difficult to ascertain which Food attribute it actually represents.

      • More difficult to debug as String type is easily modified.

      • Does not reflect good OOP practices

  • Alternative 1 (current choice): Allow substring search for both Name and Tag

    • Pros:

      • Improves user experience.

      • Can reuse common code as the approach for both Name and Tag are similar.

      • Generally easy to implement substring finding.

      • Can use regular expressions if needed, which are powerful and suitable for our purpose.

    • Cons:

      • Requires good understanding of the original project.

      • Need to know the String type, regular expressions, and their implications.

      • Need to implement searching via multiple types of Food attributes and hence introduces more dependencies.

      • Need to implement a new Parser class to detect each relevant Prefix.

  • Alternative 2: Only allow exact word matches for Name and Tag

    • Pros:

      • Can simply reuse large parts of the original project’s existing code.

      • Less prone to bugs.

      • Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project.

    • Cons:

      • Diminishes user experience.

      • May not fully satisfy the user requirements.

      • Need to implement searching via multiple types of Food attributes and hence introduces more dependencies.

      • Need to implement a new Parser class to detect each relevant Prefix.

4.3.3. Summary

In essence, this section focuses on searching which is implemented via find and list commands.

The find command performs a categorical search if a value from a nutritional category (Calorie, Protein, Carbohydrate, Fat) is specified. Otherwise, a substring search is performed to find Food objects that contain the entered substring in their Name or in one of their Tag s. These rely on the Predicate<Food> object used in constructing the FindCommand, which depend on the Prefix entered by the user.

Meanwhile, the list command simply uses the predicate already defined in Model to display all Food objects.

The above can be summarised in the activity diagram below:

FindListCommandActivityDiagram
Figure 16. Activity Diagram for Searching

4.4. Lexicographical Food order

(by Eugene)

This section addresses how the GUI Food Record entries appear in lexicographical order, which is an effect of sorting Food objects in the FoodRecord.

Over time, users will eventually have many Food entries — these should be sorted for a better experience. Intuitively, the lexicographical order is the most suitable here.

In essence, Food objects are sorted by the UniqueFoodList (which is inside FoodRecord). Sorting is performed each time Food object(s) are newly added to the UniqueFoodList, or during the initialisation of the UniqueFoodList upon App start-up. There is no need to re-sort when a Food object is deleted or edited as the order is maintained.

For a better understanding of adding and editing Food objects using the update command, please refer to its relevant section here.
Although the the list command changes the GUI Food Record display, it does not actually perform sorting. It simply resets the GUI Food Record to show all Food entries, and is usually used after a find command. You can read more about them here.

4.4.1. Implementation

The UniqueFoodList is able to sort Food objects because the Food class implements the Comparable<Food> interface. This allows us to specify the lexicographical order for sorting Food objects via their Name, using the following compareTo method in the Food class:

public int compareTo(Food other) {
    String currentName = this.getName().toString();
    String otherName = other.getName().toString();
    return currentName.compareTo(otherName);
}

How the sorting process works:

  • When the App starts up, a new UniqueFoodList is created from the source json file (if available) or otherwise the default entries, and the created Food objects are sorted as they are added to it.

  • Existing Food objects are therefore arranged in lexicographic order by Name.

  • Thereafter, UniqueFoodList sorts the Food objects whenever new Food objects are added.

It should be noted that sorting is only performed by the addFood and setFoods method of the UniqueFoodList, which calls the sortInternalList method. Not to be confused, the setFood method, which is used when a Food object is edited, does not perform any sorting.

The sequence diagram below shows how the lexicographical ordering is performed when Calgo starts up:

LexicographicalOrderingStartupSequenceDiagram
Figure 17. Lexicographical Ordering Sequence Diagram for App Start-up

Based on the above diagram, when Calgo starts:

Step 1: We initialise the ModelManager object. For this, we use previously stored user data if available (by reading in from the source json files). Otherwise, we use the default Calgo Food entries.

Step 2: Before we can finish constructing a new ModelManager object, we require the creation of a new FoodRecord object which in turn requires the creation of a new UniqueFoodList object.

Step 3: Once UniqueFoodList is constructed, we introduce the initialising data into it using the setFoods method. This calls the sortInternalList method, which sorts the newly added Food objects in the ObservableList<Food> contained in UniqueFoodList, according to the specified lexicographical order (defined in the Food class).

Moving on, the sequence diagram below (which is a reference frame omitting irrelevant update command details) describes the lexicographical sorting process when Food objects are added (not edited) using the update command:

LexicographicalOrderingUpdateSequenceDiagram
Figure 18. Lexicographical Ordering Sequence Diagram for Updating
This is in a reference frame as it is reused in the update section here)

Here, the diagram describes what happens after parsing the user input and creating an UpdateCommand object. Since the Food entered by the user is an entirely new Food object without a Name-equivalent Food existing in the UniqueFoodList:

Step 1: We call the respective addFood and add methods as seen in the diagram, eventually adding the Food object into the UniqueFoodList and arriving at its sortInternalList method call.

Step 2: The sortInternalList method then sorts the Food objects in the ObservableList<Food> contained in UniqueFoodList, according to the specified lexicographical order defined in the Food class.

During an update command, we do not perform sorting if the user enters a Food object that already has an existing counterpart with an equivalent Name in the UniqueFoodList.

Any re-ordering will eventually be reflected in the GUI, facilitated by the following (in the case of an update command) or otherwise something similar:

model.updateFilteredFoodRecord(Model.PREDICATE_SHOW_ALL_FOODS);

4.4.2. Design considerations

Aspect: Frequency of sorting operation.
  • Alternative 1 (current choice): Sort whenever a new Food is added or during App start-up.

    • Pros:

      • Guarantees correctness of sorting.

      • Saves on computational cost by not sorting during deletion or edits as the order is preserved.

      • Computational cost is not too expensive since the introduced Food objects usually come individually rather than as a collection (except during App start-up).

    • Cons:

      • Need to ensure implementations of various commands changing the Model are correct and do not interfere with the sorting process.

      • May be computationally expensive if there are many unsorted Food objects at once, which is possible when Calgo starts up.

  • Alternative 2: Sort only when calling the list command.

    • Pros:

      • Easier to implement with fewer existing dependencies.

      • Uses less computational resources since sorting is only done when list command is called.

    • Cons:

      • Diminishes user experience.

      • May be incompatible with certain Storage functions.

      • May lead to bugs in overall product due to incompatible features.

Aspect: Data structure to store Food objects.
  • Alternative 1 (current choice): Use UniqueFoodList to store all Food objects.

    • Pros:

      • Can reuse existing code, removing the need to maintain a separate list-like data structure.

      • Based on existing code, any changes to the Model from the sorting process are automatically reflected in the GUI. This is very useful for testing and debugging manually.

    • Cons:

      • Many of the underlying ObservableList methods are built-in and cannot be edited. They are also difficult to understand for those unfamiliar. This can make development slightly trickier, especially in following certain software engineering principles.

  • Alternative 2: Use a simpler data structure like an ArrayList.

    • Pros:

      • Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project.

    • Cons:

      • More troublesome as we require self-defined methods, abstracted over the existing ones. If not careful, these self-defined methods can possibly contain violations of certain software engineering principles, which may introduce regression in the future.

      • May be inefficient in using resources.

4.4.3. Summary

The UniqueFoodList facilitates the lexicographical ordering of Food objects and hence how their respective entries appear in the GUI Food Record. This can be summarised in the activity diagram below:

LexicographicalOrderingActivityDiagram
Figure 19. Activity Diagram for Lexicographical Ordering

4.5. Exporting the current FoodRecord into a portable file

(by Eugene)

This section addresses how the export command works, creating a FoodRecord.txt file showing details of all the Food objects currently stored in the FoodRecord. The information is presently neatly in table form and the file is created in the data/exports folder.

The export command mainly uses an ExportGenerator object to generate the file. All formatting options and methods to write the contents of the file are included in the ExportGenerator class, which extends the DocumentGenerator class.

You may find the report command similar as they both create a new file for the user. You can read more about it here.

4.5.1. Implementation

Most of the work in generating the file is done by the generateExport method of ExportGenerator. You can access the class to view its methods for writing the header and footer components, which are relatively easily to understand.

However, the methods for writing the file body is likely where some explaining is required. Here, the formatting of the table body is determined by the following:

    private static final int NAME_COLUMN_SIZE = 45;
    private static final int VALUE_COLUMN_SIZE = 20;

NAME_COLUMN_SIZE represents the allowed space for the Name. If a Food object has a Name which is too long, the Name will be truncated and continued on the following lines. Meanwhile, VALUE_COLUMN_SIZE represents the allowed space for each nutritional value of Calorie, Protein, Carbohydrate, and Fat in the table. These are guaranteed to be within a length of 5 characters when parsing, and should not exceed the given space.

The nutritional values will always be shown in the first line of their respective Food object after its (possibly truncated) Name. This is facilitated by the printBody method of ExportGenerator, which calls its printBodyComponent method and subsequently its generateFinalisedEntryString method, which performs the truncation and amendment of the Name as necessary.

Moving on, the sequence diagram below demonstrates how the export command works to create the user copy of the current FoodRecord:

ExportSequenceDiagram
Figure 20. Sequence Diagram for export command: Generating FoodRecord.txt
The lifeline for the ExportCommand object and that of the ExportGenerator object should end at their destroy markers (X) but due to a limitation of PlantUML, the lifelines reach the end of diagram.

From the above, creating FoodRecord.txt involves the following steps:

Step 1: LogicManager executes the user input, using CalgoParser to realise this is a export command, and a new ExportCommand object is created.

Step 2: LogicManager then calls the execute method of this ExportCommand object. This results in a call to the Model to get the current FoodRecord, which is used to construct a new ExportGenerator object. The ExportGenerator is responsible for creating the FoodRecord.txt file and writing to it.

Step 3: ExportCommand then calls the generateExport method of ExportGenerator, writing the required parts to the file. This returns a boolean indicating whether the file creation and writing are successful.

Step 4: A new CommandResult object indicating the result of the execution is then constructed and reflected in the GUI.

4.5.2. Design considerations

Aspect: Type of file to create.
  • Alternative 1 (current choice): Create a .txt file to represent the FoodRecord.

    • Pros:

      • Satisfies user requirements by allowing editing of the file to include custom entries.

    • Cons:

      • Need to define new classes and methods for file writing, which may introduce more dependencies.

      • May be more resource-intensive than other alternatives.

      • New developers may be unfamiliar with String manipulation and regular expressions.

  • Alternative 2: Create a .pdf file to represent the FoodRecord

    • Pros:

      • The contents appear to be more legitimate.

      • Can use external libraries for convenience.

      • May be less resource-intensive.

    • Cons:

      • May not satisfy user requirements as the file cannot be edited easily.

      • May introduce more bugs, additional dependencies, and become prone to external factors.

      • More difficult to debug due to lack of familiarity with external libraries.

      • May require more space.

Aspect: Abstraction for ExportGenerator and ReportGenerator.
  • Alternative 1 (current choice): Create DocumentGenerator abstract class which both ExportGenerator and ReportGenerator extends.

    • Pros:

      • Good OOP practice, following its principles.

      • Allows for code reuse and neater code.

      • Able to apply concepts of polymorphism, if required.

      • May be now easier to debug.

    • Cons:

      • Need to define new class, possibly introducing more dependencies.

      • Need to identify what is common to both ExportGenerator and ReportGenerator.

  • Alternative 2: Use an interface which both classes will implement.

    • Pros:

      • Similar to Alternative 1.

    • Cons:

      • Does not allow methods to be defined in the interface. (Some exceptions: default methods, etc)

      • May need to repeat definitions which may be the same for both classes.

  • Alternative 3: Do not use an interface or abstract class.

    • Pros:

      • Requires less effort.

      • Does not introduce additional dependencies.

    • Cons:

      • Unable to reap benefits of the above alternatives.

4.5.3. Summary

In short, this section addresses how users are able to obtain an editable copy of the current FoodRecord using the export command.

The export command largely relies on the ExportGenerator class, which facilitates creating the file and writing to it.

The above can be summarised in the activity diagram below:

ExportActivityDiagram
Figure 21. Activity Diagram for Searching

4.6. Food consumption management

(by Ruicong)

This section addresses how nom, vomit, and stomach commands work. They are the 3 commands that you will use to interact with ConsumptionRecord. nom allows you to add Food, vomit allows you to remove Food, and stomach gives you a way to browse a list of Food within the ConsumptionRecord at a different date. ConsumptionRecord is an important component because it serves as a backend for features such as goal, report and graph.

The high level idea of how Food consumption is managed is that ConsumptionRecord stores all the Food consumed. Whenever nom, vomit, or stomach is used, a list will be retrieve and sent to a FilteredList. Such a list consists of DisplayFood objects, for the purpose of displaying information compiled from each Food. The FilteredList is an observable, so whenever it is updated, the GUI will be informed and display the contents accordingly.

4.6.1. Implementation

In this section, I will be walking you through the implementation of the ConsumptionRecord, what happens on App startup, and what happens when a consumption related command is called. I will be talking about the nom command more specifically. This is because vomit and stomach work very similarly, and you will see that it’s easy to understand once you have read through this section.

In Calgo, you will find that the GUI ConsumptionRecord use a uniqueDateToLogMap to map each LocalDate to a DailyFoodLog. As you can guess, LocalDate keys are unique.
Each DailyFoodLog is related to a LocalDate object and contains 2 LinkedHashMap, one to map Food consumed to their portion, another to map Food to an ArrayList of Integer, which represents the ratings given to that Food item consumed on that day.

On App startup, initModelManager of MainApp class is invoked. This will cause storage to read consumption record data from a .json file which stores App data. The .json file stores JsonAdaptedDailyFoodLog, which is similar to DailyFoodLog in every way, but deals with JsonAdaptedFood class instead of Food. Notice that there are a chain of toModelType commands called as we dive deeper into the method call stack. toModelType is actually the method to return a working counterpart of JsonSerializable and JsonAdapted classes that will be delivered to the model of Calgo.

So you might ask, what does JsonAdapted mean? Well, JsonAdapted classes are specially formatted versions of their counterparts that makes it easy for the Jackson API to read and write to.

Here is what happens when different classes call toModelType:

  • JsonSerializableConsumptionRecord returns its equivalent copy of ConsumptionRecord.

  • JsonAdaptedDailyFoodLog returns its equivalent copy of DailyFoodLog.

Below shows the high level view of the initialization process:

For the subsequent sequence diagrams in the section, the lifelines for objects should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
ConsumptionRecordSequenceDiagram
Figure 22. Sequence Diagram for loading of Consumption Record

Now that the ConsumptionRecord has been initialized, the App can start interacting with the user.

Whenever the user enters a nom command into the GUI, a sequence of events occur. Here is a a step-by-step guide to what happens in such a scenario:

Step 1: UI component MainWindow receives the input as a String. That String is then passed into LogicManager, which calls the parseCommand of CalgoParser. Suppose the String is "nom n/Apple d/2020-04-12 portion/2 r/7" CalgoParser detects that this is a nom command. CalgoParser then delegates this job by creating a new NomCommandParser object which will parse this String.

Step 2: NomCommandParser gets relevant values from Prefixes of input String, and then checks with the ModelManager. It specifically checks if there exists a DailyFoodLog with the same LocalDate as what was parsed so that it can use existing information if they are already present. From all these information, a DailyFoodLog object representing the result of consuming a Food is created, and supplied to create NomCommand. The diagram below shows how this happens:

NomCommandParserSequenceDiagram
Figure 23. Sequence Diagram for parsing of Nom command

Step 3: NomCommand updates the ModelManager with the DailyFoodLog obtained during its execution by LogicManager. Such information cascades down the layers of abstraction until it reaches ConsumptionRecord, which updates its underlying 'uniqueDateToLogMap' aforementioned here.

NomCommandSequenceDiagram
Figure 24. Sequence Diagram of how Nom command executes

Step 4: NomCommand then informs the ModelManager to update its FilteredList, which gets information from the updated ConsumptionRecord. Since the FilteredList is a wrapper of ObservableList, its update will inform the UI components that utilise JavaFx of changes. This results in the GUI automatically updating to reflect the changes.

Step 5: A new commandResult object is created an passed back to MainWindow, and displayed in Result Display.

Step 6: Finally, the changes in ConsumptionRecord are saved to StorageManager.

4.6.2. Design considerations

Aspect: How nom executes
  • Alternative 1 (current choice): Create a new DailyFoodLog to pass into ModelManager and then ConsumptionRecord.

    • Pros:

      • Maintain comprehensive layers of abstraction

      • Allows code to be easily testable.

    • Cons: Difficult for newcomers or even existing users to trace because of long execution path.

  • Alternative 2: Bypass ModelManager or even not use ConsumptionRecord for storage of data during runtime by allowing everything to be done from parser.

    • Pros: Reduce dependencies on ModelManager and ConsumptionRecord, and make code contained in a single class file easier to navigate.

    • Cons: Violates layers of abstraction set in place by previous structure of AddressBook3. Violates Single Responsibility Principle and reduce cohesiveness of code.

Aspect: Data structure to support the consumption commands
  • Alternative 1 (current choice): Use a single FilteredList to store food for any day by repopulating it each time a consumption related command is used.

    • Pros: Only uses a single FilteredList, so it is clear which list you are using for display.

    • Cons: May have performance issue in terms of speed when there are too many entries.

  • Alternative 2: Use a FilteredList for each date, to store food consumed on that date.

    • Pros: Faster retrieval for display of ConsumedFood items. However, under practical circumstances, the difference is negligible.

    • Cons: May have performance issue in terms of storage because it requires many lists to be stored in addition to LinkedHashMap in DailyFoodLog for each LocalDate.

Aspect: Selecting items to delete from ConsumptionRecord using Vomit command
  • Alternative 1 (current choice): Use index to select item to delete.

    • Pros: When the list is short, user can quickly identify the entry to delete.

    • Pros: Convenient for user as he is required to type less.

    • Pros: User need not spend effort remembering names.

    • Cons: When there are too many records, user is required to scroll through records.

  • Alternative 2: Use name to select item to delete

    • Pros: Can utilise the Result Display suggestion to improve user experience.

    • Cons: User face the hassle of memorising names and typing more.

4.6.3. Summary

This section is a summary on all the above discussed. I would do so with the aid of a few activity diagrams so that you are clear about the flow of the processes covered.

OverallConsumptionActivityDiagram
Figure 25. Activity Diagram for the 3 consumption related command

The 2 diagrams below serves as (rakes), which shows more details.

NomActiivityDiagram
Figure 26. Activity Diagram for Nom command
VomitCommandActivityDiagram
Figure 27. Activity Diagram for Vomit command

4.7. Modifying the FoodRecord

(By Zhang Xian)

This section addresses how the FoodRecord can be modified by the update and delete commands.

The update command allows the user to modify the FoodRecord by either adding a new Food into the FoodRecord or editing the nutritional values of an existing Food in the FoodRecord.

From the user’s perspective, the update command does either of the adding and editing functions. This implementation of update decides whether to override an existing Food in the FoodRecord with new values, or create a new Food in the FoodRecord for them.

For better user experience, for all new Food being updated into the FoodRecord with the update command, the Name attribute will be formatted to proper case. This means that if the user updates a new Food into the FoodRecord with the Name as "char kuay teow", the Food that is stored in the FoodRecord will be of Name "Char Kuay Teow".

When a new Food is updated into the FoodRecord, the FoodRecord is sorted in lexicographical order. For more information on how this is implemented, please refer to its relevant section here.

The delete command allows the user to modify the FoodRecord by deleting a specified Food entry from the FoodRecord. This command takes in the Name of the Food entry to be deleted.

For both delete and update commands, the Name parameter is implemented to be case-insensitive. This means that n/APPLE and n/apple refers to the same Food entry with Name stored as Apple.

4.7.1. Implementation

The modification of the FoodRecord is facilitated by UniqueFoodList, which is responsible for storing all the Food entries in the FoodRecord. Additional abstractions were used by Model and Logic for any operations that results in a modification of the UniqueFoodList.

Both commands require an additional operation, hasFood, in FoodRecord to be implemented. hasFood checks if there is an existing Food in FoodRecord by checking if there is any Food in the FoodRecord with the same Name. Two Food entries is deemed to be of the same Name if their lowercase variant is the same.

This operation was exposed in the Model interface as hasFood, allowing UpdateCommand and DeleteCommand this functionality.

Implementation of update command:

For the update command, the hasFood operation decides whether UpdateCommand adds a new Food into UniqueFoodList or edits the nutritional values of an existing Food in the UniqueFoodList.

The following sequence diagram shows how the update operation works in both cases:

UpdateSequenceDiagram
Figure 28. Sequence Diagram for update command
The lifeline for UpdateCommandParser and UpdateCommand should end at their destroy markers (X) but due to a limitation of PlantUML, the lifelines reach the end of diagram.

How the update command works:

Step 1: LogicManager executes the user input of update n/apple cal/52 p/2 c/14 f/1", using `CalgoParser to realise this is an update command and creates a new UpdateCommandParser object.

Step 2: UpdateCommandParser then parses the arguments provided by CalgoParser with the parse method. During this parsing process, UpdateCommandParser calls the covertToTitleCase method on the Name argument, converting it to proper case.

Step 3: UpdateCommandParser then creates a new UpdateCommand object, which LogicManager calls the execute method with this object as an argument.

Step 4: UpdateCommand now checks if there exists an existing Food in the FoodRecord by calling Model 's hasFood method.

Step 5:

  • Scenario 1: If Food already exists in the FoodRecord:

    • Model calls the getExistingFood method with the user inputted Food as a parameter to get the existing Food, existingFood in the UniqueFoodList. It thens call the setFood method to replace the existing Food in the UniqueFoodList with the new Food which contains new nutritional values.

  • Scenario 2: If Food does not exist in FoodRecord:

    • This scenario is handled by the Lexicographical Ordering feature. Please refer to its relevant section here.

    • Model calls the addFood method with the user inputted Food as a parameter to add the new Food into the UniqueFoodList in FoodRecord

    • After the Food is added into the UniqueFoodList, the UniqueFoodList is also sorted in lexicographical order.

Step 6: A new CommandResult object is then created and returned back to LogicManager.

Implementation of delete command:

For the delete command, the hasFood operation allows UpdateCommand to check whether the Food that the user requests to be deleted exists in the UniqueFoodList.

The following sequence diagram shows how the delete command works:

DeleteSequenceDiagram
Figure 29. Sequence Diagram for delete command
The lifeline for DeleteCommandParser and DeleteCommand should end at their destroy markers (X) but due to a limitation of PlantUML, the lifelines reach the end of diagram.

How the delete command works:

Step 1: LogicManager executes the user input of "delete n/Apple", using CalgoParser to realise this is an delete command and creates a new DeleteCommandParser object.

Step 2: DeleteCommandParser then parses the arguments provided by CalgoParser with the parse method, before creating a new DeleteCommand object that is returned back to the LogicManager which calls the execute method with this as an argument.

Step 3: DeleteCommand now checks if there exists an existing Food in the FoodRecord by calling Model’s `hasFood method, which checks if there is such Food in the UniqueFoodList.

Step 4: Model then calls the getExistingFood method to return the Food object to be removed from the UniqueFoodList. Thereafter, Model calls the deleteFood method with this Food object as an argument to remove this Food from the UniqueFoodList.

Step 5: A new CommandResult object is then created and returned back to the LogicManager.

4.7.2. Design considerations

Aspect: Updating the FoodRecord when there is an existing Food item in FoodRecord
  • Alternative 1 (current choice): Overrides the existing Food item with the new Food item

    • Pros:

      • No need for an additional command of edit just for the user to edit an existing Food item in the FoodRecord.

    • Cons:

      • Might not be intuitive for the user since the word "update" is generally assumed to be for editing something only and not necessarily adding something.

      • May result in additional performance overhead.

  • Alternative 2: Informs the user that there is already an existing Food item, and direct him to use another command edit to edit the existing Food instead.

    • Pros:

      • More intuitive for user, since he might not know that he is overriding an existing Food item

    • Cons:

      • Additional command has to be created just to handle editing

      • More tedious for user since more steps are required to achieve the same result.

4.7.3. Summary

In summary, this section explains how commands related to modifying the FoodRecord is implemented.

The update command is a smart command that either updates an existing Food entry in the FoodRecord with new nutritional information, or updates a new Food item into the FoodRecord The following activity diagram summarises what happens when a user enters a valid update command:

UpdateActivityDiagram
Figure 30. Activity Diagram for update command

The delete command allows the user to remove a Food entry from the FoodRecord by specifying it’s Name as an parameter. The following activity diagram summarises what happens when a user enters a valid 'delete' command:

DeleteActivityDiagram
Figure 31. Activity Diagram for delete command

4.8. Real-time Suggestions for existing Food in FoodRecord

(By Zhang Xian)

This section addresses how the GUI Result Display suggests Food with similar Name to the user for the commands update, delete and nom.

When the user have many Food entries in the FoodRecord, they may have difficulties finding out if a particular Food exists in the FoodRecord. For better user experience, this feature listens to the input of the user for these three commands and suggests similar existing Food entries in real time in the GUI’s Result Display.

This feature listens to the input of the user after the Prefix n/ and checks if there is a Food entry in the FoodRecord with a similar Name.

The Name parameter is case-insensitive and searches the Food entries in the FoodRecord by whether they start with the user input so far after the Prefix n/.

4.8.1. Implementation

To be able to process user’s input in real-time, we set a listener in the CommandBox to listen for the input of any of the three commands: update, delete or nom This feature is then facilitated by different objects, mainly MainWindow and UniqueFoodList. MainWindow interacts with LogicManager 's method of getSimilarFood which exposes the FoodRecord, allowing a filtered list of similar Food entries in the UniqueFoodList to be returned back to the user.

A predicate, FoodRecordContainsFoodNamePredicate is also essential in this implementation in ensuring that the correct similar Food items can be filtered from the UniqueFoodList back to the LogicManager to be displayed by the GUI. The test method of this predicate which is responsible for the above is shown:

public boolean test(Food food) {
    boolean foodStartsWithInputFoodName = food.getName().fullName.toLowerCase()
        .startsWith(foodName.toLowerCase().trim());
    boolean inputFoodNameStartsWithFood = foodName.toLowerCase().trim()
        .startsWith(food.getName().fullName.toLowerCase());

    return foodStartsWithInputFoodName || inputFoodNameStartsWithFood;
}

Both of the boolean used for this predicate is essential. For instance, if "Laksa is already present" in the FoodRecord:

  • If the user keys in "Lak", the first boolean foodStartsWithInputFoodName ensures that "Laksa" will be suggested to the user.

  • If the user keys in "Laksa Spicy", the second boolean inputFoodNameStartsWithFood ensures that "Laksa" will be suggested to the user.

The following sequence diagram will explain how the different objects interact to achieve the Real-time Suggestion Feature.

RealTimeSuggestionSequenceDiagram
Figure 32. Real-time Suggestion Feature Sequence Diagram

Based on the above diagram, when a user has already entered any of the CommandWord: update, delete or nom, and also the Prefix n/:

Step 1: CommandBox calls the MainWindow method of getSuggestions with the parameter as the entire String of user input in the CommandBox.

Step 2: MainWindow then parses the user inputted String and calls LogicManager method of getSimilarFood with the parameter foodName which is the entire String after the Prefix n/

Step 3: The Model then does the necessary work by calling methods getFoodRecord and getFoodList. This results in the current UniqueFoodList being returned

Step 4: The UniqueFoodList is then filtered with the Predicate<Food>, FoodRecordContainsFoodNamePredicate which returns a List<Food> of Food objects that have similar Name fields to the user input.

Step 5: Finally, the filtered List<Food> is then parsed into a String for the user by the MainWindow and then displayed in the GUI’s Result Display.

4.8.2. Design Considerations

Aspect: How the suggestions is shown to the user.
  • Alternative 1: (current choice): ResultDisplay displays the names of similar Food entries in Food Record.

    • Pros:

      • Improved user experience, allowing user to still view the unfiltered FoodRecord in the GUI.

      • User can have access to the raw String of the Name similar Food entries for copying and pasting.

    • Cons:

      • Additional interacting with UI components required, instead of just filtering UniqueFoodList

      • Cannot reusing existing lexicographical sorting feature of FoodRecord.

  • Alternative 2: Filter the GUI’s Food Record to show similar Food entries.

    • Pros:

      • Feature is limited to minimal interactions with UI, making use of existing UI-Model abstractions.

      • Compatible with existing code relating to the FoodRecord, allowing code to be reused.

    • Cons:

      • Takes away most of the need for find and list features since they achieve mostly the same purpose.

Aspect: Commands that utilise Real-time Suggestions
  • Alternative 1: (current choice): Only three commands: update, delete, nom

    • Pros:

      • Improves computational performance, since real-time features for every command will be computationally expensive.

      • Keeps the desired outcomes of other features such as find and list intact

    • Cons:

      • Decrease in user experience, as they might expect this feature to be universal for all commands

  • Alternative 2 All the commands

    • Pros:

      • Better standardisation of feature across all commands.

    • Cons:

      • Additional computational overhead.

      • Not all commands have a Name field.

      • Additional implementation or significant change in how this feature works is necessary to make it universal.

4.8.3. Summary

CommandBox listens for any of the three commands as mentioned, allowing LogicManager and FoodRecord to facilitate the suggestions of similar Food entries from the UniqueFoodList to display in the GUI’s Result Display. This can be summarised in the activity diagram below:

RealTimeSuggestionActivityDiagram
Figure 33. Real-time Suggestion Feature Activity Diagram

4.9. Command guide help command

(by Janice)

This section addresses how the help command works.

The help command allows users to reference a summarised version of the User Guide (called the command guide) containing the usages of the commands and their formats, arranged in alphabetical order. Users may enter an optional command word that filters the displayed command guide.

command word filters out only commands which contain the command word as a substring. If no commands contain it as a substring, an error message will be displayed at the top of the GUI component Help Window and the full command guide will be shown.

4.9.1. Implementation

To generate a command guide using the help command, a HelpCommand object generates the relevant command guides based on the provided command word in the input.

The sequence diagram below demonstrates how the help command works, should a command word of "nom" be provided.

HelpSequenceDiagram
Figure 34. Sequence Diagram for Help

Step 1: LogicManager executes the user input, using CalgoParser to realise it is a help command, and thus creates HelpCommand

Step 2: HelpCommand constructor generates the necessary mapping of command name to the corresponding command guide.

Step 3: LogicManager calls the execute method on the HelpCommand object, which produces the String containing the relevant command guides. A CommandResult object is produced reflecting the response to the help command.

  • In the above sequence diagram, one possibility shown, where the user provides a command_word. setFilteredGuide will attempt to retrieve only relevant command guides, defaulting to a list of all guides if no relevant guides exist. Otherwise, by default a list of all guides will be provided.

Step 4: The CommandResult is eventually passed to the MainWindow class, which then displays the command guide in a separate window, using the HelpWindow class.

4.9.2. Design considerations

Aspect: How Help is displayed
  • Alternative 1 (current choice): GUI component Help Window is displayed as a separate popup.

    • Pros:

      • User can refer to the command guide in a window separately from Calgo, keeping it present as they use the App.

      • Command guide can give a more detailed description of command usage and format as it has more space to display in.

      • No internet access is required as all information on commands is stored offline.

    • Cons:

      • help does not redirect to a url containing the most up-to-date User Guide. Changes made to the User Guide must be updated in HelpCommand separately.

      • GUI component Help Window might obstruct view of the App upon initially loading it, causing annoyance.

  • Alternative 2: GUI component Help Window is not used, and instead content is displayed as part of GUI component Result Display.

    • Pros:

      • No possibility of a popup blocking the main app.

      • All information is contained within a single window.

    • Cons:

      • User must use the help command every time they require a guide, as GUI component Result Display will be overwritten after other commands.

Aspect: Command guides can be selectively displayed
  • Alternative 1 (current choice): help displays all command guides by default. User can selectively filter to display only desired commands by entering an optional keyword after help.

    • Pros:

      • Desired command can be more rapidly found.

      • Removes all unwanted commands from GUI component Help Window, reducing clutter.

    • Cons:

      • Filtered help does not benefit users who don’t know the command they’re looking for.

  • Alternative 2: Always display all command guides to ensure user will find the guide they require.

    • Pros:

      • No possibility of user being unable to find their desired command after sufficient searching.

    • Cons:

      • Relatively large array of commands can be overwhelming to a new user, deterring them from using the App.

      • Can be very frustrating to search through for experienced users.

4.9.3. Summary

help will produce a popup, displaying a guide on the App’s available commands' purposes and usage format.

HelpActivityDiagram
Figure 35. Activity Diagram for Help

4.10. Past seven days calorie data graph

(by Janice)

This section addresses how the graph displaying the user’s past seven day’s daily total calorie consumption works. Note that the graph counts starting from the date on the current Food Record, and the six days prior to it.

The graph will always display the past seven days' data at the bottom of the app, and will update whenever app data is changed.

If a command changes the date of the Food Record (such as nom or stomach), the graph will update to show data for the past seven days from that date, inclusive.

4.10.1. Implementation

GraphPanel in the Ui component. It contains a LineChart of String date against Number calories, and is populated with data from an XYChart.series. The data is in turn obtained from the Logic component, which provides only the past seven days' of DailyFoodLog. The implementation of the GraphPanel class will be further explained.

GraphPanel class implements the following operations:

  • initialiseTreeMap - Sets up the TreeMap that maps LocalDate date of DailyFoodLog to the Double total calorie consumption n that day.

  • initialseGraph - Sets up the LineChart with xAxis a String representing date, and yAxis a Double representing total calories consumed on that date.

  • updateSeries - Ensures the XYChart.series that populates the graph with data is always updated with the most recent app data.

  • makeGraph - Wrapper function that calls the above three methods.

  • getGraph - Public accessor function to generate and retrieve the LineChart.

Calgo will display the past seven days' graph automatically, and likewise update automatically. It does so by having the MainWindow class call getGraph on startup and after execution of commands.

The sequence diagram below demonstrates how the Graph feature works.

GraphSequenceDiagram
Figure 36. Sequence Diagram for GraphPanel

Sequence Diagram for Graph feature.

Step 1: MainWindow requests for an instance of GraphPanel.

If no instance exists, a new GraphPanel is created. Otherwise one is retrieved. This ensures that GraphPanel is a singleton.

Step 2: MainWindow calls GraphPanel again to generate the graph and add it to the GraphPanelPlaceholder inside MainWindow.

Step 3: Inside GraphPanel, a wrapper method makeGraph calls three methods in a row:

First, initialiseTreeMap, which has Logic call the getPastWeekLogs method onto GraphPanel, generating a TreeMap of String date mapped to Double calories using the past seven days' DailyFoodLog. Second, initialiseGraph method is called to generate the graph itself. Third, updateSeries method is called to ensure the data populating the graph is up to date.

After which, the GraphPanel adds the graph to MainWindow.

4.10.2. Design considerations

Aspect: Choice of visuals for past seven days summary
  • Alternative 1 (current choice): Summary is represented using a line graph.

  • Alternative 2: Summary is represented in a table.

Aspect: When graph’s dates are based on
  • Alternative 1 (current choice): Dates are based on past seven days starting from date of Consumption Record, inclusive.

  • Alternative 2: Dates are based on past seven days starting from today’s date, inclusive.

4.10.3. Summary

In summary, this section addresses how the graph obtains information on the past seven DailyFoodLog, and correspondingly produces a visual graph output onto Calgo’s Main Window GUI component viewable by the user.

The graph requires the LogicManager class to obtain the information, and the MainWindow class to facilitate display to the user.

4.11. 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 4.12, “Configuration” below)

  • 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 App

  • 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

4.12. Configuration

Certain properties of the App can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json).

5. Documentation

Refer to the guide here.

6. Testing

Refer to the guide here.

7. Dev Ops

Refer to the guide here.

Appendix A: Product Scope

Target user profile:

  • Can type fast

  • Is reasonably comfortable using CLI Apps

  • Wants to have, or already has, a lifestyle of eating healthy

  • Manages a significant number of Food items

  • Prefers desktop Apps over other types of Apps (such as mobile or tablet)

  • Prefers typing over mouse input

Value proposition:

  • Insights: set goals, generate consumption reports and view progress and statistics

  • Hassle-Free Convenience: conveniently handles entry conflicts, tolerates incomplete search inputs and produces fast responses

  • Flexibility: generate Food records as a portable file, tracking wherever, whenever, without a device

  • Efficiency: manage caloric tracking faster than a typical mouse/GUI driven App

Appendix B: User Stories

Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (possible future development) - *

Priority As a …​ I want to …​ So that I can…​

* * *

user who does not know what my food is made of

find out the nutritional composition of a particular food by name

locate details of the entry without having to go through the entire record.

* * *

new user

see usage instructions

refer to instructions when I forget how to use the App.

* * *

user

have a portable, editable, and readable file to store the relevant values for each entry made

edit, share, or print my personal entries.

* * *

user who may not be able to access his laptop at some time

have a copy of my past entries

use it for physical reference.

* * *

user who dislikes sieving through information and prefers to have only the relevant information presented

have a way to easily find the entries I want in the record

save time and effort and not get annoyed.

* * *

lazy user who does not like typing too many tedious characters

find entries using incomplete words or phrases

obtain the same intended results for a search through the entries as in the case of typing fully and correctly.

* * *

user who dislikes memorising things

have an option to see the entire record

know what entries currently exist in the records.

* * *

user who has many entries

view entire record in lexicographical order

easily navigate to the entry I want in the record.

* * *

user who is forgetful

be able to edit the nutritional value of a previously saved entry in the record

edit the entry if I remembered a nutrition value wrongly previously.

* * *

user who is busy

be able to create a list of goto Food with nutritional values

quickly choose a Food Item with preset values and add it to my calorie tracker.

* * *

user who doesn’t like redundant things

override a Food entry in the Food Record which already exists

save time and effort and not create a duplicate item in the Food Record.

* * *

user who gets bored of food easily

deletpe a Food item that I no longer want to eat in future from my Food Record

avoid having so many Food items in the Food Record that I no longer eat.

* * *

user who is a foodie

find out the statistics of the food that I have been consuming each day

systematically cut down on overeaten food and monitor progress.

* * *

user who cannot decide on what to eat

obtain a list of personalised food recommendations that still align with my dietary goals

avoid wasting time deciding what to eat nor will I give in to impulse and eat junk food.

* * *

user who is interested to lose weight

find out the number of calories I have consumed each day

check which days I have exceed my desired number of daily calorie and exercise more to compensate.

* * *

user who is busy

obtain an easy-to-understand consumption report

quickly understand my food consumption patterns and make plans to rectify them accordingly.

* * *

user who remembers the big picture but not the specifics

search for a particular part of a guide

not be bothered by unnecessary information.

* * *

user who values visuals

curated information expressed in a well organised graph

intuitively understand information.

* * *

user who values opinions

have some suggestions based on my goals and consumption patterns

know my options when I am indecisive on what to eat.

* * *

user who cannot fully remember the name for a particular entry

view all entries which have the nutritional value I happen to remember

obtain a list of possible Food entries that are relevant.

* *

forgetful user

be able to lookup exact command formats

so that I won’t need to go through the trouble of memorising commands.

As you continue developing the Calgo, feel free to add more user stories here.

Appendix C: Use Cases

For all use cases below, the System is the Calgo application and the Actor is the user, unless specified otherwise.

Also note that the term MSS refers to the Main Success Scenario for each Use Case.

Use case: obtain reference for app’s commands

MSS

  1. User wants to find the command guide for the commands in Calgo.

  2. User enters help command with no additional command_word.

  3. Calgo generates a popup, displaying a list of all command guides in the popup.

    Use case ends.

Extensions

2a. User enters a command_word after help, such as foe example help nom. 2a1. Calgo filters out only command guides containing the command_word "nom". 2a2. Calgo generates a popup, displaying this filtered list of command guides.

2b. User enters a command_word after help that has no corresponding command guides. 2b1. Calgo tries to filter out only command guides containing the command_word but fails to find any guides. Thus it defaults to display all command guides. Use case resumes from Step 2.

Use case: find Food item by Name or Tag keyword (which can be an incomplete word)

MSS

  1. User wants to find a Food entry by a specific keyword in Name or Tag.

  2. User enters find command with the Name Prefix, or the Tag Prefix, accordingly.

  3. Calgo shows a list of Food entries which contains the substring indicated in any part of the Name or Tag of the Food entries respectively.

    Use case ends.

Extensions

2a. User enters invalid input for particular Prefix
2a1. A message prompting the user to enter a valid input is shown.
Use case resumes from Step 2.

3a. The FoodRecord is empty
3a1. A message is shown indicating that there are zero matching Food items and prompts users to make new entries.
Use case ends.

Use case: find Food item by nutritional value

MSS

  1. User wants to find a Food item by a single nutritional value of either Calorie, Protein, Carbohydrate, or Fat.

  2. User enters find command with appropriate Prefix.

  3. Calgo shows a list of Food entries which has the same nutritional value.

    Use case ends.

Extensions

2a. User enters invalid input for particular Prefix
2a1. A message prompting the user to enter a valid input is shown.
Use case resumes from Step 2.

3a. The FoodRecord is empty
3a1. A message is shown indicating that there are zero matching Food items and prompts users to make new entries.
Use case ends.

Use case: export current FoodRecord

MSS

  1. User wants to export the current FoodRecord.

  2. User enters the export command into Calgo.

  3. Calgo creates a user-friendly text file FoodRecord.txt containing all Food item details in the data/exports folder.

    Use case ends.

Extensions

3a. User’s system prevents the file from being created
3a1. A message is shown indicating that the file is unable to be created.
Use case ends.

Use case: list all current Food entries

MSS

  1. User wants to list all current FoodRecord entries.

  2. User enters the list command into Calgo.

  3. Calgo shows a list of all Food entries in the GUI’s Food Record.

    Use case ends.

Extensions

3a. The FoodRecord is empty.
3a1. Calgo shows a message indicating that all entries are shown, with the GUI showing an empty Food Record. User is also prompted by this message to make new entries.
Use case ends.

Use case: update current FoodRecord with a new Food item

MSS

  1. User wants to add a new Food entry in the FoodRecord.

  2. User begins to type in an update command with Name Prefix.

  3. Calgo shows that there are no similar Food entries in GUI Result Display.

  4. User completes typing in remaining Prefixes of Calorie, Protein, Carbohydrate, Fat Prefixes accordingly and enters it in Calgo.

  5. Calgo adds a new Food entry into FoodRecord with paramaters as specified by User.

    Use case ends.

Extensions

3a. There are similar Food entries in the FoodRecord
3a1. Calgo the similar Food entries in the GUI’s Result Display Use case resumes from Step 3.

5a. Calgo’s FoodRecord already contains the same `Food entry
5a1. Calgo overrides this existing Food entry with the new Food entry Use case ends.

Use case: delete an existing Food item in current FoodRecord

MSS

  1. User wants to delete a Food entry from the FoodRecord

  2. User types in an delete command with the Name Prefix.

  3. Calgo shows that the Food entry that the User wishes to delete exists in one of the similar Food items message in the GUI Result Display.

  4. User enters the command into Calgo

  5. Calgo deletes the Food entry from the FoodRecord.

    Use case ends.

Extensions

3a. The Food entry that the User wishes to delete does not exists in the FoodRecord.
3a1. GUI Result Display shows that there are no similar Food items in the FoodRecord. Use cases resumes from Step 3.

Use case: set a daily goal

MSS

  1. User enters the goal command with the intended value.

  2. Calgo updates the user’s goal to the new value provided by the user.

Use case ends.

Extensions

1a. User enters an invalid input for the goal.
1a1. Calgo shows a message indicating the acceptable range of values for the goal command.
1a2. User enters goal command with a new value.
Steps 1a1 and 1a2 are repeated until user enters a valid input.

Use case resumes from step 2.

Use case: generate a report on a specific date.

MSS

  1. User enters the report command with a particular date.

  2. Calgo analyses the Food consumed on that date and generates a report text file in the data/reports folder for the user.

Use case ends.

Extensions

1a. There is no Food consumed on the inputted date.
1a1. Calgo shows a message indicating that there was no Food consumed on the given date.

Use case ends.

1b. Inputted date in wrong format.
1b1. Calgo shows a message indicating the correct format for the date.
1b2. User enters report command with the date in the correct format.

Use case resumes from step 2.

1c. User enters report command without setting a daily calorie goal.
1c1. Calgo generates a report without the sections related to the goal.

Use case ends.

Use case: consuming food on a on a specific day with nom.

MSS

  1. User wants to record their Food consumption on a particular day.

  2. User enters nom command with the appropriate Prefixes and values.

  3. Calgo processes the command and update display.

Extensions

2a. User misspells the command or Prefix
2a1. A message prompting the user to enter a valid input is shown.
Use case resumes from Step 2.

2b. The Food does not exist in FoodRecord
2b1. A message prompting the user to enter a valid input is shown.
Use case resumes from Step 2.

2c. User enters invalid value for particular Prefix
2c1. A message prompting the user to enter a valid input is shown.
Use case resumes from Step 2.

Appendix D: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 11 or above installed.

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

  3. 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.

  4. Calgo should work on both 32-bit and 64-bit environments.

  5. The minimum screen size for the App window to fully display its GUI is 1250 x 600.

  6. Calgo should be designed for a single-user (i.e. Calgo should not be a multi-user App).

  7. The product should be developed incrementally over the project duration.

  8. The software’s codebase should adhere to OOP.

  9. The product should have minimal network usage. Therefore, it is expected that users will find out about the respective nutritional values of a Food entry whenever they want to update it into the FoodRecord for the first time.

Appendix E: Glossary

Application User Interface (API)

A set of tools for building software application.

Command Line Interface (CLI)

Text-based user interface used to view and manage computer files.

Food

Food items entered by the user to represent a real life Food. This contains nutritional values of each of their Calorie s, number of grams of Protein s, Carbohydrate s and Fat s. They can also contains a series of Tag s.

Food Entry

An entry in the GUI’s Food Record box, which shows all details for one Food object.

FoodRecord

The accumulated list of all Food objects entered by the user.

Food Record

The GUI’s Food Record box, which shows all details for every Food entry.

GUI

The Graphical User Interface of Calgo.

Mainstream OS

Windows, Linux, Unix, OS-X.

Nutritional Information

Refers to Calorie s, Protein s, Carbohydrate s and Fat s.

OOP

Objected-Oriented Paradigm.

Prefix

A set of characters placed before a parameter when entering a command.

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

F.1. Launch and Shutdown

  1. Initial launch

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

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

  4. Saving window preferences

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

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

F.2. Getting help

  1. Displays a guide for all commands. Can also display only commands containing the given command word.

  2. Prerequisites: Launch Calgo succesfully.

  3. Test case: help
    Expected: A help window pops up and shows you how to use each command.

  4. Test case: help abcd
    Expected: A help window pops up and shows you how to use each command anyway, but tells you abcd does not exist.

F.3. Creating a Food

  1. Adding a Food item to the FoodRecord

  2. Prerequisites: Launch Calgo successfully

  3. Test case update n/Apple cal/50 p/3 c/2 f/45

  4. Expected: Updated all foods into Food Record: Apple Calories: 50 Proteins (g): 3 Carbohydrates (g): 2 Fats (g): 45

  5. Test case: update x

  6. Expected:
    Invalid command format. update: Updates the food entered into the Food Record. If the food entered already exists, it will be overwritten by input values. Parameters: n/NAME cal/CALORIES p/PROTEIN c/CARBOHYDRATE f/FAT [t/TAG]…​ Example: update n/Kiwi cal/150 p/2 c/25 f/3 t/Green t/Sweet

  7. Other incorrect commands to try: update n/Apple c/2 f/45 (where parameters are missing)
    Expected: Similar to previous.

F.4. Editing a Food

  1. Editing a Food item in the FoodRecord

  2. Prerequisites: Launch Calgo successfully and Food already exists in FoodRecord.

  3. Test case update n/Apple cal/53 p/3 c/3 f/45

  4. Expected:
    Updated all foods into Food Record: Updated existing food item in Food Record: Apple Calories: 50 Proteins (g): 3 Carbohydrates (g): 3 Fats (g): 45

  5. Test case: update x

  6. Expected: Invalid command format. update: Updates the food entered into the Food Record. If the food entered already exists, it will be overwritten by input values. Parameters: n/NAME cal/CALORIES p/PROTEIN c/CARBOHYDRATE f/FAT [t/TAG]…​ Example: update n/Kiwi cal/150 p/2 c/25 f/3 t/Green t/Sweet

  7. Other incorrect commands to try: update n/Apple c/2 f/45 (where parameters are missing)
    Expected: Similar to previous.

F.5. Deleting a Food

  1. Deleting a Food item from the FoodRecord

    1. Prerequisites: Launch Calgo successfully and a Food item Apple already exists in FoodRecord

    2. Test case: delete n\Apple

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

  3. Other incorrect delete commands to try: delete, delete n/Banana (where Food banana does not exists in FoodRecord)
    Expected: Similar to previous.

F.6. Listing all Food entries

  1. Listing down all entries, regardless of previous commands

  2. Prerequisites: Launch Calgo successfully.

  3. Test case: list
    Expected: The GUI will show all Food entries existing in the FoodRecord.

  1. Searches through the Calgo’s Food entries and displays relevant ones based on the specifications entered.

  2. Prerequisite: Launch Calgo succesfully, and Food being searched exists in Food Record

  3. Test case: find n/Apple
    Expected: Food having name that partially match "Apple" will be displayed

  4. Test case: find t/sWeet
    Expected: `Food
    having tag that partially match "sweet" will be displayed

  5. Test case: find t/swEeT n/Apple
    Expected: Please specify 1 and only 1 correct parameter for filtering using the find command.

  6. Other incorrect commands to try: find n/
    Expected: Names should only contain alphanumeric characters and spaces, and it should not be blank.

F.8. Adding Food to consumption record

  1. Adds a Food to a specific day’s Consumption Record.

  2. Prerequisite: Launch Calgo succesfully, and Food being consumed exists in Food Record

  3. Test case: nom n/chicken d/2020-03-04 portion/1.5 r/8
    Expected: Successfully consumed Chicken Calories: 32 Proteins (g): 20 Carbohydrates (g): 1 Fats (g): 11

  4. Test case: nom n/chickn d/2020-03-04 portion/1.5 r/8
    Expected: You can’t eat that because it does not exist in food record.

  5. Other incorrect commands to try: nom n/chicken d/2020-03-04 portion/-1 r/8
    Expected: Portion should be a positive number.

F.9. Removing a portion of Food from consumption record

  1. Deletes a portion of a specific Food from the Consumption Record.

  2. Prerequisite: Launch Calgo succesfully, and Food being consumed exists in Consumption Record

  3. Test case: vomit num/1 d/2020-03-04
    Expected: Successfully throw up Chicken Calories: 32 Proteins (g): 20 Carbohydrates (g): 1 Fats (g): 11

  4. Test case: vomit num/
    Expected: Position should be a positive integer!

  5. Other incorrect commands to try: vomit num/-1
    Expected: Position required an integer within range of list!

  1. Deletes a portion of a specific Food from the Consumption Record.

  2. Prerequisite: Launch Calgo succesfully, and have eaten something on the day you want to browse.

  3. Test case: stomach d/
    Expected: Display all food consumed. (As long as you ate on that day before, applies even if your record is empty due to using vomit)

  4. Test case: stomach d/1930-04-01
    Expected: Your consumption record is empty because you have not consumed food on 1930-04-01 before

F.11. Setting a goal

  1. Sets your daily calorie goal.

  2. Prerequisite: Launch Calgo succesfully.

  3. Test case: goal 69
    Expected: That is a really low goal to set. Warning: You may suffer from malnutrition. We’ll accept this now because Calgo will eventually help you to reach a daily calorie count of 1200, which is the minimum calories you should eat to stay moderately healthy.

  4. Test case: goal 0
    Expected: Please key in a whole number that is at least 1 calorie and at most 99999 calories.

F.12. Generating report for the day

  1. Generates consumption report for a given date.

  2. Prerequisite: Launch Calgo succesfully, and have eaten something on the day you want to generate report on.

  3. Test case: report d/
    Expected: Successfully generated a report in the data/reports folder for the following date: 2020-04-13.

  4. Test case: report d/2070-04-12
    Expected: Did not manage to generate report. There was no food consumed on 2070-04-12.

F.13. Generating a copy of your Food Record

  1. Generates a neat and editable file containing the current Food entries.

  2. Prerequisite: Launch Calgo succesfully

  3. Test case: export
    Expected: Successfully generated FoodRecord.txt in the data/exports folder.

F.14. Clearing Food Record data

  1. Clears all food entries from Calgo. Note that data in Consumption Record is not deleted.

  2. Test case: clear
    Expected: Food Record has been cleared! Use the update command to add new food into your Food Record.

F.15. Saving data

  1. Dealing with missing/corrupted data files.

  2. Calgo will start from a fresh state if your files are corrupted.

  3. Warning: You can edit the .json files in the /data folder. Be careful, if the files you edit ends up with invalid format, you risk losing all existing data.

Appendix G: Effort

G.1. Challenges and Difficulties

At the start, all of us were very new to software engineering projects. Hence, the learning curve was very steep. Because of this, most of the time, we were very confused. However, we demonstrated good teamwork because we always met often and helped each other out by explaining frameworks and teaching each other on software development tools like Git, Intellij and JavaFX.

Due to the COVID-19 situation, there was a lot of uncertainty and our style of meetings were significantly affected. However, everyone demonstrated good attitude and the team was full of good sports, so we covered each other’s weaknesses and supported one another, therefore being able to realise a strong team potential.

G.2. Effort put in by the team

  • 3-4 meetings weekly on average

  • Many unrecorded hours were put in for self-learning and managing the project.

  • We placed a lot of emphasis on brainstorming our features and implementing them to make it user-centric. For e.g. a real-time suggestion feature

G.3. Achievements

  • Product Design

    • Our team successfully morphed AB3 and its relevant tests into the Calgo you see today.

    • Our team’s project idea was validated and appreciated by peers and tutors, most notably from our CS2101 presentation, CS2103T demo and PE-Dry Run testing. It also has potential to be collaborated with other peer projects such as FitBiz (Group F11-2).

  • Implementation

    • Ambitious in experimenting with new interesting features. For e.g. intelligent insights and graphs.

    • Implemented features consistently and incrementally, allowing us to make changes to past features and

    • Experimented with new workflow before deciding on one, Agile(Scrum), which we liked the most.

    • Put in additional effort in making the GUI different and novel, even though it is not part of the grading rubric. This is another example of how we go the extra mile to make our product more user-centric. For e.g. we tried out new JavaFX APIs like LineChart and TableView.

  • Project Management

    • Predominantly followed the forking workflow.

    • Diligently created issues and assigned them on GitHub, while also consistently communicating with each other on Telegram.

    • Planned and incrementally implemented our user stories throughout different milestones.

    • Regularly reviewed each other’s code on and off Github.