Overview
Given the task to augment an address book desktop application for our software engineering project, my team and I decided to transform it into Xpire, an expiry date tracker. Xpire targets students living on campus, allowing them to track, identify and replenish any items, including but not limited to, perishable items like food, makeup and medicine, that have either already expired, are fully depleted, or expiring soon.
Notations and formatting used
This symbol is often followed by essential information that you can take note of. |
sort
:
A word styled in a different font (otherwise known as a markup) indicates a
command that can be inputted into the command line and executed by the application.
This notation can also represent a component, class or object in the architecture.
Summary of contributions
This section shows a summary of my coding, documentation and other helpful contributions to the team project. |
-
Major enhancement: the ability to view different lists and execute only commands permissible for the list specified
-
What it does: allows the user to view the list of items to be replenished from the main tracking list of items and vice versa. In the replenish list, certain commands are intentionally prohibited.
-
Justification: This feature improves the product significantly as a user can view and search for his or her items that have run out and replenish them as soon as possible. Items in the replenish list are also differentiated from the items in the main tracking list, by limiting the commands available. This is founded on the basis that certain commands such as sorting by date would be rendered irrelevant as items to be replenished do not have an expiry date or quantity unlike those in the main list.
-
Highlights: This enhancement works with existing as well as future commands. An in-depth analysis of design alternatives was necessary to avoid making major changes to existing commands and littering of duplicate code chunks. The implementation was also challenging because it requires a good command of generics to ensure that existing commands would work well with both items in the main tracking list and items to be replenished, without making drastic changes to the code base.
-
-
Major enhancement: the ability to recommend similar words for correction if any when a user makes mistakes in his or her input
-
What it does: recommends closely-related words detected in comparison to the user input
-
Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. With spelling corrections, they can now easily identify their input mistakes and correct them with ease.
-
Highlights: This enhancement works with existing as well as future commands. An in-depth analysis of design alternatives was necessary to avoid major lags in the performance of existing commands.
-
Credits: [Damerau-Levenshten algorithm]
-
-
Minor enhancement: added the ability to store both the main list and replenish list in one single json object. This allows the user to read and write to the json file from the command-line interface for both lists.
-
Minor enhancement: made certain commands such as sort and search stackable. This has great utility when there are lots of items and one search is insufficient to sieve out an item. This allows users to narrow down to items with greater ease, by sorting or searching the results of previous searches.
-
Minor enhancement: added the ability to auto-sort as well as manually sort items in both lists by their name or date, allowing the user to add items and have them arranged in an organised fashion.
-
Minor enhancement: added expiry date and quantity fields to item, and disallowed adding of items with expiry dates before today.
-
Code contributed: [Reposense]
-
Other contributions:
-
Team task: Refactor Person to Item (Pull request #3)
-
Team task: Set up and maintain the issue tracker, assign issues to team members for each milestone, integrate the team repository with Github’s project board
-
-
Enhancements to existing features:
-
Add functionality to view different lists, edit FXML files, allow storage of both lists in a single json object (Pull request #122)
-
Create new parser and integrate existing commands with the replenish list(Pull request #198)
-
Suggest closely related words when command executed displays no results (Pull requests #92, #121)
-
Wrote Sort Command, and relevant tests for its dependencies (Pull requests #7, #16, #82)
-
Render commands stackable (Pull requests #71)
-
Disallow adding of items with expiry dates before today (Pull request #53)
-
Contributions to the User Guide
Given below are some snippets of the sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users. For more of my personal work, please refer to sections 4.1.1 (Help), 4.2.5 (Sort) and 6.1 (Table Summary) of the User Guide. |
Viewing all items : view
Viewing all items in another list
To toggle between the main list and the list of items to be replenished, you
can use the command view|<list name>
. This shows you all the items in the list automatically sorted by their name
(lexicographical order) then date (chronological order).
Examples:
Let’s say that you have completely depleted some items, namely Cherry
and Coco Crunch
in the main tracking list, and you wish to view these items
that have been automatically shifted to the replenish list. You can do so by typing view|replenish
in the command box
to change your current list view to that shown in the figure below.
Perhaps after viewing all your items in the replenish list, you wish to return to the main tracking list to add some items
that you have recently purchased. You can easily return to the main list, by typing view|main
in the command box.
This is also depicted in the figure below.
Viewing all items in the current list
To view all the items in the current list, you can simply enter the command view
.
If you have not previously toggled the list view, this command will display the main tracking list by default. |
Example:
Let’s say that you have been searching for an item in the main tracking list, and you are shown the item found.
To return back to the main list that displays all the items tracked, you can simply enter the command view
.
Contributions to the Developer Guide
Given below are some snippets of the 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. For more of my personal work and the omitted details, please refer to sections 3.2 (Auto-sorting of items by name then date), 3.8 (Spelling correction with alternative recommendations), Appendix D, G.2, and G.5 to G.9 of the Developer Guide. |
Commands in the replenish list
Implementation
As shown in a snippet of the Logic
class diagram below, both XpireParser
and ReplenishParser
implement the interface Parser
.
In particular, ReplenishParser
is the one that parses and handles the commands in the replenish list.
Certain commands such as sorting by date, or deleting
of item quantities are not permitted by ReplenishParser , as items in the replenish list do not have expiry dates or quantities.
|
The activity diagram below follows the general path of a command executed in either the main tracking list Xpire
, or the
replenish list.
In the event that ReplenishParser
is selected, it will prevent any invalid or prohibited commands and also check for spelling mistakes in the user input.
This will be further explained to you in a later section.
Design Considerations
When designing the replenish list, I had to make decisions on how best to parse and execute commands in an efficient manner that would minimise code repetition and delay in runtime. The following is a brief summary of my analysis and decisions.
Aspect: How commands are executed in the list
-
Alternative 1 (current choice): Create two separate parsers, one for the main tracking list and another for the replenish list.
-
Pros: This allows us to reuse existing commands that are currently functional for the main tracking list in the replenish list as well, without extensive repetition of code.
-
Cons: This would require us to check which parser is to be used every time a command is executed.
-
-
Alternative 2: Create two versions of each command, one for each list.
-
Pros: This allows us to greatly customise the command for each list.
-
Cons: This however would lead to unnecessary repetition of code across the code base.
-
Auto-sorting of items by name then date
Implementation
As mentioned previously, items in both lists are automatically sorted by their name then date.
This auto-sorting mechanism is facilitated by SortedUniqueXpireItemList
and SortedUniqueReplenishItemList
that both implement SortedUniqueItemList
, in a relationship summarised in the class diagram below.
In both SortedUniqueXpireItemList
and SortedUniqueReplenishItemList
, items are stored in a SortedList<Item>
and
subsequently sorted based on the comparator defined. SortedUniqueXpireItemList
supports a new function, SortedUniqueItemList#setMethodOfSorting()
, that specifies the
MethodOfSorting
and comparator to be used for the list.
The following sequence diagrams break down the intricacies in the view operation that works to display the sorted items in each list:
Parsers are omitted from the diagram above to place greater emphasis on the parser selection process and sorting mechanism. |
The figure above shows a view|replenish
command executed to change the current view from that of the main tracking list Xpire
to the replenish list, while the figure below initialises this process.
LogicManager
creates and allocates a parser to parse commands entered by the user each time. It does so by first identifying the current view
displayed. In this example, the current view is found to be XPIRE
, and thus XpireParser
is selected. Following that,
new objects ViewCommandParser
and ViewCommand
are created and returned to LogicManager
to be used in the execution of the view|replenish
command.
The figure below pictures the process of retrieving the internal sorted list of items in ReplenishList
.
As items in the replenish list lack expiry dates, the command to sort by date is rendered irrelevant and thereby disallowed entirely in the replenish list.
Instead, items are automatically sorted by their names. Therefore, in the diagram above, a nameComparator
is always returned by default.
The list returned is the sortedInternalList wrapped as an unmodifiable list. |
this.internalUnmodifiableList = FXCollections.unmodifiableList(this.sortedInternalList);
Every time view is called, the current method of sorting specified is retrieved. If it has not been explicitly specified,
the default method of sorting (by name) is then retrieved. |
Design Considerations
In the process of actualising this feature, I contemplated on when items should be automatically sorted by their names and displayed. I also tried and tested varied options to derive an optimal data structure to store the sorted items. The following is a brief summary of my analysis and decisions.
Aspect: When items auto-sorted by their names are displayed
-
Alternative 1 (current choice): Maintain the current method of sorting unless a sort command is executed.
-
Pros: Does not reset the method of sorting back to name by default with the addition of every item.
-
Cons: The user might not be able to find items recently added.
-
-
Alternative 2: Re-sort the list of items by their names with the addition of every item.
-
Pros: Allows the user to find any added item with ease as items are sorted by their name in lexicographical order.
-
Cons: Resets the method of sorting back to name by default every time an item is added.
-
Aspect: Data structure to store the auto-sorted items
-
Alternative 1 (current choice):
SortedList<Item>
.-
Pros: Smooth integration with the internal ObservableList. Comparator can also be easily changed when necessary.
-
Cons: Sorted List can only be viewed when
asUnmodifiableObservableList()
inSortedUniqueItemList
is called.
-
-
Alternative 2:
TreeSet<Item>
.-
Pros: Disallows addition of identical items to the set.
-
Cons: May not be as compatible with the internalList which is of type ObservableList.
-
Spelling correction with alternative recommendations
Implementation
Invalid commands are checked for spelling mistakes.
The spelling correction mechanism is based primarily on the Damerau–Levenshtein distance algorithm, which computes the edit distance between two strings.
This distance is based on the number of substitutions, deletions, insertions or transpositions of characters, needed to convert the source string into the target string.
Relevant functions supporting this operation are implemented in StringUtil
.
Only keywords with edit distance of less than 2 are recommended, to filter away less similar word recommendations. |
The recommendations will be made solely based on the list of items previously displayed rather than all items currently in the list. |
As shown in the diagram below, Banana
was not recommended even though it exists in the original list. This is because it had been filtered from the previous list prior to when the second search command was executed.
On the other hand, if green
was misspelled as gren
, the algorithm will be able to identify green
as the closest match, as Green Apple
is present in the previous list.
The figure below presents what happens when a user executes a command with invalid arguments.
Only search and sort commands support this operation. |
In the example below encapsulated in a sequence diagram, the user has misspelled "date"
as "dat"
in a sort command.
sort|dat
The sequence diagram titled find similar words
below expands on the process omitted above.
"date"
is found to be the most similar word to "dat"
The function findSimilar
in StringUtil
is called upon to return a set containing strings that are most similar to the misspelled argument, "dat"
.
In this process, "dat"
is compared with a set of valid inputs, i.e. both "name"
and "date"
, and the corresponding edit distances are stored.
getSuggestions("dat")
then filters the results and finds "date"
to be the best match.
At last, a ParseException
which contains the recommendation "date"
is then thrown to the user as feedback.
Design Considerations
When tasked to implement this feature, I had to decide on what was the best way to display any form of recommendations to the user. I also evaluated multiple options to derive an optimal data structure to store the recommendations. The following is a brief summary of my analysis and decisions.
Aspect: How recommendations execute
-
Alternative 1 (current choice): Displays recommendations after the user inputs a command that fails to produce results.
-
Pros: Simpler and straightforward implementation.
-
Cons: May be less intuitive to the user as opposed to auto-completed commands.
-
-
Alternative 2: Auto-completion of commands.
-
Pros: Lowers likelihood of spelling mistakes in user input.
-
Cons: We must ensure that the structure of every single command and their variations are taken into consideration.
-
Aspect: Data structure to store the recommendations
-
Alternative 1 (current choice): Use a TreeMap to store entries that comprise a set of recommendations and their corresponding edit distance.
-
Pros: Entries are automatically sorted by their edit distance, thus words with a smaller edit distance will be recommended first. Duplicate entries are also prohibited.
-
Cons: May have performance issues in terms of memory usage.
-
-
Alternative 2: Store all possible recommendations in a long list.
-
Pros: Simpler implementation.
-
Cons: Not closely related words may also be recommended to the user.
-