Skip to content

Commit

Permalink
Merge pull request AY2324S1-CS2103T-F12-4#93 from qz1004/update-findc
Browse files Browse the repository at this point in the history
Modify findc to search for more fields
  • Loading branch information
howenc authored Oct 26, 2023
2 parents ab4a995 + 99100e2 commit 99df5ca
Show file tree
Hide file tree
Showing 37 changed files with 1,102 additions and 122 deletions.
33 changes: 19 additions & 14 deletions docs/UserGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Adds a contact to OutBook.

Format: `addc n/NAME p/PHONE_NUMBER e/EMAIL l/LAST_CONTACTED_TIME s/STATUS [t/TAG]…​`

* NAME, PHONE_NUMBER, EMAIL and LAST_CONTACTED_TIME are compulsory fields. STATUS and TAG are optional.
* NAME, PHONE_NUMBER, and EMAIL are compulsory fields. STATUS, TAG and LAST_CONTACTED_TIME are optional.
* PHONE_NUMBER must contain only numbers, and be at least 3 digits long.
* EMAIL must be of the format local-part@domain and adhere to the following constraints:
1. The local-part should only contain alphanumeric characters and these special characters, excluding the parentheses, (+_.-).
Expand Down Expand Up @@ -174,23 +174,28 @@ Examples:
* `viewc 2` Displays detailed information related to the 2nd contact on the list.


### Locating persons by name: `findc`
### Search for persons using contact fields: `findc`
Find persons whose contact details match the keywords specified for at least 1 of these fields: name, phone, email, status, tag

Find contacts whose names contain any of the given keywords.
Format: `findc [n/KEYWORDS] [p/KEYWORDS] [e/KEYWORDS] [l/DATETIME] [s/KEYWORDS] [t/KEYWORDS]`

Format: `findc 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 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`
* The search is case-insensitive. e.g `shop` will match `SHOP`
* The order of the keywords does not matter. e.g. `Shop Meet` will match `Meet Shop`
* For name, status and tags, only full words will be matched e.g. `Meet` will not match `Meeting`
* For email, any characters (alphanumeric, special characters) will be matched e.g. `_` will match `[email protected]`
* For phone, the entire length of the input digits will be matched e.g. `913` will match `90091300` but not `90103000`
* For last contacted time, the input must adhere to the dd.MM.yyyy HHmm format e.g. 9th October 2023 10.30am will be `09.10.2023 1030`
* For a single field, a Person must match at least one keyword to be returned as a result (i.e. `OR` search).
e.g. `John Doe` will return `John Lee`, `James Doe`
* If there are multiple fields specified, the Person must match at least one keyword in each field to be returned as a result (i.e. `AND` search).
e.g. `m/Shop Meet a/Mall` will return `Meeting: Shop at mall, Location:Mall`

Examples:
* `findc John` returns `john` and `John Doe`
* `findc alex david` returns `Alex Yeoh`, `David Li`<br>
![result for 'find alex david'](images/findAlexDavidResult.png)
* `findc n/alice` returns `Alice` and `alice tan`
* `findc p/51` returns `95163890` and `40351`
* `findc e/_@GMAIL` returns `[email protected]`
* `findc p/9 s/inactive claimant t/friend` returns persons with a `9` in their phone number, whose status is either `inactive` or `claimant`, and has a `friend` tag
![result for 'findContact'](images/findContactResult.png)


## Meeting commands
Expand Down
Binary file added docs/images/findContactResult.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions docs/team/qz1004.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ My contributions to the project are listed below.

- **Enhancements to existing features**:
- Add Status field to Person
- Improve `findc` to search for phone, email, status and tags in addition to name

- **Documentation**:
- Update Developer Guide with the features that I implemented
- Update User Guide with the features that I implemented
- "Add attendee" feature in Developer Guide
- Modified the User Guide for `addc`, `findc` and `addmc`

- **Community**:

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/seedu/address/logic/commands/AddCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class AddCommand extends Command {
+ PREFIX_NAME + "NAME "
+ PREFIX_PHONE + "PHONE "
+ PREFIX_EMAIL + "EMAIL "
+ PREFIX_LASTTIME + "LAST CONTACTED TIME "
+ "[" + PREFIX_LASTTIME + "LAST CONTACTED TIME] "
+ "[" + PREFIX_STATUS + "STATUS] "
+ "[" + PREFIX_REMARK + "REMARK] "
+ "[" + PREFIX_TAG + "TAG]...\n"
Expand Down
18 changes: 12 additions & 6 deletions src/main/java/seedu/address/logic/commands/FindCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
import seedu.address.model.Model;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.GeneralPersonPredicate;

/**
* Finds and lists all persons in address book whose name contains any of the argument keywords.
Expand All @@ -15,14 +15,20 @@ public class FindCommand extends Command {

public static final String COMMAND_WORD = "findc";

public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
public static final String MESSAGE_USAGE = COMMAND_WORD
+ ": Finds all contacts whose specified fields (except remarks) contain any of "
+ "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ "Example: " + COMMAND_WORD + " alice bob charlie";
+ "Parameters: n/KEYWORDS p/KEYWORDS e/KEYWORDS l/LOCALDATETIME s/KEYWORDS t/KEYWORDS\n"
+ "Example: " + COMMAND_WORD + " n/alice l/10.10.2023 0900 s/Prospective t/Health";

private final NameContainsKeywordsPredicate predicate;
private final GeneralPersonPredicate predicate;

public FindCommand(NameContainsKeywordsPredicate predicate) {
/**
* Constructs a FindCommand object.
* @param predicate The predicate that will be used by the FindCommand object.
*/
public FindCommand(GeneralPersonPredicate predicate) {
requireNonNull(predicate);
this.predicate = predicate;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Email;
import seedu.address.model.person.LastContactedTime;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
Expand All @@ -39,7 +38,7 @@ public AddCommand parse(String args) throws ParseException {
ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_LASTTIME,
PREFIX_STATUS, PREFIX_REMARK, PREFIX_TAG);

if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_LASTTIME)
if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL)
|| !argMultimap.getPreamble().isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
Expand All @@ -49,10 +48,7 @@ public AddCommand parse(String args) throws ParseException {
Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
LocalDateTime lastContactedTime = ParserUtil.parseContactTime(argMultimap.getValue(PREFIX_LASTTIME).get());
if (!LastContactedTime.isValidLastContactedTime(lastContactedTime)) {
throw new ParseException(LastContactedTime.MESSAGE_CONSTRAINTS);
}
LocalDateTime lastContactedTime = ParserUtil.parseContactTime(argMultimap.getValue(PREFIX_LASTTIME).orElse(""));
Status status = ParserUtil.parseStatus(argMultimap.getValue(PREFIX_STATUS).orElse(""));
Remark remark = ParserUtil.parseRemark(argMultimap.getValue(PREFIX_REMARK).orElse(""));
Set<Tag> tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
Expand Down
55 changes: 47 additions & 8 deletions src/main/java/seedu/address/logic/parser/FindCommandParser.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,72 @@
package seedu.address.logic.parser;

import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
import static seedu.address.logic.parser.CliSyntax.PREFIX_LASTTIME;
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_STATUS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;

import java.util.Arrays;
import java.time.LocalDateTime;
import java.util.logging.Logger;

import seedu.address.Main;
import seedu.address.commons.core.LogsCenter;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.GeneralPersonPredicate;
import seedu.address.model.person.LastContactedTime;

/**
* Parses input arguments and creates a new FindCommand object
*/
public class FindCommandParser implements Parser<FindCommand> {
private static Logger logger = LogsCenter.getLogger(Main.class);

/**
* Parses the given {@code String} of arguments in the context of the FindCommand
* and returns a FindCommand object for execution.
* @throws ParseException if the user input does not conform the expected format
*/
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));
logger.info("Begin FindCommand parse");
assert args != null;

ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
PREFIX_LASTTIME, PREFIX_STATUS, PREFIX_TAG);
if (!argMultimap.getPreamble().isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
PREFIX_LASTTIME, PREFIX_STATUS, PREFIX_TAG);

logger.info("Begin creation of Meeting predicates");
String[] nameKeyWords = argMultimap.getValue(PREFIX_NAME).orElse("").split("\\s+");
String[] phoneValues = argMultimap.getValue(PREFIX_PHONE).orElse("").split("\\s+");
String[] emailKeyWords = argMultimap.getValue(PREFIX_EMAIL).orElse("").split("\\s+");
String[] statusKeyWords = argMultimap.getValue(PREFIX_STATUS).orElse("").split("\\s+");
String[] tagKeyWords = argMultimap.getValue(PREFIX_TAG).orElse("").split("\\s+");

LocalDateTime lastContacted = LocalDateTime.MIN;
if (argMultimap.getValue(PREFIX_LASTTIME).isPresent()) {
lastContacted = ParserUtil.parseContactTime(argMultimap.getValue(PREFIX_LASTTIME).get());
if (!LastContactedTime.isValidLastContactedTime(lastContacted)) {
throw new ParseException(LastContactedTime.MESSAGE_CONSTRAINTS);
}
}

GeneralPersonPredicate generalPersonPredicate = new GeneralPersonPredicate(
nameKeyWords,
phoneValues,
emailKeyWords,
lastContacted,
statusKeyWords,
tagKeyWords);

String[] nameKeywords = trimmedArgs.split("\\s+");
logger.info("All Person predicates created");

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ public FindMeetingCommand parse(String args) throws ParseException {
String[] attendeeKeyWords = argMultimap.getValue(PREFIX_NAME).orElse("").split("\\s+");
String[] tagKeyWords = argMultimap.getValue(PREFIX_TAG).orElse("").split("\\s+");


GeneralMeetingPredicate generalMeetingPredicate = new GeneralMeetingPredicate(
titleKeyWords,
locationKeyWords,
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/seedu/address/logic/parser/ParserUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,16 @@ public static Email parseEmail(String email) throws ParseException {
public static LocalDateTime parseContactTime(String time) throws ParseException {
requireNonNull(time);
String trimmedStart = time.trim();
//set last contacted to LocalDateTime.MIN if last contacted field is not specified
if (trimmedStart.isEmpty()) {
return LocalDateTime.MIN;
}
try {
return LocalDateTime.parse(trimmedStart, FORMAT);
LocalDateTime preppedTime = LocalDateTime.parse(trimmedStart, FORMAT);
if (!LastContactedTime.isValidLastContactedTime(preppedTime)) {
throw new ParseException(LastContactedTime.MESSAGE_CONSTRAINTS);
}
return preppedTime;
} catch (DateTimeParseException e) {
throw new ParseException(LastContactedTime.MESSAGE_CONSTRAINTS);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@
import java.util.Arrays;
import java.util.function.Predicate;

import seedu.address.model.tag.TagContainsKeywordsPredicate;


/**
* The predicate class that brings together of all the other predicate class.
* The predicate class that brings together of all the other predicate class for Meeting.
*/
public class GeneralMeetingPredicate implements Predicate<Meeting> {
private final TitleContainsKeywordsPredicate titlePredicate;
private final LocationContainsKeywordsPredicate locationPredicate;
private final MeetingTimeContainsPredicate meetingTimePredicate;
private final AttendeeContainsKeywordsPredicate attendeePredicate;
private final TagContainsKeywordsPredicate tagPredicate;
private final MeetingTagContainsKeywordsPredicate tagPredicate;

/**
* Constructs a predicate class that fulfills all the argument predicates
Expand All @@ -29,7 +26,7 @@ public GeneralMeetingPredicate(TitleContainsKeywordsPredicate titlePredicate,
LocationContainsKeywordsPredicate locationPredicate,
MeetingTimeContainsPredicate meetingTimePredicate,
AttendeeContainsKeywordsPredicate attendeePredicate,
TagContainsKeywordsPredicate tagPredicate) {
MeetingTagContainsKeywordsPredicate tagPredicate) {
this.titlePredicate = titlePredicate;
this.locationPredicate = locationPredicate;
this.meetingTimePredicate = meetingTimePredicate;
Expand All @@ -52,7 +49,7 @@ public GeneralMeetingPredicate(String[] titleKeyWords, String[] locationKeyWords
this.locationPredicate = new LocationContainsKeywordsPredicate(Arrays.asList(locationKeyWords));
this.meetingTimePredicate = new MeetingTimeContainsPredicate(start, end);
this.attendeePredicate = new AttendeeContainsKeywordsPredicate(Arrays.asList(attendeeKeyWords));
this.tagPredicate = new TagContainsKeywordsPredicate(Arrays.asList(tagKeyWords));
this.tagPredicate = new MeetingTagContainsKeywordsPredicate(Arrays.asList(tagKeyWords));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
package seedu.address.model.tag;
package seedu.address.model.meeting;

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

import seedu.address.commons.util.StringUtil;
import seedu.address.commons.util.ToStringBuilder;
import seedu.address.model.meeting.Meeting;

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

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

Expand All @@ -31,11 +30,12 @@ public boolean equals(Object other) {
}

// instanceof handles nulls
if (!(other instanceof TagContainsKeywordsPredicate)) {
if (!(other instanceof MeetingTagContainsKeywordsPredicate)) {
return false;
}

TagContainsKeywordsPredicate otherTagContainsKeywordsPredicate = (TagContainsKeywordsPredicate) other;
MeetingTagContainsKeywordsPredicate otherTagContainsKeywordsPredicate =
(MeetingTagContainsKeywordsPredicate) other;
return keywords.equals(otherTagContainsKeywordsPredicate.keywords);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import seedu.address.commons.util.ToStringBuilder;

/**
* Tests that a {@code Meetings}'s {@code MeetingTime} duration within the given start and end.
* Tests that a {@code Meeting}'s {@code MeetingTime} duration within the given start and end.
*/
public class MeetingTimeContainsPredicate implements Predicate<Meeting> {
private final LocalDateTime start;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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 Email} matches any of the keywords given.
*/
public class EmailContainsKeywordsPredicate implements Predicate<Person> {
private final List<String> keywords;

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

@Override
public boolean test(Person person) {
String email = person.getEmail().toString();
Boolean result = false;
for (String keyword : keywords) {
result |= email.contains(keyword.toLowerCase());
}
return result;
}

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

// instanceof handles nulls
if (!(other instanceof EmailContainsKeywordsPredicate)) {
return false;
}

EmailContainsKeywordsPredicate otherEmailContainsKeywordsPredicate = (EmailContainsKeywordsPredicate) other;
return keywords.equals(otherEmailContainsKeywordsPredicate.keywords);
}

@Override
public String toString() {
return new ToStringBuilder(this).add("keywords", keywords).toString();
}
}
Loading

0 comments on commit 99df5ca

Please sign in to comment.