This is my portfolio page primarily for showcasing my contributions to the project, Rolodex.
This is not my full portfolio. To view my full portfolio, visit https://zy-ang.github.io/Portfolio.
- 1. Project: Rolodex
- 1.1. Enhancement Added: Find by close words (fuzzy searching) and find by tags
- 1.2. Enhancement Added: Sort
- 1.3. Enhancement Added: Open existing file and Create new file commands
- 1.4. Enhancement Improved:
java.util.Set
implementation for command abbreviations - 1.5. Enhancement Added: Search Highlighting in HelpWindow
- 1.6. Enhancement Added: Suggesting Commands for Typos
- 1.7. Enhancement Proposed: Markdown/HTML parser
- 1.8. Other contributions
- 2. Project: AutoNameGenerator
- 3. Other projects
1. Project: Rolodex
Rolodex is a desktop contact management application for users who prefer working without a mouse to manage their contacts more efficiently. If you are a businessman with a list of clients to remember, a teacher who wants to organize their student information, or anyone with a need for contact management, Rolodex provides you with a way to organize your important contacts in a fast and productive manner.
The user interacts with the application using a CLI (Command Line Interface), and it has a GUI created with JavaFX.
It is written in Java, and has about 10 kLoC.
Code contributed:
1.1. Enhancement Added: Find by close words (fuzzy searching) and find by tags
1.1.1. For the User
find
, filter
, search
or f
: Locating Persons in Rolodex (Since v1.4)
Finds persons in Rolodex.
Format: find KEYWORD [MORE_KEYWORDS] [SORT_ARGUMENTS]
Press Tab after typing |
Alternatives: find
can be replaced by filter
, search
or f
Keyboard shortcut: Ctrl+F
Examples:
-
find John
orfind jhon
Returnsjohn
andJohn Doe
-
search Betsy Tim John
Returns any person having namesBetsy
,Tim
, orJohn
-
find School
Returns any person having tagSchool
-
find School werk
Returns any person having tagSchool
or tagwerk
Sort arguments do not count as search arguments. |
Examples:
-
find John p/
orfind jhon p/asc
Returnsjohn
andJohn Doe
, sorted by ascending phone number. -
search Betsy Tim John n/ p/desc
Returns any person having namesBetsy
,Tim
, orJohn
, sorted by name then by descending phone if names are equal. -
find School a/desc
Returns any person having tagSchool
, sorted by descending address. -
find School werk e/
Returns any person having tagSchool
or tagwerk
, sorted by email. -
find e/ p/desc
Returns an error (do not count as search arguments).
1.1.2. Why would we need this?
Users may sometimes key commands too quickly or forget the names of their contacts. This enhancement allows them to find contacts by close name words or by tags previously assigned to make finding contacts in the Rolodex a lot easier.
1.1.3. Implementation
Fuzzy Finding Mechanism
The fuzzy finding mechanism is powered by Levenshtein distance, a string heuristic for measuring the difference between two character sequences. It can be informally viewed as the number of hops that are required to get from a string to another, where hops are insertions, deletions and substitution operations.
The activity diagram for the fuzzy finding can be loosely described as seen in the figure below:
-
A user sends a find request
-
Application looks at all contacts currently in the database
-
If the search parameters loosely match any of the name words of the current contact, add it into the list view.
-
Otherwise, if it is an exact match on any of the name words of the current contact, add it into the list view.
-
Otherwise, if it matches any other conditions specified under the find command, add it into the list view.
If a contact’s name word has a shorter length than the specified global minimum, the condition automatically triggers to false and starts searching for exact matches. |
The settings for the loose matching can be found as a static constant of the Person class.
public class Person implements ReadOnlyPerson {
private static final int FIND_NAME_GLOBAL_TOLERANCE = 4;
private static final int FIND_NAME_DISTANCE_TOLERANCE = 2;
...
}
FIND_NAME_GLOBAL_TOLERANCE
is the global minimum distance of the currently examined name word for the loose matching to execute.
FIND_NAME_DISTANCE_TOLERANCE
is the maximum levenshtein distance from the currently examined search parameter
to the currently examined name word in which the contact will be added.
Design Considerations
Alternative 1 (current choice): Use a global minimum for defining when the fuzzy finding should execute.
Pros: Name words have higher minimum hop to length ratio.
Cons: User might need fuzzy finding on name words with character length lesser than the global minimum.
Pros: Easy logic to understand, implement and maintain.
Cons: Rudimentary algorithm.
Alternative 1 (current choice): Use private static constant in a person.
Pros: User does not need to worry about changing the settings or the intricacies of the fuzzy finding.
Cons: User might need stricter or looser limits than the ones defined for them.
Pros: Developers can easily change and manage settings and limits.
Cons: Users have no way of accessing the settings and limits.
Alternative 2: Allow user to set fuzzy find settings in a document file.
Pros: Advanced users can change the settings to suit their needs better.
Cons: Developers need to spend more work maintaining more components including storage.
1.2. Enhancement Added: Sort
1.2.1. For the User
list
, show
, display
or l
: Listing All Persons (Since v1.3)
Shows a list of all persons in Rolodex, sorted by the specified sort order or default sort order.
Format: list [SORT_ARGUMENTS]
Alternatives: list
can be replaced by show
, display
or l
Keyboard shortcut: Ctrl+L
Examples:
-
list
orl
displays all persons by the default sort order. -
l n/desc
displays all persons sorted by descending name. -
list p/ a/desc
orlist p/asc a/desc
displays all persons sorted by ascending phone, then by descending address.
find
, filter
, search
or f
: Locating Persons in Rolodex (Since v1.4)
Finds persons in Rolodex.
Format: find KEYWORD [MORE_KEYWORDS] [SORT_ARGUMENTS]
Press Tab after typing |
Alternatives: find
can be replaced by filter
, search
or f
Keyboard shortcut: Ctrl+F
Examples:
-
find John
orfind jhon
Returnsjohn
andJohn Doe
-
search Betsy Tim John
Returns any person having namesBetsy
,Tim
, orJohn
-
find School
Returns any person having tagSchool
-
find School werk
Returns any person having tagSchool
or tagwerk
Sort arguments do not count as search arguments. |
Examples:
-
find John p/
orfind jhon p/asc
Returnsjohn
andJohn Doe
, sorted by ascending phone number. -
search Betsy Tim John n/ p/desc
Returns any person having namesBetsy
,Tim
, orJohn
, sorted by name then by descending phone if names are equal. -
find School a/desc
Returns any person having tagSchool
, sorted by descending address. -
find School werk e/
Returns any person having tagSchool
or tagwerk
, sorted by email. -
find e/ p/desc
Returns an error (do not count as search arguments).
1.2.2. Why would we need this?
Users may sometimes have too many contacts in a listing.
This enhancement allows them to find contacts quickly by sorting the displayed list of persons.
Users can operate on a sorted list for longer displayed lists via list
or find
command.
1.2.3. Implementation
Sorting Mechanism
The sorting mechanism is integrated into the list
and find
commands which resides in the application’s logic
component.
It supports sorting in ascending or descending order of most fields of a person (e.g. Name, Phone).
The sort commands update the state of the application at the model level, which resides in ModelManager
,
via the method updateSortComparator
.
The activity diagram for a sort can be loosely described as seen in the figure below:
Sort arguments are case-sensitive. |
The case for a list command:
-
A user executes a
list
command with sort arguments. -
The application checks for invalid sort arguments.
-
The application model creates a comparator using the sort arguments, in order.
-
The application displays the results in the specified sort order.
The case for a find command:
-
A user executes a
find
command. -
The application separates the command arguments into find data arguments and sort arguments.
-
The application executes a search on the find arguments.
-
The application model creates a comparator using the sort arguments, in order.
-
The application displays the filtered results in the specified sort order.
Find cannot work on name words matching sort arguments. |
The list command’s execute()
implementation before sorting implementation:
@Override
public CommandResult execute() {
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
return new CommandResult(MESSAGE_SUCCESS);
}
The list command’s execute()
implementation after sorting implementation:
@Override
public CommandResult execute() {
model.updateSortComparator(sortArguments);
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
return new CommandResult(MESSAGE_SUCCESS);
}
Notice that model.updateSortComparator(sortArguments)
is added to the command logic. The update will give the model a
comparator before executing a sort and filter which is handled by the event consumer.
Design Considerations
Alternative 1 (current choice): Integrate sort into existing commands.
Pros: User can quickly sort and list or find the data as specified without executing another separate command.
Cons: User might need to use the sort arguments for other purposes (e.g. find n/
does not return anyone matching n/
).
Pros: Adds onto existing functionality and reduces clutter and unnecessary creation of a new command.
Cons: Bad SLAP on a user level.
Alternative 2: Create a new sort
command.
Pros: User can decide when to sort the current list view
Cons: User needs an additional step before sorting a list.
add
commandsAlternative 1 (current choice): Reset the current displayed list on an add
command.
Pros: The displayed list always complies with the model comparator.
Cons: User might need to continue operating on the previous listing.
Alternative 2: Append added person to the current list view.
Pros: Users can immediately edit
the person using the last listing index.
Cons: The displayed listing will not comply with the model comparator.
1.3. Enhancement Added: Open existing file and Create new file commands
1.3.1. For the User
open
, o
, cd
, ls
or <
: Opening an Existing Rolodex Storage File (Since v1.3)
Reloads the application with data from a rolodex at the specified filepath.
Format: open FILEPATH
or o FILEPATH
Keyboard Shortcut: Ctrl+O
When the application is opened, Rolodex will load the last accessed Rolodex directory. |
After opening the Rolodex, verify that you have correctly opened the Rolodex at the correct directory by checking the status bar of your application:
Opening a file without a |
The |
Examples:
-
open C:/Users/Rolodex/downloads/myOwn.rldx
o C:/Users/Rolodex/downloads/myOwn.rldx
o C:\Users\Rolodex\downloads\myOwn.rldx
Loads the application with the data at the directoryC:/Users/Rolodex/downloads/myOwn.rldx
. -
open data/default.rldx
open data\default.rldx
Loads the application with the data indefault.rldx
in the folderdata
.
new
, n
, touch
or >
: Creating a New Rolodex Storage File (Since v1.3)
Creates a new Rolodex at the specified filepath and reloads the application with new sample data.
Format: new FILEPATH
or n FILEPATH
Keyboard Shortcut: Ctrl+N
After creating a new Rolodex, verify that you have correctly created the Rolodex at the correct directory by checking the status bar of your application:
The |
Examples:
-
new C:/Users/Rolodex/downloads/myOwn.rldx
n C:/Users/Rolodex/downloads/myOwn.rldx
n C:\Users\Rolodex\downloads\myOwn.rldx
Creates a new Rolodex filemyOwn.rldx
at the directoryC:\Users\Rolodex\downloads
and reloads the application with sample data created for the new Rolodex. -
new data/default.rldx
new data\default.rldx
Creates a new Rolodex filedefault.rldx
at the relative directorydata
and reloads the application with sample data created for the new Rolodex.
1.3.2. Why would we need this?
Users may want to work on a different group of data for different purposes. Users with too many contacts can also create a separate database for dividing up the contacts into smaller, more manageable sizes.
1.3.3. Implementation
Open File and New File Mechanism
The open new file and creating new file mechanism are handled by two new types of requests under the EventsCenter
,
OpenRolodexRequestEvent
and RolodexChangedDirectoryEvent
. Upon receiving a new command, the MainApp
attempts to
reload the instance model
and logic
but not the UI
.
The high level sequence diagram for a typical open
request can be seen below:
-
A user requests to open
data/default.rldx
. -
The command is handled the same way as other commands, by the parser in logic, then via the
OpenRolodexCommand
object itself, under theLogic
component of the application. -
Upon a successful parsing of the command and execution, the command would raise a new
OpenRolodexRequestEvent
to be handled by theEventsCenter
. -
The
EventsCenter
posts a newOpenRolodexRequestEvent
back to theMainApp
where the new Rolodex path would be loaded with the successfully read data from the file path specified:-
The preferences file of the user specified under
config.json
would now point to the latest active directory. -
The
storage
,model
andlogic
instances of theMainApp
would be now loaded with data from the new active directory.
-
The behaviour of the save
operation remains unchanged, to be handled under the RolodexChangedEvent
.
The Rolodex only writes to the active database upon an editor command (e.g. add
, edit
).
The UI
however, is updated as seen in the following sequence diagram:
-
Upon successful handling of a
OpenRolodexRequestEvent
,MainApp
raises a newRolodexChangedDirectoryEvent
. -
The event is then reposted by the
EventsCenter
. -
The
MainWindow
of theUI
instance consumes the event and updates the status bar with the new active filepath.
The New
command only differs from the sequence of the open new Rolodex by being executed when no valid file exists at
the specified active directory:
@Override
public CommandResult execute() throws CommandException {
if (new File(filePath).exists()) {
throw new CommandException(String.format(MESSAGE_ALREADY_EXISTS, filePath));
} else {
EventsCenter.getInstance().post(new OpenRolodexRequestEvent(filePath));
return new CommandResult(String.format(MESSAGE_CREATING, filePath));
}
}
In contrast, the Open
command is a reverse of the above:
@Override
public CommandResult execute() throws CommandException {
if (new File(filePath).exists()) {
EventsCenter.getInstance().post(new OpenRolodexRequestEvent(filePath));
return new CommandResult(String.format(MESSAGE_OPENING, filePath));
} else {
throw new CommandException(String.format(MESSAGE_NOT_EXIST, filePath));
}
}
The XmlRolodexStorage.java now only saves upon recognizing that the current file has a .rldx extension, which is a
subset of xml for the Rolodex application, as seen in the snippet below:
|
public void saveRolodex(ReadOnlyRolodex rolodex, String filePath) throws IOException {
...
if (!isValidRolodexStorageExtension(filePath)) {
throw new InvalidExtensionException(
String.format(MESSAGE_INVALID_EXTENSION_FORMAT, ROLODEX_FILE_EXTENSION));
}
...
}
Design Considerations
Alternative 1 (current choice): Reload the current MainApp
with the new database.
Pros: The application can be quickly reloaded onto the active window.
Cons: User might need to switch back and forth between the Rolodex directories.
Alternative 2: Reload MainApp
with a new application instance.
Constraint: JavaFX only allows launch(args) to be launched once per execution time on a code level.
Another approach would be to reload the application with a loaded and built .jar.
Pros: Easier logic to understand for new developers.
Cons: Building and debugging with a second .jar will be confusing and problematic to any development workflow.
Alternative 3: Open a new window with the loaded data.
Pros: The user can continue to operate on the previous Rolodex application.
Cons: The developer would have to spend time configuring different EventsCenter
posts for the different application windows.
.rldx
extensionsAlternative 1 (current choice): Write to the active directory only if it has a .rldx
extension.
Pros: The application control becomes stricter, preventing users from writing to a file with say a .png
extension.
Cons: Extensions become forced even for valid databases with .xml
file types.
Alternative 2: Allow writable to all file extensions.
Pros: Users can have more freedom in choice of file naming.
Cons: If the user accidentally writes to a file that is not a valid database, the user would corrupt the file data.
open
and new
commandsAlternative 1 (current choice): Two commands, one for opening existing files and one for creating new files.
Pros: The user can easily be notified if a file exists or if it has been moved.
Cons: The user would have to take note of two commands instead of one.
1.4. Enhancement Improved: java.util.Set
implementation for command abbreviations
1.4.1. Why would we need this?
Using constants to manually check if a command word belongs to a particular command is extremely tedious for both
developers and users. In addition, using constants forces the program to have to check through all possible command
abbreviations belonging to a command word in the worst case. A java HashSet
takes care of these problems by improving
access time and improves manageability of the codebase.
1.4.2. Implementation
Abbreviation Mechanism
The ability for users to enter a variety of different words for a same command is implemented using java.util.set
.
An example as implemented in the open command:
import java.util.HashSet;
import java.util.Set;
public class OpenRolodexCommand extends Command {
public static final String COMMAND_WORD = "open";
public static final Set<String> COMMAND_WORD_ABBREVIATIONS =
new HashSet<>(Arrays.asList(COMMAND_WORD, "o", "cd", "ls", "<"));
...
}
COMMAND_WORD_ABBREVIATIONS
is implemented using a java.util.HashSet
which offers reasonably fast access times.
The user would be able to use a wide variety of different commands such as o
, cd
like on Windows cmd, ls
on
UNIX-like systems or the loose <
such as when you run a java program < input > output
on a wide variety of systems.
It can easily be seen that using a set implementation, the developer would have an extremely easy time adding/ deleting
new abbreviations. A set implementation also allows an easy way of testing if there are conflicting abbreviations, by
simply permuting the COMMAND_WORD_ABBREVIATIONS
property of all possible commands and checking if all permutations are
pairwise disjoint:
@Test
public void parseAllCommandAbbreviationsAreDisjoint() {
ArrayList<Pair<Set<String>, Set<String>>> commandAbbreviationPermutations =
generateCommandAbbreviationPermutations(POSSIBLE_COMMAND_ABBREVIATIONS);
for (Pair<Set<String>, Set<String>> commandAbbreviationPair : commandAbbreviationPermutations) {
assertTrue(Collections.disjoint(commandAbbreviationPair.getKey(), commandAbbreviationPair.getValue()));
}
}
Design Considerations
Alternative 1 (current choice): A Set of strings
Pros: Constant access time per command level. Improves performance and reliability significantly.
Pros: Ease of testing for conflicting abbreviations, particularly where developers forget if they used a
same abbreviation for two different commands (e.g. assigning h
to both the history
and help
commands).
Cons: If you have a better suggestion than the above implementation, feel free to create a new issue for discussion
on github.
Alternative 2 (previous choice): Manually named string Constants
Pros: Forces developer to be more prudent in checking through the conflicting abbreviations.
Cons: Developers would have to manually check through all commands to ensure the accuracy of the abbreviations,
which is time-consuming and makes way for human errors.
Cons: A switch-case for all possible commands abbreviations would perform significantly slower than direct memory
access on a java HashSet
, in the worst case.
1.5. Enhancement Added: Search Highlighting in HelpWindow
1.5.1. Why would we need this?
JavaFx’s native WebView does not allow the user to easily search a html document, putting it on a subpar standard to modern web browsers. Implementing a search feature in the help window will allow the user to search for terms in the user guide with ease.
1.5.2. Implementation
Help Window Highlighting
Rolodex uses JavaFx version 8.0.111, where WebView does not support search functionality that mimics Ctrl + F capability of modern web browsers. As a workaround, Johann Burkard’s jQuery text highlighting library is being used to implement such a feature.
Users can easily find keywords by using the highlighting feature of the help window, as opposed to manually looking through the help document for information, which can be counterproductive:
The discussion for JavaFX to implement a native search feature within WebView is ongoing and you can find it here.
Removing jQuery implementation
In a future version of JavaFx where such an implementation is no longer necessary, you may revert the changes by reverting pull request #99 on the Rolodex repo on GitHub. You may also choose to manually do this by reverting the following code:
Original HelpWindow.fxml
:
<StackPane fx:id="helpWindowRoot" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<WebView fx:id="browser" />
</StackPane>
HelpWindow.fxml
with makeshift user guide search functionality:
<StackPane fx:id="helpWindowRoot" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<VBox fx:id="layout">
<children>
<TextField fx:id="txtSearch" promptText="Search and press ENTER" />
<HBox fx:id="controls">
<children>
<Button fx:id="btnSearch" mnemonicParsing="false" text="Search">
<HBox.margin>
<Insets right="10.0" />
</HBox.margin>
</Button>
<Button fx:id="btnClear" mnemonicParsing="false" text="Clear" />
</children>
</HBox>
<WebView fx:id="browser" minHeight="-Infinity" minWidth="-Infinity" prefHeight="100000.0" />
</children>
</VBox>
</StackPane>
Original HelpWindow.java
:
@FXML
private WebView browser;
private final Stage dialogStage;
public HelpWindow() {
super(FXML);
Scene scene = new Scene(getRoot());
//Null passed as the parent stage to make it non-modal.
dialogStage = createDialogStage(TITLE, null, scene);
dialogStage.setMaximized(true); //TODO: set a more appropriate initial size
FxViewUtil.setStageIcon(dialogStage, ICON);
String userGuideUrl = getClass().getResource(HELP_FILE_PATH).toString();
browser.getEngine().load(userGuideUrl);
}
HelpWindow.java
with makeshift user guide search functionality:
@FXML
private WebView browser;
@FXML
private TextField txtSearch;
@FXML
private Button btnSearch;
@FXML
private Button btnClear;
@FXML
private HBox controls;
private final Stage dialogStage;
public HelpWindow() {
super(FXML);
txtSearch.setOnAction(event -> {
if (browser.getEngine().getDocument() != null) {
highlight(browser.getEngine(), txtSearch.getText());
}
});
btnSearch.setDefaultButton(true);
btnSearch.setOnAction(actionEvent -> txtSearch.fireEvent(new ActionEvent()));
btnClear.setOnAction(actionEvent -> clearHighlights(browser.getEngine()));
btnClear.setCancelButton(true);
controls.disableProperty().bind(browser.getEngine().getLoadWorker().runningProperty());
txtSearch.disableProperty().bind(browser.getEngine().getLoadWorker().runningProperty());
Scene scene = new Scene(getRoot());
//Null passed as the parent stage to make it non-modal.
dialogStage = createDialogStage(TITLE, null, scene);
dialogStage.setMaximized(true); //TODO: set a more appropriate initial size
FxViewUtil.setStageIcon(dialogStage, ICON);
String userGuideUrl = getClass().getResource(HELP_FILE_PATH).toString();
browser.getEngine().load(userGuideUrl);
}
private void highlight(WebEngine engine, String text) {
engine.executeScript("$('body').removeHighlight().highlight('" + text + "')");
}
private void clearHighlights(WebEngine engine) {
engine.executeScript("$('body').removeHighlight()");
txtSearch.clear();
}
Help.adoc
passthrough imports:
<script type="text/javascript" src="scripts/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="scripts/jquery.highlight-5.js"></script>
asciidoctor.css
.highlight
class:
.highlight{background-color:yellow}
1.6. Enhancement Added: Suggesting Commands for Typos
1.6.1. For the User
y
or yes
or k
or ok
or yea
or yeah
: Confirming a suggested command (Since v1.5)
Confirms a command suggested by Rolodex.
Format: y
or yes
or ok
or yea
or yeah
A command will be suggested by Rolodex if you make a typo in your command. |
Examples:
-
s3
, a typo for the commands 3
will be suggested.y
oryes
ork
orok
oryea
oryeah
executess 3
as normal and Charlotte Oliviero will be selected:
-
rmke3 Likes to swim
will be suggested as the formatted command,rmk 3 r/Likes to swim
.y
oryes
ork
orok
oryea
oryeah
executesrmk 3 r/Likes to swim
as normal andLikes to swim
will be added to Charlotte Oliviero:
-
add Hansel Black 410 Madison Avenue NY 10029 friends neighbours ab@de.fg 98437653
will be suggested as the formatted command,add n/Hansel Black p/98437653 e/ab@de.fg a/410 Madison Avenue NY 10029 t/friends t/neighbours
.y
oryes
ork
orok
oryea
oryeah
executes theadd
command as normal and Hansel Black with appropriate fields will be added to the Rolodex:
1.6.2. Why would we need this?
Users may find it too much of a hassle to remember many of the syntaxes required by Rolodex such as n/
, r/
, etc.
Suggesting commands for such occasions would give the user a shallow learning curve and improve user experience significantly.
1.6.3. Implementation
Suggestions Mechanism
The suggestion mechanism is implemented at Rolodex’s parser level under the logic component.
The suggestion is stored as an instance object to be evaluated only if the next command matches any of the suggestion’s affirmation commands, y
, yes
, k
, ok
, yea
or yeah
, otherwise, the suggestion is destroyed.
Upon receiving a bogus command, the command string is evaluated by the suggestion object which determines if it can be turned into a valid suggestion to be executed.
For this section’s explanation of the suggestions mechanism, the terms commandWord, arguments or rawArgs are usually a reference to the following:
-
commandWord: The first resulting word in a whitespace delimited string.
-
rawArgs: The raw argument string to be parsed for the suggestion, after excluding the commandWord.
-
arguments: Can be the same meaning or rawArgs or mean arguments in a broader context. Please adjust to the context if necessary.
The high level sequence diagram for a bogus udon is yummy
request can be seen below:
-
User enters a bogus command
udon is yummy
. -
RolodexParser
parses the command and separates the stringudon is yummy
into the commandWord,udon
and arguments,is yummy
.-
udon
is not a recognized command word -
The execution is passed on to a new Suggestion instance.
-
-
Suggestion determines which command abbreviation
udon
is closest to up to a specified threshold, which in this case, isundo
.-
Since
undo
does not require any arguments, the supplied argumentsis yummy
are ignored -
The suggestion is noted as a suggestible exception.
-
-
The suggestion is stored as an instance variable in
RolodexParser
. -
RolodexParser
throws aSuggestibleParseException
to be handled separately fromParseException
by UI elements.-
The
CommandBox
is cleared to make way for the user to easily enter any of the suggestion affirmation commands. -
The
ResultDisplay
panel displays a formatted string for the suggestion of the command to be alternatively executed.
-
-
At this point, the user enters an affirmation command,
k
for theRolodexParser
to be handled. -
As a suggestion is present in
RolodexParser
, the user inputk
is converted into the parsedSuggestion
and handled as usual.
The behaviour of the undo
operation remains unchanged, as explained in the [Undo/Redo Mechanism] section.
The suggestions are not limited undo
commands and are applicable to all commands in the Rolodex.
Levenshtein distance is used to determine the closest command to the whitespace delimited first user input.
If two command abbreviations are of equal distance to the bogus word, an arbitrary command is selected for the suggestion.
The suggestions are handled differently by different types of Command
.
Commands with No Arguments
The behaviour of commands that are known to have no arguments by default, namely,
-
Clear
-
List
-
History
-
Exit
-
Help
-
Undo
-
Redo
are handled in a manner similar to undo
as described above. The arguments are ignored.
Commands with a Single Index Type Argument
The commands that hold a single, indexed type argument are
-
Delete
-
Select
These commands require an argument of an index type, within the range from 1 to the total number of persons in the Rolodex. The behaviour of index parsing can be seen as such:
public static int parseFirstIndex(String value, int rolodexSize) throws NumberFormatException {
Matcher m = Pattern.compile(INDEX_VALIDATION_REGEX).matcher(value);
while (m.find()) {
int firstIndex = Math.abs(Integer.parseInt(m.group()));
if (firstIndex > 0 && firstIndex <= rolodexSize) {
return firstIndex;
}
}
throw new NumberFormatException();
}
The algorithm searches for instances of an integer in the supplied string and checks if it falls within the possible range of indices.
If no more integers can be found, the method throws a NumberFormatException
to be converted into an non-suggestible ParseException
.
Sometimes users may include the index in the command word, without a whitespace separation. To handle such a case, we first look for the index within the arguments, as usual. If no valid indices are found, we search the command word instead.
For example, the invalid command string s3lect
,
-
RolodexParser
determines that the closest command word tos3lect
isselect
and use parse logic for single index typed arguments. -
The parser would attempt to search for an index in the empty arguments and fail.
-
The parser then attempts to search for an index in the command word and finds
3
. -
The parser builds the arguments into the formatted command string,
select 3
and prompts the user:
Should the user choose to execute the prompted command, the person Alpha Charlie would be selected.
Commands with Directory Type Arguments
The commands that hold a single file path argument are
-
Open
-
New
Arguments for these commands are parsed by using the same filepath validation as seen in Open File and New File Mechanism, except with looser regex that does not validate from the beginning to the end of the string given.
public static final String FILEPATH_REGEX_NON_STRICT = "(.+)/([^/]+)";
The rest of the string is then formatted into a valid file path and a .rldx
extension is added to the end of the string.
public static String parseFirstFilePath(String value) throws IllegalArgumentException {
Matcher m = Pattern.compile(FILEPATH_REGEX_NON_STRICT).matcher(replaceBackslashes(value).trim());
if (m.find() && isValidRolodexStorageFilepath(m.group())) {
return m.group().replaceAll(ROLODEX_FILE_EXTENSION, "").trim() + ROLODEX_FILE_EXTENSION;
}
throw new IllegalArgumentException();
}
Find Command Suggestions
The find
command uses a simpler suggestion format by simply taking all arguments, checking if it is not empty and returning it all together.
public static String parseArguments(String rawArgs) {
// Check if null and is a non-empty string.
requireNonNull(rawArgs);
if (!rawArgs.trim().isEmpty()) {
return " " + rawArgs.trim();
}
return null;
}
Remark/Email Command Suggestions
The Remark
and Email
command are in the format of
-
a command word followed by,
-
an index then,
-
a memo field.
For the remark command, the memo is simply the remark to be entered into the person’s remark field, preceded by the prefix r/
.
The email command’s memo field is the subject of the email to be sent, preceded by the prefix s/
.
Similar to Commands with a Single Index Type Argument, if the index does not exist in the arguments, the command word is checked instead.
public static String parseArguments(String commandWord, String rawArgs) {
// Check if index (number) exists, removes Remark prefix (if it exists) and re-adds it before returning.
if (isParsableIndex(rawArgs, getLastRolodexSize())) {
String indexString = Integer.toString(parseFirstIndex(rawArgs, getLastRolodexSize()));
String remark = parseRemoveFirstIndex(rawArgs, getLastRolodexSize()).trim().replace(PREFIX_REMARK.toString(), "");
return " " + indexString + " " + PREFIX_REMARK + remark;
} else if (isParsableIndex(commandWord, getLastRolodexSize())) {
String indexString = Integer.toString(parseFirstIndex(commandWord, getLastRolodexSize()));
String remark = rawArgs.trim().replace(PREFIX_REMARK.toString(), "");
return " " + indexString + " " + PREFIX_REMARK + remark;
}
return null;
}
However, instead of stopping at the index, the remainder of the arguments after the index has been removed is parsed as the memo field’s contents.
Add Command Suggestions
When adding a new person, we have to note that a Person
has four compulsory fields, Name
, Phone
, Email
and Address
.
Tag
and Remark
are optional and have to be handled accordingly.
The implementation for Add
suggestions follow a decision flow model that can be seen through the following activity diagram, from AddCommandParser.parseArguments(rawArgs)
execution point of view:
During the handling of a suggestible command arguments for an Add
command,
-
rawArgs
is set to be the initial value of the remaining arguments. -
Mandatory
Phone
is checked first. If present, extract, remove and continue. Otherwise stop execution and return as non-suggestible.-
Phones are expected to match the phone regex,
-
having a minimum of digits, or
-
optionally prepended with a
+
, -
making it easily distinguishable from other numeric formats like the index, building numbers or postal codes.
-
-
Mandatory
Email
is checked. If present, extract, remove and continue. Otherwise stop execution and return as non-suggestible.-
Emails are expected to have the
@
character inside them and regex matching is straightforward, -
making emails easily distinguishable from other fields.
-
-
Existing
Tag
words are checked. If present, extract, remove and continue. Otherwise continue.-
Words (remaining string delimited by whitespace) that are also tags in the Rolodex session are existing tag words.
-
-
Tag prefixes (
t/
) are checked. If present, extract, remove and continue. Otherwise continue.-
t/TAG
behavior as per normal.
-
-
Remark prefixes (
r/
) are checked. If present, extract, remove and continue. Otherwise continue.-
r/REMARKS
behavior as per normal.
-
-
Mandatory
Address
is checked. If present, extract, remove and continue. Otherwise stop execution and return as non-suggestible.-
Addresses commonly begin with
Blk
,Block
or a short number -
Addresses are expected to be entered after a name (i.e.
COMMAND NAME … ADDRESS
), making it highly possible to obtain the address by getting the substring from the first occurrence ofBlk
,Block
or the number to the end of the remaining string.
-
-
Mandatory
Name
is checked. If present, extract, remove and continue. Otherwise stop execution and return as non-suggestible.-
Name is the last mandatory argument to be parsed.
-
Name must be non-empty and matches the defined name regex.
-
The unpredictable nature of name words makes it difficult to identify a person’s name.
-
As seen in
Address
, ifName
is entered after (to the right of)Address
, there would be no remaining words to be parsed. SinceName
must be non-empty, if it is empty, thenName
is not present.
-
-
Build the arguments into a formatted command argument string.
As observed via the decision flow, the suggestion does not always suggest the correct format so it is up to the user to validate the command. |
Any missing compulsory fields will result in an immediate failure of the command as specified by a person’s requirements. Optional arguments, on the other hand, may not be present and are represented as empty strings when sent to the final argument builder.
If any of the prefixes are missing, they will be added when the parsed arguments are built, with the exception of remarks and tags with prefixes.
Given a typical unformatted add command string without prefixes add Bobby Lee bobby@lee.com 583 Lexington Avenue NY 10048 friends 95849301
:
We should receive the following suggestion Did you mean add n/Bobby Lee p/95849301 e/bobby@lee.com a/583 Lexington Avenue NY 10048 t/friends?
:
Using the decision flow modeled above, we can see that the argument suggestions were parsed in the following order:
-
Bobby’s phone,
95849301
is detected as a long number and is extracted first. -
The email,
bobby@lee.com
is detected as the@
character is present and is extracted. -
The tag,
friends
is an existing tag in the currently loaded Rolodex and we can extractfriends
as a tag. -
The address,
583 Lexington Avenue NY 10048
begins with a short number and will be picked up till the end of the remaining string. -
The remainder of the arguments should only contain
Bobby Lee
and is matched as his name.
As the Add command’s decision flow requires many acyclic execution paths to its parser, its corresponding NPath complexity
is known to be large.
|
Edit Command Suggestions
Unlike the Add
command, the Edit
command only requires optional (minimum 1) fields and we handle all fields as such.
However, the Edit
command also requires a mandatory argument of an index type,
similar to Remark/Email Command Suggestions.
The implementation for Edit
suggestions follow a decision flow model that can be seen through the following activity diagram, from EditCommandParser.parseArguments(commandWord, rawArgs)
execution point of view:
During the handling of a suggestible command arguments for an Edit
command,
-
rawArgs
is set to be the initial value of the remaining arguments. -
Mandatory
Index
is checked in the remaining arguments first. If present, extract, remove and continue. Otherwise,-
Mandatory `Index is checked in command word. If present, extract, remove and continue. Otherwise stop execution and return as non-suggestible.
-
Index extraction is similar to behavior in Commands with a Single Index Type Argument section.
-
-
Optional
Phone
is checked first. If present, extract, remove and continue. Otherwise continue.-
Phones are expected to match the phone regex,
-
having a minimum of digits, or
-
optionally prepended with a
+
, -
making it easily distinguishable from other numeric formats like the index, building numbers or postal codes.
-
-
Optional
Email
is checked. If present, extract, remove and continue. Otherwise continue.-
Emails are expected to have the
@
character inside them and regex matching is straightforward, -
making emails easily distinguishable from other fields.
-
-
Existing
Tag
words are checked. If present, extract, remove and continue. Otherwise continue.-
Words (remaining string delimited by whitespace) that are also tags in the Rolodex session are existing tag words.
-
-
Tag prefixes (
t/
) are checked. If present, extract, remove and continue. Otherwise continue.-
t/TAG
behavior as per normal.
-
-
Remark prefixes (
r/
) are checked. If present, extract, remove and continue. Otherwise continue.-
r/REMARKS
behavior as per normal.
-
-
Optional
Address
is checked. If present, extract, remove and continue. Otherwise continue.-
Addresses commonly begin with
Blk
,Block
or a short number -
Addresses are expected to be entered after a name (i.e.
COMMAND NAME … ADDRESS
), making it highly possible to obtain the address by getting the substring from the first occurrence ofBlk
,Block
or the number to the end of the remaining string.
-
-
Optional
Name
is checked. If present, extract, remove and continue. Otherwise continue.-
Name is the last argument to be parsed.
-
Name must be non-empty and matches the defined name regex.
-
The unpredictable nature of name words makes it difficult to identify a person’s name.
-
As seen in
Address
, ifName
is entered after (to the right of)Address
, there would be no remaining words to be parsed. SinceName
must be non-empty, if it is empty, thenName
is not present.
-
-
If no arguments exist (i.e.
Name
,Phone
,Email
,Address
,Tag
,Remark
are all empty strings), return as non-suggestible. Otherwise, build the arguments into a formatted command argument string.
As observed via the decision flow, the suggestion does not always suggest the correct format so it is up to the user to validate the command. |
Any missing or invalid index would result in an immediate failure of the command as specified by the command specifications.
Optional arguments, on the other hand, may not be present and are represented as empty strings when sent to the final argument builder.
However, if completely no arguments exist (e.g. edit 1
), the Edit parser would fail a well, defaulting to the original behavior.
If any of the prefixes are missing, they will be added when the parsed arguments are built, with the exception of remarks and tags with prefixes.
Given a typical unformatted edit command string with an invalid address prefix edit1 p/911A Lexington Avenue
:
We should receive the following suggestion Did you mean edit 1 a/911A Lexington Avenue?
:
Using the decision flow modeled above, we can see that the argument suggestions were parsed in the following order:
-
The index,
1
is incorrectly placed within the command word.911
is attempted but since 911 is greater than the Rolodex’s size, no index can be found in the arguments. -
The index,
1
is matched in the command word and extracted. -
No phone is matched.
911
is detected as a short number and is skipped. The prefixp/
is removed. -
No email is matched.
-
No tags are matched.
-
No remarks are matched.
-
The address,
911A Lexington Avenue
begins with a short number and will be picked up till the end of the remaining string. -
The remainder of the arguments are empty when trimmed.
-
Address is the only valid optional field present and the command is suggested as
edit 1 a/911A Lexington Avenue
.
As the Edit command’s decision flow requires many acyclic execution paths to its parser, its corresponding NPath complexity
is known to be large.
|
Design Considerations
Alternative 1 (current choice): Parsing easy fields first, then parsing address before name in the Add
command.
Pros: Application behaves sufficiently well and efficiently in matching field data given certain input assumptions.
Pros: Shallow learning curve for new users to learn application functions.
Cons: Extremely high NPath complexity
Cons: Deviates from the Object-Oriented nature of the codebase into a procedural/functional style.
Alternative 2: Dictionary analysis of field data.
Pros: Easier logic to understand for new developers.
Cons: Maintaining dictionary would be taxing on the development team and may get outdated as data keeps getting outdated.
Cons: Memory performance decreases drastically as memory has to be used to load dictionary into memory for field analysis.
Cons: Storage performance decreases drastically as space has to be used to store dictionary.
Alternative 3: No suggestions for add/edit
Pros: Clean codebase.
Cons: Steep learning curve for new users to learn application functions.
1.7. Enhancement Proposed: Markdown/HTML parser
To replace the content field of the note/remark command with 'rich-text', to be displayed on the browser panel.
3. Other projects
You can view my full portfolio here.