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:
-
Documentation (Developer Guide):
-
Documentation (User Guide):
-
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(ignoreslist-tand reversesclear-s) -
clear-s
delete-g 1
undo(reversesdelete-g 1)
undo(reversesclear-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(ignoreslist-tand reversesclear-s)
redo(repeatsclear-s) -
clear-s
delete-g 1
undo(reversesdelete-g 1)
undo(reversesclear-s)
redo(repeatsclear-s)
redo(repeatsdelete-g 1) -
clear-s
undo(reversesclear-s)
delete-g 1
redo
Theredofails asdelete-g 1will 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
UserPrefobject that represents the user’s preferences. -
stores three sets of versioned data:
VersionedAddressBook,VersionedInventoryandVersionedTransactionHistory, which inherit features from their non-versioned counterparts -
exposes three unmodifiable lists:
ObservableList<Supplier>,ObservableList<Good>andObservableList<Transaction>to be observed and displayed by the UI. -
does not depend on any of the other three components.
The AddressBook stores a list of Supplier objects, which each:
-
stores details of a supplier:
Name,Phone,Address,EmailandOffer. -
can have variable number of
Offerobjects, representing an offer to sell a specific good at a specific price. -
links to a
GoodNameand aPricevia each of itsOfferobjects
The Inventory stores a list of Good objects, which each stores details of a good:
-
its name
GoodName, -
two quantities represented by two
GoodQuantityobjects, one indicating the current quantity and the other the minimum threshold quantity
The TransactionHistory stores a list of Transaction objects. Each Transaction stores common details of a transaction:
-
TransactionIdfor unique identification, -
GoodNamefor the transaction good, and -
GoodQuantityfor the transaction quantity.
A Transaction can be either SellTransaction or BuyTransaction:
-
SellTransactionhas aPriceto indicate the price at which the goods is sold. -
BuyTransactionhas aSupplierand aPriceto indicate the supplier and the price the goods is bought at respectively.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
-
User enters the undo command through the command line.
-
InventoryManager moves to the state before the latest modifying command e.g. add supplier.
-
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
-
User enters the redo command through the command line.
-
InventoryManager moves to the state before the latest undo command.
-
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.
-