Skip to content

Commit

Permalink
Merge pull request #190 from Rachael-Chan/find-tags
Browse files Browse the repository at this point in the history
Update find to find tags
  • Loading branch information
Rachael-Chan authored Nov 6, 2024
2 parents cc9010a + 00211ae commit 8d6b1e1
Show file tree
Hide file tree
Showing 8 changed files with 396 additions and 27 deletions.
6 changes: 4 additions & 2 deletions docs/UserGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Examples:

Finds contacts whose names or/and phone numbers or/and address contain any of the given field keywords.

Format: `find [n/NAMEKEYWORDS] [p/PHONEKEYWORDS] [a/ADDRESSKEYWORDS]`
Format: `find [n/NAMEKEYWORDS] [p/PHONEKEYWORDS] [a/ADDRESSKEYWORDS] [t/TAGKEYWORDS]`

**NOTE:** At least one field MUST be provided
e.g. `find n/Hans` or `find p/82345678` or `find a/wall street` will work
Expand All @@ -141,6 +141,8 @@ Format: `find [n/NAMEKEYWORDS] [p/PHONEKEYWORDS] [a/ADDRESSKEYWORDS]`
* Contacts matching at least one keyword will be returned (i.e. `OR` search).
e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
* If more than one fields are specified, contacts will be matched by multiple fields (i.e. `AND` search).
* For multiple address or tag keywords, they are separated by "_". e.g `find t/friends_colleague_owes money` or `find a/wall street_michigan`
* For multiple name or phone keywords, they are separated by " ". e.g `find n/andy ben carl` or `find p/98233211 81212899`

<box type="tip" seamless>

Expand Down Expand Up @@ -355,7 +357,7 @@ Action | Format, Examples
**Clear** | `clear`
**Delete** | `delete INDEX`<br> e.g., `delete 3`
**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​ [d/DATE_OF_LAST_VISIT] [ec/EMERGENCY_CONTACT] [r/REMARK]` <br> e.g.,`edit 2 n/James Lee e/[email protected]`
**Find** | `find [n/NAMEKEYWORD] [p/PHONEKEYWORD] [a/ADDRESSKEYWORD]`<br> e.g., `find n/James Jake a/clementi street_woodlands`
**Find** | `find [n/NAMEKEYWORD] [p/PHONEKEYWORD] [a/ADDRESSKEYWORD] [t/TAGKEYWORD]`<br> e.g., `find n/James Jake a/clementi street_woodlands`
**List** | `list`
**View** | `view INDEX`<br> e.g.,`view 1`
**Help** | `help`
Expand Down
77 changes: 67 additions & 10 deletions src/main/java/seedu/address/logic/commands/FindCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;

import java.util.Objects;
import java.util.function.Predicate;
Expand All @@ -15,6 +16,7 @@
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
import seedu.address.model.person.PhoneContainsKeywordsPredicate;
import seedu.address.model.person.TagContainsKeywordsPredicate;

/**
* Finds and lists all persons in address book whose name contains any of the argument keywords.
Expand All @@ -29,7 +31,8 @@ public class FindCommand extends Command {
+ "Parameters: "
+ PREFIX_NAME + "NAME KEYWORD(s) "
+ PREFIX_PHONE + "PHONE KEYWORD(s) "
+ PREFIX_ADDRESS + "ADDRESS KEYWORD(s)\n"
+ PREFIX_ADDRESS + "ADDRESS KEYWORD(s) "
+ PREFIX_TAG + "TAG KEYWORD(s)\n"
+ "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "alice bob charlie\n"
+ COMMAND_WORD + " " + PREFIX_ADDRESS + "blk 40_blk 50_blk 60\n"
+ COMMAND_WORD + " " + PREFIX_PHONE + "9243 9312";
Expand All @@ -38,6 +41,7 @@ public class FindCommand extends Command {
private final NameContainsKeywordsPredicate namePredicate;
private final PhoneContainsKeywordsPredicate phonePredicate;
private final AddressContainsKeywordsPredicate addressPredicate;
private final TagContainsKeywordsPredicate tagPredicate;

/**
* Constructs a FindCommand object with optional predicates for filtering by name, phone, and address.
Expand All @@ -48,17 +52,22 @@ public class FindCommand extends Command {
* or null if no phone filtering is required.
* @param addressPredicate The predicate used to filter persons by their address,
* or null if no address filtering is required.
* @param tagPredicate The predicate used to filter persons by their tag,
* or null if no tag filtering is required.
*/
public FindCommand(NameContainsKeywordsPredicate namePredicate,
PhoneContainsKeywordsPredicate phonePredicate,
AddressContainsKeywordsPredicate addressPredicate) {
AddressContainsKeywordsPredicate addressPredicate,
TagContainsKeywordsPredicate tagPredicate) {
this.namePredicate = namePredicate;
this.phonePredicate = phonePredicate;
this.addressPredicate = addressPredicate;
this.tagPredicate = tagPredicate;

Predicate<Person> basePredicate = ((namePredicate == null)
&& (phonePredicate == null)
&& (addressPredicate == null))
&& (addressPredicate == null)
&& (tagPredicate == null))
? person -> false : person -> true;

if (namePredicate != null) {
Expand All @@ -70,35 +79,81 @@ public FindCommand(NameContainsKeywordsPredicate namePredicate,
if (addressPredicate != null) {
basePredicate = basePredicate.and(addressPredicate);
}
if (tagPredicate != null) {
basePredicate = basePredicate.and(tagPredicate);
}

this.combinedPredicate = basePredicate;
}

public FindCommand(NameContainsKeywordsPredicate namePredicate,
PhoneContainsKeywordsPredicate phonePredicate,
AddressContainsKeywordsPredicate addressPredicate) {
this(namePredicate, phonePredicate, addressPredicate, null);
}

public FindCommand(NameContainsKeywordsPredicate namePredicate,
PhoneContainsKeywordsPredicate phonePredicate,
TagContainsKeywordsPredicate tagPredicate) {
this(namePredicate, phonePredicate, null, tagPredicate);
}

public FindCommand(NameContainsKeywordsPredicate namePredicate,
AddressContainsKeywordsPredicate addressPredicate,
TagContainsKeywordsPredicate tagPredicate) {
this(namePredicate, null, addressPredicate, tagPredicate);
}

public FindCommand(PhoneContainsKeywordsPredicate phonePredicate,
AddressContainsKeywordsPredicate addressPredicate,
TagContainsKeywordsPredicate tagPredicate) {
this(null, phonePredicate, addressPredicate, tagPredicate);
}

public FindCommand(NameContainsKeywordsPredicate namePredicate,
PhoneContainsKeywordsPredicate phonePredicate) {
this(namePredicate, phonePredicate, null);
this(namePredicate, phonePredicate, null, null);
}

public FindCommand(PhoneContainsKeywordsPredicate phonePredicate,
AddressContainsKeywordsPredicate addressPredicate) {
this(null, phonePredicate, addressPredicate);
this(null, phonePredicate, addressPredicate, null);
}

public FindCommand(NameContainsKeywordsPredicate namePredicate,
TagContainsKeywordsPredicate tagPredicate) {
this(namePredicate, null, null, tagPredicate);
}

public FindCommand(NameContainsKeywordsPredicate namePredicate,
AddressContainsKeywordsPredicate addressPredicate) {
this(namePredicate, null, addressPredicate);
this(namePredicate, null, addressPredicate, null);
}

public FindCommand(PhoneContainsKeywordsPredicate phonePredicate,
TagContainsKeywordsPredicate tagPredicate) {
this(null, phonePredicate, null, tagPredicate);
}

public FindCommand(AddressContainsKeywordsPredicate addressPredicate,
TagContainsKeywordsPredicate tagPredicate) {
this(null, null, addressPredicate, tagPredicate);
}

public FindCommand(NameContainsKeywordsPredicate namePredicate) {
this(namePredicate, null, null);
this(namePredicate, null, null, null);
}

public FindCommand(PhoneContainsKeywordsPredicate phonePredicate) {
this(null, phonePredicate, null);
this(null, phonePredicate, null, null);
}

public FindCommand(AddressContainsKeywordsPredicate addressPredicate) {
this(null, null, addressPredicate);
this(null, null, addressPredicate, null);
}

public FindCommand(TagContainsKeywordsPredicate tagPredicate) {
this(null, null, null, tagPredicate);
}

@Override
Expand All @@ -124,7 +179,8 @@ public boolean equals(Object other) {

return (Objects.equals(this.namePredicate, otherFindCommand.namePredicate))
&& (Objects.equals(this.phonePredicate, otherFindCommand.phonePredicate))
&& (Objects.equals(this.addressPredicate, otherFindCommand.addressPredicate));
&& (Objects.equals(this.addressPredicate, otherFindCommand.addressPredicate))
&& (Objects.equals(this.tagPredicate, otherFindCommand.tagPredicate));
}

@Override
Expand All @@ -133,6 +189,7 @@ public String toString() {
.add("namePredicate", namePredicate == null ? "null" : namePredicate.toString())
.add("phonePredicate", phonePredicate == null ? "null" : phonePredicate.toString())
.add("addressPredicate", addressPredicate == null ? "null" : addressPredicate.toString())
.add("tagPredicate", tagPredicate == null ? "null" : tagPredicate.toString())
.toString();
}
}
17 changes: 13 additions & 4 deletions src/main/java/seedu/address/logic/parser/FindCommandParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;

import java.util.Arrays;

Expand All @@ -12,6 +13,7 @@
import seedu.address.model.person.AddressContainsKeywordsPredicate;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.PhoneContainsKeywordsPredicate;
import seedu.address.model.person.TagContainsKeywordsPredicate;

/**
* Parses input arguments and creates a new FindCommand object
Expand All @@ -25,14 +27,14 @@ public class FindCommandParser implements Parser<FindCommand> {
*/
public FindCommand parse(String args) throws ParseException {
ArgumentMultimap argMultimap =
ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS);
ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, PREFIX_TAG);

if (!argMultimap.getPreamble().isEmpty() || args.trim().isEmpty()) {
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}

argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS);
argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, PREFIX_TAG);

String[] nameKeywords = argMultimap.isPresent(PREFIX_NAME)
? argMultimap.getValue(PREFIX_NAME).get().trim().split("\\s+")
Expand All @@ -41,7 +43,12 @@ public FindCommand parse(String args) throws ParseException {
? argMultimap.getValue(PREFIX_PHONE).get().trim().split("\\s+")
: null;
String[] addressKeywords = argMultimap.isPresent(PREFIX_ADDRESS)
? argMultimap.getValue(PREFIX_ADDRESS).get().trim().split("_")
? Arrays.stream(argMultimap.getValue(PREFIX_ADDRESS).get().trim().split("_"))
.map(String::trim).toArray(String[]::new)
: null;
String[] tagKeywords = argMultimap.isPresent(PREFIX_TAG)
? Arrays.stream(argMultimap.getValue(PREFIX_TAG).get().trim().split("_"))
.map(String::trim).toArray(String[]::new)
: null;

// Create the specific predicates, or pass null if no valid keywords were provided
Expand All @@ -51,8 +58,10 @@ public FindCommand parse(String args) throws ParseException {
? new PhoneContainsKeywordsPredicate(Arrays.asList(phoneKeywords)) : null;
AddressContainsKeywordsPredicate addressPredicate = addressKeywords != null
? new AddressContainsKeywordsPredicate(Arrays.asList(addressKeywords)) : null;
TagContainsKeywordsPredicate tagPredicate = tagKeywords != null
? new TagContainsKeywordsPredicate(Arrays.asList(tagKeywords)) : null;

return new FindCommand(namePredicate, phonePredicate, addressPredicate);
return new FindCommand(namePredicate, phonePredicate, addressPredicate, tagPredicate);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package seedu.address.model.person;

import java.util.List;
import java.util.function.Predicate;

import seedu.address.commons.util.ToStringBuilder;

/**
* Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
*/
public class TagContainsKeywordsPredicate implements Predicate<Person> {
private final List<String> keywords;

public TagContainsKeywordsPredicate(List<String> keywords) {
this.keywords = keywords;
}

@Override
public boolean test(Person person) {
// Checks if the string i.e (tags) contains a keyword, allowing partial matching of tags via find command
return keywords.stream()
.anyMatch(keyword -> person.getTags().stream()
.anyMatch(tag -> tag.tagName.toLowerCase().contains(keyword.toLowerCase())));
}

@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}

// instanceof handles nulls
if (!(other instanceof TagContainsKeywordsPredicate) && other != null) {
return false;
}

TagContainsKeywordsPredicate otherTagContainsKeywordsPredicate = (TagContainsKeywordsPredicate) other;
return keywords.equals(otherTagContainsKeywordsPredicate != null
? otherTagContainsKeywordsPredicate.keywords : null);
}

@Override
public String toString() {
return new ToStringBuilder(this).add("keywords", keywords).toString();
}
}
44 changes: 43 additions & 1 deletion src/test/java/seedu/address/logic/commands/FindCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BENSON;
import static seedu.address.testutil.TypicalPersons.CARL;
import static seedu.address.testutil.TypicalPersons.DANIEL;
import static seedu.address.testutil.TypicalPersons.ELLE;
import static seedu.address.testutil.TypicalPersons.FIONA;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
Expand All @@ -23,6 +25,7 @@
import seedu.address.model.person.AddressContainsKeywordsPredicate;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.PhoneContainsKeywordsPredicate;
import seedu.address.model.person.TagContainsKeywordsPredicate;


public class FindCommandTest {
Expand Down Expand Up @@ -116,6 +119,26 @@ public void execute_multipleAddressKeywords_multiplePersonsFound() {
assertEquals(List.of(CARL, ELLE, FIONA), model.getFilteredPersonList());
}

@Test
public void execute_tagKeywords_onePersonFound() {
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1);
TagContainsKeywordsPredicate predicate = prepareTagPredicate("owes money");
FindCommand command = new FindCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(List.of(BENSON), model.getFilteredPersonList());
}

@Test
public void execute_multipleTagKeywords_multiplePersonsFound() {
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3);
TagContainsKeywordsPredicate predicate = prepareTagPredicate("owes money_friend");
FindCommand command = new FindCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(List.of(ALICE, BENSON, DANIEL), model.getFilteredPersonList());
}

@Test
public void execute_multipleFields_onePersonFound() {
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1);
Expand All @@ -142,13 +165,28 @@ public void execute_multipleFields_multiplePersonsFound() {
assertEquals(Arrays.asList(BENSON, CARL, ELLE), model.getFilteredPersonList());
}

@Test
public void execute_allFields_onePersonFound() {
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1);
NameContainsKeywordsPredicate namePredicate = prepareNamePredicate("benson");
PhoneContainsKeywordsPredicate phonePredicate = preparePhonePredicate("98765432");
AddressContainsKeywordsPredicate addressPredicate = prepareAddressPredicate("Clementi Ave 2_");
TagContainsKeywordsPredicate tagPredicate = prepareTagPredicate("friends");

FindCommand command = new FindCommand(namePredicate, phonePredicate, addressPredicate, tagPredicate);
expectedModel.updateFilteredPersonList(namePredicate.and(phonePredicate)
.and(addressPredicate).and(tagPredicate));
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(Arrays.asList(BENSON), model.getFilteredPersonList());
}

@Test
public void toStringMethod() {
NameContainsKeywordsPredicate namePredicate = new NameContainsKeywordsPredicate(Arrays.asList("keyword"));
PhoneContainsKeywordsPredicate phonePredicate = new PhoneContainsKeywordsPredicate(Arrays.asList("phone"));
FindCommand findCommand = new FindCommand(namePredicate, phonePredicate);
String expected = FindCommand.class.getCanonicalName() + "{namePredicate=" + namePredicate + ", phonePredicate="
+ phonePredicate + ", addressPredicate=null}";
+ phonePredicate + ", addressPredicate=null, tagPredicate=null}";
assertEquals(expected, findCommand.toString());
}

Expand All @@ -166,4 +204,8 @@ private PhoneContainsKeywordsPredicate preparePhonePredicate(String userInput) {
private AddressContainsKeywordsPredicate prepareAddressPredicate(String userInput) {
return new AddressContainsKeywordsPredicate(Arrays.asList(userInput.split("_")));
}

private TagContainsKeywordsPredicate prepareTagPredicate(String userInput) {
return new TagContainsKeywordsPredicate(Arrays.asList(userInput.split("_")));
}
}
Loading

0 comments on commit 8d6b1e1

Please sign in to comment.