By: Team SE-EDU Since: Jun 2016 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 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 seedu.address.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 Appendix A, Suggested Programming Tasks to Get Started.

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.

SDforDeletePerson
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, SourceListPanel, 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 SourceManagerParser 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 person).

  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.

DeletePersonSdForLogic
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 Source Manager data.

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

As a more OOP model, we can store a Tag list in Address Book, which Person can reference. This would allow Address Book to only require one Tag object per unique Tag, instead of each Person needing their own Tag object. An example of how such a model may look like is given below.

ModelClassBetterOopDiagram

2.5. Storage component

StorageClassDiagram
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 Source Manager data in json format and read it back.

2.6. Common classes

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

3. Implementation

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

3.1. Recycle Bin Mode feature

The recycle bin mode allows users to access and manage the research data that they previously deleted.

Users may do one of the following: 1. Restore a previously delete source 2. Permanently delete a source from the recycle bin 3. Clear all sources from the recycle bin 4. Undo and Redo any action in the recycle bin mode 5. Exit the recycle bin and return to the source manager mode

3.1.1. Overview

The restore feature is facilitated by Infinity Machine. It extends Infinity Machine with a recycle bin feature, allowing user to delete and restore source from a persistent deleted sources list. The Recycle Bin was extended from interactions with the delete command. Our rationale to create an entire mode is so that users can safely delete sources and have a place to access and retrieve them even after exiting the application accidentally or intentionally. Previously, this feature was implemented seperatedly as a restore and delete feature in the source manager however, after testing it, it proved to be very messy and confusing for our users if they did not had a mode that specifically allows them to interact with the deleted sources.

3.1.2. Implementation

Logic Component
  • In the Recycle bin, a RecycleBinParser was created which is a now child of SourceManagerParser in the logic component. Through the separation of different commands to different parsers, it allows us to enable or disable certain commands for Source Manager mode or Recycle Bin mode which helps to increase the usability of our application.

  • A listener has also been added in the LogicManager so that operations regarding the Recycle Bin can be detected accordingly.

Model Component
  • In the Model Component, a ParserMode enum class was added which consists of two different modes RECYCLE_BIN and SOURCE_MANAGER.

  • A DeletedSources class which implements a ReadonlyDeletedSources class and is extended by VersionedDeletedSources is created. This class contains operations that allow commands to interact with the model such as adding a deleted source to deletedsources.json data folder, deleting a source from the json file mentioned and committing any changes to the DeletedSources state to name a few.

  • The file path for the deletedsource.json data file was also added to the UserPrefs class in order to save the file path of the recycle bin database.

Storage Component
  • A JsonDeletedSourcesStorage class was created so that Recycle Bin operations can interact with the deletedsources.json data storage. It consists of reading and writing operations implemented to manage the adding, updating and removing of json data.

  • A JsonSerializableDeletedSources is also created as is it necessary in assisting the generation of sources structured in Json data format.

3.1.3. Operation

RecycleBinSequenceDiagram

This sequence diagram shows what happens when a source is deleted pertaining to the recycle bin.

Given below is an example usage scenario and how the recycle bin mode behaves at each step when a user deletes and restores a source.

Step 1. The user launches the application for the first time. The Infinity Machine will be initialized with the initial source database state, by default listing all the sources in an indexed fashion, with all details and in order of their addition.

Step 2. The user executes delete 1 command and only one entry, the first one, is deleted.

Step 3. The user executes recycle-bin command and the Infinity Machine switches from the Source Manager mode to the Recycle Bin mode. A list of sources that was previously deleted in the Recycle Bin is shown.

Step 4. The user executes restore 1 and only one entry will be deleted, which is the source recently deleted in the Source Manager mode.

Step 5. The user executes exit-bin and only switches the Source Manager mode.

restore alone, without any arguments, will result in error. See restore command for enumerating all database entries.

3.1.4. Design Considerations

  • Alternative 1 : Using a simple read and write class in the storage.

    • Pros: Easy to implement.

    • Cons: May have performance issues in terms of time usage as well as limited usability in terms of operations.

  • Alternative 2 (current choice): Using a full DeletedSource interaction structure

    • Pros: High usability and little to none performance issues.

    • Cons: Highly complex structure and since implementation touches on many components of the application there is a high chance of breaking other components or commands.

3.1.5. Additional Considerations

  • Deleting a source from the Source Manager mode that already exists in the Recycle Bin mode: Previously this issue caused a bug. When the user tried to delete a source from the Source Manager mode that already exists in the Recycle Bin mode. To guard against this, we deleted the source that the user is trying to delete from the source manager permanently if it already exists in the Recycle Bin so that it will not cause any conflicts this is one of the many solutions available and we chose it because this was the most efficient and logic solution to implement.

  • Restoring a source from the Recycle Bin mode that already exists in the Source Manager mode: We also considered this case as it may also cause conflicts and resolved it by displaying an advising error message that the source that the user is trying to restore already exists in the Source Manager. This is a better solution in this case compared to deleting it permanently as the user may want to do additional changes to this source.

3.2. Panic Mode feature

The panic mode feature allows the user to temporarily hide user data and replace it with dummy data.

3.2.1. Overview

The user’s original data is replaced by dummy data for the duration that panic mode is enabled. Enabling panic mode can be thought of as "stashing" the user’s data temporarily in memory. This is reflected both on-screen and on-disk. On the screen, the list of sources is replaced by an empty dummy list. On disk, the contents of the JSON file storing the user’s sources is replaced by dummy content that tracks and reflects the dummy data.

3.2.2. Implementation

This is implemented by "swapping" the source manager with an empty dummy source manager. This "swap" is carried out by storing the original source manager in a private variable sourceManagerBackup, and then resetting the original source manager with a new empty source manager instance. We also set the boolean variable panicMode = true.

When the user disables panic mode, we restore the original source manager, and reset panicMode = false.

3.2.3. Elaboration

We use a boolean variable panicMode to keep track of whether panic mode has been activated. This is to guard against the scenario of entering panic mode while already in panic mode, which results in permanent data loss.

This is because when panic mode is activated, we store the original source manager in the private variable sourceManagerBackup, and reset the original source manager, as described above.

Therefore, should panic mode be activated while already in panic mode, sourceManagerBackup will now store the dummy source manager, and the original source manager will be deallocated and eventually purged from memory by Java’s garbage collector.

Since the JSON file on disk automatically tracks the source manager through the observer pattern, it automatically updates to track and reflect the data in the dummy source manager.

3.3. Command Alias feature

The command alias feature allows users to use shorthand commands to rapidly "get things done", for instance using a instead of add, or c instead of count.

Users may do one of the following:

  1. Add a new alias

  2. Remove an existing alias

  3. List all aliases

  4. Clear all aliases

In designing and implementing this feature, the overarching principle is to maximize transparency and compatibility. This means that it should be transparent to future developers/maintainers (they should not need to understand how this feature works, or be subject to any design constraints). It should also be backwards-compatible with existing commands (existing code should not be modified). This allows for maximum extensibility and maintainability.

3.3.1. Overview

This feature is backed by an in-memory database implemented as a Java HashMap<String, String>. A HashMap is chosen for the following reasons:

  • Adding and removing an alias is straightforward (using Java HashMap API) and efficient (O(1) time)

  • Checking whether an alias exists (membership) is efficient (O(1) time)

  • HashMaps naturally facilitate the process of associating a key-value pair

Alternative: No reasonable alternative implementations exist. For instance, using a Java ArrayList adds additional code complexity, as there needs to be a way of associating a key with a value. For instance, we could create an ArrayList<AliasWrapper>, where AliasWrapper is a wrapper class to associate 2 strings. However, that is inelegant and inefficient, as opposed to a HashMap solution. Furthermore, checking for membership in an ArrayList is an O(N) operation in an unsorted list, or O(log(N)) in a sorted list.

AliasManagerClassDiagram

3.3.2. Aliasing feature: implementation

Meta-commands are not implemented as regular commands. Regular commands inherit Command, and operate on the model (their main method is public CommandResult execute(Model model, CommandHistory history) throws CommandException {}). On the other hand, meta-commands operate on an AliasManager object. Therefore, it is desirable to draw a distinction between regular commands and meta-commands throughout the codebase.

To implement aliasing, we first create an AliasManager interface to practice design by contract. AliasManager is command-agnostic. It operates through its API (specified in the interface), and is not concerned with the choice of meta-commands (e.g. alias-rm vs alias-remove). We also created a class ConcreteAliasManager to implement the AliasManager interface.

As for SourceManagerParser, we created an alternative constructor to accept an AliasManager object to support dependency injection. Otherwise, the default constructor instantiates ConcreteAliasManager.

We chose to create the AliasManager interface to decouple SourceManagerParser and ConcreteAliasManager. In normal operation, we would always use ConcreteAliasManager. However, working through an interface (and implementing an alternative constructor) provides the flexibility to swap out ConcreteAliasManager for an alternative AliasManager implementation, such as a stub, for unit testing. This improves testability, maintainability, and extensibility.

To implement the meta-commands, we create an abstract superclass AliasMetaCommandParser that implements Parser<DummyCommand>. This serves as an alternative class of command parsers (for meta-commands), in contrast to the regular ones which are of the type Parser<? extends Command>. (As mentioned above, meta-commands are fundamentally different from regular commands, and it is desirable to maintain this distinction.) The key difference between the two is that an AliasMetaCommandParser has a field storing a reference to the AliasManager object which it requires to interact with (e.g. when adding/removing an alias).

Parsers are expected to return a Command object which SourceManagerParser returns in its parseCommand(String userInput) method. Typically, a Command object operates on the Model (e.g. AddCommand calls model.addSource()). However, meta-commands operate on the AliasManager, and not the model. Therefore, for this purpose, we created a class DummyCommand which nominally extends Command, but actually does nothing except return a CommandResult object to display feedback to the user. This promotes transparency and compatibility.

Finally, we create a CommandValidator interface. AliasManager uses the CommandValidator for two purposes:

  1. Validate a command before registering an alias to it

  2. Ensure that a command isn’t designated as an un-aliasable command

We chose this implementation and design pattern for several reasons:

  1. By designating an object as a CommandValidator, we are able to avoid hardcoding the list of valid and un-aliasable commands into AliasManager. This makes for a more reusable component and improves testability and maintainability. It also embodies the Open-Closed Principle.

  2. Typically, the SourceManagerParser (which by definition should know about the various valid commands) is the designated CommandValidator. However, the SourceManagerParser also has an association with the AliasManager. By creating an interface, we avoid a situation of circular dependency whereby both components are tightly coupled to each other.

3.3.3. Aliasing feature: operation

Meta-commands

When a meta-command is detected to have been entered, SourceManagerParser delegates it to the appropriate AliasMetaCommandParser to handle. For instance, alias FOO BAR is delegated to the AliasAddMetaCommandParser (a concrete subclass of AliasMetaCommandParser) with the arguments "FOO BAR". The appropriate AliasMetaCommandParser parses the arguments and returns a DummyCommand response object.

The following sequence diagram illustrates the operation of the "add alias" meta-command (assuming that valid user input is provided).

AliasManagerMetaCommandSequenceDiagram
This delegation design pattern is chosen for 2 reasons: Firstly, it hides complexity in SourceManagerParser by abstracting the logic of interacting with AliasManager away. This makes SourceManagerParser more readable, declarative, and maintainable. This also allows us to practice the Single Responsibility Principle and Single Layer of Abstraction Principle, among others. Secondly, it improves testability by facilitating unit testing of smaller blocks of logic, rather than a single giant block.

If user input is valid, the AliasMetaCommandParser, which stores a reference to the AliasManager object, operates on it through the AliasManager API.

Aliases

In normal operation, when the user enters an alias, SourceManagerParser parses the user input to extract the "command word". It checks whether the "command word" is a pre-existing alias using AliasManager’s isAlias() method. If so, it fetches the original command that the alias is associated to using AliasManager’s getCommand() method.

Finally, SourceManagerParser recursively calls itself once using the original command retrieved from AliasManager to execute the original command that the alias is associated with.

This sequence diagram provides a high-level overview of this operation. Finer-level details have been omitted.

AliasManagerSequenceDiagram
AliasManager doesn’t allow the aliasing of invalid commands, nor the aliasing of an alias. This is to guard against the risk of an infinite loop, e.g. where alias1 is the alias of alias2, which is the alias of alias1. With the current implementation, we can be assured that the recursion depth is at most 2.

3.3.4. Persistence feature: implementation

The usefulness of aliases would be significantly diminished if they do not persist between sessions. Therefore, we want aliases to be stored on disk and automatically loaded in future sessions on application startup.

To accomplish this, we create an AliasStorage interface, and an implementing class ConcreteAliasStorage. We also modify ConcreteAliasManager to accept an AliasStorage object during its instantiation. To facilitate unit testing, we allow a null AliasStorage object which disables data persistence.

The motivations for this design pattern is similar to the discussion above for creating the AliasManager interface. Essentially, we want to decouple components as much as possible, support dependency injection, and improve testability and maintainability.

ConcreteAliasStorage is responsible for reading/writing from/to disk, and therefore converting the in-memory database (HashMap object) of aliases into/from an encoded representation. When AliasManager’s aliases database is mutated (i.e. create or remove alias), it calls ConcreteAliasStorage’s saveAliases() method.

Alternative: A more elegant implementation would be to apply the observer pattern, with the observer observing the aliases HashMap database, and calling saveAliases() when it is mutated. However, given the simplicity of AliasManager, we believe that applying the observer pattern will result in unnecessary overhead, with minimal (or no) tangible benefits.

Within ConcreteAliasStorage, its saveAliases() method encodes aliases and commands into a string, in the following format: alias1:command1;alias2:command2;alias3:command3. Conversely, readAliases() parses this string and reconstructs the aliases HashMap database.

Alternative: We opted to use our own very simple encoding scheme instead of JSON. JSON is more suited for "document-like" objects with different properties, some of which are possibly nested multiple layers. However, in our case, we only have a series of key:value pairs, in a predictable form, with no nesting. Therefore, we thought that a simple semicolon-separated key:value pair encoding scheme would suffice.

3.4. Undo/Redo feature

3.4.1. Current Implementation

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

  • VersionedSourceManager#commit() — Saves the current source manager state in its history.

  • VersionedSourceManager#undo() — Restores the previous source manager state from its history.

  • VersionedSourceManager#redo() — Restores a previously undone source manager state from its history.

These operations are exposed in the Model interface as Model#commitSourceManager(), Model#undoSourceManager() and Model#redoSourceManager() 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 VersionedSourceManager will be initialized with the initial source manager state, and the currentStatePointer pointing to that single source manager state.

UndoRedoStartingStateListDiagram

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

UndoRedoNewCommand1StateListDiagram

Step 3. The user executes add i/algorithm …​ to add a new source. The add command also calls Model#commitSourceManager(), causing another modified source manager state to be saved into the sourceManagerStateList.

UndoRedoNewCommand2StateListDiagram
If a command fails its execution, it will not call Model#commitSourceManager(), so the source manager state will not be saved into the sourceManagerStateList.

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

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

If the currentStatePointer is at index sourceManagerStateList.size() - 1, pointing to the latest source manager state, then there are no undone source manager states to restore. The redo command uses Model#canRedoSourceManager() 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 source manager, such as list, will usually not call Model#commitSourceManager(), Model#undoSourceManager() or Model#redoSourceManager(). Thus, the sourceManagerStateList remains unchanged.

UndoRedoNewCommand3StateListDiagram

Step 6. The user executes clear, which calls Model#commitSourceManager(). Since the currentStatePointer is not pointing at the end of the sourceManagerStateList, all address book states after the currentStatePointer will be purged. We designed it this way because it no longer makes sense to redo the add i/algorithm …​ 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.4.2. Design Considerations

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

    • 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 source 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 source manager 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 VersionedSourceManager.

  • 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.5. Search feature

3.5.1. Current Implementation

Format: search [n/TITLE] [y/TYPE] [d/DETAILS] [t/TAG] [t/TAG]…​

The search feature is facilitated by Infinity Machine. It extends Infinity Machine with an find feature, allowing user to search through source entries by the title, type, detail and/or tags, with substring matching.

This search function now has an added functionality of being able to take in multiple arguments of the type of source fields [i.e. title, type, detail and tags], and search for sources based on that. It searches in conjunction using multiple fields including title, type, detail and tag(s) input by the user, listing only those sources that satisfy all the input constraints of the matching fields, with all there corresponding field values.

Another addition to its functionality is that this search feature is enabled with substring matching as against exact field matching. This renders this feature more powerful as the user may not always be able to remember exactly the title or tag of the source. It’s major usage is in the fact that the user will store the bulk of their data in the details field, and it is unintuitive to have them list the entire contents of the source in order to match and search it. Thus now, the user is only required to search using as many consecutive words they are able to recall to narrow the listings.

How it works is, it allows the user to search through all the entries in the database through various fields at a time, and display source entries that satisfy all of the entered tags in conjunction, by checking if the source value contains these parameters. It allows compound searches to be made, allowing user to narrow down their search, hence helping in efficient retrieval of the sources, and making working on the database more efficient.

Lastly, the search is able to find string matches with minor typing errors in the spelling. This feature renders the search more powerful by accommodating any minor typing error user may make when keying in their search argument. This includes minor flips of two characters or a missing character or few extra characters etc. The implementation current accounts for less than 5 character swaps needed to transform between the strings.

Auto-correction feature only works when entire field value is entered and not for substring matching.

i.e. misspelling a single word in the title will not be caught unless the entire title is entered, then the user entry will be matched against the entire title to see if there are less than 5 corrections needed to transform between the two strings.

However, it may seem that such an additional renders the search feature a bit too general, thus making the search output space broadened by including more sources that would have otherwise been ignored. But, one may see this as an advantage as:

  1. Obviously, this is targeted at accounting for the slightest chance that the user may have made a typo.

  2. If not, this feature at the very least shows user 'similar' or 'other related' entries that may be useful in their research project as they search for a particular entry. This can help in giving user more ideas about related sources in the same field.

This distance is know as the Levenshtein distance or the edit distance, after the Russian scientist Vladimir Levenshtein who devised the algorithm in 1965. This algorithm is used to determine how different two strings are from each other by outputing the integer number of transformations (insertions, deletions and substitutions) needed to transform one string to the other.

The algorithm implementation for this section of the code was inspired from Baeldung.

If any seemingly unwanted results are displayed after a search command is executed, it should not be seen as a bug and this is the intended behaviour because of the reasons and rationale explained above. Rest assured, the intended results will never be missed out.

Additionally, it uses:

  • SourceContainsKeywordsPredicate.java — Here, the logic of running through all the respective fields of all the sources and matching it with the user inputs (trimmed by space, case insensitive and take as substring) is implemented. It is split by the CLI prefixes and implements conjunction logic, by only returning true for those sources that satisfy all the constraints, i.e. have all the fields matching as entered by the user, where matching is checked by if the string contains the keywords entered by the user (case insensitive).

  • checkAllEmpty — method inside SourceContainsKeywordsPredicate which checks if all the entries of all the tags is empty, and returns true thus showing all sources.

  • levenshtienDist — returns the number of swaps needed to transform one string to the other

  • checkLevenshtienSimilarity — returns true if the number of swaps needed, as returned by levenshtienDist above is less than the LEVENSHTIEN_DISTANCE_CONSTANT below

  • LEVENSHTIEN_DISTANCE_CONSTANT — a positive integer which determines the number of swaps a string can have for it to pass the similarity test as per the checkLevenshtienSimilarity method. Currently set to 5, thus if less than 5 swaps are needed to convert between the user entered string and the entire string value fo the source field.

Given below is a sequence diagram representation of the search command of the Infinity Machine:

SearchCommandSequenceDiagram

This feature improves the product significantly because a user can now search an entry with a particular title AND a particular type and so on. Not only that, the user can now just input whatever they are able to recall and the search returns all super strings instead of carrying out an exact matching. It helps user greatly narrow down their search should they be looking for a specific source entry with particular values, instead of cluttering the screen with all those sources with share the same title as the one the user searches using the command. It renders the search more powerful by resulting all super-strings should the user have meant something else or to prompt them about other similar source entries containing what they are looking for. It also allows user to search sources based on other fields and not just title, such as type, tags and details, and even their logical combination.

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

Step 1. The user launches the application for the first time. The Infinity Machine will be initialized with the initial source database state, by default listing all the sources in an indexed fashion, with all details and in order of their addition.

Step 2. The user executes search i/algorithms command and only those sources that have their title as algorithms are displayed.

Step 3. The user executes search i/homework y/website and only those entries are listed that have both their title as homeowork and type as website.

Step 4. The user executes search t/CS and all those sources that have any of their tags having 'CS' in it listed, including CS2030, CS2040 and CS2103

search alone, without any arguments, will result in error. However search with empty CLI tags will output all sources. Thus, a shortcut to display all sources is to search search i/ and a source with any title will be displayed.

Step 5. The user executes search i/algorihtm as a typing error, the command still displays all those sources that have their title as algorithms or other related words exactly [not contains].

Step 6. The user executes search d/training an intelligent agent t/CS2039, the search displays all sources with having the exact sentence 'training an intelligent agent' or any of its related similar strings in it’s body, and those with tag 'CS2039' or any of the related modules such as 'CS2030', 'CS2040'.

3.5.2. Design Considerations

Aspect: How search executes
  • Alternative 1 (current choice): Runs through all entries and matches the arguments, field by field, and uing && operation to combine the results.

    • Pros: Easy to implement as exact String matching can be done in Java using streams and StringUtil.containsWordIgnoreCase(str1, str2).

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

Aspect: Data structure to support the undo/redo commands
  • Alternative 1 (current choice): Using streams and StringUtil functions.

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

    • Cons: May not be the most efficient implementation.

3.6. Count feature

3.6.1. Current Implementation

The count command is facilitated by Infinity Machine. It extends Infinity Machine with a count functionality calculating the total number of sources retrieved from the database. Additionally, it uses:

  • FilteredSourceList — 

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

Step 1. The user launches the application for the first time. The Infinity Machine will be initialized with the initial source database state, by default listing all the sources in an indexed fashion, with all details and in order of their addition.

Step 2. The user executes count command. All entries retrieved using the command entered will be counted.

list does not take any arguments. If given, it will ignore it.

Step 3. The user executes a 'search' command to search for all entries matching a certain keyword.

Step 4. The user executes count command again. The count of the total number of entries retrieved through the search command will be returned.

Alternative: It can be implemented in the Model and ModelManager instead of directly in the execute command however, this is an inefficient implementation and thus is not used.

3.7. List feature

3.7.1. Current Implementation

The list command is facilitated by Infinity Machine. It extends Infinity Machine with a list functionality, enumerating all or a specific number of entries in the source database and their all their details, in the order of their addition, or custom order as may be supported by the application.

The four main formats and their usages are described below:

1. list: (no arguments)

When no arguments are passed to list, it works same way as in the original AB4 logic, listing all the sources in the entire database with all their title, type, author, detail and tag values. The sources are unfiltered and listed in entirety, with indexes 1, 2.. so on.

Example: list
Lists all the sources indexed from 1 onwards with all their details.

2. list N: (one positive argument)

When one positive integer is passed to list, it lists the first N sources from the top, again listing all their title, type, author, detail and tag values. Top N sources are listed with respect to the original source database list with indexes 1, 2 …​ till N. The number N must be a positive, non-zero number for the command logic to work. A negative N alludes to the 3rd case below.

Example: list 5
Lists top 5 sources from the entire database indexed from 1, 2 .. 5 with all their details.

3. list -N: (one negative argument)

When one negative integer is passed to list, it lists the last N resources from the top or first N sources from the bottom, again listing all their title, type, author, detail and tag values. Bottom N sources are listed with respect to the original source database list with indexes 1, 2 …​ till N. The number after the negative sign but be a non-zero positive number for the command logic to work.

Example: list -5
Lists the last 5 sources from the entire database indexed from 1, 2 .. 5 with all their details.

4. list N M: (two positive arguments)

When two positive integers are passed to list, it lists the sources between N and M (included) from the top, again listing all their title, type, author, detail and tag values. N to M sources are listed with respect to the original source database list with indexes 1, 2 …​ till (M-N+1). For valid functioning of the command, the two numbers passed must be positive and the first number must not be greater than the second number. I.e. both numbers should be non-zero and the second number can only be same or greater than the first number to produce a valid listing of the sources.

Example: list 6 9
Lists the 4 sources from index 6 to 9 from the entire database list, indexed from 1, 2 .. till 4 with all their details.

Some salient features which affect the command’s working are discussed below:

  1. An argument passed which more than the current number of sources in the list is reduced to the index of the last source [i.e. the maximum number of sources]. For example, executing list 100 when the database has only 50 entries will automatically cap its display to 50 instead of throwing an error. Similarly, for other command formats too, the listing is capped by the total number of sources and the success message is too altered appropriately.

  2. Any number of arbitrary spaces between the list command word and its argument is accepted. The command parser will look for the valid command word and when list command is called, then the numbers will be plucked out in the right order and the appropriate command format will be executed. For example, 'list 2 3', ' <n spaces> list <x spaces> 2 <y spaces> 3 <z spaces> ' all commands work in the same, intended fashion for all values of n and z and all non-zero values of x and y.

  3. More than two arguments are ignored and only the first two arguments are evaluated as per the 4th case above. Thus, list 2 3 4 and list 2 3 4 …​ all are executed as list 2 3 only.

  4. This list feature controls how many sources [based on indices passed] are displayed, with respect to the original source list only, and not the one currently being displayed to the user. Thus, if list 2 4 is carried out after list 7 10 then the list index 2, 3 and 4 of the original list will be outputted and not of this currently displaying list. Similarly, if a user executes list 3 post an operation, say search, then the first three sources of the entire databae are shown and not the first three sources of the filtered list of the resulting search operation.

  5. Having said (4), the new displayed list of sources after the executing of any list operation will update the internally maintained current list displayed, thus any operation [such as edit, delete] which are based on the indexes of the current displaying list will still function as per normal after a list command is executed. For example, after displaying list 3 5, if a user executes delete 1, it is akin to deleting the 3rd source in the entire list database.

The sequence diagram of the working of the list command is as below:

ListCommandSequenceDiagram

Motivation for such an enhancement is that one may feel that such act of populating all the sources on the GUI may be cluttering the view unnecessarily. Now, what could be the parameters a user may want to limit the list by? Limiting merely by their field values is akin to search, which would make the logic redundant. In contrast, the user may want to control the number of sources he wants to view, or be able to limit by some sense of the time at which it was added.

This could be helpful in:

  1. Iteratively examining all the sources by restricting how many are shown at one time. The user may begin with listing list 10, then list 11 20 and so on to analyze all the source entries 10 at a time.

  2. Making more effective use of the GUI display to the user by not unnecessarily enumerating all sources, but rendering it more powerful by allowing the user to control what and how many sources he wants to see the details of.

  3. Enabling a pseudo-filtering by time-of-addition of the sources, something the application logic does not support currently [e.g. TimeAdded field]. This is possible because the list command alway alludes to the original databse list of all the sources, which are by defaul maintained in the order of their addition [with most recent at the end].

Thus, the new modification to the command changes its format optionally, allowing user to be able to pass either one, two or no parameters and list only those sources which have their indices falling in the range entered [as covered by the cases above]. This may be intuitively useful when say you want to perform certain operations in this new list of sources that are displayed by their time of addition, since the sources are by default arranged in descending order of their time of addition.

Example, a user wants to delete all of the sources that were added yesterday. And if 10 entries were added yesterday, the user could just execute list 10 to access those entries and then perform a delete <INDEX> accordingly.

The original working of the list command showing all entries is still intact, when the command is called without any parameter, thus this modification just appends extra functionality which renders the command more useful and powerful than it was before in AB4.

Additionally, it uses:

  • PREDICATE_SHOW_ALL_SOURCES — when the user does not pass an argument to the list command, all sources must be displayed. This predicate results true for every source tested. Also, this filtering mode is internally called before any list operation so the command is able to utilize the entire database list in its filtering logic and not just the one currently displayed.

  • makePredicateForTopN — when the user passes a positive argument, only first N sources must be listed. This method returns a new object of type Predicate<Source> that keeps track of the count of sources, evaluating true for the first N sources and false for all the rest.

  • makePredicateForLastN — when the user passes a negative argument, the last N sources must be listed. This method returns a new object of the type Predicate<Source> that keeps track of the count of the sources, evaluating true for the las N sources an false for all others.

  • makePredicateForXToY — when the user passes two positive arguments, the sources between the two indices (included) must be listed. This method returns a new object of the type Predicate<Source> that keeps track of the count of the sources, evaluating true for all sources between indices N and M included, and false for all others.

  • targetIndex — the number N up to which the most recent sources are to be displayed to the user, in both the positive N and negative N case (case 2 and 3). It is maintained internally. Set to the current size of the database if the value input is more that this current size.

  • fromIndex — in the case of two arguments, this is the first index, must be non-zero positive number not greater than the toIndex below.

  • toIndex — in the case of two arguments, this is the second index, must be non-zero positive number not less than the fromIndex above.

  • posFlag — internally maintained, passed as true for positive single argument N and false for negative single argument N.

This feature improves the product significantly because a user can now list only as many item he wants and need not clutter the screen by displaying all. It helps him to narrow down his search, say should he want to view the N first or last added sources. This ensures more effective retrieval and operations on the sources, such as following it by index dependent operations such as edit and delete for instance.

Some points to note: - This enhancement does not affect existing commands and commands to be added in future. - It required an in-depth analysis of design alternatives. Especially when it came to adding the ability for the command to be able to work both with 1 parameter and no parameters. Some design considerations were Using variable arguments: in parser method of ListCommandParser class, but this would require changing the Interface Parser<T>. This technique did not work for making ListCommand objects for the same reason. Using method overloading: This did not work for parser method because of the interface restrictions, however this was used in the constructor of LogicCommand class, creating two objects depending on whether a targetIndex was passed or not, and whether two indexes where passed or none. ** Using args.length(): Ultimately used in parse method for a simple check whether an argument is passed and how many are passed. - The implementation too was challenging, as the current format of list command had to be changed and be prepared to accept and parse optional arguments, with the choice of either one or two parameters, ie. implementing overloading functionality for the list command logic based on whether the number of arguments passed by the user if any.

Given below is an example usage scenario and how the list mechanism behaves.

Step 1. The user launches the application for the first time. The Infinity Machine will be initialized with the initial source database state, by default listing all the sources in an indexed fashion, with all details and in order of their addition.

Step 2. The user executes an add command to add another source entry to the database.

Step 3. The user executes list command (with no arguments). All the entries in the database are listed again, showing all the details and in the order of their addition.

Step 4. The user now executes delete to delete an entry.

Step 5. The user executes list command again. All the updated entries in the database, leaving out the last deleted one, are listed again, showing all the details and in the order of their addition.

Step 6. The user now executes list 2 command. The first 2 entries akin to the previous output are displayed.

Step 7. The user now executes list 3 5 command. The source entries 3, 4 and 5 are shown, indexed as 1, 2 and 3, with all their details.

Step 8. The user now executes list 3 command. The first 3 entries of the entire source database are listed with all their details, and not the first three of the currently showcased list.

Step 9. The user now executes list -2 and the last two sources from the entire database list (and not the currently displayed list) are shown.

Step 10. The user now executes delete 1 and the first entry of the current list or the second last entry of the entire database is deleted.

3.7.2. Design Considerations

Aspect: How list, list N, and list N M executes
  • List (current choice): Filters using predicate that returns true for every source.

    • Pros: Easy to implement.

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

  • List N (current choice): Filters using predicate that returns true for first/last N sources.

    • Pros: Easy to implement. Intuitive to understand

    • Cons: Can be made faster and cleaner using List operations or streams.

  • List N (current choice): Filters using predicate that returns true for sources between N and M included.

    • Pros: Easy to implement. Intuitive to understand

    • Cons: Can be made faster and cleaner using List operations or streams.

Aspect: Data structure to support the list/list N/list N M commands
  • Alternative 1 (current choice): Forms predicates based on the input parameter, maintain targetIndex, fromIndex, toIndex and posFlag.

    • Pros: Uses simple count parameter initialized to 0 or 1 and incremented each time a source is evaluated returning true/false depending on the format of the list command. Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project.

    • Cons: Maybe not the best implementation in terms of the number of internal flags and indices maintained.

3.8. Pin and Unpin Features

3.8.1. Current Implementation

The concept of pinned sources works on an index bases system rather than a separate list or any additional implementation. This allows pinned sources to function just like ordinary sources in that they can be searched and listed as normal.

Pinned sources can be deleted with the delete command but cannot be swapped.

Essentially, the pinned sources are governed by a single number within the ModelManager and is managed through a separate class called the PinnedSourcesCoordinationCenter. This coordination center is responsible for all operations which modify the number of pinned sources.

pinUnpinStructure

When a new source is pinned using the pin INDEX command, the coordination center will increment the number of pinned sources by 1 as well as bring the newly pinned source to the top of the list.

pinSequence

When a pinned source is unpinned using the unpin INDEX command, the coordination center will decrement the numer of pinned sources by 1 and move the unpinned source down to the position of the first unpinned source.

unpinSequence

3.8.2. Operational Process

When a source is unpinned and there are other pinned sources, the recently unpinned source will be pushed back to the position of the first unpinned source.

An example of this could be unpin 1 command in a database with 3 pinned sources.

unpin1

The first source will be unpinned and moved to the position of the first unpinned source, in this case position 3.

unpin2

3.8.3. Source Checks

When checking to see if a source can be pinned or unpinned, the command will call on the coordination center to check if a source is already pinned or unpinned respectively. An already pinned source cannot be pinned again and an unpinned source cannot be unpinned.

These very same source checks will also be called during the delete and order commands. For the delete command, the coordination center will check if the source to be deleted is a pinned source. If so, then the number of pinned sources will decrement by 1. If not, it just carries on with the deletion as usual. For the order command, the coordination center will check both the source being moved as well as the move location. If either of these indexes are of pinned sources, the order command will fail.

3.8.4. Pinned Source Persistence

For the pinned sources to be persistent, the number of pinned sources is updated into an external text file whenever a change is made to the number. This is consistent with the source database itself so the reordering of the sources when something is pinned or unpinned will occur together with the update to the number of pinned sources.

The external storage is handled by a simple class called the PinnedSourcesStorageOperationsCenter which contains the path of the file which the number will be saved to.

Dynamic pathing was necessary because when testing the function using a default file path, changes made during the test were saved to the actual file and that caused major problems for the program. Dynamic pathing ensured that for testing, a separate test file is written to thus maintaining the integrity of the actual file.

3.8.5. Pinned Source UI Tag

Pinned sources are denoted by a little golden badge on top of the source that says "Pinned". This is kept updated vis a flag set in the source object itself.

At the start of the program, the ModelManager will use the number of pinned sources retrieved from the external storage to assign the flags accordingly. Every time a function affects the pinned sources, namely delete, pin and unpin, is called, the flag will be appropriately updated as well for consistency.

It is important to note that the flag itself is not stored externally and is not persistent. It is assigned at the start of every session and modified accordingly as the functions are called. The rationale behind this implementation choice is to ensure that the external source storage is kept as clean and minimalist as possible.

3.9. Custom Order Feature

3.9.1. Current Implementation

The function is currently implemented using the functionality of the source model. It allows users to designate a source they want to move and a location they want to move it to.

The primary uses of this feature are to facilitate source management and ordering sources by some user defined metric like personal importance.

Given the function works purely on the parameters the user enters, a parser file was necessary to filter out invalid inputs like alphabets or special characters.

Further consideration was necessary because of nature of the inputs. The inputs are array indexes which are very prone to being out or bounds that can result in system failures. Therefore, in addition to traditional exception handling, the function also implements its own checks for invalid user indexes.

These check include the following:

  • Checking for inputs which are 0 or smaller

    • The user entered input follows traditional listing which starts from 1. However array indexes in Java start from 0. Therefore, 1 is always subtracted from any input added.

    • This means that any user input less than 1 are automatically invalid since that index cannot possibly exist

  • Checking for inputs which are larger than the size of the list of sources

    • The classic out of bounds exception occurs when a function attempts to extract an index from a point larger than the list’s current size.

    • The function therefore checks the user input to ensure that it is always within the size of the list.

Once the inputs are deemed as valid, the actual moving can begin. The model uses a list implementation for its primary storage model. This means that when a source is moved to a location, every source around it will need to be shifted to the front or the back depending on where the original source originated at.

Thankfully, the Java List implementation does come with the function to add an item to the lest at a particular index, pushing everything aside automatically. The function called addSourceAtIndex was added to the model which took in the source to add as well as an index which the source should be added at.

The function takes the following steps to make the swap:

  • Step 1 — The function stores a copy of the source to be moved locally

    • The source to be moved is found using the index entered by the user and the List.get function that takes in an index and returns the source to be moved

  • Step 2 — The function then deletes the source to be moved from the list

    • The deleteSource function automatically moves sources up to fill up the void left by the deleted source

    • A deletion is necessary in this step because the model does not accept duplicate sources. Therefore, adding the source to be moved first before deleting it would result in the function failing.

  • Step 3 — The addSourceAtIndex function is used to place the recently deleted source back into the list at the designated location.

  • Step 4 — The function then commits the database to save the recent changes.

customOrderSequence

3.9.2. Swapping process

The process for swapping is slightly different for each type of swap, namely forward swapping and backwards swapping.

Forward Swapping

Forward swapping means that the index of the source to be moved is smaller than the index of the position it is to be moved to.

An example of such a command could be order 2 5.

forward1

Once the source to be moved and the position it is to be moved to have been identified and validated, the source to be moved will be deleted from the list and stored separately. Notice that for forward swapping the initial source at the move position, in this case position 5, changes.

forward2

The source to be moved is then inserted into the current position 5, displacing all other sources after that and pushing them back.

forward3
Backward Swapping

Backward swapping means that the index of the source to be moved is larger than the index of the position it is to be moved to.

An example of such a command could be order 5 2.

backward1

Once the source to be moved and the position it is to be moved to have been identified and validated, the source to be moved will be deleted from the list and stored separately. Notice that for backward swapping the initial source at the move position, in this case position 2, does not change.

backward2

The source to be moved is then inserted into the current position 2, displacing all other sources after that and pushing them back.

backward3

3.10. Biblio feature

3.10.1. Overview

Given that the Infinity Machine is used to manage sources, a user might reasonably expect to use the information stored to generate usable bibligraphy entries. This functionality is implemented by the biblio command and the biblioEdit command.

3.10.2. BiblioFields

When a Source object is created, in addition to the compulsory fields of Title, Author, Type, and Detail, it is also created with a set of BiblioFields for storing additional information that might be needed for creating a bibliography entry.

BiblioFields is used to store information under the following headers: "City", "Journal", "Medium", "Pages", "Publisher", "URL", "Website", "Day", "Month", "Year"

BiblioFields is empty by default.

3.10.3. Biblio Command Implementation

The biblio command extends Infinity Machine with a bibliography generating functionality.

It is currently implemented with the following syntax: biblio INDEX FORMAT

  • INDEX — Indicates the index of the source to generate a bibliography entry for.

  • FORMAT — Indicates the format which the bibliography entry should follow.

    • Currently only APA and MLA formats are supported.

An example would be biblio 1 APA.

When the command is entered, the following occurs:

  • The associated parser, BiblioCommandParser, checks the validity of the entered arguments.

  • The indicated source is fetched from the SourceManager.

  • A check is performed on the source to ensure that it is of a supported type.

    • Currently only Book, Journal Article and Website sources are supported. These are chosen as they are the most likely to be used.

  • Based on the type of the source and the requested format, a bibliography entry is generated by accessing each of the source’s relevant fields.

    • If any of the necessary fields is empty, a placeholder value is used instead.

    • The empty field is noted for feedback to user later.

  • The bibliography entry is returned to the user along with feedback on empty fields.

Extensions: Future implementations can include additional supported source types.

3.10.4. BiblioEdit Command Implementation

The biblioEdit command is a supporting command for the Biblio feature. It is responsible for changing the information stored in the BiblioFields of a source.

It is currently implemented with the following syntax: biblioEdit INDEX HEADER BODY

  • INDEX — Indicates the index of the source to generate a bibliography entry for.

  • HEADER — Indicates the header for the information which the user wishes to change.

    • As mention in relation to BiblioFields above, valid headers are "City", "Journal", "Medium", "Pages", "Publisher", "URL", "Website", "Day", "Month", "Year"

  • BODY — Indicates the new value that should stored under the relevant header.

BiblioEditCommand

An example would be biblioEdit 1 City London

Alternative: The bibliofields of a source can be set at object creation with the add command. However, this makes using the add command excessively onerous. The current implementation allows the user to change each field as needed and preserves the information stored under the other headers in Bibliofields.

Extensions: A future implementation may allow information under multiple headers to be edited at once. Additionally, checks can be introduced to ensure the entered field bodies are appropriate e.g. the Month bibliofield should only accept values matching the English name of months.

3.10.5. Example Usage Scenario

Given below is an example usage scenario and how the BiblioEdit mechanism behaves.

  • Step 1 — The user launches Infinity Machine for the first time.

  • Step 2 — The user clears the database of sample sources using the clear command.

  • Step 3 — The user executes an add command to add a source entry to the database. The source has type "Textbook".

    • BiblioFields is empty

  • Step 4 — The user executes a biblio command with the following arguments: 1 APA

    • The user is informed that "Textbook" is not a supported source type

  • Step 5 — The user executes an Edit command with the following arguments: 1 y/Journal Article

    • The source type is changed to "Book"

  • Step 6 — The user executes a biblio command with the following arguments: 1 APA

    • A bibliography entry is generated with placeholder values as the requisite biblio fields are empty.

    • The user receives the bibliography entry along with a warning that some required fields are empty

    • The warning is appended with a list of empty but recommened fields: "Year", "City", "Publisher"

  • Step 7 — The user executes a biblioEdit command with the following arguments: 1 City London

  • Step 8 — The user executes a biblioEdit command with the following arguments: 1 Publisher Penguin

  • Step 9 — The user executes a biblioEdit command with the following arguments: 1 Year 2001

  • Step 10 — The user executes a biblio command with the following arguments: 1 APA

    • The user receives an appropriately formatted bibliography entry

3.11. Logging

We are using java.util.logging package for logging. The LogsCenter class is used to manage the logging levels and logging destinations.

  • The logging level can be controlled using the logLevel setting in the configuration file (See Section 3.12, “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.12. 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 9. 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.address.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.address.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.address.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.address.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, Address Book 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: Suggested Programming Tasks to Get Started

Suggested path for new programmers:

  1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in Section A.1, “Improving each component”.

  2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. Section A.2, “Creating a new command: remark explains how to go about adding such a feature.

A.1. Improving each component

Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work).

Logic component

Scenario: You are in charge of logic. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases.

Do take a look at Section 2.3, “Logic component” before attempting to modify the Logic component.
  1. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing clear, the user can also type c to remove all sources in the list.

    • Hints

    • Solution

      • Modify the switch statement in AddressBookParser#parseCommand(String) such that both the proper command word and alias can be used to execute the same intended command.

      • Add new tests for each of the aliases that you have added.

      • Update the user guide to document the new aliases.

      • See this PR for the full solution.

Model component

Scenario: You are in charge of model. One day, the logic-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the SourceManager, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command.

Do take a look at Section 2.4, “Model component” before attempting to modify the Model component.
  1. Add a removeTag(Tag) method. The specified tag will be removed from everyone in the SourceManager.

    • Hints

      • The Model and the AddressBook API need to be updated.

      • Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags?

      • Find out which of the existing API methods in SourceManager and Source classes can be used to implement the tag removal logic. AddressBook allows you to update a person, and Person allows you to update the tags.

    • Solution

      • Implement a removeTag(Tag) method in SourceManager. Loop through each source, and remove the tag from each source.

      • Add a new API method deleteTag(Tag) in ModelManager. Your ModelManager should call SourceManager#removeTag(Tag).

      • Add new tests for each of the new public methods that you have added.

      • See this PR for the full solution.

Ui component

Scenario: You are in charge of ui. During a beta testing session, your team is observing how the users use your SourceManager application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn’t prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last source in the list. Your job is to implement improvements to the UI to solve all these problems.

Do take a look at Section 2.2, “UI component” before attempting to modify the UI component.
  1. Use different colors for different tags inside source cards. For example, CS2030 tags can be all in brown, and CS2040 tags can be all in yellow.

    Before

    getting started ui tag before

    After

    getting started ui tag after
    • Hints

      • The tag labels are created inside the PersonCard constructor (new Label(tag.tagName)). JavaFX’s Label class allows you to modify the style of each Label, such as changing its color.

      • Use the .css attribute -fx-background-color to add a color.

      • You may wish to modify DarkTheme.css to include some pre-defined colors using css, especially if you have experience with web-based css.

    • Solution

      • You can modify the existing test methods for SourceCard 's to include testing the tag’s color as well.

      • See this PR for the full solution.

        • The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes.

  2. Modify NewResultAvailableEvent such that ResultDisplay can show a different style on error (currently it shows the same regardless of errors).

    Before

    getting started ui result before

    After

    getting started ui result after
  3. Modify the StatusBarFooter to show the total number of people in the address book.

    Before

    getting started ui status before

    After

    getting started ui status after
    • Hints

      • StatusBarFooter.fxml will need a new StatusBar. Be sure to set the GridPane.columnIndex properly for each StatusBar to avoid misalignment!

      • StatusBarFooter needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated.

    • Solution

Storage component

Scenario: You are in charge of storage. For your next project milestone, your team plans to implement a new feature of saving the source manager to the cloud. However, the current implementation of the application constantly saves the source manager after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the source manager storage.

Do take a look at Section 2.5, “Storage component” before attempting to modify the Storage component.
  1. Add a new method backupSourceManager(ReadOnlySourceManager), so that the address book can be saved in a fixed temporary location.

A.2. Creating a new command: remark

By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app.

Scenario: You are a software maintainer for source manager, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible remark field for each contact, rather than relying on tags alone. After designing the specification for the remark command, you are convinced that this feature is worth implementing. Your job is to implement the remark command.

A.2.1. Description

Edits the remark for a source specified in the INDEX.
Format: remark INDEX r/[REMARK]

Examples:

  • remark 1 r/Due tomorrow.
    Edits the remark for the first source.

  • remark 1 r/
    Removes the remark for the first source.

A.2.2. Step-by-step Instructions

[Step 1] Logic: Teach the app to accept 'remark' which does nothing

Let’s start by teaching the application how to parse a remark command. We will add the logic of remark later.

Main:

  1. Add a RemarkCommand that extends Command. Upon execution, it should just throw an Exception.

  2. Modify SourceManagerParser to accept a RemarkCommand.

Tests:

  1. Add RemarkCommandTest that tests that execute() throws an Exception.

  2. Add new test method to AddressBookParserTest, which tests that typing "remark" returns an instance of RemarkCommand.

[Step 2] Logic: Teach the app to accept 'remark' arguments

Let’s teach the application to parse arguments that our remark command will accept. E.g. 1 r/due tomorrow

Main:

  1. Modify RemarkCommand to take in an Index and String and print those two parameters as the error message.

  2. Add RemarkCommandParser that knows how to parse two arguments, one index and one with prefix 'r/'.

  3. Modify AddressBookParser to use the newly implemented RemarkCommandParser.

Tests:

  1. Modify RemarkCommandTest to test the RemarkCommand#equals() method.

  2. Add RemarkCommandParserTest that tests different boundary values for RemarkCommandParser.

  3. Modify AddressBookParserTest to test that the correct command is generated according to the user input.

[Step 3] Ui: Add a placeholder for remark in SourceCard

Let’s add a placeholder on all our SourceCard s to display a remark for each source later.

Main:

  1. Add a Label with any random text inside SourceListCard.fxml.

  2. Add FXML annotation in SourceCard to tie the variable to the actual label.

Tests:

  1. Modify SourceCardHandle so that future tests can read the contents of the remark label.

[Step 4] Model: Add Remark class

We have to properly encapsulate the remark in our Source class. Instead of just using a String, let’s follow the conventional class structure that the codebase already uses by adding a Remark class.

Main:

  1. Add Remark to model component (you can copy from Address, remove the regex and change the names accordingly).

  2. Modify RemarkCommand to now take in a Remark instead of a String.

Tests:

  1. Add test for Remark, to test the Remark#equals() method.

[Step 5] Model: Modify Person to support a Remark field

Now we have the Remark class, we need to actually use it inside Source.

Main:

  1. Add getRemark() in Source.

  2. You may assume that the user will not be able to use the add and edit commands to modify the remarks field (i.e. the Source will be created without a remark).

  3. Modify SampleDataUtil to add remarks for the sample data (delete your data/sourcemanager.json so that the application will load the sample data when you launch it.)

[Step 6] Storage: Add Remark field to JsonAdaptedPerson class

We now have Remark s for Source s, but they will be gone when we exit the application. Let’s modify JsonAdaptedSource to include a Remark field so that it will be saved.

Main:

  1. Add a new JSON field for Remark.

Tests:

  1. Fix invalidAndValidPersonAddressBook.json, typicalPersonsAddressBook.json, validAddressBook.json etc., such that the JSON tests will not fail due to a missing remark field.

[Step 6b] Test: Add withRemark() for PersonBuilder

Since Source can now have a Remark, we should add a helper method to SourceBuilder, so that users are able to create remarks when building a Source.

Tests:

  1. Add a new method withRemark() for PersonBuilder. This method will create a new Remark for the person that it is currently building.

  2. Try and use the method on any sample Person in TypicalPersons.

[Step 7] Ui: Connect Remark field to PersonCard

Our remark label in PersonCard is still a placeholder. Let’s bring it to life by binding it with the actual remark field.

Main:

  1. Modify SourceCard's constructor to bind the Remark field to the Source 's remark.

Tests:

  1. Modify GuiTestAssert#assertCardDisplaysPerson(…​) so that it will compare the now-functioning remark label.

[Step 8] Logic: Implement RemarkCommand#execute() logic

We now have everything set up…​ but we still can’t modify the remarks. Let’s finish it up by adding in actual logic for our remark command.

Main:

  1. Replace the logic in RemarkCommand#execute() (that currently just throws an Exception), with the actual logic to modify the remarks of a Source.

Tests:

  1. Update RemarkCommandTest to test that the execute() logic works.

A.2.3. Full Solution

See this PR for the step-by-step solution.

Appendix B: Product Scope

Target user profile:

  • has a need to manage a significant number of research data

  • needs efficient search and retrieval of research data

  • wants a safe place to save and store information

Value proposition: manage research data faster than a typical mouse/GUI driven app

Appendix C: 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

be able to add a new source

store my research materials

* * *

user

be able to remove sources I do not want

have a clean and updated database

* * *

user

edit an existing source

change a source without deleting it

* * *

user

be able to tag my sources

easily determine what this source is about

* * *

user

be able to search for sources by type, name and tags

easily navigate through my sources

* * *

user

see all the sources that I have stored

have an overview of what I have at the moment

* * *

new user

have access to all the commands available

have a guide in case I forget how to use the application

* *

user

be able to hide sensitive data

maintain the privacy of my research materials

* *

user

my incorrect searches to show me the closest search terms

still find what I want even if I do not know what it is

* *

user

be able to customise the application commands to my own linking

have easy to use aliases for my commands

* *

user

be able to view past commands

easily trace back what I added, removed or modified

* *

user who is prone to making mistakes

be able to undo previous commands

retrieve lost sources if I accidentally deleted them

* *

user

be able to order the sources in the application however I like

put important sources on top or group them together

*

very picky user

be able to customise the word colour in the application

make the application personalized to me

*

user

see some ASCII art representations in the application

have nicer things to look at other than just text

Appendix D: Use Cases

(For all use cases below, the System is the Infinity Machine and the Actor is the User, unless specified otherwise)

Use case: Add a source

MSS

  1. User requests to add a specified source to the database

  2. The Infinity Machine adds the source to the database

  3. User requests to list all sources in the database

  4. The Infinity Machine displays all sources in the database including the newly added one

    Use case ends.

Extensions

  • 1b. The command entered is invalid. Either it is misspelled or does not have the correct arguments and parameters.

    • 1b1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

Use case: Delete a source

MSS

  1. User requests to list all sources

  2. The Infinity Machine shows a list of the sources currently in the database

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

  4. The Infinity Machine deletes that source as per the user’s request

    Use case ends.

Extensions

  • 2a. There are no sources in the list. The database is empty.

    Use case ends.

  • 3a. The given index is invalid. Either it is a negative number or exceeds the current list total.

    • 3a1. The Infinity Machine shows an error message.

      Use case resumes at step 2.

  • 3b. The command entered is invalid. Either it is misspelled or does not have the correct arguments and parameters.

    • 3b1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 2.

Use case: Edit a source

MSS

  1. User requests to list all sources

  2. The Infinity Machine shows a list of the sources currently in the database

  3. User requests to edit a specific source in the list with some parameters

  4. The Infinity Machine modifies that source as per the user’s request

    Use case ends.

Extensions

  • 2a. There are no sources in the list. The database is empty.

    Use case ends.

  • 3a. The given index is invalid. Either it is a negative number or exceeds the current list total.

    • 3a1. The Infinity Machine shows an error message.

      Use case resumes at step 2.

  • 3b. The command entered is invalid. Either it is misspelled or does not have the correct arguments and parameters.

    • 3b1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 2.

Use case: Search for a source

MSS

  1. User requests to search for a source with specific keywords and values of CLI prefixes.

  2. The Infinity Machine shows a list of the sources currently in the database that match the user’s keywords

    Use case ends.

Extensions

  • 1a. The command entered is invalid. Either it is misspelled or does not have any keywords.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

  • 2a. There are no sources in the list. The database is empty.

    Use case ends with nothing being displayed.

  • 2b. There are no sources in the list that match the user’s keywords.

    Use case ends with nothing being displayed.

Use case: List the sources in the database

MSS

  1. User requests to list all sources, or sources of specific indices

  2. The Infinity Machine shows a list of the sources currently in the database, as per the index entered by the user [optionally].

    Use case ends.

Extensions

  • 1a. The command entered is invalid. The command was misspelled.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

  • 2a. There are no sources in the list. The database is empty.

    Use case ends with nothing being displayed.

Use case: Reorder sources in the database

MSS

  1. User requests to list all sources

  2. The Infinity Machine shows a list of the sources currently in the database

  3. User requests to move a specific source in the list to another position either further up or below

  4. The Infinity Machine moves that source to the targeted location as per the user’s request

    Use case ends.

Extensions

  • 2a. There are no sources in the list. The database is empty.

    Use case ends.

  • 3a. The given source index is invalid. Either it is a negative number or exceeds the current list total.

    • 3a1. The Infinity Machine shows an error message.

      Use case resumes at step 2.

  • 3b. The given location index is invalid. Either it is a negative number or exceeds the current list total.

    • 3b1. The Infinity Machine shows an error message.

      Use case resumes at step 2.

  • 3c. The command entered is invalid. Either it is misspelled or does not have the correct arguments and parameters.

    • 3c1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 2.

  • 3d. The source index and the location index are the same.

    • 3d1. The Infinity Machine performs the move but nothing changes.

      Use case ends with no change to the database.

Use case: Generate bibliography entry of a source

MSS

  1. User requests to list all sources

  2. The Infinity Machine shows a list of the sources currently in the database

  3. User requests to generate a bibliography entry of a specific source in the list

  4. The Infinity Machine generates a bibliography entry of that source as per the user’s request in a predefined citation standard

    Use case ends.

Extensions

  • 2a. There are no sources in the list. The database is empty.

    Use case ends.

  • 3a. The given index is invalid. Either it is a negative number or exceeds the current list total.

    • 3a1. The Infinity Machine shows an error message.

      Use case resumes at step 2.

  • 3b. The command entered is invalid. Either it is misspelled or does not have the correct arguments and parameters.

    • 3b1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 2.

Use case: List the history of entered commands

MSS

  1. User requests to list all previously entered commands

  2. The Infinity Machine shows a list of the commands entered previously

    Use case ends.

Extensions

  • 1a. The command entered is invalid. The command was misspelled.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

  • 2a. There are no previous commands.

    Use case ends with nothing being displayed.

Use case: Creating an alias of a command

MSS

  1. User requests to create an alias of a command

  2. The Infinity Machine creates and assigns the user defined alias to the command

  3. User executes that command with the alias

  4. The Infinity Machine executes the user requested command as though the original command was entered

    Use case ends.

Extensions

  • 1a. The command entered is invalid. Either the command was misspelled or the parameters entered were incorrect.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

  • 1b. The alias the user is assigning already exists.

    • 1b1. The Infinity Machine will override the original aliased command with the new command entered.

      Use case ends.

  • 1b. The command the user is attempting to alias is already an alias.

    • 1b1. The Infinity Machine shows an error message.

      Use case resumes at step 0.

  • 3a. The alias creation was not successful.

    • 3a1. The Infinity Machine shows an error message since the command is invalid.

      Use case ends.

Use case: Removing an alias of a command

MSS

  1. User requests to remove an alias of a command

  2. The Infinity Machine removes the alias to the command

  3. User executes that command with the now removed alias

  4. The Infinity Machine will not execute the command since it is no longer an alias and instead displays an error

    Use case ends.

Extensions

  • 1a. The command entered is invalid. Either the command was misspelled or the parameters entered were incorrect.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

  • 1b. The alias the user is attempting to remove does not exist.

    • 1b1. The Infinity Machine will not perform any action.

      Use case resumes at step 2 with no change.

  • 3a. The alias was not successful removed.

    • 3a1. The Infinity Machine will execute the command when the alias is used.

      Use case ends.

Use case: List all the aliases currently active

MSS

  1. User requests to list all active aliases

  2. The Infinity Machine shows a list of the active aliases for the various commands

    Use case ends.

Extensions

  • 1a. The command entered is invalid. The command was misspelled.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

  • 2a. There are no aliases in the application.

    Use case ends with nothing being displayed.

Use case: Count the total number of sources in the database

MSS

  1. User requests to count how many sources there are in the database

  2. The Infinity Machine shows the number of sources in the database

    Use case ends.

Extensions

  • 1a. The command entered is invalid. The command was misspelled.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

Use case: Undo a previous command

MSS

  1. User requests to undo a previous command

  2. The Infinity Machine undoes the previous command and sets the database to its state before the command

    Use case ends.

Extensions

  • 1a. The command entered is invalid. The command was misspelled.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

  • 1b. There are no previous commands.

    Use case ends with no changes to the database.

  • 1c. All previous commands are not "undoable" (they do not modify the sources or are valid redo commands)

    • 1c1. The Infinity Machine will not undo anything and the sources will be unchanged.

      Use case ends with no changes to the database.

Use case: Redo a previous command

MSS

  1. User requests to redo a previous command

  2. The Infinity Machine redoes the previous command and sets the database to its state after the command

    Use case ends.

Extensions

  • 1a. The command entered is invalid. The command was misspelled.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

  • 1b. There are no previous commands.

    Use case ends with no changes to the database.

  • 1c. All previous commands are not "redoable" (they do not modify the sources or are valid undo commands)

    • 1c1. The Infinity Machine will not undo anything and the sources will be unchanged.

      Use case ends with no changes to the database.

Use case: Enable panic mode

MSS

  1. User requests to enable panic mode

  2. The Infinity Machine goes into panic mode and hides all sources

  3. User requests to list all sources

  4. The Infinity Machine will display nothing since it is in panic mode

    Use case ends.

Extensions

  • 1a. The command entered is invalid. The command is misspelled.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

  • 1b. The system is already in panic mode.

    • 1b1. The Infinity Machine does not perform any action since it is already in panic mode.

      Use case continues at step 3.

  • 3a. The earlier enable panic command is invalid.

    • 3a1. The Infinity Machine displays all sources since it did not enter panic mode.

      Use case ends.

Use case: Disable panic mode

MSS

  1. User requests to disable panic mode

  2. The Infinity Machine goes into normal mode and reveals all sources

  3. User requests to list all sources

  4. The Infinity Machine will display all sources since it is no longer in panic mode

    Use case ends.

Extensions

  • 1a. The command entered is invalid. The command is misspelled.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

  • 1b. The system is already in normal mode.

    • 1b1. The Infinity Machine does not perform any action since it is already in panic mode.

      Use case continues at step 3.

  • 3a. The earlier enable panic command is invalid.

    • 3a1. The Infinity Machine displays nothing since it is still in panic mode.

      Use case ends.

  • 3b. There are no sources in the list. The database is empty.

    • 3b1. The Infinity Machine displays nothing since there are no sources.

      Use case ends.

Use case: Clear all sources in the database

MSS

  1. User requests to clear all sources in the database

  2. The Infinity Machine clears all the sources in the database making it empty

  3. User requests to list all sources

  4. The Infinity Machine will display nothing since all the sources have been removed

    Use case ends.

Extensions

  • 1a. The command entered is invalid. The command was misspelled.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

  • 3a. The earlier clear command is invalid.

    • 3a1. The Infinity Machine displays all sources it has since it did not lose any sources.

      Use case ends.

Use case: Exit application

MSS

  1. User requests exit application

  2. The infinity machine saves and shuts down

    Use case ends.

Extensions

  • 1a. The command entered is invalid. The command was misspelled.

    • 1a1. The Infinity Machine shows an error message together with a help text to guide the user along.

      Use case resumes at step 0.

Appendix E: 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 sources without a noticeable sluggishness in performance for typical usage.

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

Appendix F: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Private contact detail

A contact detail that is not meant to be shared with others

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

G.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 sources. 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.

  3. Shutdown:

    1. Execute command exit on the command line (or)

    2. Click on dropdown menu 'file' and press 'exit' button
      Expected: The GUI should disappear.

G.2. Deleting a source

  1. Deleting a source while all sources are listed

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

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

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

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

G.3. Searching a source

  1. Searches through the entire database of sources

    1. Prerequisites: Pass values with the appropriate CLI tags to search the sources against

    2. Test case: search i/Algorithm
      Expected: the source(s) with the word 'algorithm' (case insensitive and minor typo accounted for) in their entire title string will be showed, with all its details.

    3. Test case: search i/AlGo i/dataa t/CS
      Expected: the source(s) with their title containing both the words 'algo', 'data' and tags contain 'cs' will be displayed with all their details.

    4. Test case: search i/
      Expected: list all sources as title can be anything [does not throw an error].

    5. Test case: search y/web y/
      Expected: the source(s) with their types containing the word 'web' will be displayed with all their details. Here, the empty tag implies the type can be anything, thus after conjunction, the 'web' constraint dominates the result.

G.4. Listing sources

  1. Lists sources based on the indices passed by the user

    1. Prerequisites: Lists all sources when no argument passed, list top N sources when one positive integer passed, lists last N sources when one negative integer passed, lists sources from N to M (inclusive) when two positive arguments N and M are passed with N not greater than M.

    2. Test case: list Expected: lists all sources with their details

    3. Test case: list 5 Expected: lists top 5 sources with their details

    4. Test case: list -4 Expected: lists last 4 sources with their details

    5. Test case: list 3 5 Expected: lists sources 3, 4 and 5 with their details

    6. Test case: list 5 3 Expected: error because range is invalid

    7. Test case: list 0 Expected: error because all indices must be positive