diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 4abb17e3e..e7928e539 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -37,6 +37,7 @@ What's different from AddressBook-Level1: * Support for storing address (`a/`) and tags (`t/`) * Support for marking a contact detail as 'private' (`pa/`) (`pe/`) (`pp/`) * View details of a person (`view` : shows non-private details), (`viewall` : shows all details) +* Search for persons by tags (`find` : searches for persons by name), (`findtag` : searches for persons by tags) == Viewing help : `help` @@ -89,7 +90,25 @@ Examples: Returns `John Doe` but not `john`. * `find Betsy Tim John` + -Returns Any person having names `Betsy`, `Tim`, or `John`. +Returns any person having names `Betsy`, `Tim`, or `John`. + +== Finding all persons containing any keyword in their tags: `findtag` + +Finds persons who are tagged with the given keywords. + +Format: `findtag KEYWORD [MORE_KEYWORDS]` + +[NOTE] +==== +The search is case sensitive, the order of the keywords does not matter, only the tags searched, +and persons with at least one tag in the keywords will be returned. +==== + +Examples: +* `findtag friends` + +Returns all the people with the tag `friends`. + +* `findtag adorable pretty hot` + +Returns any person with any one of the tags `adorable`, `pretty` or `hot`. == Deleting a person : `delete` diff --git a/src/seedu/addressbook/commands/FindTagCommand.java b/src/seedu/addressbook/commands/FindTagCommand.java new file mode 100644 index 000000000..a8ecd56d7 --- /dev/null +++ b/src/seedu/addressbook/commands/FindTagCommand.java @@ -0,0 +1,68 @@ +package seedu.addressbook.commands; + +import seedu.addressbook.data.person.ReadOnlyPerson; +import seedu.addressbook.data.tag.Tag; +import seedu.addressbook.data.exception.IllegalValueException; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.List; +import java.util.Collections; + + +/** + * Finds and lists all the people in the address book who are tagged with the argument keywords. + * Keyword matching is case sensitive. + */ +public class FindTagCommand extends Command { + + public static final String COMMAND_WORD = "findtag"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Finds all the people who are tagged with the specified keywords (case-sensitive) and " + + "displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " isCute isHandsome"; + + private final Set keywords; + + public FindTagCommand(Set keywords) { this.keywords = keywords; } + + /** + * Returns a copy of keywords in this command. + */ + public Set getKeywords() { + return new HashSet<>(keywords); + } + + @Override + public CommandResult execute() { + final List personsFound = getPersonsWithTagsContainingAnyKeyword(keywords); + return new CommandResult(getMessageForPersonListShownSummary(personsFound), personsFound); + } + + private List getPersonsWithTagsContainingAnyKeyword (Set keywords) { + // convert the keywords into tags + final Set keywordTags = new HashSet<>(); + for (String keyword : keywords) { + try { + keywordTags.add(new Tag(keyword)); + } catch (IllegalValueException e) { + // if an invalid string is provided as argument, just ignore, since + // the string will not be able to be the tag of a Person. + continue; + } + } + + // compare tags + final List matchedPersons = new ArrayList<>(); + for (ReadOnlyPerson person : addressBook.getAllPersons()) { + final Set personsTags = person.getTags(); + if (!Collections.disjoint(keywordTags, personsTags)) { + matchedPersons.add(person); + } + } + return matchedPersons; + } +} diff --git a/src/seedu/addressbook/parser/Parser.java b/src/seedu/addressbook/parser/Parser.java index abddb3f45..d54219ceb 100644 --- a/src/seedu/addressbook/parser/Parser.java +++ b/src/seedu/addressbook/parser/Parser.java @@ -22,6 +22,7 @@ import seedu.addressbook.commands.ListCommand; import seedu.addressbook.commands.ViewAllCommand; import seedu.addressbook.commands.ViewCommand; +import seedu.addressbook.commands.FindTagCommand; import seedu.addressbook.data.exception.IllegalValueException; /** @@ -97,6 +98,9 @@ public Command parseCommand(String userInput) { case ExitCommand.COMMAND_WORD: return new ExitCommand(); + case FindTagCommand.COMMAND_WORD: + return prepareFindTag(arguments); + case HelpCommand.COMMAND_WORD: // Fallthrough default: return new HelpCommand(); @@ -249,4 +253,23 @@ private Command prepareFind(String args) { } + /** + * Parses arguments in the context of the find tag command. + * + * @param args full command args string + * @return the prepared command + */ + private Command prepareFindTag(String args) { + final Matcher matcher = KEYWORDS_ARGS_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindTagCommand.MESSAGE_USAGE)); + } + + // keywords delimited by whitespace + final String[] keywords = matcher.group("keywords").split("\\s+"); + final Set keywordSet = new HashSet<>(Arrays.asList(keywords)); + return new FindTagCommand(keywordSet); + } + } diff --git a/test/expected.txt b/test/expected.txt index 56fe5fcac..915f60ef0 100644 --- a/test/expected.txt +++ b/test/expected.txt @@ -233,6 +233,41 @@ || || 2 persons listed! || =================================================== +|| Enter command: || [Command entered: findtag] +|| Invalid command format! +|| findtag: Finds all the people who are tagged with the specified keywords (case-sensitive) and displays them as a list with index numbers. +|| Parameters: KEYWORD [MORE_KEYWORDS]... +|| Example: findtag isCute isHandsome +|| =================================================== +|| Enter command: || [Command entered: findtag fre] +|| +|| 0 persons listed! +|| =================================================== +|| Enter command: || [Command entered: findtag nonexistent] +|| +|| 0 persons listed! +|| =================================================== +|| Enter command: || [Command entered: findtag FRIENDS] +|| +|| 0 persons listed! +|| =================================================== +|| Enter command: || [Command entered: findtag secretive] +|| 1. Betsy Choo Tags: [secretive] +|| +|| 1 persons listed! +|| =================================================== +|| Enter command: || [Command entered: findtag friends] +|| 1. Charlie Dickson Email: charlie.d@nus.edu.sg Address: 333, gamma street Tags: [school][friends] +|| 2. Dickson Ee Phone: 444444 Address: 444, delta street Tags: [friends] +|| +|| 2 persons listed! +|| =================================================== +|| Enter command: || [Command entered: findtag secretive school] +|| 1. Betsy Choo Tags: [secretive] +|| 2. Charlie Dickson Email: charlie.d@nus.edu.sg Address: 333, gamma street Tags: [school][friends] +|| +|| 2 persons listed! +|| =================================================== || Enter command: || [Command entered: delete] || Invalid command format! || delete: Deletes the person identified by the index number used in the last person listing. diff --git a/test/input.txt b/test/input.txt index eb8df81f8..2cf611400 100644 --- a/test/input.txt +++ b/test/input.txt @@ -106,6 +106,26 @@ # find multiple with some keywords find Charlie Betsy +########################################################## +# test find tag command +########################################################## + + # should consider no keywords as invalid command format + findtag + # should only match full words in tag names + findtag fre + # does not match if none have keyword + findtag nonexistent + # matching should be case-sensitive + findtag FRIENDS + + # find unique keyword + findtag secretive + # find multiple with same keyword + findtag friends + # find multiple with some keywords + findtag secretive school + ########################################################## # test delete person command ########################################################## diff --git a/test/java/seedu/addressbook/commands/FindTagCommandTest.java b/test/java/seedu/addressbook/commands/FindTagCommandTest.java new file mode 100644 index 000000000..2c04f12a5 --- /dev/null +++ b/test/java/seedu/addressbook/commands/FindTagCommandTest.java @@ -0,0 +1,66 @@ +package seedu.addressbook.commands; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.Test; + +import seedu.addressbook.data.AddressBook; +import seedu.addressbook.data.exception.IllegalValueException; +import seedu.addressbook.data.person.ReadOnlyPerson; +import seedu.addressbook.util.TypicalPersons; + +public class FindTagCommandTest { + + private final AddressBook addressBook = new TypicalPersons().getTypicalAddressBook(); + private final TypicalPersons td = new TypicalPersons(); + + @Test + public void execute() throws IllegalValueException { + //same word, same case: matched + assertFindTagCommandBehavior(new String[]{"test1"}, Arrays.asList(td.amy, td.dan)); + + //same word, different case: not matched + assertFindTagCommandBehavior(new String[]{"Test1"}, Collections.emptyList()); + + //partial word: not matched + assertFindTagCommandBehavior(new String[]{"test"}, Collections.emptyList()); + + //multiple tags: matched + assertFindTagCommandBehavior(new String[]{"test1", "test2"}, + Arrays.asList(td.amy, td.bill, td.dan)); + + //repeated tags: matched + assertFindTagCommandBehavior(new String[]{"test2", "test2"}, Arrays.asList(td.bill)); + + //Keyword matching a name: not matched + assertFindTagCommandBehavior(new String[]{"Amy Buck"}, Collections.emptyList()); + + //Keyword matching a word in address: not matched + assertFindTagCommandBehavior(new String[]{"Clementi"}, Collections.emptyList()); + } + + /** + * Executes the findtag command for the given keywords and verifies + * the result matches the persons in the expectedPersonList exactly. + */ + private void assertFindTagCommandBehavior(String[] keywords, List expectedPersonList) { + FindTagCommand command = createFindTagCommand(keywords); + CommandResult result = command.execute(); + + assertEquals(Command.getMessageForPersonListShownSummary(expectedPersonList), result.feedbackToUser); + } + + private FindTagCommand createFindTagCommand(String[] keywords) { + final Set keywordSet = new HashSet<>(Arrays.asList(keywords)); + FindTagCommand command = new FindTagCommand(keywordSet); + command.setData(addressBook, Collections.emptyList()); + return command; + } + +} diff --git a/test/java/seedu/addressbook/util/TypicalPersons.java b/test/java/seedu/addressbook/util/TypicalPersons.java index cd3a3f819..9f2239a4f 100644 --- a/test/java/seedu/addressbook/util/TypicalPersons.java +++ b/test/java/seedu/addressbook/util/TypicalPersons.java @@ -21,13 +21,13 @@ public class TypicalPersons { public TypicalPersons() { try { amy = new Person(new Name("Amy Buck"), new Phone("91119111", false), new Email("ab@gmail.com", false), - new Address("1 Clementi Road", false), Collections.emptySet()); + new Address("1 Clementi Road", false), Collections.singleton(new Tag("test1"))); bill = new Person(new Name("Bill Clint"), new Phone("92229222", false), new Email("bc@gmail.com", false), - new Address("2 Clementi Road", true), Collections.emptySet()); + new Address("2 Clementi Road", true), Collections.singleton(new Tag("test2"))); candy = new Person(new Name("Candy Destiny"), new Phone("93339333", true), new Email("cd@gmail.com", false), new Address("3 Clementi Road", true), Collections.emptySet()); dan = new Person(new Name("Dan Smith"), new Phone("1234556", true), new Email("ss@tt.com", true), - new Address("NUS", true), Collections.singleton(new Tag("test"))); + new Address("NUS", true), Collections.singleton(new Tag("test1"))); } catch (IllegalValueException e) { e.printStackTrace(); assert false : "not possible";