diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 576381ed784..d7e8e2fceaa 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,38 +1,131 @@ +# User Guide + +* Quick Start +* Features + * Viewing help: `help` + * Adding a person: `add` + * Deleting a person: `delete` + * Listing all contacts: `list` + * Locating persons by keywords: `find` + * Add an image for contacts: `add-image` + * Delete an image for contacts: `delete-image` + * Quick import admin contacts: `import` +* FAQ +* Command summary + +--- + +## Quick start + +1. Ensure you have Java `11` or above installed in your computer. +2. Download the latest `bookface.jar` from [here](https://github.com/AY2223S2-CS2103-F11-4/tp/releases). +3. Place `bookface.jar` file in the folder you would like to use as the *home directory*. +4. Run the application. The following GUI will appear upon first use of the application. +![GUI upon first use](images/GUIOnInitialUsage.png) + +5. The application is initially loaded with sample data for new users to try out the [features](#Features) listed below. +Experienced users can delete the sample data and proceed with regular usage. + +--- + +## Features + +### Help command: `help` + +Shows a link to the user guide to help new users get familiar with the commands for the application. + +Format: `help` + +### Add user contacts: `add` + +Format: `add [name] [year/course] [phone number] [email] [address]` Optional to add: `t/TAGS` + +* User is *required* to enter **name, status, phone number, email, address** +* Tags can be optional +* If the account exists, user can add in related field of interests to share with others + +Example: +* `add n/Shenghan s/Year2 Computer-science p/99999999 e/david@gmail.com a/punngol place 696a #12-348` will displays the + necessary basic information that are the user's name, year/course, phone number, email, address. Optional fields are tags, + for which there are commitment/cca tags, module tags and lastly the general tags for users to enter non-specific typed tags. + +Example (with the addition of tags): +* `add n/Shenghan s/Year2 Computer-science p/99999999 e/david@gmail.com a/punngol place 696a #12-348 t/developer ct/soccer + mt/cs2103` Note that the tags can be placed in any part of the command, and it will not break! + +Tags are categorised according to tag colors: +* Commitment tags: `coral pink` +* Module tags: `Dark green` +* General tags: `default blue` + +### Delete user contacts: `delete` + +Delete a contact. +Format: `delete INDEX` + +* Show contact details specified by `INDEX` +* The index refers to the index number shown in the displayed person list. +* The index *must* be a positive integer 1, 2, 3, … +* Extra: Will prompt user to re-confirm again before the contact is erased from BookFace + Example: +* `delete 2` Brings up the 2nd person in the address book and prompt user to confirm before deleting. + ### Listing all contacts: `list` List all contacts in the address book. + Format: `list` -### Locating persons by name/class/group: `find` +### Locating persons by keywords: `find` + +Finds persons whose contact details contain any of the given keywords based on the +prefix specified. -Finds persons whose names contain any of the given keywords. -Format: `find KEYWORD [MORE_KEYWORDS]` +Format: `find [PREFIX]/KEYWORD [MORE [PREFIX]/KEYWORD]...` * 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 will filter by the `PREFIX` provided, e.g. `n/` searches through the + names of the contacts, `p/` searches through the phone number of the contacts, `t/` + searches through the tags of the contact, etc... +* Each prefix must be followed by one and only one keyword. See below for example usage. +* The search is done via the logical ***AND*** operator, i.e. `find n/john t/cs` will return + the list of contacts where his name is `john` and has a tag that contains `cs`. +* The following shows a list of allowed prefixes: + 1. `n/` which represents the name + 2. `s/` which represents the status + 3. `p/` which represents the phone number + 4. `e/` which represents the email + 5. `a/` which represents the address + 6. `t/` which represents the tags + +Example: + +`find n/amy t/cs2103 e/gmail` will return the list of contacts whose names are `amy`, +has a tag labeled `cs2103`, and whose emails contain `gmail`. + +### Add an image for contacts -### Add image for contacts +Add a contact image for each contact. -Add a contact image for each contact -Format: `add-image INDEX [NAME-OF-IMAGE]` +Format: `add-image INDEX [PATH-TO-IMAGE]` * Adds an image to the contact at the specified `INDEX` * The index refers to the index number shown in the displayed person list. * The index **must be a positive integer** 1, 2, 3,... -* The image must be placed in a specific folder for BookFace to locate * If the image cannot be found or user did not specify a contact image, a default image will be used +> **Note:** The `[PATH-TO-IMAGE]` provided must be an absolute path, and should not be provided in quotation marks. +> For instance: `add-image 2 "C:/Users/user/Downloads/weekiat.png"` will be invalid, whereas +> `add-image 2 C:/Users/user/Downloads/weekiat.png` will be valid. + Examples: -* `list` followed by `add-image 2 weekiat.png` adds the image `weekiat.png` to the 2nd person in the address book +* `list` followed by `add-image 2 C:/Users/user/Downloads/weekiat.png` adds the image `weekiat.png` to the 2nd person in the address book -## Delete Image for contacts +### Delete an Image for contacts Delete the image of a contact. + Format: `delete-image INDEX` * Deletes the image of contact specified by `INDEX` @@ -43,9 +136,10 @@ Format: `delete-image INDEX` Example: * `delete-image 2` deletes the image of the 2nd person in the address book. -## Quick Import for admin contacts: `import` +### Quick Import for admin contacts: `import` Import administrative contacts for relevant faculties. + Format: `import [faculty]` * Faculty acronyms (e.g. soc) @@ -56,49 +150,7 @@ Example: * `import soc` adds all important administrative contact for School of Computing * `import chs` adds all important administrative contact for College of Humanities and Sciences - -## Add user contacts: `add` - -Format: `add [name] [year/course] [phone number] [email] [address]` Optional to add: `t/TAGS` - -* User is *required* to enter **name, status, phone number, email, address** -* Tags can be optional -* If the account exists, user can add in related field of interests to share with others - -Example: -* `add n/Shenghan s/Year2 Computer-science p/99999999 e/david@gmail.com a/punngol place 696a #12-348` will displays the -necessary basic information that are the user's name, year/course, phone number, email, address. Optional fields are tags, -for which there are commitment/cca tags, module tags and lastly the general tags for users to enter non-specific typed tags. - -Example (with the addition of tags): -* `add n/Shenghan s/Year2 Computer-science p/99999999 e/david@gmail.com a/punngol place 696a #12-348 t/developer ct/soccer -mt/cs2103` Note that the tags can be placed in any part of the command, and it will not break! - -Tags are categorised according to tag colors: -* Commitment tags: `coral pink` -* Module tags: `Dark green` -* General tags: `default blue` - -## Delete user contacts: `delete` - -Delete a contact. -Format: `delete INDEX` - -* Show contact details specified by `INDEX` -* The index refers to the index number shown in the displayed person list. -* The index *must* be a positive integer 1, 2, 3, … -* Extra: Will prompt user to re-confirm again before the contact is erased from BookFace - Example: -* `delete 2` Brings up the 2nd person in the address book and prompt user to confirm before deleting. - -## Help command: `help` - -Show a list of command to help users to navigate around -Format: `help` - -* Include list of commands to enable users to refer to in terminal. - ------------------------ +--- ## Command summary @@ -110,7 +162,7 @@ Format: `help` | **Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` | | **List** | `list` | | **Help** | `help` | -| **Add-Image** | `add-image INDEX [NAME-OF-IMAGE]`
e.g., `add-image 2 weekiat.png` | +| **Add-Image** | `add-image INDEX [PATH-TO-IMAGE]`
e.g., `add-image 2 C:/Users/user/Downloads/weekiat.png` | | **Delete-Image** | `delete-image INDEX`
e.g., `delete-image 2` | | | **Import** | `import [faculty]`
e.g., `import soc, import chs` | diff --git a/docs/images/GUIOnInitialUsage.png b/docs/images/GUIOnInitialUsage.png new file mode 100644 index 00000000000..14f513ca7ec Binary files /dev/null and b/docs/images/GUIOnInitialUsage.png differ diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..6a8ea3b1ce6 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -26,8 +26,7 @@ public class StringUtil { public static boolean containsWordIgnoreCase(String sentence, String word) { requireNonNull(sentence); requireNonNull(word); - - String preppedWord = word.trim(); + String preppedWord = word.trim().toLowerCase(); checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); @@ -35,7 +34,7 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); return Arrays.stream(wordsInPreppedSentence) - .anyMatch(preppedWord::equalsIgnoreCase); + .anyMatch(wordInPreppedSentence -> wordInPreppedSentence.toLowerCase().contains(preppedWord)); } /** diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 41b39d09bfb..5556101dc07 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -2,38 +2,35 @@ import static java.util.Objects.requireNonNull; +import java.util.function.Predicate; + import seedu.address.commons.core.Messages; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.model.person.PhoneContainsKeywordsPredicate; +import seedu.address.model.person.Person; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all persons in address book whose details contains any of the + * argument keywords based on the prefixes in the user input. * Keyword matching is case insensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names 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"; - - public static final String MESSAGE_USAGE_2 = COMMAND_WORD + ": Finds all persons whose phone numbers contain any " - + "of the specified keywords / phone number substring and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " 99999999"; - - private NameContainsKeywordsPredicate predicate; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Finds all persons whose details contain any of " + + "the specified keywords (case-insensitive) based on " + + "the prefix provided and displays them as a list with index numbers.\n" + + "Each prefix must be followed by one and only one keyword.\n" + + "Please use the \"help\" command for more information on " + + "the usage of this command.\n" + + "Parameters: [PREFIX]/KEYWORD [MORE [PREFIX]/KEYWORD]...\n" + + "Example: " + COMMAND_WORD + " n/alice s/y4 p/91234567" + + " e/alice@example.com a/blk 123 t/cs2103"; - private PhoneContainsKeywordsPredicate phonePredicate; - - public FindCommand(PhoneContainsKeywordsPredicate phonePredicate) { - this.phonePredicate = phonePredicate; - } + private Predicate predicate; - public FindCommand(NameContainsKeywordsPredicate predicate) { + public FindCommand(Predicate predicate) { this.predicate = predicate; } diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 5d3c9371375..5a9da390d29 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,19 +1,40 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +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.util.List; +import java.util.function.Predicate; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.model.person.PhoneContainsKeywordsPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.person.predicates.AddressContainsKeywordPredicate; +import seedu.address.model.person.predicates.EmailContainsKeywordPredicate; +import seedu.address.model.person.predicates.NameContainsKeywordPredicate; +import seedu.address.model.person.predicates.PhoneContainsKeywordsPredicate; +import seedu.address.model.person.predicates.StatusContainsKeywordsPredicate; +import seedu.address.model.person.predicates.TagContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object */ public class FindCommandParser implements Parser { + private Prefix[] possiblePrefixes = { + PREFIX_NAME, + PREFIX_STATUS, + PREFIX_PHONE, + PREFIX_EMAIL, + PREFIX_ADDRESS, + PREFIX_TAG + }; + /** * Parses the given {@code String} of arguments in the context of the FindCommand * and returns a FindCommand object for execution. @@ -21,34 +42,57 @@ public class FindCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - Boolean isNumber = onlyDigits(trimmedArgs); - if (trimmedArgs.isEmpty()) { - if (isNumber) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } else { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - } - if (isNumber) { - return new FindCommand(new PhoneContainsKeywordsPredicate(trimmedArgs)); - } else { - String[] nameKeywords = trimmedArgs.split("\\s+"); + if (args.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, possiblePrefixes); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + Predicate predicate = null; + for (Prefix p : possiblePrefixes) { + List prefixArguments = argMultimap.getAllValues(p); + if (prefixArguments.isEmpty()) { + continue; + } + for (String arg : prefixArguments) { + if (!isValidArgument(arg)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + Predicate currentPredicate = null; + if (p == PREFIX_NAME) { + currentPredicate = new NameContainsKeywordPredicate(arg); + } else if (p == PREFIX_STATUS) { + currentPredicate = new StatusContainsKeywordsPredicate(arg); + } else if (p == PREFIX_PHONE) { + currentPredicate = new PhoneContainsKeywordsPredicate(arg); + } else if (p == PREFIX_EMAIL) { + currentPredicate = new EmailContainsKeywordPredicate(arg); + } else if (p == PREFIX_ADDRESS) { + currentPredicate = new AddressContainsKeywordPredicate(arg); + } else if (p == PREFIX_TAG) { + currentPredicate = new TagContainsKeywordsPredicate(arg); + } + assert currentPredicate != null; + if (predicate == null) { + predicate = currentPredicate; + } else { + predicate = predicate.and(currentPredicate); + } + } + } + if (predicate == null) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } + + return new FindCommand(predicate); } - private boolean onlyDigits(String str) { - for (int i = 0; i < str.length(); i++) { - if (!Character.isDigit(str.charAt(i))) { - return false; - } + private static boolean isValidArgument(String argument) { + String preppedWord = argument.trim().toLowerCase(); + if (preppedWord.isEmpty() || preppedWord.split("\\s+").length != 1) { + return false; } return true; } - } diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427c..00000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordPredicate.java b/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordPredicate.java new file mode 100644 index 00000000000..6eccf6ff369 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordPredicate.java @@ -0,0 +1,30 @@ +package seedu.address.model.person.predicates; + +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Address} matches any of the keywords given. + */ +public class AddressContainsKeywordPredicate implements Predicate { + private final String keyword; + + public AddressContainsKeywordPredicate(String keyword) { + this.keyword = keyword; + } + + @Override + public boolean test(Person person) { + return StringUtil.containsWordIgnoreCase(person.getAddress().toString(), keyword); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddressContainsKeywordPredicate // instanceof handles nulls + && keyword.equals(((AddressContainsKeywordPredicate) other).keyword)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordPredicate.java b/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordPredicate.java new file mode 100644 index 00000000000..cce124dc277 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordPredicate.java @@ -0,0 +1,30 @@ +package seedu.address.model.person.predicates; + +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Email} matches any of the keywords given. + */ +public class EmailContainsKeywordPredicate implements Predicate { + private final String keyword; + + public EmailContainsKeywordPredicate(String keyword) { + this.keyword = keyword; + } + + @Override + public boolean test(Person person) { + return StringUtil.containsWordIgnoreCase(person.getEmail().toString(), keyword); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EmailContainsKeywordPredicate // instanceof handles nulls + && keyword.equals(((EmailContainsKeywordPredicate) other).keyword)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordPredicate.java b/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordPredicate.java new file mode 100644 index 00000000000..5b63118688f --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordPredicate.java @@ -0,0 +1,50 @@ +package seedu.address.model.person.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class NameContainsKeywordPredicate implements Predicate { + private final List keywords; + private final String keyword; + + /** + * Constructor that takes in a list of keywords + * @param keywords The list of keywords for the predicate + */ + public NameContainsKeywordPredicate(List keywords) { + this.keywords = keywords; + this.keyword = null; + } + + /** + * Constructor that takes in a singular {@code String} keyword + * @param keyword The keyword for the predicate + */ + public NameContainsKeywordPredicate(String keyword) { + this.keywords = null; + this.keyword = keyword; + } + + @Override + public boolean test(Person person) { + if (keywords != null) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + } + return StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof NameContainsKeywordPredicate // instanceof handles nulls + && keyword.equals(((NameContainsKeywordPredicate) other).keyword)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java similarity index 66% rename from src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java index d3f7f550592..49bf727b3d9 100644 --- a/src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java @@ -1,9 +1,12 @@ -package seedu.address.model.person; +package seedu.address.model.person.predicates; import java.util.function.Predicate; +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. */ public class PhoneContainsKeywordsPredicate implements Predicate { private final String keyword; @@ -12,7 +15,7 @@ public PhoneContainsKeywordsPredicate(String keyword) { this.keyword = keyword; } public boolean test(Person person) { - return keyword.matches(person.getPhone().value); + return StringUtil.containsWordIgnoreCase(person.getPhone().toString(), keyword); } @Override diff --git a/src/main/java/seedu/address/model/person/predicates/StatusContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/StatusContainsKeywordsPredicate.java new file mode 100644 index 00000000000..8251cb7ed4f --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/StatusContainsKeywordsPredicate.java @@ -0,0 +1,23 @@ +package seedu.address.model.person.predicates; + +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Status} matches any of the keywords given. + */ +public class StatusContainsKeywordsPredicate implements Predicate { + private final String keyword; + + public StatusContainsKeywordsPredicate(String keyword) { + this.keyword = keyword; + } + + @Override + public boolean test(Person person) { + return StringUtil.containsWordIgnoreCase(person.getStatus().toString(), keyword); + } + +} diff --git a/src/main/java/seedu/address/model/person/predicates/TagContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/TagContainsKeywordsPredicate.java new file mode 100644 index 00000000000..778643dd69d --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/TagContainsKeywordsPredicate.java @@ -0,0 +1,34 @@ +package seedu.address.model.person.predicates; + +import java.util.Set; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class TagContainsKeywordsPredicate implements Predicate { + private final String keyword; + + public TagContainsKeywordsPredicate(String keyword) { + this.keyword = keyword; + } + + @Override + public boolean test(Person person) { + Set tags = person.getTags(); + return tags.stream().anyMatch(tag -> + StringUtil.containsWordIgnoreCase(tag.tagName, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TagContainsKeywordsPredicate // instanceof handles nulls + && keyword.equals(((TagContainsKeywordsPredicate) other).keyword)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/tag/CommitmentTag.java b/src/main/java/seedu/address/model/tag/CommitmentTag.java index 8b795d93886..a45d64906e8 100644 --- a/src/main/java/seedu/address/model/tag/CommitmentTag.java +++ b/src/main/java/seedu/address/model/tag/CommitmentTag.java @@ -17,13 +17,6 @@ public String tagColor() { return "#f88379"; } - @Override - public boolean equals(Object other) { - return other == this - || (other instanceof CommitmentTag - && tagName.equals(((CommitmentTag) other).tagName)); - } - @Override public String toString() { return " [Commitment: " + tagName.split("XXXXX")[1] + "] "; diff --git a/src/main/java/seedu/address/model/tag/ModuleTag.java b/src/main/java/seedu/address/model/tag/ModuleTag.java index 0e3f94e2c47..85a2fe30a6d 100644 --- a/src/main/java/seedu/address/model/tag/ModuleTag.java +++ b/src/main/java/seedu/address/model/tag/ModuleTag.java @@ -9,13 +9,6 @@ public ModuleTag(String tagName) { super(tagName); } - @Override - public boolean equals(Object other) { - return other == this - || (other instanceof ModuleTag - && tagName.equals(((ModuleTag) other).tagName)); - } - /** * @return the corresponding color code for css */ diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index debcdf51b6c..dc1664ab73b 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -44,7 +44,7 @@ public String tagColor() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Tag // instanceof handles nulls - && tagName.equals(((Tag) other).tagName)); // state check + && tagName.equalsIgnoreCase(((Tag) other).tagName)); // state check } @Override diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..072d798ad0b 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,8 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = + "https://github.com/AY2223S2-CS2103-F11-4/tp/blob/master/docs/UserGuide.md"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/seedu/address/commons/util/StringUtilTest.java index c56d407bf3f..458ab21fe64 100644 --- a/src/test/java/seedu/address/commons/util/StringUtilTest.java +++ b/src/test/java/seedu/address/commons/util/StringUtilTest.java @@ -109,7 +109,7 @@ public void containsWordIgnoreCase_validInputs_correctResult() { assertFalse(StringUtil.containsWordIgnoreCase(" ", "123")); // Matches a partial word only - assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bb")); // Sentence word bigger than query word + assertTrue(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bb")); // Sentence word bigger than query word assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bbbb")); // Query word bigger than sentence word // Matches word in the sentence, different upper/lower case letters diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 3933135a71b..e722cc07a85 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -18,8 +18,8 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.AddressBook; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; +import seedu.address.model.person.predicates.NameContainsKeywordPredicate; import seedu.address.testutil.EditPersonDescriptorBuilder; /** @@ -127,7 +127,7 @@ public static void showPersonAtIndex(Model model, Index targetIndex) { Person person = model.getFilteredPersonList().get(targetIndex.getZeroBased()); final String[] splitName = person.getName().fullName.split("\\s+"); - model.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0]))); + model.updateFilteredPersonList(new NameContainsKeywordPredicate(Arrays.asList(splitName[0]))); assertEquals(1, model.getFilteredPersonList().size()); } diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java index 9b15db28bbb..3fbdecd178b 100644 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java @@ -18,7 +18,7 @@ import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.UserPrefs; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.predicates.NameContainsKeywordPredicate; /** * Contains integration tests (interaction with the Model) for {@code FindCommand}. @@ -29,10 +29,10 @@ public class FindCommandTest { @Test public void equals() { - NameContainsKeywordsPredicate firstPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("first")); - NameContainsKeywordsPredicate secondPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("second")); + NameContainsKeywordPredicate firstPredicate = + new NameContainsKeywordPredicate("John"); + NameContainsKeywordPredicate secondPredicate = + new NameContainsKeywordPredicate("Adam"); FindCommand findFirstCommand = new FindCommand(firstPredicate); FindCommand findSecondCommand = new FindCommand(secondPredicate); @@ -57,7 +57,7 @@ public void equals() { @Test public void execute_zeroKeywords_noPersonFound() { String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); - NameContainsKeywordsPredicate predicate = preparePredicate(" "); + NameContainsKeywordPredicate predicate = preparePredicate(" "); FindCommand command = new FindCommand(predicate); expectedModel.updateFilteredPersonList(predicate); assertCommandSuccess(command, model, expectedMessage, expectedModel); @@ -67,7 +67,7 @@ public void execute_zeroKeywords_noPersonFound() { @Test public void execute_multipleKeywords_multiplePersonsFound() { String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); - NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz"); + NameContainsKeywordPredicate predicate = preparePredicate("Kurz Elle Kunz"); FindCommand command = new FindCommand(predicate); expectedModel.updateFilteredPersonList(predicate); assertCommandSuccess(command, model, expectedMessage, expectedModel); @@ -75,9 +75,9 @@ public void execute_multipleKeywords_multiplePersonsFound() { } /** - * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. + * Parses {@code userInput} into a {@code NameContainsKeywordPredicate}. */ - private NameContainsKeywordsPredicate preparePredicate(String userInput) { - return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + private NameContainsKeywordPredicate preparePredicate(String userInput) { + return new NameContainsKeywordPredicate(Arrays.asList(userInput.split("\\s+"))); } } diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index 1c548c3f854..66207adcc49 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -7,10 +7,6 @@ import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - import org.junit.jupiter.api.Test; import seedu.address.logic.commands.AddCommand; @@ -23,8 +19,8 @@ import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; +import seedu.address.model.person.predicates.NameContainsKeywordPredicate; import seedu.address.testutil.EditPersonDescriptorBuilder; import seedu.address.testutil.PersonBuilder; import seedu.address.testutil.PersonUtil; @@ -73,10 +69,10 @@ public void parseCommand_exit() throws Exception { @Test public void parseCommand_find() throws Exception { - List keywords = Arrays.asList("foo", "bar", "baz"); + String arguments = "n/John"; FindCommand command = (FindCommand) parser.parseCommand( - FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); - assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command); + FindCommand.COMMAND_WORD + " " + arguments); + assertEquals(new FindCommand(new NameContainsKeywordPredicate("John")), command); } @Test diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java index 9db96f64a67..dcf58c2e299 100644 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java @@ -2,14 +2,10 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; - -import java.util.Arrays; import org.junit.jupiter.api.Test; import seedu.address.logic.commands.FindCommand; -import seedu.address.model.person.NameContainsKeywordsPredicate; public class FindCommandParserTest { @@ -21,15 +17,4 @@ public void parse_emptyArg_throwsParseException() { FindCommand.MESSAGE_USAGE)); } - @Test - public void parse_validArgs_returnsFindCommand() { - // no leading and trailing whitespaces - FindCommand expectedFindCommand = - new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"))); - assertParseSuccess(parser, "Alice Bob", expectedFindCommand); - - // multiple whitespaces between keywords - assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand); - } - } diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 2cf1418d116..f75921e6319 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -15,7 +15,7 @@ import org.junit.jupiter.api.Test; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.predicates.NameContainsKeywordPredicate; import seedu.address.testutil.AddressBookBuilder; public class ModelManagerTest { @@ -118,7 +118,7 @@ public void equals() { // different filteredList -> returns false String[] keywords = ALICE.getName().fullName.split("\\s+"); - modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); + modelManager.updateFilteredPersonList(new NameContainsKeywordPredicate(Arrays.asList(keywords))); assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); // resets modelManager to initial state for upcoming tests diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/predicates/NameContainsKeywordPredicateTest.java similarity index 57% rename from src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java rename to src/test/java/seedu/address/model/person/predicates/NameContainsKeywordPredicateTest.java index f136664e017..c3f72781110 100644 --- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java +++ b/src/test/java/seedu/address/model/person/predicates/NameContainsKeywordPredicateTest.java @@ -1,31 +1,30 @@ -package seedu.address.model.person; +package seedu.address.model.person.predicates; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import java.util.Collections; -import java.util.List; import org.junit.jupiter.api.Test; import seedu.address.testutil.PersonBuilder; -public class NameContainsKeywordsPredicateTest { +public class NameContainsKeywordPredicateTest { @Test public void equals() { - List firstPredicateKeywordList = Collections.singletonList("first"); - List secondPredicateKeywordList = Arrays.asList("first", "second"); + String firstPredicateKeyword = "first"; + String secondPredicateKeyword = "first"; - NameContainsKeywordsPredicate firstPredicate = new NameContainsKeywordsPredicate(firstPredicateKeywordList); - NameContainsKeywordsPredicate secondPredicate = new NameContainsKeywordsPredicate(secondPredicateKeywordList); + NameContainsKeywordPredicate firstPredicate = new NameContainsKeywordPredicate(firstPredicateKeyword); + NameContainsKeywordPredicate secondPredicate = new NameContainsKeywordPredicate(secondPredicateKeyword); // same object -> returns true assertTrue(firstPredicate.equals(firstPredicate)); // same values -> returns true - NameContainsKeywordsPredicate firstPredicateCopy = new NameContainsKeywordsPredicate(firstPredicateKeywordList); + NameContainsKeywordPredicate firstPredicateCopy = new NameContainsKeywordPredicate(firstPredicateKeyword); assertTrue(firstPredicate.equals(firstPredicateCopy)); // different types -> returns false @@ -35,40 +34,40 @@ public void equals() { assertFalse(firstPredicate.equals(null)); // different person -> returns false - assertFalse(firstPredicate.equals(secondPredicate)); + assertTrue(firstPredicate.equals(secondPredicate)); } @Test public void test_nameContainsKeywords_returnsTrue() { // One keyword - NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.singletonList("Alice")); + NameContainsKeywordPredicate predicate = new NameContainsKeywordPredicate(Collections.singletonList("Alice")); assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); // Multiple keywords - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")); + predicate = new NameContainsKeywordPredicate(Arrays.asList("Alice", "Bob")); assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); // Only one matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Bob", "Carol")); + predicate = new NameContainsKeywordPredicate(Arrays.asList("Bob", "Carol")); assertTrue(predicate.test(new PersonBuilder().withName("Alice Carol").build())); // Mixed-case keywords - predicate = new NameContainsKeywordsPredicate(Arrays.asList("aLIce", "bOB")); + predicate = new NameContainsKeywordPredicate(Arrays.asList("aLIce", "bOB")); assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); } @Test public void test_nameDoesNotContainKeywords_returnsFalse() { // Zero keywords - NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.emptyList()); + NameContainsKeywordPredicate predicate = new NameContainsKeywordPredicate(Collections.emptyList()); assertFalse(predicate.test(new PersonBuilder().withName("Alice").build())); // Non-matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Carol")); + predicate = new NameContainsKeywordPredicate(Arrays.asList("Carol")); assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build())); // Keywords match phone, email and address, but does not match name - predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street")); + predicate = new NameContainsKeywordPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street")); assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") .withEmail("alice@email.com").withAddress("Main Street").build())); }