By: T10-3      Since: Oct 2018      Licence: MIT

1. Setting up

1.1. Prerequisites

  1. JDK 9 or later

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

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

1.2. Setting up the project in your computer

  1. Fork this repo, and 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 XmlAdaptedWord.java and MainWindow.java and check for any code errors

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

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

  10. Repeat this for the test folder as well (e.g. check XmlUtilTest.java and HelpWindowTest.java for code errors, and if so, resolve it the same way)

1.3. Verifying the setup

  1. Run the seedu.learnvocabulary.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. Take a look at [GetStartedProgramming].

2. Design

2.1. Architecture

Architecture
Figure 1. Architecture Diagram

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

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

Main has only one class called MainApp. It is responsible for,

  • At app launch: Initializes the components in the correct sequence, and connects them up with each other.

  • At shut down: Shuts down the components and invokes cleanup method where necessary.

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

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

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

The rest of the App consists of four components.

  • UI: The UI of the App.

  • Logic: The command executor.

  • 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

Events-Driven nature of the design

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

SDforDeleteWord
Figure 3. Component interactions for delete 1 command (part 1)
Note how the Model simply raises a LearnVocabularyChangedEvent when LearnVocabulary’s data is changed, instead of asking the Storage to save the updates to the hard disk.

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

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

The sections below give more details of each component.

2.2. UI component

UiClassDiagram
Figure 5. Structure of the UI Component

API : Ui.java

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

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

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

2.3. Logic component

LogicClassDiagram
Figure 6. Structure of the Logic Component

API : Logic.java

  1. Logic uses the LearnVocabularyParser 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 word) and/or raise events.

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

Given below is the Sequence Diagram for interactions within the Logic component for the execute("delete 1") API call.

DeleteWordSdForLogic
Figure 7. Interactions Inside the Logic Component for the delete 1 Command

2.4. Model component

ModelClassDiagram
Figure 8. Structure of the Model Component

API : Model.java

The Model,

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

  • stores the LearnVocabulary data.

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

StorageClassDiagram
Figure 9. 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 LearnVocabulary data in xml format and read it back.

2.6. Common classes

Classes used by multiple components are in the seedu.learnvocabulary.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 VersionedLearnVocabulary. It extends LearnVocabulary with an undo/redo history, stored internally as an learnVocabularyStateList and currentStatePointer. Additionally, it implements the following operations:

  • VersionedLearnVocabulary#commit() — Saves the current learnvocabulary state in its history.

  • VersionedLearnVocabulary#undo() — Restores the previous learnvocabulary state from its history.

  • VersionedLearnVocabulary#redo() — Restores a previously undone learnvocabulary state from its history.

These operations are exposed in the Model interface as Model#commitLearnVocabulary(), Model#undoLearnVocabulary() and Model#redoLearnVocabulary() 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 VersionedLearnVocabulary will be initialized with the initial learnvocabulary state, and the currentStatePointer pointing to that single learnvocabulary state.

UndoRedoStartingStateListDiagram

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

UndoRedoNewCommand1StateListDiagram

Step 3. The user executes add n/David …​ to add a new person. The add command also calls Model#commitLearnVocabulary(), causing another modified learnvocabulary state to be saved into the learnVocabularyStateList.

UndoRedoNewCommand2StateListDiagram
If a command fails its execution, it will not call Model#commitLearnVocabulary(), so the learnvocabulary state will not be saved into the learnVocabularyStateList.

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

UndoRedoExecuteUndoStateListDiagram
If the currentStatePointer is at index 0, pointing to the initial learnvocabulary state, then there are no previous learnvocabulary states to restore. The undo command uses Model#canUndoLearnVocabulary() 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#redoLearnVocabulary(), which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the learnvocabulary to that state.

If the currentStatePointer is at index learnVocabularyStateList.size() - 1, pointing to the latest learnvocabulary state, then there are no undone learnvocabulary states to restore. The redo command uses Model#canRedoLearnVocabulary() 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 learnvocabulary, such as list, will usually not call Model#commitLearnVocabulary(), Model#undoLearnVocabulary() or Model#redoLearnVocabulary(). Thus, the learnVocabularyStateList remains unchanged.

UndoRedoNewCommand3StateListDiagram

Step 6. The user executes clear, which calls Model#commitLearnVocabulary(). Since the currentStatePointer is not pointing at the end of the learnVocabularyStateList, all learnvocabulary 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 learnvocabulary.

    • 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 person 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 learnvocabulary 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 VersionedLearnVocabulary.

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

3.2.1. Current Implementation

The learn mechanism is facilitated by the Dictionary class. The backbone of LearnVocabulary would have to be the Learn command, because it allows the user to query words from the internet and parse their meanings into readable format for storage. The learn command is indicated as Learn in commands, and inherits the Command class. It enables the user to "learn" a word from the world wide web. This command is requires the use of Internet, should there be an absence of Internet connection, it has already been taken care of via throwing an Exception. Additionally, it implements the following operations:

  • Dictionary#invoke() — calls the main function of Dictionary which links it to LearnVocabulary

  • Dictionary#isConnectedToInternet() — checks to see if there is an Internet connection established.

  • Dictionary#isWordInOnlineDictionary(Word) — checks to see if it is a valid word and if it exists in Dictionary.com

  • Dictionary#convertWord(Word) — converts word into first letter is in big caps, whilst the others are in small caps.

  • Dictionary#isValidWord(Word) — checks to see if it word contains any illegal characters.

Dictionary#invoke() is exposed in the LearnCommandParser class as LearnCommandParser#parse(), while all the other operations are self-contained within the Dictionary#invoke() operation.

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

Step 1. The user launches the application for the first time. The user types in learn magic into the CLI.

Step 2. learn magic will be parsed by LearnVocabularyParser, where the learn command will be triggered, calling LearnCommandParser#parse().

Step 3. This in turn calls Dictionary class and all of its relevant operations, starting with Dictionary#invoke().

Step 4. The word magic will be checked against the model and the current LearnVocabulary to see if they hold the same exact word.

Step 4a. The word does not exist and will be stored, together with the meaning that was queried as a result of Dictionary#invoke().

Step 4b. The word already exists and the command will throw a Duplicate Word Exception.

The newly queried word would be attached with a "toLearn" tag, which allows the user to know that he/she just queried the word and can leave it for the future to learn it.

Step 5. The word, meaning will be assigned a default tag and be displayed in the Command Box.

The following sequence diagram shows how the learn command works:

LearnSequenceDiagram

3.2.2. Design Considerations

Aspect: How Words are queried in Dictionary
  • Alternative 1 (current choice): Online querying of every word being learned.

    • Pros: Easy to implement and change (in the future) to accommodate extra meanings.

    • Cons: Requires the use of Internet Connection.

  • Alternative 2: Offline querying of every word being learned.

    • Pros: Does not require Internet to query word.

    • Cons: Untested, but a corrupt xml file would be disastrous causing corrupt findings, memory space will be an issue as well.

Aspect: How Words are stored in Dictionary
  • Alternative 1 (current choice): Offline storage of every word being learned.

    • Pros: Easy to implement and does not require the use of a cloud storage.

    • Cons: Not mobile and accessible to the user

  • Alternative 2: Online storage of every word being learned.

    • Pros: Mobile access by the user on the go.

    • Cons: Will consume a lot of resources. Might not be necessary.

3.2.3. Rejected/Put off implementations

Idea: Multiple words to be learned at the same time
  • learn fire ice will simultaneously pull data of 2 words fire and ice into LearnVocabulary

  • This was put off in terms of implementations because:

    • Firstly, loading speed will be slow, affecting performance

    • Secondly, it causes the command box to freeze

A simple fix was thought of, which was to run threads in LearnVocabulary to pull data simultaneously to reduce time and improve overall performance. That will be explored in later versions.

Idea: Synonyms of the same word to be learned at the same time
  • learn hot 10 will simultaneously pull data of hot and 10 of its synonyms. Effectively querying and storing 11 words in total.

  • This was rejected terms of implementations because:

    • Firstly, it does not serve the intended target audience well. Words can be fetched at random without bearing resemblance to the original queried word.

    • Secondly, Speed and performance issue as mentioned in the first idea.

A simple fix for the speed and performance issue was mentioned above.

3.3. Group features

3.3.1. Current Implementation

The group feature is mainly for user to manage their word lists inside our LearnVocabulary application and it now supports three functionalitys, which are groupadd, groupdelete, and showgroup.

groupadd functionality implementation

Similar to the other existed functionalities, the groupadd functionality mainly consists of a GroupaddCommandParser and a GroupaddCommand. The groupadd parser is indicated as GroupaddCommand in parser, and inherits the Parser class. It enables the application to handle the input given by the the user, check its validity and delete whitespaces. The groupadd command is indicated as Groupadd in commands, and inherits the Command class. It handles a valid group name input and enables the user to "add" a empty word group to the application.

Given below is an example usage scenario and how the groupadd functionality behaves at each step.

Step 1: User calls “groupadd + [groupname]” where groupname is the name of the newly added group.

Step 2: GroupAddCommandParser checks the validity of the groupname, trim the given groupname and create the GroupaddCommand.

Step 3a: If the user set a group name that has already existed, the system throws an exception to the user to indicate that the group name has existed.

Step 3b: Otherwise it lets the model component to add the group with given groupname.

groupaddSequentialDiagram
groupdelete functionality implementation

Similar to the other existed functionalities, the groupdelete functionality mainly consists of a GroupDeleteCommandParser and a GroupdeleteCommand. The groupdelete parser is indicated as GroupDeleteCommand in parser, and inherits the Parser class. It enables the application to handle the input given by the the user. The groupdelete command is indicated as Groupdelete in commands, and inherits the Command class. It handles a valid group name input and enables the user to "delete" a word group and all the words inside.

Given below is an example usage scenario and how the groupdelete functionality behaves at each step.

Step 1: User calls “groupdelete + [groupname]” where groupname is the name of the desired deleted group.

Step 2: GroupDeleteCommandParser checks the validity of the groupname, trim the given groupname and create the GroupdeleteCommand.

Step 3: GroupDeleteCommand checks passed arguments.

Step 4a: If the user set a group name that does not exist, the system throw an exception to indicate that the desired deleted group does not exist.

step 4b: Otherwise it lets the model component to delete the group with given groupname. Especially, for those words which exist in this only deleted group, those words would also be deleted automately.

groupdeleteSequentialDiagram
showgroup functionality implementation

Similar to the other existed functionalities, the showgroup functionality mainly consists of a ShowGroupCommandParser and a ShowGroupCommand. The showgroup parser is indicated as ShowGroupCommand in parser, and inherits the Parser class. It enables the application to handle the input given by the the user. The showgroup command is indicated as ShowGroup in commands, and inherits the Command class. It handles a valid group name input and enables the user to either see all the existing groups or open a selected word group and see all the words inside.

Given below are two example usage scenarios and how the showgroup functionality behaves at each step.

Example a

Step 1: User calls “showgroup + [groupname]” where groupname is the name of the desired checked group.

Step 2: ShowGroupCommandParser checks the validity of the groupname, trims the given groupname and create the ShowGroupCommand.

Step 3: ShowgroupCommand checks passed arguments.

Step 4a: If the user set a group name that does not exist, the system throw an exception to indicate that the desired checked group does not exist.

Step 4b: Otherwise it lets the model component to update the groupname as the predicate in the filterList.

showgroupSequentialDiagram2
Example b

Step 1: User calls “showgroup”.

Step 2: ShowGroupCommandParser creates the ShowGroupCommand.

Step 3: ShowGroupCommand gets all the existing groupname from the model by calling getTags() and show these groupnames to the user.

showgroupSequentialDiagram1

3.4. Basic features

3.4.1. Current Implementation

Improved from the previous versions, the Add and Edit commands have been refined in LearnVocabulary. There is also a minor feature added Show, which displays a filtered word list on the Ui.

Each tag being added follows AB4’s implementation, separate tags are considered via t/
So, t/happy, t/emotions, t/sad is considered 3 tags and they MUST have already been added via groupadd. Tags will not be added as a whole if any of them do not exist.
Add command improvements

When adding a new word to LearnVocabulary, only the Name and Meaning fields must be entered as follows: add n/fire m/something hot. As can be seen from the example given, there is no Tag being entered at all. However, LearnVocabulary will enforce that every word would require at least one tag. As such, the default tag "toLearn" will be given to words without any tags at the start.

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

Step 1. The user types in add n/fire m/something hot into the CLI.

Step 2. add n/fire m/something hot will be parsed by LearnVocabularyParser, where the add command will be triggered, calling AddCommandParser#parse().

Step 3a. Illegal characters will cause the command to throw an exception and await a new command.

Step 3b. Word with letters being parsed will be checked against LearnVocabulary to ensure no duplicate words.

Step 4. Word will have a default tag attached, be added to LearnVocabulary after ensuring no duplicates.

Edit command improvements

When editing an existing word in LearnVocabulary, there cannot be empty tags for any words provided.

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

Step 1. The user types in edit 1 t/ which shows the user wanting to clear all tags from existing word.

Step 2. The command box outputs that it is not possible, at least one tag must be attached to the word.

Show feature

The show command is an improvement in leaps and bounds ahead of the find command, but it exists as a separate command on its own due to its nature. Simply put, when entering show fire magic life, it will display the 3 words on the Ui and automatically outputs the word, meaning and tags onto the Command Box. The first word from the list will also be automatically selected.

As such, Show was implemented with a more Ui state of mind, to ensure the user’s ease of use when finding the words for display of their meanings as AddressBook level 4 did not cater to this particular arrangement.

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

Step 1. The user types in show fire magic life, it will display the 3 words in order as it was in the list.

Multiple words are allowed to be queried. Allows for more flexibility compared to the Ui option.

Step 2. From the filtered list of words, the program automatically selects and displays first word from list.

Step 3. The 3 words will appear in the Command Box as well.

3.5. Trivia feature

The trivia function is facilitated by LearnVocabulary. Besides storing a UniqueWordList, LearnVocabulary also stores the current trivia question as triviaQuestion and stores a list of trivia questions is triviaQuestionList. Scores for a trivia game are also stored using currentScore and maxScore.

Additionally, it also implements the following operations:

  • setTriviaList — sets the trivia question list based on the current vocabulary list

  • setTrivia — sets the trivia question based on triviaQuestionList

  • getTrivia — outputs the current trivia question.

  • updateScore — adds 1 to the the`currentScore`

  • clearTrivia — clears the current trivia question as well as removing it from the triviaQuestionList

  • isTriviaMode — a boolean to indicate whether the model is currently in trivia mode.

  • toggleTriviaMode — toggle trivia mode

These operations are exposed in the Model interface as Model.setTrivia() and Model.getTrivia().

Outlined below is how the trivia function operates at each step:

Step 1. The user inputs trivia in the CLI.

Step 2. TriviaCommand.execute() checks lastShownList to see if it is empty.

If lastShownList is empty, TriviaCommand.execute() will terminate and a message will be displayed to the user indicating that the user has to add words in before trivia can be used

Step 3. TriviaCommand.execute() calls toggleTriviaMode to indicate that LearnVocabulary is in trivia mode.

While in trivia mode, every command the user types will be parsed as TriviaAnsCommand. Trivia mode can be exited by either completing the trivia or typing "triviaExit"

Step 4. TriviaCommand.execute() now calls Model.setTriviaList() to set the trivia question within LearnVocabulary.

Step 5. TriviaCommand.execute() calls Model.getTrivia() and outputs to the user.

Below shows a sequence diagram of how the trivia feature works.

TriviaSequenceDiagram

Answer

The answer command is indicated as TriviaAnsCommand in commands. It will take in arguments passed in by the user and checks whether it is the correct/wrong answer to the current trivia question.

Outlined below is how the answer function operates:

Step 1: User enters his/her argument.

Attempting to call the answer command without first calling trivia will result in an error.

Step 2: TriviaAnsCommandParser processes the argument and parses it to TriviaAnsCommand.

If the user inputs triviaExit or triviaShow, the inputs will be processed as commands instead.

Step 3: TriviaAnsCommand.execute() checks the passed argument is the same word as triviaQuestion in LearnVocabulary.

Step 4: Outputs a correct or wrong message based on the result in step 3.

Below shows a sequence diagram of how the answer feature works.

TriviaAnsSequenceDiagram

Select

The select command is indicated as SelectCommand in commands, and inherits the Command class. It selects a word identified by a user using its index from the review list.

Below is a description on how the select function operates:

Step 1: User calls “select + [index]” where index is the index of the word that the user wants to select.

Step 2: SelectCommand checks passed arguments.

Step 3: If the user leaves out or in other ways enters an invalid index, SelectCommand throws an exception. Otherwise it returns the desired word.

3.6. WordOfTheDay feature

3.6.1. Current Implementation

The Word Of The Day command is indicated as WordOfTheDay in commands, and inherits the Command class. It enables the user to display the current word of the day taken from website Dictionary.com. Similarly to the "learn" command, this command also requires a working internet connection. It is impossible to make this feature available offline since the word of the day cannot be predicted on Dictionary.com

The Word Of The Day command utilizes the same functions in the Dictionary class made for the 'LearnCommand', but also adds the following functions:

  • Dictionary#doesWordOfTheDayExist() — Checks to see if the word of the day exist on Dictionary.com and returns the word of the day page as a Document object.

  • Dictionary#fetchWordOfTheDay() — Parses the word of the day and its meaning from the Document object returned by doesWordOfTheDayExist() and returns itself (Dictionary object).

Dictionary#fetchWordOfTheDay() is used in the WordOfTheDayParser class as WordOfTheDayParser#parse(), while all the other executions are self-contained within the Dictionary#fetchWordOfTheDay() function.

Below is a description on how the Word Of The Day function operates:

Step 1: User cannot think of a word so he/she wants to display the word of the day on Dictionary.com.

Step 2: User would make sure that there is a working internet connection.

Step 3: User would type "word" into the command line.

Step 4: The word of the day will be parsed by WordOfTheDayParser, calling WordOfTheDayParser#parse() The WordOfTheDayParser exists solely for the purpose of utilizing the existing functions made for parsing the online Dictionary and putting the word into a Word object.

Step 5: This calls the Dictionary class and all relevant functions. The first one being Dictionary#fetchWordOfTheDay().

Step 6: The Word Of The Day will be fetched online from Dictionary.com and displayed by the WordOfTheDayCommand together with its meaning. Should the internet-connection fail, the operation throws a MESSAGE_NO_INTERNET.

The Word Of The Day is displayed together with a "WordOfTheDay" tag. The word and the tag are not stored in any group somewhere, but is only displayed for the user. If the user wants to learn the word, he or she shall use the LearnCommand.

The following sequence diagram shows how the Word Of The Day command works:

WordOfTheDaySequentialDiagram

3.7. 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.8, “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.8. Configuration

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

4. Documentation

We use asciidoc for writing documentation.

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

4.1. Editing Documentation

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

4.2. Publishing Documentation

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

4.3. Converting Documentation to PDF format

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

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

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

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

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

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

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

4.4. Site-wide Documentation Settings

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

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

site-name

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

not set

site-githuburl

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

not set

site-seedu

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

not set

4.5. Per-file Documentation Settings

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

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

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

site-section

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

* Official SE-EDU projects only

not set

no-site-header

Set this attribute to remove the site navigation bar.

not set

4.6. Site Template

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

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

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

5. Testing

5.1. Running Tests

There are three ways to run tests.

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

Method 1: Using IntelliJ JUnit test runner

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

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

Method 2: Using Gradle

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

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

Method 3: Using Gradle (headless)

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

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

5.2. Types of tests

We have two types of tests:

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

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

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

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

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

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

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

5.3. Troubleshooting Testing

Problem: HelpWindowTest fails with a NullPointerException.

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

  • Solution: Execute Gradle task processResources.

6. Dev Ops

6.1. Build Automation

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

6.2. Continuous Integration

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

6.3. Coverage Reporting

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

6.4. Documentation Previews

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

6.5. Making a Release

Here are the steps to create a new release.

  1. Update the version number in MainApp.java.

  2. Generate a JAR file using Gradle.

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

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

6.6. Managing Dependencies

A project often depends on third-party libraries. For example, LearnVocabulary depends on the Jackson library for XML parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives.
a. Include those libraries in the repo (this bloats the repo size)
b. Require developers to download those libraries manually (this creates extra work for developers)

Appendix A: Product Scope

Target user profile:

  • has a need to learn, storage and retrieve words they are currently learning

  • non-native English speakers who want to improve their vocabulary

  • schools who want to use this in language courses

  • prefer desktop apps over other types

  • prefers typing over mouse input

  • is reasonably comfortable using CLI apps

Value proposition: personalized storage per user for their own list of vocabulary words

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…​

* * *

Non-native English speaker

Search for the meaning of words

Improve my English abilities

* * *

Non-native English speaker

Add/keep words that I have searched

Reference them easily to learn better

* * *

Non-native English speaker

Play a game that would improve my English capabilities

Not get bored of the learning process and have fun at the same time

* * *

Non-native English speaker

Refer to my native tongue while looking at English words

Speed up the learning process much faster

* * *

Non-native English speaker

Group words together

Refer to them easily in the future

* *

English speaker

Look up words

Learn the meanings of words

* *

English speaker

Play a game

Improve my English further

Appendix C: Use Cases

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

Use case: Delete word

MSS

  1. User requests to list words

  2. LearnVocabulary shows a list of words

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

  4. LearnVocabulary deletes the word

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. LearnVocabulary shows an error message.

      Use case resumes at step 2.

Use case: Add word - Add a word to LearnVocabulary

MSS

  1. User enters a word to add to the review list.

  2. System checks if word exists in the LearnVocabulary

  3. Status will appear saying word has been successfully added.

  4. It also displays the meaning of the word.

    Use case ends

Extensions

  • 3a. Word exists in LearnVocabulary

    Use case ends

Use case: Find word - Find a word in LearnVocabulary

MSS

  1. User enters the word they want to look up

  2. System filters the list in LearnVocabulary and displays the word within the Ui.

    Use case ends

Use case: Trivia mode

MSS

  1. User inputs command (trivia)

  2. System displays meaning of a selected word in user’s review list

  3. User enters the word which corresponds to the meaning displayed.

  4. System reveals the answer

    Repeat steps 2 - 4 until 10 questions have been displayed

    Use case ends.

Extensions

  • 2a. User’s review list is empty

    • 2a1. System displays that user’s review list is empty

      Use case ends.

Use case: GroupAdd - Group words together

MSS

  1. User inputs command (group)

  2. System responds with helper text (group [word] in [group])

  3. User enters “group gazelle in animals

  4. System groups the word “gazelle” in “animals”

    Use case ends

Use case: List words - List all words within LearnVocabulary

MSS

  1. User inputs command (list)

  2. All words within LearnVocabulary will be displayed

    Use case ends.

Use case: Learn words - Query words and their meaning, store into LearnVocabulary

MSS

  1. User inputs command (learn)

  2. User types in learn [word], the word will be queried online.

  3. The definition of the entered word will be displayed.

  4. The word is then saved within the storage.

    Use case ends.

Extensions

  • 2a. Word already exists in LearnVocabulary, error message will be displayed.

    Use case ends.

  • 3a. There is no internet connection, error message will be displayed.

    Use case ends.

  • 3a. Word contains illegal characters, error message will be displayed.

    Use case ends.

  • 3a. Word does not exist in Dictionary.com, error message will be displayed.

    Use case ends.

Use case: Show words - Filter and display words with their meanings on Ui.

MSS

  1. User inputs command (show)

  2. User types in show [word], the word card will be displayed on the Ui.

  3. Word will be selected as well, being displayed with its meaning.

    Use case ends.

Use case: Show Word Of The Day - Todays random, special word from Dictionary.com

MSS

  1. User inputs command (word).

  2. The word of the day will be queried online.

  3. The word and its meaning will be displayed.

  4. User can choose to learn the word by using the learn command

    Use case ends.

Extensions

  • 2a. There is no internet connection, error message will be displayed.

    Use case ends.

Appendix D: Non Functional Requirements

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

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

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

  4. Handles at least one complete dictionary (e.g. English)

{More to be added}

Appendix E: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Appendix F: Instructions for Manual Testing

Given below are instructions to test the app manually.

These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.

F.1. Launch and Shutdown

  1. Initial launch

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

    2. Double-click the jar file
      Expected: Shows the GUI with a set of sample words. 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. Learning a word (Manual Testing)

  1. Learning a word from Dictionary.com

    1. Prerequisites: Ensure Internet Connection is established.

    2. Test case: learn potato
      Expected: The word potato is added to LearnVocabulary, with its definition and a default tag. Details of the learnt word shown in the status message. Timestamp in the status bar is updated.

    3. Test case: learn !potato
      Expected: No word is learnt. Error details shown in the status message. Status bar remains the same.

    4. Other incorrect learn commands to try: learn, learn x, learn 1, learn notarealword
      Expected: Similar to previous.

F.3. Showing a word (Manual Testing)

  1. Showing a word from LearnVocabulary’s list.

    1. Prerequisites: List all words using the list command. Multiple words in the list.

    2. Test case: show potato
      Expected: The word potato is filtered and returned inside LearnVocabulary, together with its definition and tags attached to it. Details of the word is shown in the status message. Timestamp in the status bar is updated.

    3. Test case: show potato gravy happy
      Expected: The words potato, gravy, happy are filtered and returned inside LearnVocabulary, together with their definitions and tags attached to it. Details of the words are shown in the status message. Timestamp in the status bar is updated.

    4. Test case: show
      Expected: No word is shown. Error details shown in the status message. Status bar remains the same. Display remains the same.

    5. Other incorrect learn commands to try: show 1, show x, show !potato, show notarealword
      Expected: Similar to previous. Except now, filtered list is empty.

F.4. Adding a word (Manual Testing)

  1. Adding a word to LearnVocabulary’s list.

    1. Prerequisites: Word must not have been added to LearnVocabulary.

    2. Test case: add n/potato m/type of starch
      Expected: The word potato is added inside LearnVocabulary, together with its definition and default tag toLearn attached to it. Details of the word is shown in the status message. Timestamp in the status bar is updated.

    3. Test case: add n/potato
      Expected: The word potato will not be added inside LearnVocabulary, because meaning field is missing. Error details shown in the status message. Status bar remains the same. Display remains the same.

F.5. Editing a word (Manual Testing)

  1. Editing a word in LearnVocabulary’s list.

    1. Prerequisites: List must be non-empty.

    2. Test case: edit 1 m/something lightweight
      Expected: The word potato in index 1 will have its meaning changed from its existing meaning to its new definition "something lightweight". Details of the word is shown in the status message. Timestamp in the status bar is updated.

    3. Test case: edit 1 m/something lightweight t/light t/weight
      Expected: The word potato in index 1 will have its meaning changed from its existing meaning to its new definition "something lightweight". It’s tags (should they already been created in groupadd), will attach 2 tags light and weight to the word. Details of the word is shown in the status message. Timestamp in the status bar is updated.

    4. Test case: edit n/potato t/
      Expected: The word potato will not be edited inside LearnVocabulary, because tag field is missing, it is enforced that there can no longer be any words that have 1 tag. Error details shown in the status message. Status bar remains the same. Display remains the same.

F.6. Create a word group (Manual Testing)

  1. Create a new empty word group.

    1. Test case: groupadd test
      Expected: If the group test has not been created, then "test" will be created as a new word group. Otherwise some exception is handled and the user will see "The group typed has existed"

    2. Test case: 'groupadd '
      Expected: Some exception is handled and the user will see "please type in the group".

    3. Test case: groupadd stud_ispm
      Expected: Some exception is handled and the user will see "Tags names should be alphanumeric".

    4. Test case: groupadd groupa groupb
      Expected: Some exception is handled and the user will see "Only one group can be created once".

F.7. Delete a word group (Manual Testing)

  1. Delete a word group with all the word inside.

    1. Test case: groupdelete test
      Expected: If the group test do exist, then group "test" and all the word inside will be deleted. Otherwise some exception is handled and the user will see "The group typed does not exist."

    2. Test case: 'groupdelete '
      Expected: Some exception is handled and the user will see "please type in the group".

    3. Test case: 'groupdelete toLearn test'
      Expected: Some exception is handled and the user will see "Only one group can be deleted once.".

    4. Test case: groupdelete stud_ispm
      Expected: Some exception is handled and the user will see "Tags names should be alphanumeric".

F.8. Show a word group or all the word group name (Manual Testing)

  1. Show a word group or all the word group name.

    1. Test case: showgroup test
      Expected: If the group test do exist, then all the words inside test group will be filtered and shown. Otherwise some exception is handled and the user will see "The group typed does not exist."

    2. Test case: 'showgroup '
      Expected: All the existed word group will be shown.

    3. Test case: 'showgroup toLearn test'
      Expected: Some exception is handled and the user will see "Only one group can be shown once.".

    4. Test case: showgroup stud_ispm
      Expected: Some exception is handled and the user will see "Tags names should be alphanumeric".

F.9. Trivia

  1. Starting the trivia

    1. Prerequisites: Empty vocabulary list.

    2. Test case: trivia Expected: Trivia is unable to start. Error message shown on command box indicating that user needs to add words to the trivia list.

    3. Prerequisites: Non-empty vocabulary list.

    4. Test case: trivia Expected: Trivia mode starts. Command box shows the first question in the trivia === Trivia answers

    5. Prerequisites: Trivia mode started.

    6. Test case: triviaExit Expected: Trivia mode is exited. The CLI should recognized and execute commands again.

    7. Test case: triviaexit Expected: Trivia will output a wrong/correct message as the input is recognized as an answer to the trivia question instead of a command.

    8. Test case: triviaShow Expected: Command box shows the current trivia question. ..Test case: triviashow Expected: Similar to triviaexit