By: Team W10-4      Since: Sep 2018      Licence: MIT

1. Setting up

1.1. Prerequisites

  1. JDK 9 or later

    JDK 10 on Windows will fail to run tests in headless mode due to a JavaFX bug. Windows developers are highly recommended to use JDK 9.
  2. IntelliJ IDE

    IntelliJ by default has Gradle and JavaFx plugins installed.
    Do not disable them. If you have disabled them, go to File > Settings > Plugins to re-enable them.

1.2. Setting up the project in your computer

  1. Fork this repo, and then clone the fork to your computer

  2. Open IntelliJ (if you are not in the welcome screen, click File > Close Project to close the existing project dialog first)

  3. Set up the correct JDK version for Gradle

    1. Click Configure > Project Defaults > Project Structure

    2. Click New…​ and find the directory of the JDK

  4. Click Import Project

  5. Locate the build.gradle file and select it. Click OK

  6. Click Open as Project

  7. Click OK to accept the default settings

  8. Open a console and run the command gradlew processResources (Mac/Linux: ./gradlew processResources). It should finish with the BUILD SUCCESSFUL message.
    This will generate all resources required by the application and tests.

  9. Open XmlAdaptedPerson.java and MainWindow.java and check for any code errors

    1. Due to an ongoing issue with some of the newer versions of IntelliJ, code errors may be detected even if the project can be built and run successfully

    2. To resolve this, place your cursor over any of the code section highlighted in red. Press ALT+ENTER, and select Add '--add-modules=…​' to module compiler options for each error

  10. Repeat this for the test folder as well (e.g. check link:https://github.com/CS2103-AY1819S1-W10-4/main/tree/master/src/test/java/seedu/souschef/commons/util/XmlUtilTest .java[XmlUtilTest.java] and HelpWindowTest.java for code errors, and if so, resolve it the same way)

1.3. Verifying the setup

  1. Run the seedu.souschef.MainApp and try a few commands

  2. Run the tests to ensure they all pass.

1.4. Configurations to do before writing code

1.4.1. Configuring the coding style

This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,

  1. Go to File > Settings…​ (Windows/Linux), or IntelliJ IDEA > Preferences…​ (macOS)

  2. Select Editor > Code Style > Java

  3. Click on the Imports tab to set the order

    • For Class count to use import with '*' and Names count to use static import with '*': Set to 999 to prevent IntelliJ from contracting the import statements

    • For Import Layout: The order is import static all other imports, import java.*, import javax.*, import org.*, import com.*, import all other imports. Add a <blank line> between each import

Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.

1.4.2. Setting up CI

Set up Travis to perform Continuous Integration (CI) for your fork. See UsingTravis.adoc to learn how to set it up.

After setting up Travis, you can optionally set up coverage reporting for your team fork (see UsingCoveralls.adoc).

Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your personal fork.

Optionally, you can set up AppVeyor as a second CI (see UsingAppVeyor.adoc).

Having both Travis and AppVeyor ensures your App works on both Unix-based platforms and Windows-based platforms (Travis is Unix-based and AppVeyor is Windows-based)

1.4.3. Getting started with coding

When you are ready to start coding,

  1. Get some sense of the overall design by reading Section 2.1, “Architecture”.

  2. Take a look at [GetStartedProgramming].

2. Design

2.1. Architecture

Architecture
Figure 1. Architecture Diagram

The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.

The .pptx files used to create diagrams in this document can be found in the diagrams folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose Save as picture.

Main has only one class called 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 method where necessary.

Commons represents a collection of classes used by multiple other components. Two of those classes play important roles at the architecture level.

  • EventsCenter : This class (written using Google’s Event Bus library) is used by components to communicate with other components using events (i.e. a form of Event Driven design)

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of the App consists of four components.

  • UI: The UI of the App.

  • Logic: The command executor.

  • ModelSet: Holds the data of the App in-memory.

  • Storage: Reads data from, and writes data to, the hard disk.

Each of the four components

  • Defines its 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 it’s API in the Logic.java interface and exposes its functionality using the LogicManager.java class.

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

Events-Driven nature of the design

The Sequence Diagram below shows how the components interact for the scenario where the user issues the command delete 1.

SDforDeletePerson
Figure 3. Component interactions for delete 1 command (part 1)
Note how the Model simply raises a AppContentChangedEvent when the Sous Chef data are changed, instead of asking the Storage to save the updates to the hard disk.

The diagram below shows how the EventsCenter reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time.

SDforDeletePersonEventHandling
Figure 4. Component interactions for delete 1 command (part 2)
Note how the event is propagated through the EventsCenter to the Storage and UI without Model having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components.

The sections below give more details of each component.

2.2. UI component

UiClassDiagram
Figure 5. Structure of the UI Component

API : Ui.java

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, GenericListPanel, StatusBarFooter, DetailPanel 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,

  • Executes user commands using the Logic component.

  • Binds itself to some data in the Model so that the UI can auto-update when data in the Model change.

  • Responds to events raised from various parts of the App and updates the UI accordingly.

2.2.1. Modularisation

List Panel

Brief overview: Many of the features (such as MealPlanner, HealPlan, IngredientManager, Recipes, Favourites) need to use the list panel section to display their own UniqueType classes. Therefore, this has been abstracted and modularised for easier implementation of usage and for any future implementation of new components which may need to use the list panel. This is done by inserting generic classes into the hierarchy.

Old Hierarchy:

UiPanelOldHierarchy

New Hierachy:

UiPanelNewHierarchy
Not all the components are shown, only MealPlanner and Recipe components are shown in the diagram as an example. Notice how the new generic abstract classes are simply inserted between the superclass and subclass of the old hierarchy to give the new hierarchy.

Explanation:

With the GenericListPanel, GenericCard and GenericPanelSelectionChangedEvent classes, new features which wish to use the UI list panel simply have to have their respective classes extend these three abstract classes and implement their methods accordingly. Then the respective fxml files for [feature]ListPanel and [feature]Card have to be created.

MainWindow.java and ListPanelSwitchEvent.java:

ListPanelSwitchEvent encapsulates an event where the list panel needs to change context. It extends BaseEvent and contains a Context attribute to store information on which context the list panel needs to switch to.
To switch between different list panels for different features, switchTo[feature]ListPanel() methods have to be implemented in MainWindow. This method gets the respective filteredList from the model within modelSet within logic and uses it to change the generalListPanel. The handleListPanelSwitchEvent() method is invoked when a ListPanelSwitchEvent is posted to the EventsCenter, and the method then checks the context attribute of the ListPanelSwitchEvent object to determine which context the list panel needs to switch to, and then calls the correct method.

Usage:

Now in order to switch between the different feature list panels, the developer just has to create their handler method, instantiating a ListPanelSwitchEvent with their desired context and then raising the event by posting it to the EventsCenter.

2.3. Logic component

LogicClassDiagram
Figure 6. Structure of the Logic Component

API : Logic.java

  1. Logic uses the AppContentParser class to parse the user command.

  2. AppContentParser passes necessary model to each feature parser such as IngredientParser according to the context.

  3. This results in a Command object which is executed by the LogicManager.

  4. The command execution can affect the Model (e.g. adding an ingredient) and/or raise events.

  5. The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui.

Given below is the Sequence Diagram for interactions within the Logic component for the execute("add onion 100 gram 10-23-2018") API call.

LogicSequenceDiagram
Figure 7. Interactions Inside the Logic Component for the adding an ingredient

2.4. Model component

ModelComponent
Figure 8. Structure of the Model Component

The ModelSet,

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

  • stores the Sous Chef application data.

    • Contains multiple Model each in-charge of a feature’s data.

  • shares a single instance of VersionedAppContent to ensure single version of truth.

  • does not depend on any of the other three components.

The Model,

  • each represents a feature-unique data.

    • ensures data abstraction for each feature.

  • is reusable as model is now generic.

  • exposes an unmodifiable ObservableList<T extends UniqueType> 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.

2.5. Storage component

StorageClassDiagramRevamp
Figure 9. Structure of the Storage Component (The given diagram only shows the extensions of 2 functions.)

API : Storage.java

The Storage component,

  • can save UserPref objects in json format and read it back.

  • can save the various xml files from each of the different functions. Namely recipe.xml, ingredients.xml and healthplan.xml

  • can do a context switch for storing information into the correct xml file for the function.

  • generalized in its implementation for easy extension

  • controls information by passing a single appContent unit around the system.

  • With the new implementation of the storage component, calls at the upper layers are generalized and all components shares common calls for load and saving.

  • The new storage component introduces a new abstract parent (XmlFeatureStorage) and also a new interface (XmlSerializableGeneric) to make all the calls of load and save to be generic to allow easy functional implementation without much creation of new methods at the storage level. Just by calling the context switch and also the save and load calls the intended result will be achieved.

2.6. Common classes

Classes used by multiple components are in the seedu.souschef.commons package.

3. Implementation

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

3.1. Ingredient Manager

3.1.1. Total management of ingredients that user currently have

Ingredient Manager is an address book of ingredients, with some basic functions similar to that of a normal address book such as add, edit, delete and list. Additionally, Ingredient Manager has some useful functions specific for management of ingredients.
Firstly, serving units can be converted to a common serving unit with reference to the serving unit dictionary. Conversion to a common serving unit is necessary to enable arithmetic calculation between amounts. Also, it is easier to compare between ingredients when the amounts are shown in common serving units. Secondly, when you apply the list command, ingredients are sorted by date. Since ingredients have to be consumed in a limited period, it keeps user be reminded of old ingredients.

Current implementation of serving units dictionary

Ingredient Manager uses gram for the common serving unit. Serving unit dictionary is implemented with static HashMap in IngredientServingUnit class, which contains names of a serving unit as a key and a IngredientServingUnitDefinition object as a value. IngredientServingUnitDefinition has three fields, which are a serving unit to be converted, a common serving unit, and a constant to be multiplied for conversion. When conversion is to be done, you find the value in the HashMap with an input serving unit as a key. Then, you multiply the amount of ingredient with the constant and replace the current unit with a common unit. This process is wrapped in a method called convertToCommonUnit() in ingredient models which contains amount field.

Alternative Implementation of serving units dictionary

In the current implementation, a serving unit to be converted exists both as a key and in a value of the HashMap. This duplication can be removed by transforming the form of the value in the HashMap from a definition object to an array containing the other two fields of the object. However, creating a separate class to wrap three fields is more desired for abstraction. In the current version of the application, definition object is only used for conversion to a common serving unit. But in later version, conversion can be done the other way around for the purpose of display. Also, in a more complete version of the dictionary, definition should be expanded to cover information related to validity of combination between specific serving unit and ingredient, and its conversion value. Also, using HashMap brings huge convenience in further implementation of functions which include searching of value in the dictionary, cancelling out the cost of duplication.

3.1.2. View recipes according to ingredients, and check shop amount

This command works on separate context called cross, because the result of command manipulates list of recipes, not ingredients. The command filters(include) and sorts(prioritize) recipes based on ingredients given in the command, and makes use of information stored in Ingredient Manager in two ways. Firstly, you can provide all ingredients in Ingredient Manager as parameter for filtering or sorting. This might be useful when a user wants to find recipe that maximizes the use of existing ingredients. Secondly, for the recipes that are on the list as a result of filtering and sorting, additional amount of ingredients that need to be prepared for a recipe is calculated. Because ingredients in Ingredient Manager are considered as stock that the user currently have, their amounts are subtracted from the desired amount, calculated considering the number of servings. After cooking, she can go back to Ingredient Manager and edit amount of ingredients or delete ingredients used for cooking.

Current implementation of cross recipe model

Although it looks similar, models used for recipe context and cross context are different. crossRecipe class extends Recipe class with one more additional field, a Map named neededIngredients. This has IngredientDefinition object as a key, and IngredientPortion object as a value. It is to store information of needed amount of an ingredient to amount field of IngredientPortion object after calculation is done.

Current implementation of ingredient models
IngredientModelDiagram

There are three ingredient related models, namely IngredientDefinition, IngredientPortion, and Ingredient. IngredientDefinition is a wrapper class for ingredient name. IngredientPortion inherits IngredientDefinition and additionally includes amount field. Ingredient again inherits IngredientPortion, and it is the most complete version of ingredient with date information, used by Ingredient Manager. Reason for hierarchical implementation is that there exists different needs for ingredient related fields across features or functions. For example, ingredient in recipe uses IngredientPortion, and ingredient in Ingredient Manager uses Ingredient. IngredientDefinition is used as a key of a HashMap in implementing 'view recipes according to ingredients' function. It can be used to construct ingredient dictionary or to expand unit dictionary in the future version of the application.

3.2. Meal Planner feature

3.2.1. Current Implementation

Model

The Meal Planner feature is supported by the classes Meal and Day.

Supporting classes:

  • Day: encapsulates a LocalDate date and an ObservableList<Meal> mealList containing 3 meals (breakfast, lunch, dinner). Day objects are always instantiated with 3 empty Meal objects in the mealList.

    • An empty Day is defined as having 3 empty meals in its mealList.

    • 2 Day objects are defined as same (through overriding equals() method) as long as they have the same LocalDate date. This is to ensure no duplication of dates within the Meal Planner.

  • Meal: abstract class which encapsulates a Optional<Recipe> recipe, an int index indicating which meal index each Meal object is (0 for breakfast, 1 for lunch, 2 for dinner), and a String slot containing its corresponding name.

    • Breakfast: inherits from Meal class and represents the breakfast meal slot of the day. Contains a predefined integer index of 0 and a predefined String slot name of "breakfast".

    • Lunch: inherits from Meal class and represents the lunch meal slot of the day. Contains a predefined integer index of 1 and a predefined String slot name of "lunch".

    • Dinner: inherits from Meal class and represents the dinner meal slot of the day. Contains a predefined integer index of 2 and a predefined String slot name of "dinner".

    • An empty Meal is defined as having no recipe, i.e. empty Optional.

Class diagram:

MealPlannerClassDiagram

Brief explanation:

Meal Planner is currently implemented as a list of non-empty Day objects. This is ensured as Day objects are only instantiated when recipes are added to a certain date, with that date being assigned to its Day object. When all the recipes are deleted from a certain Day, the Day object is removed from the Meal Planner. Day objects in the Meal Planner list panel are sorted in chronological order according to their dates. This is done by sorting the list when instantiated and when Day objects are added or removed.

Logic

For Meal Planner commands to be accessible, context has to be switched to "Meal Planner via the command -mealplanner (except for the case of the plan command for adding recipes to the Meal Planner, which has to be done in the recipe context since the user needs to be able to see the recipes). From there, the Meal Planner commands can be used and AppContentParser will redirect the different commands to their respective CommandParser.

Plan Command: plan

The plan command adds a recipe from the recipeModel to the mealPlannerModel. Therefore both models have to be passed to PlanMealCommandParser.

Within PlanMealCommandParser, it parses the target date from a String into a LocalDate. Only dates from the present or future are allowed to be entered. This LocalDate object is then used to find out whether mealPlannerModel currently contains a Day with the target date. If such a Day object already exists, it is taken from mealPlannerModel to be used in PlanMealCommand. Else, a new Day object is instantiated with the target LocalDate date. The target meal is then taken from the Day object. mealPlannerModel, target day target recipe and target meal are then passed to PlanMealCommand.

Within the execute method of PlanMealCommand, target recipe is then added the target meal and target day is then added to mealPlannerModel.

Finally, the new content is committed via mealPlannerModel.commitAppContent().

Select Command: select

The select command selects a recipe of a specified meal of a specified day and displays the details of the recipe (ingredients, instructions, etc) in the DetailPanel.
MealPlannerParser invokes the parseMealRecipe() command of SelectCommandParser. The desired index and meal index are parsed from the user input and used to extract the recipe from the specified day’s desired meal index. Using this recipe, the recipeModel is searched and the index of the matching recipe is returned.
The index and the recipeModel are then passed to a SelectCommand which executes the displaying of the recipe details in the same manner as in the recipe context.

Clear Command: clear

The clear command simply clears the Meal Planner of all days.
This is done by setting (i.e. replacing) the existing UniqueList of mealPlannerModel with a new empty UniqueList.

Delete Command: delete

The delete command deletes a specified Day from the mealPlannerModel along with all its meals. The Day is specified by its index as seen on the list panel.
MealPlannerParser invokes the parseMealPlan() method of DeleteCommandParser. The user input is then parsed to get the index of the Day to be deleted. The Day object at the given index of the mealPlannerModel list is then deleted.

Alternative implementation

Earlier MealPlanner implementation used a ObservableHashMap with LocalDate and Day as the key and value pairs, each LocalDate being the key for the Day with the same LocalDate. Theoretically, this would work as well. However, this was scrapped as a simple ObservableList of Days would work equally well. In addition, since AB4 already has an implemented UniqueList, using a simple List makes it easier to integrate the Meal Planner into the existing infrastructure.
HashMaps are mainly used as they give a time complexity advantage, having an O(1) time complexity. However, that advantage is not present here since our Lists usually access elements by index, and thus also have a time complexity of O(1).

3.3. Favourites list

This feature allows users to have a quick reference to all of their favourite recipes. Users are able to add recipes that they like a lot, or that they cook often to the favourites list. Users are also able to search, display details, and remove any entry within the favourites list.

Model

The model is simply a UniqueList of Recipes.

Logic

To access favourite context commands, context has to be switched to "Favourites" via the command -favourite. From there, the Favourite commands can be used and AppContentParser will redirect the different commands to their respective CommandParsers.favourite command for adding recipes to the Favourites list is done in recipe context.

3.3.3. Adding to Favourites

While browsing the recipes in the recipe context, and when a user likes a recipe at a particular INDEX, they can execute the favourite INDEX command. The recipe selected would be copied from the recipe model, and added to an array list in the favourites model.

3.3.4. Selecting a Favourite Recipe

While browsing the favourite recipes in the favourite context, if the user wishes to view the details of the recipe, they can execute the select INDEX command. From the array list in the favourites model, the 1 based index will be accessed, and its details will be displayed in the panel.

3.3.5. Finding a Favourite Recipe

The user can also browse and search favourite recipes by tag. Similar implementation as 'find' command in recipe context.

3.3.6. Deleting a Favourites Recipe

While browsing the recipes in the recipe context, and when a user wants to remove a recipe at a particular INDEX, they can execute the delete INDEX command. From the array list in the favourites model, the index will be accessed, and the recipe will be deleted form the favourites model.

3.3.7. Clearing Favourites

The clear command clears the Favourites List of all Recipes.
The command replaces the existing UniqueList of favouriteModel with a new empty UniqueList.

3.4. Recipe Management

3.4.1. Multi-line Recipe Adding

Current Implementation

As the adding of recipes deal with lengthy input parameters from the basic details such as name to a list of instructions, single-line add command proves to be inefficient and often providing an inconvenient user experience. To allow flexibility, adding of recipes have been implemented as a multi-line command fitting into this CLI application.

Multi-line add function consists of 3 main commands: add cont end. The add command requires user input of the recipe basic information such as Name, Difficulty, CookTime and the optional Tag. The cont command requires user input of the recipe Instruction. Each cont command allows only 1 Instruction. Again this is to break down the lengthy input parameter of Instruction. Lastly, with the end command the Recipe is being recognised and is added to the Model.

The following is the sequence diagram of the add command:

recipe add add

Once user’s input is being parsed, a RecipeBuilder is created instead of a Recipe instance. This RecipeBuilder will be stored in History when the command is being executed.

Repeating use of add will replace existing RecipeBuilder rendering the previous data unsaved until user complete the add recipe command.

The following is the sequence diagram of the cont command:

recipe add cont

Upon parsing the user’s input that contains Instruction, a Instruction instance will be created. This Instruction is to be passed to the command for execution. The execution process once again make use of the History to append the Instruction into RecipeBuilder.

The following is the sequence diagram of the end command:

recipe add end

Upon receiving the end command, Recipe will be built from the RecipeBuilder. The parsing process checks to ensure that the Recipe built contains at least a single Instruction to prevent adding of Recipe without Instruction. With a valid Recipe, a AddCommand will be created and the RecipeBuilder in History will be cleared (i.e. set to null). This AddCommand when executed will add the Recipe into Model.

Why it is implemented that way:

Due to the nature of a multi-line add command where the Recipe is incomplete together with the inability to execute the add function immediately, 2 mechanisms namely, a builder and a historical storage, have to be put in place. The builder works effectively in managing the data of a partial Recipe while allowing continuous modification. The historical storage, History in this context, ensures that lossless partial Recipe data. Moreover, since History interaction with commands has already been well establish in Logic component and a partial Recipe is indeed a representation the historical commands, storing in History becomes a dominant choice.

Alternatives:
  • Adding of incomplete recipe to the model and edit that recipe with the subsequent commands.

    • Pros - Make use of existing add and edit command without the need of additional implementation.

    • Cons - Model contains incomplete recipe that violates the validity of recipe data. This might be exploited and in the process the application can be filled with incomplete recipes.

The all-field recipe search matches keywords with details of Recipe. Each keyword goes through the matching process against the Name, Tag, Difficulty, CookTime and even IngredientPortion from each Instruction of every recipe. Such search feature provides a complex querying, rather than a superficial search on merely recipes' name.

Current Implementation

The following recipe class diagram shows the data structure of Recipe to support searching:

recipe class diagram

Other than the basic Recipe details accessible via the getters(), nested attribute such as IngredientPortion can post difficulty for the search function since Recipe and IngredientPortion is not associated initially. To allow direct access to all IngredientPortion of that recipe, a tabulateIngredients() method is added. This method stores all IngredientPortion in the HashMap of ingredients attribute by searching through all Instruction. As Recipe is immutable, the generating of the ingredients is done in the constructor of Recipe (i.e. tabulateIngredients() is called in the constructor). This allow the direct accessing all attributes required for the search to completed.

To implement this feature, recipe Model is implemented with FilteredList. This list takes in a predicate to filter away recipes that does not meet the requirement.

The desired search keywords, once parsed as List of String will be turned to Stream. Each keyword is checked against a recipe Name for any matching word, Difficult for exact matching value, Tag for any matching word, and Ingredient for any matching IngredientDefinition. All keywords must return true (i.e. matching at least one attribute of the recipe) before the recipe is deemed fit to be kept in the FilteredList.

The following code snippet shows how the predict determines each recipe:

public boolean test(Recipe recipe) {
    if (keywords.size() == 0) {
        return false;
    }
    return (keywords.stream()
            .allMatch(keyword -> {
                return StringUtil.containsWordIgnoreCase(recipe.getName().fullName, keyword)
                        || recipe.getCookTime().toString().toLowerCase().equalsIgnoreCase(keyword.toLowerCase())
                        || recipe.getDifficulty().toString().equals(keyword)
                        || recipe.getTags().stream()
                        .anyMatch(tag -> StringUtil.containsWordIgnoreCase(tag.tagName, keyword))
                        || recipe.getIngredients().keySet().stream().anyMatch(ingredientDefinition -> StringUtil
                        .containsWordIgnoreCase(ingredientDefinition.toString(), keyword));
            }));
}

The following sequence diagram shows how the keywords are being parsed to feed into the predicate:

recipe search seq
Why it is implemented that way:

Due to the existing data structure implemented by model, to access the attributes within a recipe, the system needs to use its getter and perform matching with its keywords. Hence, a filter via predicate testing is deemed fit.

Alternatives:
  • Tag saving recipes that uses it.

    • Pros - Search done more effectively as the system only need to display all recipes stored in a tag

    • Cons - Bi-directional association, increase difficulty in maintaining data integrity

  • Saving IngredientPortion directly in recipe instead of individual instruction.

    • Pros - Remove the need to search through the initial every instruction to gather the ingredients for every new instance of recipe. Speed up the recipe add and read process.

    • Cons - Since ingredients are aggregated from instruction, by allowing IngredientPortion to be a direct attribute of recipe imply a possibility of mismatch between the ingredients in the instruction and the ingredients in the recipe. (e.g. User entering ingredients that are not used by any instruction)

3.5. Health Plan

This is a feature that users can access to have health plans to manage their diet and health due to dietary needs of their choice. Users are able to add a health list to based on their specifications: whether they intend to gain/lose/maintain weight from their set age, current weight and also their current height. this will then tell them what is the required calories to eat per day to hit their target.

3.5.2. Current implementation

  • Users are able to add to the list based on their personal settings: name, age, height, weight, target weight, duration, scheme (gain/loss/maintain)

  • Users are able to delete entries that they do not want to keep anymore.

  • Users are aso able to edit entries which information is incorrect.

  • Users are able to add days of the meal planner segment into chosen health plan

  • Users are able to display all days of the meal planner segment concurrently with the health plan lists

  • Users are able to display the details of a health plan to see the days stored and also other information

  • Users are able to remove days of the health plan they added.

  • Users are now able to view the calories count from the meals they included into the healthplan and when the details of the health plan is shown the target to hit will be shown to the user, in value and also bar chart. Along with such info, there is also a percentage met to show how much of the target is already achieved.

3.5.3. Ongoing implementation (Implementing plan comparisons)

This new implementation of this feature will allow the users to select 2 plans of their choice and compare with a side by side comparison of the calories consumed. This information will be displayed to the user much like the show details command that is available with slight tweaks. There will not be a percentage met but instead the feature will try to cover breakdown of the calories intake by day using the set duration of the targetted plans. This will provided clarity of the day by day consumption of calories of the user across 2 plans. The information will be in calculated values and then displayed in a series of bar graphs for quick understanding of the information by just looking at the graphs.

3.5.4. Ongoing implementation (Expiry of health plans)

The new implementation of this feature should have an expiry feature that counts down the system base on the app’s date against the duration set by the user.

3.6. 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 3.7, “Configuration”)

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

  • 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

3.7. Configuration

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

4. Documentation

We use asciidoc for writing documentation.

We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting.

4.1. Editing Documentation

See UsingGradle.adoc to learn how to render .adoc files locally to preview the end result of your edits. Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc files in real-time.

4.2. Publishing Documentation

See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.

4.3. Converting Documentation to PDF format

We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.

Here are the steps to convert the project documentation files to PDF format.

  1. Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the docs/ directory to HTML format.

  2. Go to your generated HTML files in the build/docs folder, right click on them and select Open withGoogle Chrome.

  3. Within Chrome, click on the Print option in Chrome’s menu.

  4. Set the destination to Save as PDF, then click Save to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.

chrome save as pdf
Figure 10. Saving documentation as PDF files in Chrome

4.4. Site-wide Documentation Settings

The build.gradle file specifies some project-specific asciidoc attributes which affects how all documentation files within this project are rendered.

Attributes left unset in the build.gradle file will use their default value, if any.
Table 1. List of site-wide attributes
Attribute name Description Default value

site-name

The name of the website. If set, the name will be displayed near the top of the page.

not set

site-githuburl

URL to the site’s repository on GitHub. Setting this will add a "View on GitHub" link in the navigation bar.

not set

site-seedu

Define this attribute if the project is an official SE-EDU project. This will render the SE-EDU navigation bar at the top of the page, and add some SE-EDU-specific navigation items.

not set

4.5. Per-file Documentation Settings

Each .adoc file may also specify some file-specific asciidoc attributes which affects how the file is rendered.

Asciidoctor’s built-in attributes may be specified and used as well.

Attributes left unset in .adoc files will use their default value, if any.
Table 2. List of per-file attributes, excluding Asciidoctor’s built-in attributes
Attribute name Description Default value

site-section

Site section that the document belongs to. This will cause the associated item in the navigation bar to be highlighted. One of: UserGuide, DeveloperGuide, LearningOutcomes*, AboutUs, ContactUs

* Official SE-EDU projects only

not set

no-site-header

Set this attribute to remove the site navigation bar.

not set

4.6. Site Template

The files in docs/stylesheets are the CSS stylesheets of the site. You can modify them to change some properties of the site’s design.

The files in docs/templates controls the rendering of .adoc files into HTML5. These template files are written in a mixture of Ruby and Slim.

Modifying the template files in docs/templates requires some knowledge and experience with Ruby and Asciidoctor’s API. You should only modify them if you need greater control over the site’s layout than what stylesheets can provide. The SE-EDU team does not provide support for modified template files.

5. Testing

5.1. Running Tests

There are three ways to run tests.

The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies.

Method 1: Using IntelliJ JUnit test runner

  • To run all tests, right-click on the src/test/java folder and choose Run 'All Tests'

  • To run a subset of tests, you can right-click on a test package, test class, or a test and choose Run 'ABC'

Method 2: Using Gradle

  • Open a console and run the command gradlew clean allTests (Mac/Linux: ./gradlew clean allTests)

See UsingGradle.adoc for more info on how to run tests using Gradle.

Method 3: Using Gradle (headless)

Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.

To run tests in headless mode, open a console and run the command gradlew clean headless allTests (Mac/Linux: ./gradlew clean headless allTests)

5.2. Types of tests

We have two types of tests:

  1. GUI Tests - These are tests involving the GUI. They include,

    1. System Tests that test the entire App by simulating user actions on the GUI. These are in the systemtests package.

    2. Unit tests that test the individual components. These are in seedu.souschef.ui package.

  2. Non-GUI Tests - These are tests not involving the GUI. They include,

    1. Unit tests targeting the lowest level methods/classes.
      e.g. seedu.souschef.commons.StringUtilTest

    2. Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
      e.g. seedu.souschef.storage.StorageManagerTest

    3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
      e.g. seedu.souschef.logic.LogicManagerTest

5.3. Troubleshooting Testing

Problem: HelpWindowTest fails with a NullPointerException.

  • Reason: One of its dependencies, HelpWindow.html in src/main/resources/docs is missing.

  • Solution: Execute Gradle task processResources.

6. Dev Ops

6.1. Build Automation

See UsingGradle.adoc to learn how to use Gradle for build automation.

6.2. Continuous Integration

We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.

6.3. Coverage Reporting

We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.

6.4. Documentation Previews

When a pull request has changes to asciidoc files, you can use Netlify to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See UsingNetlify.adoc for more details.

6.5. Making a Release

Here are the steps to create a new release.

  1. Update the version number in MainApp.java.

  2. Generate a JAR file using Gradle.

  3. Tag the repo with the version number. e.g. v0.1

  4. Create a new release using GitHub and upload the JAR file you created.

6.6. Managing Dependencies

A project often depends on third-party libraries. For example, Sous Chef depends on the http://wiki.fasterxml .com/JacksonHome[Jackson library] for XML parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives.

Appendix A: Product Scope

Target user profile:

  • prefer home-cooked meals and has a need to manage meal preparation schedule

  • interested to explore different recipes and receive suggestions on the next meal

  • requires a kitchen-helper providing step-by-step instructions

  • like to keep track of nutrition and calories intake

  • has needs for a shopping list based on the meals planned

Value proposition: Plan and guide health-conscious occupied working adults in preparing home-cooked meals

Appendix B: User Stories

Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *

B.1. Ingredient Management

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

* * *

user

limit the search result of recipes to what I have in my inventory

cook a meal without buying additional ingredients

* * *

user

Be informed of the name/amount of ingredients that I am lacking

so that I can buy the shortfall

* * *

user with ingredient near expiry

I can refer to the purchase date on ingredient manager and search for recipes that include those ingredients

use up my ingredients near expiry

* * *

user

see recipes that can use the most number of ingredients in the ingredient manager

cook with the maximum utilization of my ingredients

* *

user who wants to cook more than one serving of dishes

get the accordingly calculated missing amount of ingredients

-

B.2. Meal Planner

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

* * *

user

plan my meals for breakfast, lunch and dinner

I can remember my meals for each day

* * *

fickle-minded user

edit my meal plans

I can add different recipes if I change my mind

* * *

efficient user

clear the meal planner immediately at the end of the week

I can save time

* *

health-conscious user

view the nutritional value of each recipe

I can choose recipes according to my nutritional needs

*

meticulous user

view the time taken for each recipe

I can plan ahead for meal preparations to fit my schedule

B.3. User Profiling

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

* * *

undecided user

get random recipe suggestions

get new ideas on what to cook

* * *

user

get suggestions to cook based on my viewing and cooking history

learn to cook similar dishes that I may like

* * *

user

favourite a recipe that I really like

save them into my favourites for easy reference

* * *

user

remove a recipe from my favourites

remove a recipe that I no longer like

* * *

user

list out all my favourite recipes

view all of my favourite recipes at a glance

B.4. Recipes Management and cook-mode

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

* * *

indian

search for recipes of Indian dishes

prepare a wholesome meal for my family

* * *

vegetarian

search for vegan option

prepare self-make food for my dietary requirements

* * *

mum

search for kids-friendly recipes

prepare food that is to my kids liking

* *

amateur cook

follow through the step-by-step cooking instructions explicitly

my cooking process is guided

*

user

provide suggestions to a recipe

improve on the recipe to value-add the community

*

picky user

search for recipes with good reviews

prepare tasty food that is worth the calories

*

user

“like/dislike” reviews on a recipe made by other users

credit/discredit valid/invalid reviews

B.5. Health Plan

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

* * *

new user

be able to easily use the interface without hassle

easily learn the interface without difficulty

* *

health-conscious user

be able to view the nutritional breakdown

Know what food to cut down or replace

* *

fickle user

be able to change plans or make new plans on the go

make changes when errors are made

* *

user trying to be more healthy

be recommended healthy recipes by the system

save the effort needed to search online

*

experimental user

be able to have many plans

try out different plans on the go

Appendix C: Use Cases

(For all use cases below, the System is the Sous Chef and the Actor is the user, unless specified otherwise)

C.1. Ingredient Management

Use case: UC11 – List Ingredient

MSS

  1. User requests to list ingredients.

  2. System shows list of ingredients.

    Use case ends.

Use case: UC12 – Add Ingredient

MSS

  1. User requests to add an ingredient.

  2. System adds the ingredient at the rear of the list and shows the list.

    Use case ends.

Extensions

  • 1a. System prompt insufficient/invalid contents.

    • 1a1. System show the message.

      Use case ends.

Use case: UC13 – Edit Ingredient

MSS

  1. User requests to edit an ingredient.

  2. System reflects the change to the list and show the list.

    Use case ends.

Extensions

  • 1a. System prompt insufficient/invalid contents.

    • 1a1. System show the message.

      Use case ends.

Use case: UC14 – Delete Ingredient

MSS

  1. User requests to delete an ingredient.

  2. System deletes the ingredient and shows the list.

    Use case ends.

Extensions

  • 1a. The ingredient does not exist in the ingredient manager.

    • 1a1. System show the message.

      Use case ends.

Use case: UC15 – Find Ingredient

MSS

  1. User requests to find ingredients.

  2. System show the list of ingredients that contains the keywords.

    Use case ends.

Use case: UC16 – Search recipes according to ingredients

MSS

  1. User selects both mandatory and desirable ingredients for the search.

  2. System shows the filtered and sorted list of recipes.

  3. User selects a recipe.

  4. System shows recipe specifying missing ingredients and its amount, calculated according to the number of servings.

    Use case ends.

C.2. Meal Planner

Use case: UC21 – Add recipe to meal planner

MSS

  1. User requests to list available recipes.

  2. System shows list of available recipes.

  3. User selects desired recipe and adds it to the desired meal index.

  4. System shows the recipe listed under the indicated meal index.

    Use case ends.

Use case: UC22 – Delete recipe from meal planner

MSS

  1. User requests to view current meal plan.

  2. System shows current meal plan.

  3. User requests to delete unwanted recipe.

  4. System shows specified meal index as empty.

    Use case ends.

Use case: UC23 – View meal plan

MSS

  1. User requests to view current meal plan.

  2. System shows current full meal plan.

    Use case ends.

Use case: UC24 – View meal recipe

MSS

  1. User requests to view recipe for a specific meal index.

  2. System shows the full recipe of the specified meal index.

    Use case ends.

C.3. User Profiling

Use case: UC31 - Request surprise recipe

MSS

  1. User requests to wants to cook something new, but has no idea what they want to try.

  2. User request for surprise recipe

  3. System displays random recipe

    Use case ends.

Use case: UC32 – Favourite recipe

MSS

  1. User lists a selection of recipes (Based on his search parameters).

  2. User selects a recipe to view.

  3. User likes that recipe, and adds it to their "Favourite Recipe" list.

    Use case ends.

Use case: UC33 – Suggest recipe

MSS

  1. User wants to try a new recipe.

  2. A list of 3 new recipes (That the user hasn’t cooked before, and based on their preferences, viewing and cooking history) will be displayed to the user.

    Use case ends.

C.4. Recipes Management and cook-mode

Use case: UC41 – Search for Recipe

MSS

  1. User chooses to search for recipe.

  2. System requests for details.

  3. User enters keywords.

  4. System displays recipes.

    Use case ends.

Use case: UC42 – View Recipe

MSS

  1. User searches for recipe (UC41).

  2. User chooses a recipe.

  3. System displays recipe details.

    Use case ends.

Use case: UC44 – Navigate Recipe [Coming up in V2.0]

MSS

  1. User views recipe (UC42).

  2. User chooses to enter navigation mode.

  3. System displays instruction.

  4. User chooses next instruction.

    Steps 3 – 4 are repeated until end of recipe’s instruction or user chooses to end navigation.

    Use case ends.

Extensions

  • 3a. System displays timer for duration specific instruction.

  • 3b. User chooses to start timer.

  • 3c. System displays countdown.

  • 3d. System displays alert when timer ends.

    Use case resumes at step 3.

  • *c. At any time, user chooses to end timer.

    • c1. System ends timer.

      Use case resumes at step 3.

Use case: UC45 – Add Recipe

MSS

  1. User chooses to add recipe.

  2. System requests for details.

  3. User enters details.

  4. User chooses to save.

  5. System displays saved recipe.

    Use case ends.

Extensions

  • 4a. System detects missing field(s).

  • 4a1. System requests for input of missing field(s).

  • 4a2. User enters details.

  • Steps 4a1 – 4a2 are repeated until the data entered are correct.

  • Use case resumes at step 5.

C.5. Health planner

Use case: UC51 – Create plan

MSS

  1. User requests to create a plan.

  2. User enters details for the plan.

  3. System creates the plan.

    Use case ends.

Extensions

  • 2a. User enters invalid details.

    • 2a1. System shows error message.

      Use case resumes at 2.

Use case: UC52 – Edit plan

MSS

  1. User requests to edit a plan.

  2. System retrieves the list of plans by user.

  3. User select the plan to edit.

  4. User enters the new details to the plan.

  5. System updates the details.

    Use case ends.

Extensions

  • 2a. No prior plan.

    Use case ends.

  • 4a. Invalid details.

    • 4a1. System shows an error message.

      Use case returns to 4.

Use case: UC53 – Delete plan

MSS

  1. User requests to delete plan.

  2. System shows list of plans.

  3. User selects plan to delete.

  4. System deletes the selected plan.

    Use case ends.

Extensions

  • 2a. No pre-existing plan.

    Use case ends.

Use case: UC54 – Enter dietary intakes for the day

MSS

  1. User request to enter intake.

  2. System shows list of plans.

  3. User selects plan from list.

  4. System shows list of recipes to select.

  5. User selects recipe to add into list.

  6. System adds the recipe to list.

    Use case ends.

Extensions

  • 2a. No existent plan.

    Use case ends.

  • 4a. No recipe to show.

    Use case ends.

Use case: UC55 – Remove an recipe from the dietary intakes for the day

MSS

  1. User request to remove recipe from intake list.

  2. System shows list of recipes.

  3. User selects recipe from the list.

  4. System removes the selected recipe from the list.

    Use case ends.

Extensions

  • 2a. No prior ingredient.

    Use case ends.

Use case: UC56 – View nutritional Intake

MSS

  1. User requests to view nutritional intake entered.

  2. System retrieves the list of intakes to show the user.

  3. System retrieves the nutritional facts based on the recipes in the list to show the user.

Extensions

  • 2a. No data entered previously.

    Use case ends.

Use case: UC57 – Compare actual intake against target

MSS

  1. User requests to compare the entered data against set target.

  2. System shows Target set by User.

  3. System calculates and shows the actual intake by user.

  4. System calculates the difference in the 2 scores and shows the user the percentage difference(positive or negative) from set target.

    Use case ends.

Appendix D: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 9 or higher installed.

  2. Should be able to hold up to 1000 recipes 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.

Appendix E: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

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

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

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

  2. Saving window preferences

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

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

F.2. Adding of recipe

  1. Adding of a non-duplicate recipe

    1. Prerequisites: Switched to -recipe context and recipe to be added is not in SousChef.

    2. Test case: add n/Chicken Rice d/3 c/20M cont i/Remove skin from #chicken 500 gram. cont i/Boil chicken in #water 700 ml for 20m c/20M end
      Expected: Recipe added to the end of recipes list. Details of the added recipe shown in the status message. Timestamp in the status bar is updated.

    3. Test case: add n/Tuna Roll d/3 c/10M end
      Expected: No recipe is added. Error details shown in the status message. Status bar remains the same.

  2. Adding of a duplicate recipe

    1. Prerequisites: Switched to -recipe context.

    2. Test case: add n/Egg Roll d/2 c/10M cont i/Spread #rice 100 gram on the mat. cont i/Put #egg 1 piece on top of the rice. cont i/Roll the rice. end add n/Egg Roll d/2 c/10M
      Expected: No recipe is added. Error details shown in the status message. Status bar remains the same.

F.3. Editing a recipe

  1. Editing a recipe while all recipes are listed

    1. Prerequisites: Switched to -recipe context and list all recipes using the list command. Multiple recipes in the list.

      1. Test case: edit 1 n/Chicken Burger
        Expected: First recipe is updated with a new name. Details of the edited recipe shown in the status message. Timestamp in the status bar is updated.

      2. Test case: edit 1
        Expected: No recipe is edited. Error details shown in the status message. Status bar remains the same.

F.4. Deleting a recipe

  1. Deleting a recipe while all recipes are listed

    1. Prerequisites: Switched to -recipe context and list all recipes using the list command. Multiple recipes in the list.

    2. Test case: delete 1
      Expected: First recipe is deleted from the list. Details of the deleted recipe shown in the status message. Timestamp in the status bar is updated.

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

    4. Other incorrect delete commands to try: delete, delete x (where x is larger than the list size)
      Expected: Similar to previous.

F.5. Finding a recipe

  1. Finding a recipe while all recipes are listed

    1. Prerequisites: Switched to -recipe context and list all recipes using the list command. Multiple recipes in the lsit.

    2. Test case: find vegetarian noodles 1 20M
      Expected: Recipes meeting all the keywords is listed. Number of listed recipes shown in the status message. Status bar remains the same.

    3. Test case: find
      Expected: No recipe listed. Error details shown in the status message. Status bar remains the same.