PROJECT: InventoryManager

Overview

InventoryManager is a desktop inventory manager application used for tracking quantity of goods, suppliers and transaction history. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.

Summary of contributions

  • Major enhancement: added the ability to undo/redo commands

    • What it does: allows the user to undo a command, and redo the command after undoing it

    • Justification: With an inventory of fast-moving consumer goods, there will be large amount of traffic of goods and consequently, large number of commands that need to be inputted. This increases the probability of error in the user, which, if irreversible, may discourage them from using the application.

    • Highlights: This enhancement allows easy compatibility with future commands as the implementation is independent of the details of the execution of command. Memory is used instead of storage to hold the previous states to avoid unwanted cluttering by temporary files. Significant refactoring was done to avoid much of the code duplication due to the presence of multiple databases. Interfaces are also extracted for possible future alternative implementations of versioning.

    • Credits: To AddressBook Level 4 by SE Initiative for describing the general implementation idea of undo and redo

  • Minor enhancement: added a basic class to represent goods-price pairs

  • Code contributed: [Code]

  • Other contributions:

    • Enhancement to existing features:

      • Fixed minor visual defect when application window is made significantly large (#155)

      • Updated the application name and icon (#150)

    • Documentation (Developer Guide):

      • Updated the section on Model for the developer guide (#168, #172)

      • Rewrote the undo/redo feature section for the developer guide according to own implementation (#89, #111, #180)

      • Added the initial version of the developer guide (#14)

    • Documentation (User Guide):

      • Added the user guide for undo and redo features (#116, #183)

      • Added the initial version of the user guide (#23)

    • Project management:

      • Managed releases v1.3 - v1.4 (2 releases) on GitHub

    • Team:

    • Tools:

      • Set up Travis and Appveyor for CI.

      • Set up Coveralls to report coverage changes for every new PR.

      • Set up branch protection rules for the master branch.

Contributions to User Guide

Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users.

Undoing a recently executed command: undo (By Nicholas Cristian Fernando)

Removes changes from a recently executed command. Commands that only affect display e.g. find and list, and undo commands, will be ignored and the next command in line will be undone.
Format: undo

Examples (assuming all other commands are valid):

  • clear-s
    list-t
    undo (ignores list-t and reverses clear-s)

  • clear-s
    delete-g 1
    undo (reverses delete-g 1)
    undo (reverses clear-s)

Redoing a previously undone command: redo

Redoes changes undone by the most recent undo command.
Format: redo

Examples (assuming all other commands are valid):

  • clear-s
    list-t
    undo (ignores list-t and reverses clear-s)
    redo (repeats clear-s)

  • clear-s
    delete-g 1
    undo (reverses delete-g 1)
    undo (reverses clear-s)
    redo (repeats clear-s)
    redo (repeats delete-g 1)

  • clear-s
    undo (reverses clear-s)
    delete-g 1
    redo
    The redo fails as delete-g 1 will remove the undone states.


(By Nicholas Cristian Fernando)

  • Undo: undo

  • Redo: redo

Contributions to Developer Guide

Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project.

Model component (by Nicholas Cristian Fernando)

API : Model.java

The Model,

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

  • stores three sets of versioned data: VersionedAddressBook, VersionedInventory and VersionedTransactionHistory, which inherit features from their non-versioned counterparts

  • exposes three unmodifiable lists: ObservableList<Supplier>, ObservableList<Good> and ObservableList<Transaction> to be observed and displayed by the UI.

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

ModelClassDiagram
Figure 1. Structure of the Model Component

The AddressBook stores a list of Supplier objects, which each:

  • stores details of a supplier: Name, Phone, Address, Email and Offer.

  • can have variable number of Offer objects, representing an offer to sell a specific good at a specific price.

  • links to a GoodName and a Price via each of its Offer objects

SupplierModelClassDiagram
Figure 2. Structure of the AddressBook

The Inventory stores a list of Good objects, which each stores details of a good:

  • its name GoodName,

  • two quantities represented by two GoodQuantity objects, one indicating the current quantity and the other the minimum threshold quantity

GoodModelClassDiagram
Figure 3. Structure of the Inventory

The TransactionHistory stores a list of Transaction objects. Each Transaction stores common details of a transaction:

  • TransactionId for unique identification,

  • GoodName for the transaction good, and

  • GoodQuantity for the transaction quantity.

A Transaction can be either SellTransaction or BuyTransaction:

  • SellTransaction has a Price to indicate the price at which the goods is sold.

  • BuyTransaction has a Supplier and a Price to indicate the supplier and the price the goods is bought at respectively.

TransactionModelClassDiagram
Figure 4. Structure of the TransactionHistory

Undo/Redo feature (by Nicholas Cristian Fernando)

Implementation

The undo/redo mechanism is facilitated by three versioned databases VersionedInventory, VersionedAddressBook and VersionedTransactionHistory for Good, Supplier and Transaction data respectively. These versioned classes extend their non-versioned counterparts. These classes also implement the Versionable interface, which has these methods:

  • Versionable#commit() — Adds the current state to the tracked states.

  • Versionable#undo() — Restores the previous database state.

  • Versionable#redo() — Restores the most recently undone database state.

These operations are exposed in the Model interface, which extends Versionable as well. Each call of these methods will call the respective methods of each of the versioned classes.

The class diagram below shows how the classes are related to each other.

VersionClassDiagram

The three versioned classes use the same logic for versioning, so only VersionedInventory will be mentioned in subsequent examples and diagrams.

The sequence diagram below illustrates the events that occur when a user calls the undo command assuming that there is a state to return to. VersionedAddressBook#undo() and VersionedTransactionHistory#undo() are called as well, but omitted for brevity.

UndoSequenceDiagram

Currently, VersionedInventory uses LinearHistory for versioning, and delegates all Versionable methods to it. LinearHistory can store objects of Inventory class, which has implemented the Copyable interface to allow creation of independent copies for storage. On the other hand, LinearHistory implements the interface Version, which extends from Versionable and has the following additional method:

  • Version#getCurrentState() — Returns the current state of the stored object

The class diagram below shows how the classes are connected such that VersionedInventory is able to use LinearHistory.

LinearHistoryClassDiagram
The lifeline for UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. For simplicity, goods are each represented with strings containing their name and quantity.

Step 1. The user launches the application for the first time. The VersionedInventory will be created with a list of Good objects from storage, while creating a LinearHistory that stores a copy of this state, and also stores another copy in its history. Using copy() method from Copyable ensures currentState and saved0 are independent Inventory objects.

UndoRedoState0

Step 2. The user executes delete-g 3 command to delete the 3rd good in the inventory list. The delete-g command first deletes the 3rd good in the currentState of the LinearHistory, exposed by VersionedInventory. Then, the command calls Model#commit() since it modifies the data. LinearHistory then makes a copy of the modified currentState and stores it in the history, moving the statePointer up.

UndoRedoState1

Step 3. The user executes buy 1 g/apple q/5 to buy 5 apples from the first supplier. Let us assume that the first supplier sells apples. The buy command also calls Model#commit() as it modifies the data, causing LinearHistory to save a copy of the modified currentState.

UndoRedoState2
If a command fails its execution, it will not call Model#commit(), so the currentState will not be saved into the history.

Step 4. The user now decides that buying the apples was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undo(), which will shift the statePointer one step backward, pointing it to the previous saved state saved1, and updates currentState with saved1.

UndoRedoState3
If the currentStatePointer is pointing to the first state saved0, then there is no state to return to. In this case, it will return an error to the user rather than attempting to perform the undo.

The redo command does the opposite — it calls Model#redo(), which shifts the currentStatePointer one step forward, pointing to the previously undone state, and restores the currentState to that state.

If the currentStatePointer is pointing to the latest state, then there are no states to go to. Thus, 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-t. Commands that do not modify the data, such as list-t, will not call Model#commit(). Thus, the history and currentState in LinearHistory remains unchanged.

UndoRedoState4

Step 6. The user executes sell 2 q/1 p/5 to sell 1 of the second goods in the list, banana. This calls Model#commit(). Since there is a branching in history, all states after the state pointed by statePointer will be purged. Many mainstream editing software exhibit this behaviour, which would condition the user to expect this behavior.

UndoRedoState5

The activity diagram below shows the conditions under which Model#commit() is called by a command, and its effects. As shown, only undoable commands that are successfully executed will call Model#commit() and purge the "future" states. This behavior in command execution guards against unwanted states being saved during invalid commands and confusing the user. In addition, the guard against invalid execution at the start helps to keep the currentState free of changes when the command will be invalid. Thus, the correctness of the commit() behavior is tied to the correct command execution protocol.

CommitActivityDiagram

Design Considerations

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

    • Pros: Trivial implementation.

    • Cons: May encounter performance issues due to memory load, especially with three different databases.

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

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

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

Aspect: When to save history
  • Alternative 1 (current choice) : Save all three databases even when only one database is modified.

    • Pros: Easy to implement.

    • Cons: Inefficient memory usage, especially when only one database is being modified in each action.

  • Alternative 2: Save a database only when that database is modified.

    • Pros: Saves memory usage that could be used for performance.

    • Cons: Requires information on which databases are affected by a command, which breaks abstraction on both the versioned databases and commands.

Aspect: How storage of states is implemented
  • Alternative 1 (current choice) : Store states as objects during Java runtime

    • Pros: Simple implementation and automatic cleanup.

    • Cons: Segmentation fault may occur for very long sessions and large databases.

  • Alternative 2: Store states in an external file

    • Pros: Less memory usage, leading to better performance.

    • Cons: File I/O may incur comparable overhead, and abrupt termination of the application may result in temporary files being left behind and cluttering space.


Use case: UC13 - undoing a command

MSS

  1. User enters the undo command through the command line.

  2. InventoryManager moves to the state before the latest modifying command e.g. add supplier.

  3. InventoryManager shows a message indicating success.

    Use case ends.

Extensions

  • 2a. InventoryManager is at the oldest recorded state and thus is unable to move to a previous state.

    • 2a1. InventoryManager informs the user that it is unable to undo from the oldest recorded state.

      Use case ends.

Use case: UC14 - redoing a command

MSS

  1. User enters the redo command through the command line.

  2. InventoryManager moves to the state before the latest undo command.

  3. InventoryManager shows a message indicating success.

    Use case ends.

Extensions

  • 2a. InventoryManager is unable to move to the next state as it is already at the latest state.

    • 2a1. InventoryManager informs the user that it is unable to redo from the latest state.

      Use case ends.