By: Team T09-1      Since: Feb 2019      Licence: MIT

1. Setting up

1.1. Prerequisites

  1. JDK 9

    Only JDK 9 is officially supported
  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 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 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 HelpWindowTest.java for code errors, and if so, resolve it the same way)

1.3. Verifying the setup

  1. Run the pwe.planner.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. Updating documentation to match your fork

After forking the repo, the documentation will still have the SE-EDU branding and refer to the se-edu/addressbook-level4 repo.

If you plan to develop this fork as a separate product (i.e. instead of contributing to se-edu/addressbook-level4), you should do the following:

  1. Configure the site-wide documentation settings in build.gradle, such as the site-name, to suit your own project.

  2. Replace the URL in the attribute repoURL in DeveloperGuide.adoc and UserGuide.adoc with the URL of your fork.

1.4.3. 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.4. 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. 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. The following class plays an important role at the architecture level:

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

  • Model: 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

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

SDforDeleteModule
Figure 3. Component interactions for delete 1 command

The sections below give more details of each component.

2.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, ModuleListPanel, StatusBarFooter, BrowserPanel 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.

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

2.3. Logic component

LogicClassDiagram
Figure 5. Structure of the Logic Component

API : Logic.java

  1. Logic uses the ApplicationParser 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 module).

  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 1") API call.

DeleteModuleSdForLogic
Figure 6. Interactions Inside the Logic Component for the delete 1 Command

2.4. Model component

ModelClassDiagram
Figure 7. Structure of the Model Component

API : Model.java

The Model,

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

  • stores the Application data.

  • exposes an unmodifiable ObservableList<Module> 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.

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

2.5. Storage component

StorageComponentDiagram
Figure 8. Structure of the Storage Component

API : Storage.java

The Storage component,

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

  • can save the Application data in json format and read it back.

2.6. Common classes

Classes used by multiple components are in the pwe.planner.commons package.

3. Implementation

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

3.1. Undo/Redo Feature

3.1.1. Current Implementation

The undo/redo mechanism is facilitated by VersionedApplication. It extends application with an undo/redo history, stored internally as an applicationStateList and currentStatePointer. Additionally, it implements the following operations:

  • VersionedApplication#commit() — Saves the current application state in its history.

  • VersionedApplication#undo() — Restores the previous application state from its history.

  • VersionedApplication#redo() — Restores a previously undone application state from its history.

These operations are exposed in the Model interface as Model#commitApplication(), Model#undoApplication() and Model#redoApplication() respectively.

Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.

Step 1. The user launches the application for the first time. The VersionedApplication will be initialized with the initial application state, and the currentStatePointer pointing to that single application state.

UndoRedoStartingStateListDiagram

Step 2. The user executes delete 5 command to delete the 5th module in the application. The delete command calls Model#commitApplication(), causing the modified state of the application after the delete 5 command executes to be saved in the applicationStateList, and the currentStatePointer is shifted to the newly inserted application state.

UndoRedoNewCommand1StateListDiagram

Step 3. The user executes add n/David …​ to add a new module. The add command also calls Model#commitApplication(), causing another modified application state to be saved into the applicationStateList.

UndoRedoNewCommand2StateListDiagram
If a command fails its execution, it will not call Model#commitApplication(), so the application state will not be saved into the applicationStateList.

Step 4. The user now decides that adding the module was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undoApplication(), which will shift the currentStatePointer once to the left, pointing it to the previous application state, and restores the application to that state.

UndoRedoExecuteUndoStateListDiagram
If the currentStatePointer is at index 0, pointing to the initial application state, then there are no previous application states to restore. The undo command uses Model#canUndoApplication() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo.

The following sequence diagram shows how the undo operation works:

UndoRedoSequenceDiagram

The redo command does the opposite — it calls Model#redoApplication(), which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the application to that state.

If the currentStatePointer is at index applicationStateList.size() - 1, pointing to the latest application state, then there are no undone application states to restore. The redo command uses Model#canRedoapplication() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.

Step 5. The user then decides to execute the command list. Commands that do not modify the application, such as list, will usually not call Model#commitapplication(), Model#undoapplication() or Model#redoapplication(). Thus, the applicationStateList remains unchanged.

UndoRedoNewCommand3StateListDiagram

Step 6. The user executes clear, which calls Model#commitApplication(). Since the currentStatePointer is not pointing at the end of the applicationStateList, all application states after the currentStatePointer will be purged. We designed it this way because it no longer makes sense to redo the add n/David …​ command. This is the behavior that most modern desktop applications follow.

UndoRedoNewCommand4StateListDiagram

The following activity diagram summarizes what happens when a user executes a new command:

UndoRedoActivityDiagram

3.1.2. Design Considerations

Aspect: How undo & redo executes
  • Alternative 1 (current choice): Saves the entire application.

    • Pros: Easy to implement.

    • Cons: May have performance issues in terms of memory usage.

  • Alternative 2: Individual command knows how to undo/redo by itself.

    • Pros: Will use less memory (e.g. for delete, just save the module being deleted).

    • Cons: We must ensure that the implementation of each individual command are correct.

Aspect: Data structure to support the undo/redo commands
  • Alternative 1 (current choice): Use a list to store the history of application states.

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

    • Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both HistoryManager and VersionedApplication.

  • Alternative 2: Use HistoryManager for undo/redo

    • Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase.

    • Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as HistoryManager now needs to do two different things.

3.2. Edit Module Feature

The edit feature aims to help users update module details in our application. When there are changes to be made to the module (e.g. semesters which modules are offered in have changed), users will want to be able to update the module details easily without going through the hassle of deleting and adding the new module.

This section describes the implementation and the design considerations of the edit feature, and how the edit feature is capable of cascading the updated module code from the module list to the requirement categories and degree plan.

3.2.1. Current Implementation

When the user invokes the edit command, e.g. (edit 1 code/CS1231), the following steps are taken by the application.

  1. The CommandParser invokes the EditCommandParser class to parse the user input provided. The parsed data will then be used to create a EditCommand object and will be returned to CommandParser and subsequently LogicManager.

  2. LogicManager would then invoke the EditCommand#execute(…​), which performs the following validation checks to ensure that the edited module details are valid.

    • Ensures that the edited module does not exists in module list (non-duplicate module)

    • Ensures that the edited module is not updated to be a co-requisite of itself (no self-referencing)

    • Ensures that the edited module’s co-requisite exists in the module list

    • Ensures that the edited module’s co-requisites can be taken in the same semester

    • Ensures that the edited module can be taken in the same semester as the edited module’s co-requisites

    • Ensures that the updated semesters which the edited module is offered in allows the module to remain in the current semester of the degree plan.

  3. Once the validation checks are complete, EditCommand will invoke Model#editModule(…​), which will be discussed later.

  4. EditCommand will then invoke Model#updateFilteredModuleList(…​) to display all modules in the filtered module list.

  5. Finally, a success message will be displayed to the user.

The high-level overview sequence diagram for the edit feature is shown below.

errorChecks(…​) does not actually exists in EditCommand.
It is used below as a representation of all the validation checks (discussed above) present in EditCommand.execute(…​).

SD Edit Overview
Figure 9. High-level overview sequence diagram for edit feature

If there are any changes made to the module’s code, Model#editModule(…​) will cascade the edited module code to the rest of the Model objects (namely UniqueModuleList, UniqueDegreePlannerList, UniqueRequirementCategoryList) through Application.

  1. When Model#editModule(…​) is invoked, Application#editModule(…​) will be called.

  2. Application#setModule(…​) is invoked to replace the original module with the edited module in UniqueModuleList.

  3. When setting the edited module in UniqueModuleList, UniqueModuleList will cascade the edited module code to all modules' co-requisites

  4. The edited module code is cascaded to UniqueDegreePlanner as Application#setModule(…​) checks if the original module code exists in the degree planner, and if so, updates it accordingly.

  5. Similarly, Application#setModule(…​) also checks if the original module code exists in the requirement categories, and if so, updates it accordingly.

SD Edit Detailed
Figure 10. Sequence diagram for edit feature illustrating cascading of edited module code

3.2.2. Design Considerations

Aspect: How should edited module code be cascaded down to other Model objects?

The table below shows comparisons between the two approaches.

Approach

Pros

Cons

1. Implement cascading of edited module code in EditCommand

It makes sense for a class in Logic component to handle logic-related matters.

  • It creates coupling between EditCommand and the various Model objects (i.e. UniqueModuleList, UniqueDegreePlannerList and UniqueRequirementCategoryList.

2. Implement cascading of edited module code in Application

It doesn’t create additional coupling with other Model components

  • Implementing logic (cascading effect) in Application (a class in Model component) may contradict Separation of Concerns Principle.

After weighing the pros and cons, approach 2 was chosen.

Firstly, doing so will reduce the need for additional coupling with other Model components (as compared to approach 1). Although it may seem strange to implement logic checks to achieve the desired cascading effect of the edited module code in Application (a class in the Model component), implementing logic checks in Model can also be seen as a way of maintaining data integrity. One such example is Java’s Set interface (Model), where each implementing class ensures that there are no duplicate elements in the collection of data (i.e. implementing logic in model).

Similarly, implementing cascading of edited module code in Application is deemed to be similar to implement logic in model for maintaining data integrity. As such, approach 2 is more suitable and was chosen.

3.3. Module co-requisites

3.3.1. Current Implementation

Module co-requisites are stored internally as Set<Code> within Module.

A Set<Code> is used instead of a List<Code> to ensure uniqueness and prevents duplicate co-requisites module codes.

Notice that Code is used in place of Module. This is to prevent storage of duplicated information when serializing UniqueModuleList.

AddCommand handles invalid cases by preventing adding a co-requisite module code that does not exists in the module list.
EditCommand handles invalid cases by ensuring that:

  • the edited co-requisite module code is not equivalent to the Code of the edited module

  • the edited co-requisite module Code exists in the module listing

When a module is deleted, it is cascaded down to other modules, and is removed from other modules' co-requisites.

3.3.2. Design Considerations

Aspect: How should deletion of a module be cascaded down to other modules

The table below shows comparisons between the two approaches.

Approach

Pros

Cons

1. Delete module code from other modules' corequisites in Application class

Implementing the cascading effect in Application#removeModule() protects data

  • Requires extra overhead to obtain an immutable list of modules to update and modify existing modules in the UniqueModuleList

  • Application class needs to handle logic checks to ensure data interity

2. Delete module code from other modules' corequisites in DeleteCommand class

Convenient to implement.

  • Deleting a module via Application#removeModule() does not have any cascading effect on other modules' corequisites. The user will have to delete the invalid co-requisite manually afterwards.

After weighing the pros and cons, approach 1 was chosen.
As we are expecting many similar names between modules in the university curriculum, if the user could only search with an implicit logical OR, the user would not be able to find the desired modules effectively. This can drastically reduce the effectiveness of the find command.

3.4. Find Feature

The find feature aims to help users to be able to easily locate any modules in our application. With a large number of modules available to our users, find feature is essential. Currently, the find feature only supports searching of module’s name, code, credits, tags and the semesters it is offered in.

This section shares the implementation and the design considerations gone through while enhancing the find feature. Details on how the find feature is implemented and how it supports more search parameters are also shared.

3.4.1. Overview

When a user invokes the find command. (e.g. find name/Programming || code/CS1231), the following steps are taken by the program.

  1. Extract out the text related to find command

  2. Parse the text related to each PREFIX individually.

  3. Return a composite predicate for all attributes.

Step 1 is performed by the ApplicationParser class, and no special actions is needed for the find feature.

Step 2 and 3 are performed by BooleanExpressionParser#parse

The class diagram below shows the different components and constraints for find feature.

FindCommandClassDiagram
Figure 11. Class diagram for find feature

3.4.2. Current Implementation

The FindCommandParser parses the strings of arguments provided by the user to retrieve a composite Predicate which is used by FindCommand. A ParseException is thrown in the case if the input provided by the user does not conform to the expected format.

The sequence diagram below shows the interaction within the Logic components.

FindCommandSequenceDiagram
Figure 12. Find component interactions

The main implementation of this feature is split into two components. The Tokenizer and BooleanExpressionParser

  1. Tokenizer helps to split the user provided argument into tokens which could be used by BooleanExpressionParser.

  2. BooleanExpressionParser simply performs Shunting-Yard algorithm on the boolean expression tokens provided by the Tokenizer and maps them into Predicate which could be used by FindCommand.

FindCommandParser calls BooleanExpressionParser#parse which handles the evaluation of the expression.
This process is achieved by the code snippet shown below.

String trimmedArgs = args.trim();
if (trimmedArgs.isEmpty()) {
    throw new ParseException(
            String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
Predicate<Module> predicate = BooleanExpressionParser.parse(args, PREFIXES);
return new FindCommand(predicate);

To support more parameters for our find feature. You can do the following steps.

  1. Create a new Predicate class (e.g. NameContainsKeywordsPredicate) and define your expected behaviour in it.

  2. Ensure your Predicate class extends KeywordsPredicate.

  3. Update BooleanExpressionParser#getKeywordsPredicate to handle the creation of the Predicate.

  4. Update CliSyntax on the new prefix you would like for the new parameter.

  5. Update FindCommandParser to take in the new PREFIX.

Your new parameter will now be supported after the above steps!

3.4.3. Tokenizer

This is represented by the class pwe.planner.logic.parser.BooleanExpressionTokenizer and is designed to extract all argument with PREFIX and OPERATOR as a token.

This class is initialized with the input argument and prefixes and can be queried for token multiple times.
Each query will consume the previous token and returns the next available token.
This is similar to how java.util.Scanner works.

Design Consideration

After many rounds of experiment with StringTokenizer that is provided by native Java and ArgumentMultimap.
We found three main issues which could not be satisfied by either StringTokenizer or ArgumentMultimap.

  1. ArgumentMultimap does not keep track of the order of each delimiter. They will only track if the delimiter exists.

  2. StringTokenizer has a default delimiter as a whitespace, although we could change the delimiter and parse multiple delimiters. It does not suit our situation.
    e.g. find name/AAAA code/BBBB.
    This will return us 1 token. name/AAAA code/BBBB, which we cannot use to check due to the missing operator.
    We need the tokenizer to return us 2 tokens name/AAAA and code/BBBB in order for us to know that the expression was invalid due to the missing operator.

  3. StringTokenizer can take in PREFIX as delimiter, however, this will split the argument (i.e. code/CS1231) into two tokens, code/ and CS1231.
    Additional parsing is required before we are able to pass it to BooleanExpressionParser. This parsing creates more overhead as we have to ensure that the proper checks are done.

As such, our custom Tokenizer aims to solve these three issues.

The table below shows the differences among our Tokenizer, StringTokenizer and ArgumentMultimap

Tokenizer

StringTokenizer

ArgumentMultimap

Respect the order of delimiters.

Respect the order of delimiters

Only keep track which delimiters are present.

Support multiple delimiters.

Support multiple delimiters.

Support multiple delimiters.

Splitting tokens is more flexible

Only split token based on delimiters

Does not split into token.

3.4.4. Operator

This is represented by the class pwe.planner.logic.parser.Operator and defines all valid operators to be used in BooleanExpressionParser.

The table below shows the valid operators that our application currently supports.

Operator

Description

Precedence

&&

Logical "AND" operation (both conditions A AND B must match)

Highest

||

Logical "OR" operation (either conditions A OR B must match)

Lowest

( and )

Search term surrounded by parenthesis will always be evaluated first. If there is a tie, the logical operator precedence will be taken into consideration.

N.A

To support more operators for our BooleanExpressionParser. The following steps should be performed.

  1. Add the operator and give it precedence.

  2. Update the mapping between String and Operator in Operator#getOperatorFromString

  3. Update the logic of the new operator in Operator#applyOperator

  4. Update CliSyntax.OPERATORS to include the new operator.

3.4.5. Boolean Expression Parser

This is represented by the class pwe.planner.logic.parser.BooleanExpressionParser and is designed to map user provided input into composite Predicate<Module>.

The following table shows the operators currently supported by BooleanExpressionParser(Highest precedence first).

Operators

Description

&&

Logical AND of two predicates

||

Logical OR of two predicates.

Parentheses ( and ) are also recognized and respected, and they may be nested to arbitrary depth. This is handled by Shunting Yard algorithm which respects the precedence of each operators when parsing.

The sequence diagram below shows the interactions between FindCommandParser and BooleanExpressionParser.

parserSequenceDiagram
Figure 13. Find component interactions

When FindCommandParser receives the provided user argument, it will carry out checks and pass the argument to BooleanExpressionParser which will initialize a new Tokenizer that extracts the arguments as tokens.
BooleanExpressionParser will create a Predicate based on the Prefix in each token. If the token is an Operator, BooleanExpressionParser will apply the operator on two Predicate to combine them into a composite Predicate.

See Section 3.4.3, “Tokenizer” for more details regarding the tokenizer.

The process of how the predicate for each prefix is created is shown in the code snippet below.

ArgumentMultimap argMultimap =
        ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_CODE, PREFIX_CREDITS);
KeywordsPredicate predicate = null;
if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
    String nameKeyword = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()).toString();
    predicate = new NameContainsKeywordsPredicate(List.of(nameKeyword));
} else if (argMultimap.getValue(PREFIX_CODE).isPresent()) {
    String codeKeyword = ParserUtil.parseCode(argMultimap.getValue(PREFIX_CODE).get()).toString();
    predicate = new CodeContainsKeywordsPredicate(List.of(codeKeyword));
} else if (argMultimap.getValue(PREFIX_CREDITS).isPresent()) {
    String creditKeyword = ParserUtil.parseCredits(argMultimap.getValue(PREFIX_CREDITS).get()).toString();
    predicate = new CreditsContainsKeywordsPredicate(List.of(creditKeyword));
} else {
    throw new ParseException(
            String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
return predicate;

3.4.6. Design Considerations

This section shares the design considerations we went through during the enhancing the existing find feature.

Aspect: Parsing of composite predicate

The table below shows comparisons between the two approaches.

Approach

Pros

Cons

1. Implement an algorithm Shunting Yard that parses the complex boolean expression and returns a composite predicate.

Find command can be very flexible. It can work with multiple parameters to search for the specific modules that the user wants.

  • Requires many tests to ensure expected behaviours

  • Extra work is required to integrate a new logic parser into FindCommandParser. It may result in build problems if it is not implemented properly.

2. Do an implicit logical OR for every predicate.

Very easy to implement

  • Returns a very huge set of results when the number of keywords increase.

  • Find command will be restricted to logical OR results.

After weighing both pros and cons, we decided to go with approach 1.
As we are expecting many similar names between modules in the university curriculum, if the user could only search with an implicit logical OR, the user would not be able to find the desired modules effectively. This can drastically reduce the effectiveness of the find command.

3.5. Requirement Add Feature

The requirement add feature in the application aims to help users to keep track and check if they have fulfilled a particular degree requirement category by adding module code(s) to the specified requirement category.

3.5.1. Current implementation

When the user invokes the requirement_add command, e.g. (requirement_add name/Mathematics code/MA1531).
The following steps are taken by the application.

1. The CommandParser invokes the RequirementAddCommandParser class to parse the user input provided. The parsed data will then be used to create a RequirementAddCommand object and will be returned to LogicManager.

  • The input should only consist of the name of the requirement category and module code(s) to be added

  • RequirementAddCommandParser will throw an error if the user input does not match the command format

2. Upon receiving the RequirementAddCommand object, LogicManager would then invoke the RequirementAddCommand class to pass the object received.

Once invoked, RequirementAddCommand will perform the following checks on the object received:
* Check if the degree requirement category exists in the application through getRequirementCategory
* Check if the module codes provided exists in the application through model.hasModuleCode
* Check if the module codes have already been added to other degree requirement categories
* Check if the module codes have already been added to the specified degree requirement category through RequirementCategory.hasModuleCode

RequirementAddCommand will throw an error if any of the above checks fails.

3. After passing all of the above checks, RequirementAddCommand updates the context in Model through setRequirementCategory.

4. In addition to adding module code(s) to the specified degree requirement category, the RequirementAddCommand class also saves the current database state through commitApplication (for undo/redo functions).

The sequence diagram below shows how this command interacts with the Logic components as described above.

  • You can click on the image below, so that it is enlarged in a new tab (only works on github)

  • The sequence diagram has been extracted into 2 sub-diagrams to better illustrate the component interactions

RequirementAddCommandSequenceDiagram
Figure 14. RequirementAddCommand component interactions
RequirementAddCommandSequenceDiagramExtracted
Figure 15. Component interactions of the checks performed by RequirementAddCommand class

3.5.2. Design Considerations

This section describes the various design considerations the taken when implementing the requirement_add feature.

Aspect: Choice of what is stored in the requirement category storage file

The table below shows a comparison between the two approaches that could have been implemented.

Approach

Pros

Cons

1. Storing only the module codes in the requirement category storage file

  • Lesser storage space is required as only the module code is being stored

  • Easy to maintain, as only one information is being stored

  • Extra overhead is required when additional information related to the module is retrieved

2. Storing all information related to the modules but only the module code is displayed

  • Any information related to the modules is easily retrievable

  • The module information is duplicated, additional storage space and processing time is needed to load the entire requirement category

  • Hard to maintain and manage the stored information. If a module information is updated the information in the requirement category storage needs to be updated as well.

After evaluating the pros and cons of both approaches, we have decided to implement Approach 1.

The main reason is that all module information would be duplicated again in the requirement category storage file. This would effectively make the module storage file redundant as the information can be found in the requirement category storage file.

In addition, when a module information is updated, we have to ensure that the information in both storage files are updated and consistent to avoid any conflicting information.

Another factor that heavily impacted the decision would be that each module is uniquely identified by a module code. Hence, by storing the module code only, the module’s information can be easily obtained.

3.6. Requirement Move Feature

The requirement move feature in the application aims to help users to be able to move module code(s) from any degree requirement category to the specified requirement category.

3.6.1. Current implementation

When the user invokes the requirement_move command, e.g. (requirement_move name/Mathematics code/CS1231).
The following steps are taken by the application.

1. The CommandParser invokes the RequirementMoveCommandParser class to parse the user input provided. The parsed data will then be used to create a RequirementMoveCommand object. The RequirementMoveCommand object will then be returned to LogicManager.

  • The input should only consist of the name of the requirement category and module code(s) to be moved

  • RequirementMoveCommandParser will throw an error if the user input does not match the command format

2. Upon receiving the RequirementMoveCommand object, LogicManager would then invoke the RequirementMoveCommand class to pass the object received.

Once invoked, RequirementMoveCommand will perform the following checks on the object received:
* Check if the degree requirement category specified exists in the application through getRequirementCategory
* Check if the module code(s) provided exists in the application through model.hasModuleCode
* Check if the module code(s) provided already been added to the degree requirement category

RequirementMoveCommand will throw an error if any of the above checks fails.

3. After passing all of the above checks, RequirementMoveCommand determines if the modules code(s) to be moved belongs to a single degree requirement category or from multiple degree requirement categories

4. If the module code(s) belongs to a single degree requirement category, RequirementMoveCommand will move all the module code(s) specified at once.
If the module code(s) belong to multiple degree requirement categories, RequirementMoveCommand will move the module code(s) specified at one by one

5. In addition to adding module code(s) to the specified degree requirement category, the RequirementMoveCommand class also saves the current database state through commitApplication (for undo/redo functions).

The sequence diagram below shows how this command interacts with the Logic components as described above.

  • You can click on the image below, so that it is enlarged in a new tab (only works on github)

  • The sequence diagram has been extracted into 2 sub-diagrams to better illustrate the component interactions

RequirementMoveCommandSequenceDiagram
Figure 16. RequirementMoveCommand component interactions
RequirementMoveCommandSequenceDiagramExtracted
Figure 17. Component interactions of the checks performed by RequirementMoveCommand class

3.6.2. Design Considerations

This section describes the various design considerations the taken when implementing the requirement_move feature.

Aspect: To include a check to determine if the module code(s) specified come froms a single source degree requirement category

The table below shows a comparison between the two approaches that could have been implemented.

Approach

Pros

Cons

1. Implementing to check if the module code(s) comes from a single source

  • Able to move all modules at once

  • Reduce overhead

  • Extra checks are required in the application

2. Not implementing to check if the module code(s) comes from a single source

  • Lesser checks needs to be implemented

  • More overhead is needed when handling the moving of the module code(s)

After evaluating the pros and cons of both approaches, we have decided to implement Approach 1.

The main reason is that it has a slight performance boost for the application as all the module code(s) can be moved together at once, reducing the overall overhead.

3.7. Requirement List Feature

The requirement list feature in the application allow users to display all requirement categories and the module code(s) that have been added to the requirement categories.

3.7.1. Current implementation

The requirement_list command requires no additional input other than the command itself. When the user executes the requirement_list command, the following steps are taken by the the application.

1. The CommandParser invokes the RequirementListCommand class

2. When the RequirementListCommand class is invoked, it will perform the following actions before displaying the output to the user:

  • Obtain a list of all the requirement categories and the modules added to each requirement category in the application through getFilteredRequirementCategoryList

  • Obtain the module credit information for every module added into each requirement category through getModuleByCode

3. Once the information has been populated, the application will then display all requirement categories, the current credit count for each requirement category as well as the module(s) added to each requirement category.

If there are no modules added to a requirement category, the application will display No modules in this category! for that particular requirement category.

The sequence diagram below shows the interaction with the Logic components as described above.

You can click on the image below, so that it is enlarged in a new tab (only works on github)

RequirementListCommandSequenceDiagram
Figure 18. RequirmentListCommand component interactions

3.7.2. Design Considerations

This section describes the various design considerations the taken when implementing the requirement_list feature.

Aspect: Tracking the current amount of credits in a requirement category

The table below shows a comparison between the two approaches that could have been implemented.

Approach

Pros

Cons

1. Creating a dedicated attribute to track the current amount credits of a requirement category

  • Current amount of credits is always available and easily obtainable

  • Hard to maintain. When a module credit is updated to a new value, the attribute has to be updated as well to prevent conflicting information.

2. Calculating the current amount of credits of a requirement category when needed

  • Any information related to the modules is easily retrievable as each module is uniquely identified by the module code

  • No extra maintenance of information needed

  • Able to easily calculate the credits when needed as module information are easily obtainable.

  • Extra overhead is required to retrieve the module’s information and compute the current amount of credits

After evaluating the pros and cons of both approaches, the group decided that Approach 2 was to be implemented.

The determining factor was that the module’s credits can be easily obtain. Hence the current amount of credits for a particular requirement category can be easily calculated as and when needed.

Furthermore, when a module’s credits is changed, we have to ensure that the information in the requirement category storage file is updated as well. Which is hard to maintain as the requirement category containing the module code must first be retrieved for the attribute to be updated.

3.8. Planner Add Feature

The planner_add command in PWE is used to add module code(s) to the degree plan.

3.8.1. Current implementation

The planner_add command requires the PlannerAddCommandParser class to parse the user input provided. The parsed data will then be passed to the PlannerAddCommand class.

The input should contain the year and semester of the degree plan as well as the module code(s) to be added.

PlannerAddCommandParser will throw an error if the user input does not match the command format.

When PlannerAddCommand receives the parsed data, it will perform the following checks:

  • Check if the year and semester exist in the degree plan

  • Check if the module codes provided exist in PWE through model.hasModuleCode

  • Check if the module codes have already been added to the degree plan

  • Check if the co-requisites of modules provided already exist in other semesters of the degree plan

PlannerAddCommand will throw an error if any of the above checks fails.

After passing all of the above checks, PlannerAddCommand updates the context in ModelManager through setDegreePlanner.

In addition to adding module code(s) to the degree plan, the PlannerAddCommand class also saves the current database state through commitApplication (for undo/redo functions).

PlannerAddCommandSequenceDiagram1
Figure 19. PlannerAddCommand component interactions part 1
PlannerAddCommandSequenceDiagram2
Figure 20. PlannerAddCommand component interactions part 2

3.8.2. Design Considerations

Aspect: Choice of whether to deal with certain possible errors when the application gets modified.
  • Alternative 1 (current choice): Checking for invalid co-requisites as well as non-existent year and semester in degree plan.

Pros

Software is more secure against possible errors.

Cons

Harder to maintain.

  • Alternative 2: Skipping checks for invalid co-requisites as well as non-existent year and semester in degree plan.

Pros

Easier to maintain.

Cons

When certain parts of the software get modified, some errors may occur with this command. For instance, a user may use EditCommand to edit co-requisites. If the check was not in place and the edit command fails to add the edited co-requisites to the degree plan, the user will then be able to add the co-requisites to invalid semesters of the degree plan.

3.9. Planner Remove Feature

The planner_remove command in PWE is used to remove module code(s) from the degree plan.

3.9.1. Current implementation

The planner_remove command requires the PlannerRemoveCommandParser class to parse the user input provided. The parsed data will then be passed to the PlannerRemoveCommand class.

The input should contain the module code(s) to be removed.

PlannerRemoveCommandParser will throw an error if the user input does not match the command format.

When PlannerRemoveCommand receives the parsed data, it will perform the following checks:

  • Check if the module codes to remove exist in the degree plan.

PlannerRemoveCommand will throw an error if any of the above checks fails.

After passing all of the above checks, PlannerRemoveCommand updates the context in ModelManager through setDegreePlanner.

In addition to removing module code(s) from the degree plan, the PlannerRemoveCommand class also saves the current database state through commitApplication (for undo/redo functions).

PlannerRemoveCommandClassDiagram
Figure 21. PlannerRemoveCommand class diagram

3.9.2. Design Considerations

Aspect: Choice of whether to use getDegreePlannerByCode method for removing module codes.
  • Alternative 1 (current choice): Looping through all semesters of the degree plan instead of using getDegreePlannerByCode to identify the relevant semesters of the codes to remove.

Pros

Simpler code.

Cons

May loop through extra semesters when there are few module codes to remove.

  • Alternative 2: Using getDegreePlannerByCode method to identify the relevant semesters containing the code to remove. Removing the codes in the selected semester only.

Pros

When there are few module codes to remove, the method may incur in slightly less overhead.

Cons

When there are N modules to remove, while the current method only needs 16 (total number of semesters) outer loops, this alternative method needs N outer loops. Moreover, getDegreePlannerByCode method itself also needs to loop through the DegreePlanner List to find out the right semester. The time complexity for both methods can be the same.

3.10. Planner Move Feature

The planner_move command aims to provide functionality for users to move a module between academic semesters in the degree plan along with its co-requisites.

This section shares the implementation and design considerations made during the enhancement of the planner_move feature.

3.10.1. Overview

As the users often encounter situations where they decide to take the modules in other semesters after they have completed their degree planning, the planner_move feature is essential to have.

Current planner_move feature supports the moving of a module along with its co-requisites given the module and its co-requisites are offered in the semester the users wants to move to.

3.10.2. Current Implementation

When a user execute the planner_move command (e.g. planner_move year/1 sem/2 code/CS1010), the following steps are taken by the application.

Given below is a sequence diagram for moving a module that illustrates the interactions among PlannerMoveCommand, PlannerMoveCommandParser and Model:

PlannerMoveComponentSequenceDiagram
Figure 22. PlannerMove component interactions

Step 1. The PlannerMoveCommandParser#parse method is invoked. The PlannerMoveCommandParser receives the command with the arguments as a string.

Step 2. The PlannerMoveCommandParser parses the text related to each PREFIX and constructs the PlannerMoveCommand . If more than one of each kind of PREFIX (e.g. year/1 sem/2 code/CS1231 code/CS1010) is provided, only text related to last of each PREFIX (e.g. year/1 sem/2 code/CS1010) will be parsed.

  • The input should consist of the year and the semester of the degree plan that the user wants to move to and the module code that the user want to move.

  • PlannerMoveCommandParser will throw an error if the user input does not match the command format.

Step 3. The PlannerMoveCommand with YEAR, SEMESTER and CODE specified by the user is returned.

Step 4. The PlannerMoveCommand#execute method is invoked.

Given below is a sequence diagram that illustrates the interactions among PlannerMoveCommand, Model and Application:

PlannerMoveCommandSequenceDiagramDetail
Figure 23. PlannerMoveCommand in detail interactions

During this step, the following methods are carried out in the order:

  • The getDegreePlannerByCode method will be called to check if there exists any academic semester in degree plan that has the parsed module code.

  • The getApplication method will be called followed by the getDegreePlannerList method will be called by the model to retrieve the list of academic semesters available in the degree plan. Then, filter will be carried out with the help of DegreePlanner#isSameDegreePlanner to locate the academic semester the user wants to move the module to.

null will be returned instead of DegreePlanner object if no academic semester matching the year and the semester is found.

  • The getModuleByCode method will be called to return Module object having the code toMove. Then, the Module object will be used to retrieve the semesters the module is available in with the getSemesters method to check if the module is offered in the academic semester the user is trying to move to.

  • The getModuleByCode method will be called in a loop during the filter to find out all the module’s co-requisites not offered in the academic semester the user is trying to move to.

Any violations in the checks will result in throwing of an error message.

Step 5. The Model is updated.

  • If any of the above check fails, Model will not be updated since there is nothing to be changed.

  • If all the above checks passes, the PlannerMoveCommand class will update the context in ModelManager by calling the`moveModuleBetweenPlanner` method which will call setDegreePlanner method.

  • After updating the Model The PlannerMoveCommand will save the current database state through commitApplication (for undo/redo functions).

Step 6. A CommandResult object is returned.

3.10.3. Design Considerations

Aspect: How should searching of the degree plan based on the year and the semester provided to be done
  • Alternative 1 (current choice): Construct DegreePlanner object with the year and the semester provided and use DegreePlanner#isSameDegreePlanner to compare and search for the corresponding degree plan.

Pros

There is no need to create any method that may create unnecessary coupling.

Cons

There is a need to create a DegreePlanner object in order to use DegreePlanner#isSameDegreePlanner.

  • Alternative 2: Create getDegreePlanner method which retrieves DegreePlanner object based on the year and the semester provided.

Pros

Easy to implement.

Cons

The method will create unnecessary couplings between Application and Year as well as between Application and Semester.

3.11. Planner Show Feature

Planner show feature aims to help users to be able to easily locate and display any academic semesters in the degree plan which is in our application. We support the displaying of degree planners based on year, semester and boolean expressions. This enables our users to display only specific academic semesters in the degree plan that they want.

3.11.1. Overview

When a user invokes the planner_show command. (e.g. planner_show y/YEAR && s/SEMESTER), the following steps are taken by the program.

  1. Extract the text related to planner_show command (e.g. y/YEAR && s/SEMESTER)

  2. Parse the text related to each PREFIX individually.

  3. Return a composite predicate for all attributes.

Step 1 is performed by the ApplicationParser class.

Step 2 and 3 are performed by BooleanExpressionParser#parse.

3.11.2. Current Implementation

  • Show degree planner(s) by year i.e. planner_show y/YEAR returns degree planner(s) having its year matches the year given

  • Show degree planner(s) by semester i.e. planner_show s/SEMESTER returns degree planner(s) having its semester matches the semester given

  • include year and semester attributes in one planner_show command and list degree planner(s) i.e. planner_show y/YEAR s/SEMESTER returns module having its year or semester matches the given year and semester

3.11.3. Design Considerations

Aspect: How to parse multiple attributes
  • Alternative 1 (current choice): Parse the text related to each PREFIX individually using BooleanExpressionParser

Pros

User is able to have more flexible search.

Cons

More time and work needed for developer to implement.

  • Alternative 2: Parse the text related to each PREFIX at one go without using BooleanExpressionParser

Pros

Easy to implement.

Cons

Additional overhead needed and inconsistent in the application’s commands since find utilises BooleanExpressionParser.

3.12. Planner Suggest Feature

The planner_suggest command in PWE is used to suggest module code(s) to add to the degree plan.

3.12.1. Current implementation

The planner_suggest command requires the PlannerSuggestCommandParser class to parse the user input provided. The parsed data will then be passed to the PlannerSuggestCommand class.

The input should contain the desirable credits. Another optional input is the desirable tags.

PlannerSuggestCommandParser will throw an error if the user input does not match the command format.

After passing the above check, PlannerSuggestCommand will make the result box display 3 lists of modules. The first list is the main recommendation list that sorts the modules in a specific way and displays maximum 10 most recommended modules. The second and the third list respectively displays the modules with matching tags and modules with matching credits. The modules in the two lists should also exist in the main recommendation list.

If tag is supplied as a parameter in input, modules will be sorted according to tags first. Modules with greater number of tags that match the desirable tags will be prioritized. If tie, modules with credits closer to the desirable credits will be prioritized. If tie again, modules will be sorted according to alphabetical order.

PlannerSuggestCommandActivityDiagram
Figure 24. PlannerSuggestCommand activity diagram

3.12.2. Design Considerations

Aspect: Choice of where to put ModuleToSuggest class.
  • Alternative 1 (current choice): Put ModuleToSuggest as inner class of PlannerSuggestCommand class.

Pros

Cleaner model for the Application.

Cons

The inner class has access to the private and protected members of the outer class, which can be unnecessary.

  • Alternative 2: Put ModuleToSuggest as a separate class apart from PlannerSuggestCommand class.

Pros

Better encapsulation for PlannerSuggestCommand class.

Cons

As no other class needs to access the ModuleToSuggest class, making it a separate class is unnecessary and makes the model more complex.

3.13. 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.14, “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.14. Configuration

Certain properties of the application can be controlled (e.g user prefs file location, 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 25. 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 pwe.planner.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. pwe.planner.commons.StringUtilTest

    2. Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
      e.g. pwe.planner.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. pwe.planner.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.

Problem: Keyboard and mouse movements are not simulated on macOS Mojave, resulting in GUI Tests failure.

  • Reason: From macOS Mojave onwards, applications without Accessibility permission cannot simulate certain keyboard and mouse movements.

  • Solution: Open System Preferences, click Security and PrivacyPrivacyAccessibility, and check the box beside Intellij IDEA.

testfx idea accessibility permissions
Figure 26. Accessibility permission is granted to IntelliJ IDEA

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, PlanWithEase depends on the Jackson library for JSON parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives:

  1. Include those libraries in the repo (this bloats the repo size)

  2. Require developers to download those libraries manually (this creates extra work for developers)

Appendix A: Product Scope

Target user profile:

  • National University of Singapore (NUS) Information Security freshmen

  • does not plan to undertake special programs such as NOC, BComp Dissertation, Co-Op programme, etc.

  • has a need to plan modules to be taken during University life

  • prefer desktop apps over other types

  • can type fast

  • prefers typing over other means of input

  • is reasonably comfortable using CLI apps

Value proposition:

  • Helps information security freshman plan their modules quickly and more conveniently.

  • Automatically check module pre-requisites to avoid module conflicts.

  • Provide an informed decision so that information security freshmen are able to decide which module to take at which semester.

Appendix B: User Stories

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

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

* * *

user

add modules

keep a list of modules that I want to take

* * *

user

delete modules

remove modules that I am not interested in taking

* * *

user

edit modules

edit the modules' details if there are any changes

* * *

user

list all modules

have an overview of all the modules that are added

* * *

user

find modules that are already added

know if I have previously added them

* * *

user

mark modules that are exempted

keep track of exempted modules

* * *

user

add modules into my degree plan

know which modules to bid/take in future

* * *

user

remove modules from my degree plan

remove modules that I am not interested in taking

* * *

user

move my modules to other academic semester in my degree plan

update my plan if there are any changes

* * *

user

mark those modules that are exempted in the module plan

keep track of exempted modules

* * *

user

list my degree planner

have an overview of my current plan

* * *

user

add module codes into different degree requirement categories

classify the modules according to their categories

* * *

user

remove module codes from the degree requirement categories

remove them if I made a mistake

* * *

user

move modules codes from a degree requirement categories to another

easily move them around

* * *

user

see all the degree requirement categories

get an overview of what modules fall under what categories

* *

user

undo my previous command

easily revert back if a command was entered wrongly

* *

user

redo my previous command

reverse my undo command if I have changed my opinion

* *

user

choose to overload/underload modules in a semester

manage my workload better

*

user

know the modules to put inside the degree plan

find out the suitable modules to take easily

*

user

generate my own module plan

easily plan which modules to take during university life

*

user

export my data from the application

reuse the existing data on other devices

*

user

import existing data into application

utilise existing data that was previously created

Appendix C: Use Cases

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

Use Case: Clear All Modules in Application

MSS

  1. User requests to clear all modules in the module list

  2. Application clear all modules in the module list

    Use case ends.

Extensions

  • None

Use case: Add a Module to Module List

MSS

  1. User requests to add a module to the module list

  2. Application adds the module into the module list

    Use case ends.

Extensions

  • 1a. The given input is invalid.

    • 1a1. Application shows an error message that given input is invalid.

      Use case ends.

  • 1b. The module already exists in the module list.

    • 1b1. Application shows an error message that module specified by user already exists in module list.

      Use case ends.

  • 1c. The module to be added has a corequisite that does not exists in the module list.

    • 1c1. Application shows an error message that module specified by user has a non-existent corequisite.

      Use case ends.

  • 1d. The module to be added has a corequisite that exists in the degree plan.

    • 1d1. Application shows an error message that module specified by user has a corequisite that exists in the degree plan.

      Use case ends.

Use Case: Edit a Module in Application

MSS

  1. User requests to list modules

  2. Application shows a list of modules

  3. User requests to edit a specific module in the module list

  4. Application update the module in the module list

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. Application shows an error message.

      Use case resumes at step 2.

  • 3b. The module already exists in the module list.

    • 3b1. Application shows an error message that module specified by user already exists in module list.

      Use case ends.

Use Case: Delete a Module in Application

MSS

  1. User requests to list modules

  2. Application shows a list of modules

  3. User requests to delete a specific module in the module list

  4. Application deletes the module in the module list

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. Application shows an error message.

      Use case resumes at step 2.

Use Case: List All Modules in Application

MSS

  1. User requests to list all modules in the module list

  2. Application shows a list of all modules in the module list

    Use case ends.

Extensions

  • None

Use case: Find a module in Application

Guarantee(s):

  • Modules will be listed if the input from the user is valid and can be matches the existing entries in the module list.

MSS

  1. User requests to find modules with their keyword of choice.

  2. Application shows a list of modules matched the keyword.

    Use case ends.

Extensions

  • 1a. The given input is invalid.

    • 1a1. Application shows an error message that given input is invalid.

      Use case ends.

Use case: Add module(s) to degree plan

MSS

  1. User requests to add module(s) into the Application’s degree plan

  2. Application adds the module(s) into the degree plan

    Use case ends.

Extensions

  • 1a. The given input is invalid.

    • 1a1. Application shows an error message that given input is invalid.

      Use case ends.

  • 1b. The module(s) already exists in the degree plan.

    • 1b1. Application shows an error message that the module(s) specified by user already exists in the degree plan.

      Use case ends.

  • 1c. The module(s) does not exist in the module list.

    • 1c1. Application shows an error message that the module(s) specified by user does not exist in the module list.

      Use case ends.

Use case: Remove module(s) from degree plan

MSS

  1. User requests to list modules in the Application’s degree plan

  2. Application shows a list of modules in the degree plan

  3. User requests to remove module(s) from the degree plan

  4. Application removes the module(s) from the degree plan

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given input is invalid.

    • 3a1. Application shows an error message that given input is invalid.

      Use case resumes from step 2.

  • 3b. The module(s) does not exist in the degree plan.

    • 3b1. Application shows an error message that the module(s) specified by user does not exist in the degree plan.

      Use case resumes from step 2.

Use case: Move module between academic semesters in degree plan

MSS

  1. User requests to move a specific module to another academic semester in the degree plan

  2. Application updates the degree plan

    Use case ends.

Extensions

  • 1a. The specified academic semester is empty.

    Use case ends.

  • 1b. The given input is invalid.

    • 1b1. Application shows an error message.

      Use case ends.

  • 1c. The module is already in the academic semester the user wants to move to.

    Use case resumes at step 2.

Use case: List all degree planners

MSS

  1. Student requests to list all the Application’s degree planners

  2. Application shows a list of all the degree planners

    Use case ends.

Use case: List a specific degree planner

MSS

  1. Student requests to list a specific Application’s degree planner

  2. Application shows a list of the specific degree planner

    Use case ends.

Extensions

  • 1a. The given input is invalid.

    • 1a1. Application shows an error message that given input is invalid.

      Use case resumes from step 1.

Use case: Identify modules to add to degree plan

Guarantee(s):

  • Modules will be listed if the input from the user is valid and can match the existing entries in the module list.

MSS

  1. User requests to find modules to put inside the degree plan with their criteria of choice.

  2. Application shows a list of modules sorted according to the given criteria, filtering off the modules already existing inside the degree plan.

    Use case ends.

Extensions

  • 1a. The given input is invalid.

    • 1a1. Application shows an error message that given input is invalid.

      Use case ends.

Use case: Add a module to degree requirement category

MSS

  1. User requests to add a module into the Application’s degree requirement category

  2. Application adds the module into the degree requirement category

    Use case ends.

Extensions

  • 1a. The given input is invalid.

    • 1a1. Application shows an error message that given input is invalid.

      Use case ends.

  • 1b. The requirement category does not exist in the Application.

    • 1b1. Application shows an error message that the requirement category specified by the user does not exist.

      Use case ends.

  • 1c. The module already exists in the degree requirement category.

    • 1c1. Application shows an error message that module specified by user already exists in degree requirement category.

      Use case ends.

Use case: Delete module from degree requirement category

MSS

  1. User requests to delete a specific module in the degree requirement category

  2. Application deletes the module in the degree requirement category

    Use case ends.

Extensions

  • 1a. The given input is invalid.

    • 1a1. Application shows an error message that given input is invalid.

      Use case ends.

  • 1b. The requirement category does not exist in the Application.

    • 1b1. Application shows an error message that the requirement category specified by the user does not exist.

      Use case ends.

  • 1c. The module does not exists in the specified degree requirement category.

    • 1c1. Application shows an error message that module specified by user does not exist in degree requirement category.

      Use case ends.

Use case: Move module in degree requirement category

MSS

  1. User requests to list modules in the Application’s degree requirement category

  2. Application shows a list of modules in the degree requirement category

  3. User requests to move a specific module to another academic semester in the degree requirement category

  4. Application update the degree requirement category

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given input is invalid.

    • 3a1. Application shows an error message.

      Use case resumes at step 2.

Use case: List all degree requirement categories

MSS

  1. Student requests to list all the Application’s degree requirement categories

  2. Application shows a list of all the degree requirement categories

    Use case ends.

Extensions

  • None

Appendix D: Non Functional Requirements

  1. The application should work on any mainstream OS as long as it has Java 9 installed.

  2. The application should work on both 32-bit and 64-bit environments.

  3. The application should work without requiring an installer.

  4. The application should work without requiring an Internet connection.

  5. The application should work should be able to hold up to 100 modules without a noticeable sluggishness in performance for typical usage.

  6. For a user with above average typing speed for regular English text (i.e. not code, not system admin commands), he/she should be able to accomplish most of the tasks faster using commands than using the mouse.

  7. The module and degree requirement information should be stored on the local filesystem and are able to be persisted across different runs of the application.

  8. The application should have good user documentation, which details all aspects of the application to assist new users in learning how to use the application.

  9. The application should have good developer documentation to allow new developers to understand the design of the application easily.

  10. The application’s functionalities should be easily testable.

Appendix E: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Overload

Taking above the workload of 22MC per academic semester

Underload

Taking below the workload of 18MC per academic semester

Degree planner

A planner that allows user to decide what modules to take during a specific academic semester

Degree requirement category

A category that allows classifying of modules based on the University Requirement

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 contacts. 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 a module

  1. Adding a module while all modules are listed

    1. Prerequisites: Populate module list with sample data using reset command, then list all modules using the list command.

    2. Test case: add code/AA1111 name/ABC credits/4
      Expected: New module (AA1111 ABC) is added into the module list. Details of the added module shown in the status message. Timestamp in the status bar is updated.

    3. Test case: add code/CS1010 name/DEF credits/4
      Expected: Module is not added into the module list (Module CS1010 already exists in the module list). Error details shown in the status message. Status bar remains the same.

    4. Test case: add code/AA1111 name/DEF credits/4
      Expected: Module is not added into the module list (Module AA1111 already exists in the module list). Error details shown in the status message. Status bar remains the same.

F.3. Editing a module

  1. Editing a module while all modules are listed

    1. Prerequisites: Populate module list with sample data using reset command, then list all modules using the list command.

    2. Test case: edit 1 code/CS1111
      Expected: Module code CS1010 is edited to CS1111 in the module list, requirement categories, degree plan. Details of the edited module shown in the status message. Timestamp in the status bar is updated.

    3. Test case: edit 2 name/ABC
      Expected: Module name of 2nd module in module list (CS1231 Discrete Structures) is edited to ABC in the module list and degree plan. Details of the edited module shown in the status message. Timestamp in the status bar is updated.

    4. Test case: edit 5 coreq/
      Expected: Module name of 5th module in module list (CS2101 Effective Communications for Computing Professionals) is edited to None in the module list. CS2113T’s co-requisites will also be updated to None. Details of the edited module shown in the status message. Timestamp in the status bar is updated.

    5. Test case: edit 1 coreq/CS1231
      Expected: Module is not edited, as module CS1010 and CS1231 are in different semesters of the degree plan, and hence cannot be updated to be co-requisites. Error details shown in the status message. Status bar remains the same.

F.4. Deleting a module

  1. Deleting a module while all modules are listed

    1. Prerequisites: Populate module list with sample data using reset command, then list all modules using the list command.

    2. Test case: delete 1
      Expected: First module (CS1010) is deleted from the module list and year 1 semester 1 of the degree plan. Details of the deleted module shown in the status message. Timestamp in the status bar is updated.

    3. Test case: delete 5
      Expected: Fifth module (CS2101) module is deleted from the module list and year 2 semester 1 of the degree plan. CS2113T module co-requisites is updated to None. Error details shown in the status message. Status bar remains the same.

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

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

F.5. Adding module(s) to the degree plan

  1. Adding valid module(s).

    1. Prerequisites: Populate module list with sample data using reset command, then list all modules using the list command.

    2. Test case: planner_add year/1 sem/1 code/CS1010 code/CS1231
      Expected: Module codes CS1010 and CS1231 will be added to year 1 semester 1 of the degree plan.

  2. Adding module(s) with bad bad parameters.

    1. Prerequisites: Populate module list with sample data using reset command, then list all modules using the list command.

    2. Test case: planner_add invalid/1 sem/1 code/CS1010 code/CS1231
      Expected: Invalid command format.

  3. Adding module(s) with invalid values.

    1. Prerequisites: Populate module list with sample data using reset command, then list all modules using the list command.

    2. Test case: planner_add year/-1 sem/1 code/CS1010 code/CS1231
      Expected: A message that indicates the valid value range of year.

F.6. Removing module(s) from the degree plan

  1. Removing valid module(s).

    1. Prerequisites: List all academic semesters using the planner_list command.

    2. Test case: planner_remove code/CS1010 code/CS1231
      Expected: Module codes CS1010 and CS1231 will be removed from the degree plan.

  2. Removing module(s) with bad bad parameters.

    1. Prerequisites: List all academic semesters using the planner_list command.

    2. Test case: planner_remove invalid/CS1010 code/CS1231
      Expected: Invalid command format.

F.7. Using the degree plan to suggest module(s) to take

  1. Suggesting valid module(s).

    1. Prerequisites: Populate module list with sample data using reset command, then list all modules using the list command.

    2. Test case: planner_suggest credits/1
      Expected: Maximum 10 modules recommended in a specially sorted order.

    3. Test case: planner_suggest credits/1 tag/maths
      Expected: Maximum 10 modules recommended in a specially sorted order.

F.8. Find modules

  1. Find modules in the module list using one parameter.

    1. Prerequisites: Populate module list with sample data using reset command, then list all modules using the list command.

    2. Test case: find name/programming
      Expected: Module list will displays all the modules whose name contains programming.
      Number of modules found is shown in the result box

    3. Test case: find name/PROGRAMMING
      Expected: Module list will displays all the modules whose name contains programming.
      Number of modules found is shown in the result box.
      This will have the same results as the previous test case.

    4. Test case: find code/CS1231
      Expected: Module list will displays all the modules whose code matches CS1231.
      Number of modules found is shown in the result box.

    5. Test case: find credits/4
      Expected: Module list will displays all the modules which assigned 4 modular credits.
      Number of modules found is shown in the result box.

    6. Test case: find tag/algorithm
      Expected: Module list will displays all the modules which has the tag algorithm.
      Number of modules found is shown in the result box.

    7. Test case: find sem/2
      Expected: Module list will displays all modules offered in semesters 2.
      Number of modules found is shown in the result box.

  2. Find modules in the module list using multiple parameters.

    1. Prerequisites: Populate module list with sample data using reset command, then list all modules using the list command.

    2. Test case: find name/programming || name/discrete
      Expected: Module list will displays all the modules whose name contains programming or discrete.
      Number of modules found is shown in the result box.

    3. Test case: find name/discrete || name/programming
      Expected: Module list will displays all the modules whose name contains programming or discrete.
      Number of modules found is shown in the result box.
      This will have the same results as the previous test case.

    4. Test case: find name/programming && name/methodology
      Expected: Module list will displays all the modules whose name contains programming and methodology.
      Number of modules found is shown in the result box.

    5. Test case: find name/programming && sem/2
      Expected: Module list will displays all the modules whose name contains programming and is offered in semester 2 only.

    6. Test case: find (name/programming || name/algorithm) && sem/2 Expected: Module list will displays all the module whose name contains either programming or algorithm and is offered in semesters 2.
      Number of modules found is shown in the result box.

  3. Find modules with bad parameters.

    1. Prerequisites: Populate module list with sample data using reset command, then list all modules using the list command.

    2. Test case: find nonExist/
      Expected: Invalid command format!
      A guide on how to use the command will be displayed in the result box.

  4. Find modules with invalid values.

    1. Prerequisites: Populate module list with sample data using reset command, then list all modules using the list command.

    2. Test case: find code/ZZZZZZ
      Expected: A guide on how the accepted value for code will be displayed in the result box.

F.9. Moving a module in the degree plan

  1. Moving a module in the degree plan while all modules are listed

    1. Prerequisites: List all academic semesters using the planner_list command. All academic semesters present in the degree plan. Application is started with the sample data without any modification in the data.

    2. Test case: planner_move year/1 sem/1 code/CS1010
      Expected: CS1010 module remains in Year 1 Semester 1 of the degree plan. Success message will be shown in the status message. Timestamp in the status bar is updated.

    3. Test case: planner_move year/1 sem/2 code/CS1010
      Expected: CS1010 module is moved from Year 1 Semester 1 to Year 1 Semester 2 of the degree plan. Success message will be shown in the status message. Timestamp in the status bar is updated.

    4. Test case: planner_move year/1 sem/2 code/CS2101
      Expected: CS2101 module is moved along with its co-requisite CS2113T to Year 1 Semester 2 of the degree plan. Success message will be shown in the status message. Timestamp in the status bar is updated.

    5. Other incorrect planner_move commands to try:

      • planner_move year/x sem/y code/CS1010 (where x or y is larger than 4)
        Expected: Error message is displayed.

      • planner_move year/1 sem/3 code/CS2101
        Expected: Error message is displayed since CS2101 module is not offered in semester 3 based on the module list.

F.10. Showing the academic semesters in the degree plan

  1. Showing some academic semesters in the degree plan based on the condition

    1. Prerequisites: List all academic semesters using the planner_list command. All academic semesters present in the degree plan.

    2. Test case: planner_show year/1
      Expected: Only the academic semesters with year 1 are displayed.

    3. Test case: planner_show sem/1
      Expected: Only the academic semesters with semester 1 are displayed.

    4. Test case: planner_show year/1 || year/2
      Expected: Only the academic semesters with year 1 or year 2 are displayed.

    5. Test case: planner_show sem/1 || sem/2
      Expected: Only the academic semesters with semester 1 or semester 2 are displayed.

    6. Test case: planner_show year/1 && ( sem/1 || sem2 )
      Expected: Only the academic semesters with year 1 and semester 1 or semester 2 are displayed.

    7. Other incorrect planner_show commands to try:

      • planner_show year/x, (where x is larger than 4)
        Expected: Error message is displayed.

      • planner_show sem/x, (where x is larger than 4)
        Expected: Error message is displayed.

      • planner_show sem/1 sem/2
        Expected: Error message is displayed since there is no boolean expression in between.

      • planner_show sem/1 ((,
        Expected: Error message is displayed.

      • planner_show sem/2 & yea/1,
        Expected: Error message is displayed.

      • planner_show sem/2 | yea/1,
        Expected: Error message is displayed.

F.11. Listing all the academic semesters in the degree plan

  1. Listing all the academic semesters in the degree plan when only some academic semesters are listed

    1. Prerequisites: List some academic semesters using the planner_show command (e.g. planner_show year/1). All academic semesters present in the degree plan.

    2. Test case: planner_list
      Expected: All the academic semesters are listed in the degree plan.

F.12. Degree Requirement Category

  1. Adding a module to a degree requirement category

    1. Prerequisites:

      1. Create a new module using the add command with the modular code of CS9999

      2. Ensure that the newly created module does not have corequisite of another module

      3. The newly created module do not belong to any degree requirement categories

    2. Test case: requirement_add name/Computing Foundation code/CS9999
      Expected: The module is added to the specified degree requirement category.
      Details of the module added to the degree requirement category is shown in the application result box.

    3. Test case: requirement_add name/Computing Foundation code/CS9999
      This test case is to be tested after the above test case
      Expected: Application displays an error message saying that the module is already in the degree requirement category
      Details of the error message is shown in the application result box.

    4. Test case: requirement_add name/Computing ddddd code/CS9999
      Expected: Application displays an error message saying that the specified degree requirement category does not exist
      Details of the error message is shown in the application result box.

  2. Removing a module from the degree requirement category

    1. Prerequisites:

      1. Create a new module using the add command with the modular code of CS9999

      2. Ensure that the newly created module does not have corequisite of another module

      3. Add the newly created module to a degree requirement category

    2. Test case: requirement_remove code/CS9999
      Expected: The module is removed from the degree requirement category. Details of the deleted module is shown in the application result box.

    3. Test case: requirement_remove code/CS9999
      This test case is to be tested after the above test case
      Expected: Application displays an error message saying that the module does not exist in the degree requirement category
      Details of the error message is shown in the application result box.

  3. Moving a module to a degree requirement category

    1. Prerequisites: Module to be moved must already be added to a degree requirement category.

      1. Create a new module using the add command with the modular code of CS9999

      2. Ensure that there no modules with the modular code of CS9998 in the application

      3. Ensure that the newly created module does not have corequisite of another module

      4. Add the newly created module to a degree requirement category

    2. Test case: requirement_move name/Computing Breadth code/CS9999
      Expected: The module is moved to the specified degree requirement category. Details of the moved module is shown in the application result box.

    3. Test case: requirement_move name/Computing ddddd code/CS9999
      Expected: Application displays an error message saying that the specified degree requirement category does not exist
      Details of the error message is shown in the application result box.

    4. Test case: requirement_move name/Computing Breadth code/CS9998
      Expected: Application displays an error message saying that no such module exists in the application
      Details of the error message is shown in the application result box.

  4. Listing all the degree requirement category in the application

    1. Prerequisites: NIL.

    2. Test case: requirement_list
      Expected: All the degree requirement categories in the applications, including the current modular credit count and the module code(s) added to the degree requirement categories is listed in the application result box.