Project: iContacts

iContacts is a Command Line Interface (CLI) application created for University Students who requires the functionality of managing contacts, deadlines and reminders in a single application.

The purpose of this portfolio is to summarise my contributions in iContacts and shows the readers how this contributions are implemented. In addition it contains a few proposed enhancements for upcoming versions of the application.

Code contributed: [Functional code] [Test code]

Enhancement Added: Exporting of contacts

External behavior


Start of Extract [from: User Guide]

Exporting selected contacts : export

Exports selected contacts in iContacts.
Format: export r/RANGE p/PATH

  • Exports the contact/s from a specified RANGE to a specified PATH.

  • The range refers to any index number shown in the most recent listing.

  • The range must be a positive integer and must not be larger than the last index of the list 1, 2, 3, 4-7, …​

  • The path must include the file name without the file extension c:\exports\classmates

Types of range inputs:

  • Specific contacts - 1, 2, 3, 4

  • Range of contacts - 1-4, 6-9

  • All contacts - all

Examples:

  • list
    export r/all p/c:\exports\classmates
    Exports all the contacts to the file classmates.xml in path c:\exports.

  • export r/1-4 p/c:\exports\classmates
    Exports the contacts from index 1 to index 4 to the file classmates.xml in path c:\exports.

  • export r/1-4,6,8 p/c:\exports\classmates
    Exports the contacts at index 1 to index 4 with index 6 and index 8 to the file classmates.xml in path c:\exports.

End of Extract


Justification

Exporting contacts from the application is a key feature to enable users to share contact information. As this application is targeted at university students sharing of contacts is necessary in the daily life of the student.

Implementation


Start of Extract [from: Developer Guide]

Export mechanism

The ExportCommand uses XmlAddressBookStorage class to generate a xml file based on a given range and saves it to the path provided. It takes in two String values range and path. Below is the constructor for the class:

public class ExportCommand extends Command {
    public ExportCommand(String range, String path) {
        requireNonNull(range);
        requireNonNull(path);

        this.range = range;
        this.path = path;
        exportBook = new AddressBook();
    }
}

The method getRangefromInput() splits the range using a separator and returns a String array for the different values in the range.

Below is an extract of the method getRangefromInput():

public class ExportCommand extends Command {
    private String[] getRangeFromInput() {
        private String[] getRangeFromInput() {
            String[] splitStringComma = this.range.split(",");

            return splitStringComma;
        }
    }
}

To determine which contacts should be added to the exportBook we have to check the the user input. There are three cases:

  • All (Priority)

    • if the word all is present in the user input, we will just export all the contacts from the last shown list.

  • Specific index (e.g. 1, 2, 3)

    • if the user input contains a specific index, we will add that index (one-based) to the exportBook.

  • Range of indexes (e.g. 1-5,8-10)

    • if the user input contains a range which is identified by the - character, we will add that range of index (one-based) to the exportBook.

Below is the code snippet to identify the three cases in the user input:

public class ExportCommand extends Command {

    @Override
    public CommandResult execute() throws CommandException {
        String[] multipleRange = getRangeFromInput();

        if (multipleRange[0].equals("all")) {
            exportAll();
        } else {
            for (int i = 0; i < multipleRange.length; i++) {
                if (multipleRange[i].contains("-")) {
                    String[] rangeToExport = multipleRange[i].split("-");
                    try {
                        exportRange(Integer.parseInt(rangeToExport[0]), Integer.parseInt(rangeToExport[1]));
                    } catch (NumberFormatException e) {
                        throw new CommandException(MESSAGE_EXPORT_FAIL);
                    }

                } else {
                    try {
                        exportSpecific(Integer.parseInt(multipleRange[i]));
                    } catch (NumberFormatException e) {
                        throw new CommandException(MESSAGE_EXPORT_FAIL);
                    }
                }
            }
        }
        /... storage is resolved here ...
    }
}

The final step is to create the xml file from the exportBook.

Below is the code snippet to export the data into an xml file using AddressBookStorage.

public class ExportCommand extends Command {
    @Override
    public CommandResult execute() throws CommandException {

    /... the exporting is resolved here ...

        try {
            AddressBookStorage storage = new XmlAddressBookStorage(path + ".xml");
            storage.saveAddressBook(exportBook);
        } catch (IOException ioe) {
            return new CommandResult(MESSAGE_EXPORT_FAIL);
        }
        return new CommandResult(MESSAGE_EXPORT_SUCCESS);
    }
}

End of Extract


Importing contacts from file : import

Imports contacts into iContacts.
Format: import p/PATH

  • Imports all contacts from a specified file PATH.

  • The path must include the file name with the file extension c:\exports\classmates.xml

Example:

  • import p/c:\exports\classmates.xml
    Imports all the contacts stored in the file classmates.xml* into iContacts.

End of Extract


Justification

To complete the entire contact sharing experience, students must be able to import contacts shared by their peers with a single command.

Implementation


Start of Extract [from: Developer Guide]

Import mechanism

The ImportCommand uses XmlAddressBookStorage to generate a temporary AddressBook object from a given path. It takes in a String value path. The command then adds the contacts found in the temporary AddressBook object into the main address book object. Below is the constructor for the class:

public class ImportCommand extends UndoableCommand {
    public ImportCommand(String path) {
        this.path = path;
        this.addressBookStorage = new XmlAddressBookStorage(path);
        numSuccess = 0;
        numDuplicates = 0;
    }
}

The ImportCommand first checks if an AddressBook object can be initialised from the specified path, and if it does it initialises a new Person object and adds it into the main AddressBook object through the model. Below is the code snippet:

public class ImportCommand extends UndoableCommand {

    @Override
    public CommandResult executeUndoableCommand() throws CommandException {
        try {
            if (addressBookStorage.readAddressBook().isPresent()) {
                this.addressBook = new AddressBook(addressBookStorage.readAddressBook().get());
                for (ReadOnlyPerson person : this.addressBook.getPersonList()) {
                    Person personToAdd = new Person(person);
                    personToAdd.setPopularityCounter(new PopularityCounter());
                    personToAdd.setDisplayPicture(new DisplayPicture(""));
                    try {
                        model.clearSelection();
                        model.showDefaultPanel();
                        model.addPerson(personToAdd);
                        numSuccess++;
                    } catch (DuplicatePersonException e) {
                        numDuplicates++;
                    }
                }
            } else {
                throw new CommandException(String.format(MESSAGE_INVALID_FILE, path));
            }
        } catch (DataConversionException | IOException e) {
            throw new CommandException(String.format(MESSAGE_INVALID_FILE, path));
        }

        return new CommandResult(String.format(MESSAGE_SUCCESS, numSuccess, numDuplicates));
    }
}

Design Considerations

Aspect: Should PopularityCounter and DisplayPicture be set to the default value
Alternative 1 (current choice): Both PopularityCounter and DisplayPicture should be set to the default value.
Pros: Does not affect the contact’s PopularityCounter and allows the user to set their own preferred DisplayPicture of a contact.
Cons: Users will not be able to share DisplayPicture.
Alternative 2: Import the contacts with both `PopularityCounter
and DisplayPicture unchanged.
Pros: DisplayPicture will be imported into iContacts.
Cons: The PopularityCounter of the user’s contacts will be inaccurate.

End of Extract


Locating persons by name: find

Finds persons whose names or nicknames contain any of the given keywords.
Format: find KEYWORD [MORE_KEYWORDS]

  • The search is case insensitive. e.g hans will match Hans

  • The order of the keywords does not matter. e.g. Hans Bo will match Bo Hans

  • Only the name and nickname is searched.

  • Only full words will be matched e.g. Han will not match Hans

  • Persons matching at least one keyword will be returned (i.e. OR search). e.g. Hans Bo will return Hans Gruber, Bo Yang

Examples:

  • find John
    Returns john and John Doe

  • find Betsy Tim John

Returns any person having names or nicknames Betsy, Tim, or John

End of Extract


Justification

With the implementation of the nickname feature, the find command should allow users to search for contacts using both nicknames and names. ==== Implementation


Start of Extract [from: Developer Guide]

Find mechanism

The FindCommand uses NameContainsKeywordsPredicate to find contacts with matching names or nicknames. It accepts List<String> nameKeywords as the parameter that is parsed by FindCommandParser. Below is the constructor for the class:

public FindCommand(NameContainsKeywordsPredicate predicate) {
        this.predicate = predicate;
    }

The method test(ReadOnlyPerson person) iterates through nameKeywords to find a match with the name or nickname of every person from the address book.

Below is an extract of the method test(ReadOnlyPerson person.

   @Override
    public boolean test(ReadOnlyPerson person) {
        return keywords.stream()
                .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword))
                || keywords.stream()
                .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getNickname().value, keyword));
    }

For the FindCommand to work properly nameKeywords must be non-empty. The code extract below checks for empty inputs:

    public FindCommand parse(String args) throws ParseException {
        String trimmedArgs = args.trim();
        if (trimmedArgs.isEmpty()) {
            throw new ParseException(
                    String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
        }

        String[] nameKeywords = trimmedArgs.split("\\s+");

        return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
    }

End of Extract


Enhancement Added: Sorting of contacts

External behavior


Start of Extract [from: User Guide]

Sorting all persons : sort

Sorts and shows a list of all contacts in the iContacts alphabetically.
Format: sort

End of Extract


Justification

When a contact is added to iContacts it is being appended to the end of the list, creating a very unorganised list. With sorting users can choose to sort iContacts in alphabetical order to have more organised contact list.

Implementation


Start of Extract [from: Developer Guide]

Sorting mechanism

The SortCommand uses a SortedList that is initialised with the current filteredPerson and is sorted using a comparator. Below is the code snippet:

sortedfilteredPersons = new SortedList<>(filteredPersons);

@Override
public void sortFilteredPersonList() {

    Comparator<ReadOnlyPerson> sortByName = (o1, o2) -> o1.getName().fullName.compareTo(o2.getName().fullName);
    sortedfilteredPersons.setComparator(sortByName);
    indicateAddressBookChanged();
}

End of Extract


Enhancement Proposed: Automatically add required field prefixs when a valid command is entered

It is not possible to remember all the required fields for all the different commands in iContacts. Although the user is able to refer to check the required field from the help command, it makes the entire experience troublesome. With this enhancement as long as the user types in a valid command all required fields prefix will appear. This will allow for a better user experience.

Enhancement Proposed: delete multiple contacts

The current delete feature for iContacts only allows deleting 1 contact at a time. it will be troublesome if the student wants to delete a group of contacts. With this enhancement the user will be able to delete multiple contacts in a single command.

Enhancement Proposed: Enhance sort command to allow sorting by date added

The current sort feature only sorts iContacts alphabetically, more sorting options can be added.