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-t
and 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-t
and 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
Theredo
fails asdelete-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
andVersionedTransactionHistory
, 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
,Email
andOffer
. -
can have variable number of
Offer
objects, representing an offer to sell a specific good at a specific price. -
links to a
GoodName
and aPrice
via each of itsOffer
objects
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
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 aPrice
to indicate the price at which the goods is sold. -
BuyTransaction
has aSupplier
and aPrice
to 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.
-