diff --git a/doc/DeveloperGuide.md b/doc/DeveloperGuide.md index f77866560..ff6378650 100644 --- a/doc/DeveloperGuide.md +++ b/doc/DeveloperGuide.md @@ -87,6 +87,61 @@ Use case ends. > 3a1. AddressBook shows an error message
Use case resumes at step 2 + +#### Use case: Delete person + +**MSS** + +1. User requests to list persons +2. AddressBook shows a list of persons +3. User requests to delete a specific person in the list +4. AddressBook deletes the person
+Use case ends. + +**Extensions** + +2a. The list is empty + +> Use case ends + +3a. The given index is invalid + +> 3a1. AddressBook shows an error message
+ Use case resumes at step 2 + +#### Use case: Edit person + +**MSS** + +1. User requests to list persons +2. AddressBook shows a list of persons +3. User requests to edit a specific person in the list +4. AddressBook edits the person
+Use case ends. + +**Extensions** + +2a. The list is empty + +> Use case ends + +3a. The given index is invalid + +> 3a1. AddressBook shows an error message
+ Use case resumes at step 2 + +4a. The given data has incorrect format + +> 4a1. AddressBook shows an error message
+ Use case resumes at step 2 + +#### Use case: Sort all persons + +**MSS** + +1. User requests to sort persons +2. AddressBook sort the list of all persons and display it +Use case ends. ## Appendix C : Non Functional Requirements diff --git a/doc/UserGuide.md b/doc/UserGuide.md index d03dfce73..694e8e441 100644 --- a/doc/UserGuide.md +++ b/doc/UserGuide.md @@ -34,10 +34,29 @@ Examples: * `add John Doe p/98765432 e/johnd@gmail.com a/John street, block 123, #01-01` * `add Betsy Crowe pp/1234567 e/betsycrowe@gmail.com pa/Newgate Prison t/criminal t/friend` +## Editing a person : `edit` +Edits the specified person from the address book.
+Format: `edit INDEX [p]p/PHONE_NUMBER [p]e/EMAIL [p]a/ADDRESS [t/TAG]...` + +> Edits the person at the specified `INDEX`. + The index refers to the index number shown in the most recent listing. + +Examples: +* `list`
+ `edit 2 pp/999 pe/betsy@gmail.com pa/somewhere t/secretive`
+ Edits the 2nd person in the address book. +* `find Betsy`
+ `edit 1 pp/999 pe/betsy@gmail.com pa/somewhere t/secretive`
+ Edits the 1st person in the results of the `find` command. + ## Listing all persons : `list` Shows a list of all persons in the address book.
Format: `list` +## Sorting all persons: `sort` +Sorts the address book by name in ascending order.
+Format: `sort` + ## Finding all persons containing any keyword in their name: `find` Finds persons whose names contain any of the given keywords.
Format: `find KEYWORD [MORE_KEYWORDS]` diff --git a/src/seedu/addressbook/commands/EditCommand.java b/src/seedu/addressbook/commands/EditCommand.java new file mode 100644 index 000000000..e92a6c7a2 --- /dev/null +++ b/src/seedu/addressbook/commands/EditCommand.java @@ -0,0 +1,75 @@ +package seedu.addressbook.commands; + +import java.util.HashSet; +import java.util.Set; + +import seedu.addressbook.common.Messages; +import seedu.addressbook.data.exception.IllegalValueException; +import seedu.addressbook.data.person.*; +import seedu.addressbook.data.person.UniquePersonList.PersonNotFoundException; +import seedu.addressbook.data.tag.Tag; +import seedu.addressbook.data.tag.UniqueTagList; + + +/** + * Edits a person identified using it's last displayed index from the address book. + */ +public class EditCommand extends Command { + + public static final String COMMAND_WORD = "edit"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\n" + + "Edits the person identified by the index number used in the last person listing.\n\t" + + "Parameters: INDEX [p]p/PHONE [p]e/EMAIL [p]a/ADDRESS [t/TAG]...\n\t" + + "Example: " + COMMAND_WORD + + " 1 p/98765432 e/johnd@gmail.com a/311, Clementi Ave 2, #02-25 t/friends t/owesMoney"; + + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + + private final Person toEdit; + + /** + * Convenience constructor using raw values. + * + * @throws IllegalValueException if any of the raw values are invalid + */ + public EditCommand(int targetIndex, + String phone, boolean isPhonePrivate, + String email, boolean isEmailPrivate, + String address, boolean isAddressPrivate, + Set tags) throws IllegalValueException { + super(targetIndex); + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(new Tag(tagName)); + } + this.toEdit = new Person( + new Name("dummy"), + new Phone(phone, isPhonePrivate), + new Email(email, isEmailPrivate), + new Address(address, isAddressPrivate), + new UniqueTagList(tagSet) + ); + } + + public ReadOnlyPerson getPerson() { + return toEdit; + } + + @Override + public CommandResult execute() { + try { + final ReadOnlyPerson target = getTargetPerson(); + toEdit.changeName(target.getName()); + addressBook.editPerson(target, toEdit); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, toEdit)); + + } catch (IndexOutOfBoundsException ie) { + return new CommandResult(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } catch (PersonNotFoundException pnfe) { + return new CommandResult(Messages.MESSAGE_PERSON_NOT_IN_ADDRESSBOOK); + } + } + +} diff --git a/src/seedu/addressbook/commands/HelpCommand.java b/src/seedu/addressbook/commands/HelpCommand.java index ef2ed7d0e..ad2fbe778 100644 --- a/src/seedu/addressbook/commands/HelpCommand.java +++ b/src/seedu/addressbook/commands/HelpCommand.java @@ -12,10 +12,12 @@ public class HelpCommand extends Command { + "Example: " + COMMAND_WORD; public static final String MESSAGE_ALL_USAGES = AddCommand.MESSAGE_USAGE + + "\n" + EditCommand.MESSAGE_USAGE + "\n" + DeleteCommand.MESSAGE_USAGE + "\n" + ClearCommand.MESSAGE_USAGE + "\n" + FindCommand.MESSAGE_USAGE + "\n" + ListCommand.MESSAGE_USAGE + + "\n" + SortCommand.MESSAGE_USAGE + "\n" + ViewCommand.MESSAGE_USAGE + "\n" + ViewAllCommand.MESSAGE_USAGE + "\n" + HelpCommand.MESSAGE_USAGE diff --git a/src/seedu/addressbook/commands/SortCommand.java b/src/seedu/addressbook/commands/SortCommand.java new file mode 100644 index 000000000..0c9019dd1 --- /dev/null +++ b/src/seedu/addressbook/commands/SortCommand.java @@ -0,0 +1,33 @@ +package seedu.addressbook.commands; + +import seedu.addressbook.data.exception.IllegalValueException; +import seedu.addressbook.data.person.*; +import seedu.addressbook.data.tag.Tag; +import seedu.addressbook.data.tag.UniqueTagList; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Sorts the address book by name in ascending order. + */ +public class SortCommand extends Command { + + public static final String COMMAND_WORD = "sort"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\n" + + "Sorts the address book by name in ascending order, then display it.\n\t" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_SUCCESS = "Address book is sorted as above.\n\n"; + + @Override + public CommandResult execute() { + addressBook.sort(); + List allPersons = addressBook.getAllPersons().immutableListView(); + return new CommandResult(MESSAGE_SUCCESS + + getMessageForPersonListShownSummary(allPersons), allPersons); + } + +} \ No newline at end of file diff --git a/src/seedu/addressbook/data/AddressBook.java b/src/seedu/addressbook/data/AddressBook.java index 7af05d271..3186fc457 100644 --- a/src/seedu/addressbook/data/AddressBook.java +++ b/src/seedu/addressbook/data/AddressBook.java @@ -82,6 +82,19 @@ public void addPerson(Person toAdd) throws DuplicatePersonException { allPersons.add(toAdd); } + public void editPerson(ReadOnlyPerson target, Person editedPerson) throws PersonNotFoundException { + syncTagsWithMasterList(editedPerson); + allPersons.editPerson(target, editedPerson); + } + + + /** + * Sorts the address book by name in ascending order. + */ + public void sort() { + allPersons.sort(); + } + /** * Checks if an equivalent person exists in the address book. */ diff --git a/src/seedu/addressbook/data/person/Person.java b/src/seedu/addressbook/data/person/Person.java index cf6211841..a3b6d5f87 100644 --- a/src/seedu/addressbook/data/person/Person.java +++ b/src/seedu/addressbook/data/person/Person.java @@ -1,5 +1,6 @@ package seedu.addressbook.data.person; +import seedu.addressbook.data.exception.IllegalValueException; import seedu.addressbook.data.tag.UniqueTagList; import java.util.Objects; @@ -39,6 +40,15 @@ public Name getName() { return name; } + /** + * Change the name of a person + * + * @param newName + */ + public void changeName(Name newName) { + name = newName; + } + @Override public Phone getPhone() { return phone; diff --git a/src/seedu/addressbook/data/person/UniquePersonList.java b/src/seedu/addressbook/data/person/UniquePersonList.java index c4848a1b4..c9b21bced 100644 --- a/src/seedu/addressbook/data/person/UniquePersonList.java +++ b/src/seedu/addressbook/data/person/UniquePersonList.java @@ -106,6 +106,30 @@ public void remove(ReadOnlyPerson toRemove) throws PersonNotFoundException { } } + /** + * Edits the person {@code target} in the list into {@code editedPerson}. + * + * @throws PersonNotFoundException if {@code target} could not be found in the list. + */ + public void editPerson(ReadOnlyPerson target, Person editedPerson) throws PersonNotFoundException { + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + internalList.set(index, editedPerson); + } + + /** + * Sorts the address book by name in ascending order. + */ + public void sort() { + internalList.sort(new Comparator() { + public int compare(Person p1, Person p2) { + return p1.getName().toString().compareTo(p2.getName().toString()); + } + }); + } + /** * Clears all persons in list. */ diff --git a/src/seedu/addressbook/parser/Parser.java b/src/seedu/addressbook/parser/Parser.java index 58f4f7e6c..e96d25007 100644 --- a/src/seedu/addressbook/parser/Parser.java +++ b/src/seedu/addressbook/parser/Parser.java @@ -63,6 +63,9 @@ public Command parseCommand(String userInput) { case DeleteCommand.COMMAND_WORD: return prepareDelete(arguments); + case EditCommand.COMMAND_WORD: + return prepareEdit(arguments); + case ClearCommand.COMMAND_WORD: return new ClearCommand(); @@ -72,6 +75,9 @@ public Command parseCommand(String userInput) { case ListCommand.COMMAND_WORD: return new ListCommand(); + case SortCommand.COMMAND_WORD: + return new SortCommand(); + case ViewCommand.COMMAND_WORD: return prepareView(arguments); @@ -156,6 +162,41 @@ private Command prepareDelete(String args) { } } + /** + * Parses arguments in the context of the edit person command. + * + * @param args full command args string + * @return the prepared command + */ + private Command prepareEdit(String args){ + final Matcher matcher = PERSON_DATA_ARGS_FORMAT.matcher(args.trim()); + // Validate arg string format + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + try { + final int targetIndex = parseArgsAsDisplayedIndex(matcher.group("name")); + return new EditCommand( + targetIndex, + + matcher.group("phone"), + isPrivatePrefixPresent(matcher.group("isPhonePrivate")), + + matcher.group("email"), + isPrivatePrefixPresent(matcher.group("isEmailPrivate")), + + matcher.group("address"), + isPrivatePrefixPresent(matcher.group("isAddressPrivate")), + + getTagsFromArgs(matcher.group("tagArguments")) + ); + } catch (IllegalValueException ive) { + return new IncorrectCommand(ive.getMessage()); + } catch (ParseException | NumberFormatException e) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + } + /** * Parses arguments in the context of the view command. * diff --git a/test/java/seedu/addressbook/logic/LogicTest.java b/test/java/seedu/addressbook/logic/LogicTest.java index 7efa921ca..842200e65 100644 --- a/test/java/seedu/addressbook/logic/LogicTest.java +++ b/test/java/seedu/addressbook/logic/LogicTest.java @@ -181,7 +181,64 @@ public void execute_addDuplicate_notAllowed() throws Exception { expectedAB, false, Collections.emptyList()); + } + + @Test + public void execute_edit_invalidArgsFormat() throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE); + assertCommandBehavior( + "edit wrong args wrong args", expectedMessage); + assertCommandBehavior( + "edit invalidIndex p/12345 e/valid@email.butNoPhonePrefix a/valid, address", expectedMessage); + assertCommandBehavior( + "edit 1 12345 e/valid@email.butNoPhonePrefix a/valid, address", expectedMessage); + assertCommandBehavior( + "edit 1 p/12345 valid@email.butNoPrefix a/valid, address", expectedMessage); + assertCommandBehavior( + "edit 1 p/12345 e/valid@email.butNoAddressPrefix valid, address", expectedMessage); + } + + @Test + public void execute_edit_invalidPersonData() throws Exception { + assertCommandBehavior( + "edit 1 p/not_numbers e/valid@e.mail a/valid, address", Phone.MESSAGE_PHONE_CONSTRAINTS); + assertCommandBehavior( + "edit 1 p/12345 e/notAnEmail a/valid, address", Email.MESSAGE_EMAIL_CONSTRAINTS); + assertCommandBehavior( + "edit 1 p/12345 e/valid@e.mail a/valid, address t/invalid_-[.tag", Tag.MESSAGE_TAG_CONSTRAINTS); + + } + + @Test + public void execute_edit_successful() throws Exception { + // prepare expectations + TestDataHelper helper = new TestDataHelper(); + Person p1 = helper.generatePersonWithName("Alpha"); + Person p2 = helper.generatePersonWithName("Beta"); + Person p3 = helper.generatePersonWithName("Gamma"); + + List personList = helper.generatePersonList(p1, p2, p3); + AddressBook expectedAB = helper.generateAddressBook(personList); + // prepare address book state + helper.addToAddressBook(addressBook, personList); + final Set tagSet = new HashSet<>(); + tagSet.add(new Tag("secretive")); + Person editedPerson = new Person( + new Name("Beta"), + new Phone("911", true), + new Email("beta@gmail.com", false), + new Address("somewhere", true), + new UniqueTagList(tagSet) + ); + logic.setLastShownList(personList); + expectedAB.editPerson(p2, editedPerson); + + assertCommandBehavior("edit 2 pp/911 e/beta@gmail.com pa/somewhere t/secretive", + String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson), + expectedAB, + false, + personList); } @Test @@ -201,6 +258,52 @@ public void execute_list_showsAllPersons() throws Exception { expectedList); } + @Test + public void execute_sortEmptyList_emptyAddressBook() throws Exception { + TestDataHelper helper = new TestDataHelper(); + List emptyList = new ArrayList<>(); + List expectedList = new ArrayList<>(); + AddressBook expectedAB = helper.generateAddressBook(expectedList); + + // prepare address book state + helper.addToAddressBook(addressBook, emptyList); + + String messageSuccess = SortCommand.MESSAGE_SUCCESS + + SortCommand.getMessageForPersonListShownSummary(expectedList); + + assertCommandBehavior("sort", + messageSuccess, + expectedAB, + true, + expectedList); + } + + @Test + public void execute_sortUnorderedList_showsAllPersons() throws Exception { + // prepare expectations + TestDataHelper helper = new TestDataHelper(); + Person p1 = helper.generatePersonWithName("Alpha"); + Person p2 = helper.generatePersonWithName("Beta"); + Person p3 = helper.generatePersonWithName("Gamma"); + Person p4 = helper.generatePersonWithName("Delta"); + + List unorderedList = helper.generatePersonList(p3, p4, p1, p2); + List expectedList = helper.generatePersonList(p1, p2, p4, p3); + AddressBook expectedAB = helper.generateAddressBook(expectedList); + + // prepare address book state + helper.addToAddressBook(addressBook, unorderedList); + + String messageSuccess = SortCommand.MESSAGE_SUCCESS + + SortCommand.getMessageForPersonListShownSummary(expectedList); + + assertCommandBehavior("sort", + messageSuccess, + expectedAB, + true, + expectedList); + } + @Test public void execute_view_invalidArgsFormat() throws Exception { String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE); diff --git a/test/java/seedu/addressbook/parser/ParserTest.java b/test/java/seedu/addressbook/parser/ParserTest.java index f01be613c..b2a732129 100644 --- a/test/java/seedu/addressbook/parser/ParserTest.java +++ b/test/java/seedu/addressbook/parser/ParserTest.java @@ -59,6 +59,12 @@ public void listCommand_parsedCorrectly() { parseAndAssertCommandType(input, ListCommand.class); } + @Test + public void sortCommand_parsedCorrectly() { + final String input = "sort"; + parseAndAssertCommandType(input, SortCommand.class); + } + @Test public void exitCommand_parsedCorrectly() { final String input = "exit"; @@ -246,6 +252,79 @@ public void addCommand_duplicateTags_merged() throws IllegalValueException { assertEquals(result.getPerson(), testPerson); } + @Test + public void editCommand_invalidArgs() { + final String[] inputs = { + "edit", + "edit ", + "edit wrong args format", + // not an index + String.format("edit $s $s e/$s a/$s", Name.EXAMPLE, Phone.EXAMPLE, Email.EXAMPLE, Address.EXAMPLE), + // no phone prefix + String.format("edit 1 $s e/$s a/$s", Phone.EXAMPLE, Email.EXAMPLE, Address.EXAMPLE), + // no email prefix + String.format("edit 1 p/$s $s a/$s", Phone.EXAMPLE, Email.EXAMPLE, Address.EXAMPLE), + // no address prefix + String.format("edit 1 p/$s e/$s $s", Phone.EXAMPLE, Email.EXAMPLE, Address.EXAMPLE) + }; + final String resultMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE); + parseAndAssertIncorrectWithMessage(resultMessage, inputs); + } + + @Test + public void editCommand_invalidPersonDataInArgs() { + final String invalidIndex = "[@]\\!?/[;]"; + final String validIndex = "1"; + final String invalidPhoneArg = "p/not__numbers"; + final String validPhoneArg = "p/" + Phone.EXAMPLE; + final String invalidEmailArg = "e/notAnEmail123"; + final String validEmailArg = "e/" + Email.EXAMPLE; + final String invalidTagArg = "t/invalid_-[.tag"; + + // address can be any string, so no invalid address + final String editCommandFormatString = "edit $s $s $s a/" + Address.EXAMPLE; + + // test each incorrect person data field argument individually + final String[] inputs = { + // invalid index + String.format(editCommandFormatString, invalidIndex, validPhoneArg, validEmailArg), + // invalid phone + String.format(editCommandFormatString, validIndex, invalidPhoneArg, validEmailArg), + // invalid email + String.format(editCommandFormatString, validIndex, validPhoneArg, invalidEmailArg), + // invalid tag + String.format(editCommandFormatString, validIndex, validPhoneArg, validEmailArg) + " " + invalidTagArg + }; + for (String input : inputs) { + parseAndAssertCommandType(input, IncorrectCommand.class); + } + } + + @Test + public void editCommand_validPersonData_parsedCorrectly() throws IllegalValueException{ + final Person testPerson = generateTestPerson(); + testPerson.changeName(new Name("dummy")); + final String addInput = convertPersonToAddCommandString(testPerson); + final String input = "edit 1 " + addInput.substring(10); + final EditCommand result = parseAndAssertCommandType(input, EditCommand.class); + assertEquals(result.getPerson(), testPerson); + } + + @Test + public void editCommand_duplicateTags_merged() throws IllegalValueException { + final Person testPerson = generateTestPerson(); + testPerson.changeName(new Name("dummy")); + final String addInput = convertPersonToAddCommandString(testPerson); + String input = "edit 1 " + addInput.substring(10); + for (Tag tag : testPerson.getTags()) { + // create duplicates by doubling each tag + input += " t/" + tag.tagName; + } + + final EditCommand result = parseAndAssertCommandType(input, EditCommand.class); + assertEquals(result.getPerson(), testPerson); + } + private static Person generateTestPerson() { try { return new Person(