Project: cherBook

cherBook is a desktop application designed to help teachers monitor students' academic performance and manage their schedules. My team and I built cherBook as an enhancement to the original Addressbook-level4 by Team SE-EDU. This project was built as part of the software engineering module CS2103T offered by the School of Computing (SoC) in the National University of Singapore (NUS). The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 30 kLoC.

Code contributed: [Functional code] [Test code]

Roles and Responsibilities

  • Role: Team Lead

  • Responsibilities:

    • In charge of Logic Component.

    • Git Expert: Advises the group for git matters.

    • Integration: Versioning of the code, maintaining the code repository, integrating various parts of the software to create a whole.

    • Scheduling: and tracking: Defining, assigning, and tracking project tasks.

List of Enhancements Implemented

  1. Command aliases for all commands

  2. FindTags Command to find students by Tags

  3. Statistics Panel Logic and UI

List of Enhancement Proposed

  1. Add Compare command to compare statistics of different student groups

  2. Add SendEmail command to allow sending emails from the command box


Enhancement Added: Command aliases feature

External behavior

Following the list table of shortcuts below, all commands under the Shorthand column will behave exactly like the full command in the Command column.


Start of Extract [from: User Guide]

Command Shortcuts

Here is a list of shortcuts you can use in cherBook together with some examples.

Command ShortHand Example

help

NA

help

add

a

a n/NAME student/STUDENT_NUMBER parent/PARENT_NUMBER e/EMAIL a/ADDRESS c/POSTALCODE [t/TAG]…​

list

l

l

edit

e

e INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [c/POSTALCODE] [t/TAG]…​

find

f

f KEYWORD [MORE_KEYWORDS]

findtags

ft

ft KEYWORD [MORE_KEYWORDS]

home

home

home

delete

d

d 3

select

s

s INDEX

sort

st

st

remark

rm

rm Index [rm/REMARK]

addschedule

addsch

addsch 1 s/[DATE]

viewschedule

viewsch

viewsch

deleteschedule

deletesch

deletesch 1

history

h

h

undo

u

u

redo

r

r

clear

c

c

tab

NA

tab

exit

NA

exit

End of Extract


Justification

Being a command line application, there is a need to be able to enter commands quickly and without error. Hence, the usage of shorthand commands for the more verbose full commands are needed. By adding shorthand commands, the number of key presses needed for the user to execute commands is reduced. This also reduces the chance of users misspelling commands.

Implementation


Start of Extract [from: Developer Guide]

Command Alias mechanism

The Command Alias mechanism is facilitated by the COMMAND_ALIAS in each Command class and the parseCommand() method in AddressBookParser. The AddressBookParser is responsible for analyzing command words while each command’s COMMAND_ALIAS stores the command alias to be accepted by the parser.

The aliased added to each command class is implemented like this e.g. FindCommand :

public class FindCommand extends Command {
    public static final String COMMAND_WORD = "find";
    public static final String COMMAND_ALIAS = "f";
    //...... other logic ......
}

Just like how we store each individual command word constant COMMAND_WORD inside each Command class e.g. FindCommand.COMMAND_WORD, DeleteCommand.COMMAND_WORD , a new constant was added for each command alias e.g. FindCommand.COMMAND_ALIAS.

The parseCommand() in AddressBookParser is implemented like this:

 public Command parseCommand(String userInput) throws ParseException {
        final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
        if (!matcher.matches()) {
            throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
        }

        final String commandWord = matcher.group("commandWord");
        final String arguments = matcher.group("arguments");
        switch (commandWord) {

        case AddCommand.COMMAND_WORD://Fallthrough
        case AddCommand.COMMAND_ALIAS:
            return new AddCommandParser().parse(arguments);
        // ...... more conditions ......
        default:
            throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
        }
    }

When a command alias is detected in the switch statement, it will result in the return statement below it, causing it to behave similarly to its corresponding command word. When a command word is detected, it will fall through and executes the command as per normal.

Design Considerations

Aspect: Where to add the command Aliases
Alternative 1 (current choice): Add the aliases in each Command Class
Pros: More cohesive command classes.
Cons: None.
Alternative 2: Add the aliases in the AddressBookParser.parseCommand(String) method.
Pros: Easier to read code in the AddressBookParser .
Cons: Use of magic numbers/strings means that the code is harder to trace and debug.

End of Extract


Enhancement Added: FindTags feature

External behavior


Start of Extract [from: User Guide]

Locating persons by tags: findtags Since v1.1

Finds persons whose tags contain all of the given keywords.
Format: findtags KEYWORD [MORE_KEYWORDS]
Shorthand: ft KEYWORD [MORE_KEYWORDS]

  • The search is case insensitive. e.g friends will match Friends

  • The order of the keywords does not matter. e.g. friends owesMoney will match owesMoney friends

  • Only the tags are searched.

  • Only full words will be matched e.g. friends will not match closefriends

  • Persons matching at least all keywords will be returned (i.e. AND search). e.g. friends owesMoney will not return people with only friends or only owesMoney

Only one tag can be searched at any one time.

Examples:

  • findtags friends
    Returns any person having the tag friends

  • findtags friends owesMoney
    Returns any person having both friends and owesMoney tags

  • ft John
    Returns any person having the tag friends

  • ft Betsy Tim John
    Returns any person having both friends and owesMoney tags

Example:

FindTagsImage

Figure 5.1.7.1 : Find contacts on your cherBook with the specified tags

End of Extract


Justification

As an address book application, there is a need for users to be able to find their contacts quickly and efficiently . Since contacts are also categorised by tags, and there was no way to search by tags, I decided to solve this problem by implementing FindTags.

I decided to use AND search and not OR search for FindTags as it narrows down the contact list by their of tags, making it much more efficient to find people with a specific combination of tags. In this scenario, using OR search for FindTags would not make sense as OR search increases the number of search results as more search terms are used. Thus making it more difficult and less efficient for the user to find the wanted person.

Implementation


Start of Extract [from: Developer Guide]

FindTags mechanism

The FindTagsCommand class inheriteds from the Command class. It allows users to search students by their tags.

The FindTags mechanism is facilitated by the TagsContainKeywordsPredicate, which resides within FindTagsCommand. The TagsContainKeywordsPredicate tests whether each person’s tags contain all keywords input by the user and thus supports AND search.

The following sequence diagram shows how the FindTags operation works:

FindTagsSequenceDiagramForLogic

Figure 4.4.1 : Sequence Diagram of FindTags Operation

As can seen from above, when a FindTags command is called, E.g. findtags NUS friends. An instance of TagsContainKeywordsPredicate is created.

The newly created TagsContainsKeywordsPredicate used to implement FindTagsCommand is shown below:

public class TagsContainsKeywordsPredicate implements Predicate<ReadOnlyPerson> {
    private final List<String> keywords;

    @Override
    public boolean test(ReadOnlyPerson person) {
        return keywords.stream()
                .allMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getTagsAsString(), keyword));
    }
    // ... other logic ...
}
The test is case insensitive as StringUtil.containsWordIgnoreCase() ignores case.
This search operation is an AND search So only persons with at least both friends and NUS will be displayed. Persons with only friends or only NUS will not be displayed.

Inside the TagsContainKeywordsPredicate, the search terms NUS and friends are stored inside the keywords list. The overidding test() method evluates whether the person’s tags matches all the search terms in keywords. This TagsContainKeywordsPredicate is then used to test every person in the AddressBook and filter them.

The newly created FindTagsCommand class is shown below:

public class FindTagsCommand extends Command {
    private final TagsContainsKeywordsPredicate predicate;

    @Override
    public CommandResult execute() {
        model.updateFilteredPersonList(predicate);
        return new CommandResult(
            getMessageForPersonListShownSummary(model.getFilteredPersonList().size()));
    }
    // ... other logic ...
}

The predicate is passed through model.updateFilteredPersonList(predicate) to update the filteredPersonList. This causes the filteredPersonList to only contain students whose tags match all keywords NUS and friends and also display them in the PersonListPanel UI element.

However if there are no persons with all keywords in the tags, the PersonListPanel will just be blank.

Meanwhile, the ModelManager posts a AddressBookChanged event and a FilteredPersonListChangedEvent to the EventsCenter. The AddressBookChangedEvent indicates that the model has changed and the causing the FilteredPersonListChangedEvent updates the StatisticsPanel of the UI to reflect the new Statistics for the new list of students.

This search feature is implemented using Predicate because Predicate help move the business logic to a more central place, helping in unit-testing them separately. Also, the Predicate can can be reused, improving code manageability and readability.


Design Considerations

Aspect: Have a separate predicate class to support findtags
Alternative 1 (current choice): Have a TagsContainsKeywordsPredicate to facilitate search.
Pros: Easy for new Computer Science undergraduates to understand. They are likely to be the new incoming developers of our project.
Cons: Makes the application more complex to understand according to the More Is More Complex (MIMC) principle. For example, a separate TagsContainsKeywordsPredicate class is needed when the same can be done by inserting it as an inner class in the FindTagsCommand class.
Alternative 2: Create TagsContainsKeywordsPredicate as an inner class in FindTagsCommand class.
Pros: It makes the codebase less complex as there are less classes.
Cons: It adds complexity to the FindTagsCommand source file and reduces re-usability of the Predicate outside of the FindTagsCommand


Aspect: Whether to use AND or OR search
Alternative 1 (current choice): AND search
Pros: Allows users to find students with a specific combination of tags much more efficiently.
E.g. findtags band dance only returns the list of students who have both the band and dance tag rather all students with either the band or dance tag.
Cons: Does not allow users to calculate statistics for more than one group of students at a time.
E.g. findtags band dance cannot calculate statistics for the track and dance groups combined.
Alternative 2: OR search
Pros: Allows users to calculate statistics for more than one group of students at a time.
E.g. findtags track dance calculates statistics for the track and dance groups combined.
Cons: Or search does not help teachers find students with a specific combination of tags efficiently.
E.g. findtags track dance does not narrow down the list of students returned to students who have both the track and dance tag.


End of Extract


Enhancement Added: Statistics Panel feature

External behavior


Start of Extract [from: User Guide]

Statistics Panel Since v1.4

cherBook uses the Statistics Panel to display the relevant statistics for the current list of students in the Students Panel. The statistics changes automatically when there is a change to the list of students in the Students Panel. E.g. When you enter the command findtags studentCouncil, the Students Panel will only show students with the studentcouncil tag and Statistics Panel will then show statistics for students with the studentcouncil tag. You can use these statistics to pick out trends in your student’s grades and also monitor how well they are coping with their studies.

End of Extract


Justification

The StatisticsPanel is needed to display the statistics of the students currently in the Students Panel. The statistics provided helps teachers monitor their student’s academics and identify the weaker students that need more help.

Implementation


Start of Extract [from: Developer Guide]

Statistics Panel mechanism

The StatisticsPanel class inherits from the UiPart class. It displays to users the statistics of the current list of students in the StudentsPanel element of the user interface.

The Statistics Panel is implemented using the Statistics class and The FilteredPersonListChangedEvent event. The Statistics class supports the calculating of statistics for the current list of students in the PersonListPanel and the FilteredPersonListChangedEvent indicates that the FilteredPersonList in the model has changed and is used to update the Statsistics Panel.

The following activity diagram shows how the StatisticsPanel mechanism works:

StatisticsPanelActivityDiagram

Figure 4.12.1 : Activity Diagram of updating the`Statistics Panel` .

Whenever the user enters any Command that modifies the addressBook data, the Model Component posts a FilteredPersonListChangedEvent containing the filteredPersonsList to the EventsCenter. At the same time, an AddressBookChangedEvent that indicates that the model has changed is also posted to the EventsCenter.

The FilteredPersonListChangedEvent is implemented this way:

public class FilteredPersonListChangedEvent extends BaseEvent {

    private final ObservableList<ReadOnlyPerson> currentFilteredList;

    public FilteredPersonListChangedEvent(ObservableList<ReadOnlyPerson> currentFilteredList) {
        this.currentFilteredList = currentFilteredList;
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }

    public ObservableList<ReadOnlyPerson> getCurrentFilteredPersonList() {
        return currentFilteredList;
    }
}

Meanwhile in the StatisticsPanel class, the handleFilteredPersonListChangedEvent event handler which is subscribed to EventsCenter handles the FilteredPersonListChangedEvent event.

The handleFilteredPersonListChangedEvent event handler is implemented this way:

    @FXML
    @Subscribe
    private void handleFilteredPersonListChangedEvent(FilteredPersonListChangedEvent event) {
        logger.info(LogsCenter.getEventHandlingLogMessage(event));
        statistics.initScore(event.getCurrentFilteredPersonList()); // Updates the statistics instance values
        loadListStatistics();
    }

When handling the event, the Statistics instance in StatisticsPanel has its values updated by the initScore() method. This means that the values returned from the Statistics class’s getter methods when called are now updated.

The loadListStatistics() method in StatisticsPanel is then called.

The loadListStatistics() is implemented this way:

    /**
    * Updates list Statistics in the Statistics panel
    */
    protected void loadListStatistics() {
        mean.setText(statistics.getMeanString());
        median.setText(statistics.getMedianString());
        mode.setText(statistics.getModeString());
        variance.setText(statistics.getVarianceString());
        standardDeviation.setText(statistics.getStdDevString());
        quartile1.setText(statistics.getQuartile1String());
        quartile3.setText(statistics.getQuartile3String());
        interquartileRange.setText(statistics.getInterquartileRangeString());
    }

The Labels (texts) in the User Interface are set according to the updated Statistics instance’s getter methods. This updates and shows the statistic values of the current filteredPersonList on the StatisticsPanel in the User Interface. At the same time, the AddressBookChangedEvent is also being handled, saving the addressBook data.


Design Considerations

Aspect: How to update the Statistics Panel
Alternative 1 (current choice): Create a new FilteredPersonListChangedEvent just for updating the Statistics Panel.
Pros: Adherence to the Single Responsibility Principle (SRP) increases cohesion among the different Events and reduces coupling.
Cons: Increases the number of classes in the source code, making it more complex according to the More Is More Complex (MIMC) principle.
Alternative 2: Modify AddressBookChangedEvent to also update the Statistics Panel.
Pros: Since a change in the model triggers both the AddressBookChangedEvent and the updating of the Statistics Panel, only a few changes to are needed for AddressBookChangedEvent to do also update the Statistics Panel.
Cons: This violates the Single Responsibility Principle (SRP) by having giving AddressBookChangedEvent the additional responsibility of updating the Statistics Panel.


Aspect: How to raise the FilteredPersonListChangedEvent in the ModelManager
Alternative 1 (current choice): Create a separate updateStatisticsPanel() method to raise the event to update the Statistics Panel
Pros: Reduces coupling and increases cohesion by giving a single responsibility to the updateStatisticsPanel() method.
Cons: Makes the application more complex to understand according to the More Is More Complex (MIMC) principle.
Alternative 2: Raise the FilteredPersonListChangedEvent together with the AddressBookChangedEvent
Pros: Reduces the number of similar methods in the source code, making it less complex and easier to understand
Cons: Increases coupling in the source code


End of Extract


Enhancement Proposed: Add command compare

External behavior

Teh user can enter the compare command followed by 2 Tags or FormClassNames to compare statistics of different groups with the compare command. The Statistics Panel will have a second set of values for the second group of students.

The difference in the 2 sets of values will be displayed on the right of the StatisticsPanel. The difference will be colored red if the difference is negative and geen if the difference is positive to to make it noticeable and more visually appealing.

Justification

Teachers often have to compare the grades of students or groups of students to analyse their academic progress. As the simple statistics can only tell you so much about your data, there is a need for more powerful data analytics. The comapre command gives teachers a more powerful tool to analyse data by allowing them to compare different sets of data . The color coded difference values also improve the readability of the statistics so that teachers can get a good idea of the relative performance of two groups of students at a glance.


Enhancement Proposed: Add command sendemail

External behavior

The user can enter the sendemail command followed by the INDEX of the intended recepient and an external email application with the FROM and TO fields already populated. This streamlines the process of teachers sending emails to students and parents and allows them to do it directly from within cherBook.

Justification

It is troublesome for teachers to manually and repeatedly open up their browsers to send emails to students of their parents. The sendemail command removes this process and streamlines the process by bringing the teacher straight to the compose email page with the relevant fields already filled, removing the trouble of going through a browser.


Other contributions

  • Added Travis, AppVeyor and Coveralls on the cherBook repository automated deployment and testing. (Pull Request #7, #152)

  • Added Grades and FormClass attributes to the Student (Pull Requests #39, #41)

  • Populated cherBook with a significant amount (40 student contacts) of realistic data (Pull Request #73)

  • Cleaned up unnecessary nesting in the ExtendedPersonCard UI element (Commit 1a6bc45 in Pull Request #98)

  • Cleaned up code smells in the whole repository (Pull Request #123)

Helping out other groups

  • Performed acceptance testing for other teams, reported bugs and gave suggestions

Project: NUShortcuts

NUShortcuts is a web application to provide walking shortcuts around NUS with step by step picture guides . This was done from May 2017 to Aug 2017 for CP2106 - Orbital Programme offered by the School of Computing (SoC) in the National University of Singapore (NUS). This web application was built using the MeteorJS framework and is deployed on a Heroku server.

NUShortcuts offers three route types: Fastest route, Sheltered route and Least Stairs route. Users can search for their destination in NUS, choose their desired path and follow the picture guides to their destination. For better travel time estimates, they can indicate their mode of transport and their estimated walking speeds under the settings page. My team also implemented a ranking system to track the most popular and frequently searched locations. One of the biggest features I implemented was the algorithm behind the calculation of the shortest paths around NUS. Some of the other features I implemented include the feedback page where users can give feedback on how to improve NUShortcuts.