From 32d09a0ebf398be9a3c5e9fc0627ac6b57af79aa Mon Sep 17 00:00:00 2001 From: Hong Sheng Date: Sat, 25 Mar 2023 16:52:49 +0800 Subject: [PATCH 1/6] Update UserGuide --- docs/UserGuide.md | 141 +++++++++++++++++++----------- docs/images/GUIOnInitialUsage.png | Bin 0 -> 50581 bytes 2 files changed, 90 insertions(+), 51 deletions(-) create mode 100644 docs/images/GUIOnInitialUsage.png diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 576381ed784..9d33c660cfa 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,11 +1,86 @@ +# User Guide + +--- + +* Quick Start +* Features + * Viewing help: `help` + * Adding a person: `add` + * Deleting a person: `delete` + * Listing all contacts: `list` + * Locating persons by name/tags: `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 name/tags: `find` Finds persons whose names contain any of the given keywords. + Format: `find KEYWORD [MORE_KEYWORDS]` * The search is case-insensitive e.g. `hans` will match `Hans` @@ -15,24 +90,29 @@ Format: `find KEYWORD [MORE_KEYWORDS]` * Persons matching at least one keyword will be returned (i.e. `OR` search) e.g. `Hans Bo` will return `Hans Gruber` , `Bo Yang` -### Add image for contacts +### Add an image for contacts -Add a contact image for each contact -Format: `add-image INDEX [NAME-OF-IMAGE]` +Add a contact image for each contact. + +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` @@ -46,6 +126,7 @@ Example: ## Quick Import for admin contacts: `import` Import administrative contacts for relevant faculties. + Format: `import [faculty]` * Faculty acronyms (e.g. soc) @@ -56,49 +137,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 +149,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 0000000000000000000000000000000000000000..14f513ca7ec2c903993aa2a25ea6bf7c1acd3a9a GIT binary patch literal 50581 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

$J8-Rcy$7so74U2p3Q+k2b#_Ci-e+t>8- z1n;`(H=;`$jtpe7EqKS+sqhy6m_*Zy><%byXMMm;=EV0G!$8M}-gpac05m3uP9;;$ zQ~fl@r=_ReouicLS1wHlo{e;eV9w!qwiP!liB~1$b&HQD&PYwm81l0E$MspWg+}ak zo+n4GA5OoOtO1$FLq_DTho>{cyMkuZb3ms{o45Fpmsfefp53@svYhUTCcyuXRRS!I z@{$rAI0R=VacyS5D!6V1v~da`?UI{ofXxv|t*(md?VXH+``!*q`&Q=i($)R+<9NHd z?3^6gCtngb&A+~1tOb3a(9@sp5^i)&OJ6yv%xlU`@D3jxsNmJ(-aPe0sMo5CVy6i7 z%NRNHyZ23hCxWxLfN&w#e|*^n-z64k1{?ib@&2J_1Hk}%OP>GQ2{0Q`W(q)UX^3`3 z1);Q2R?blb>128TBkvMfZ&&NmghQE(u3IH{Je7)HHF0uFE$GXCw7 zxx}0YcpiKs0DI+ptDF+NL9juxK^ES-^lgjNrp`2)m3=L$^~tv_-kG}gO&z>ls{dTM zz3BP)ll!QzRjm&htgsOUY2%AN{&DyUOP?#Ymu83|{yPIB^Qa!%@%H)RN=U)Q?X?)U zvwhUEI3^}%@#&JYm${^o`wERfE-B4G-7FU18n`b6JzZ}Nq;F9@7G?=^V`)s`Yj4*A zflsyeyon?^VjwOd*qBr}<~Ut8eP)=(GM)gM7{0_N=~6fY7hVHWIFrO*#)Za*SmRtq zB(FesZ(OO2&Gf0Df?Gd;=fj5&CIj1pSC$2O+=8`J&POd0zdiYc4x70V!CUXA-Qr^l07oO0wCi3N1 zTEM+tyA2+qK6Wb~5B)MT#L(JwrIxAUQnb5xD{tg18VHp?Bb~?O#9poe6wP`vTlw8-BkjpQ}x!|UQ^vVgTN%fY%K9S5ZCua%CWb+kYOqh#@quh>DQpXVEQ#@ z;AG4fo-<~LcNlX9owJPT#6R173Hvu_NhLeCF?~M1!Qn!J=zL? zsIC2$QMI`AV1&T5l#xH}KEa*#BbBKTa8nS{NJKGo;Doqb)7>rA;R; zwoZ+V7<2PM7s286sibHLic3hO9?0=RAlmIw5VuP3yM4FLhB$>O51{{x{r*o*=>Kp2 zr{DAclb7WVH@NNPN5exumXt)dwYA|Fa*M%`w$IeciQzhbegF)rvZvm59E`zTJ}Do; z82&UgFAwv7KlZa2!`wLfICz*O@q9>0;beSf%v7Cz3)uwFg@Fi|;*p G@!tTIy>4p& literal 0 HcmV?d00001 From d0fd67e0cf57547519bc2a4d4340a1dfaf1d5f2c Mon Sep 17 00:00:00 2001 From: Hong Sheng Date: Sat, 25 Mar 2023 16:53:53 +0800 Subject: [PATCH 2/6] Update UG --- docs/UserGuide.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 9d33c660cfa..76f1fe61ea7 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -35,6 +35,7 @@ Experienced users can delete the sample data and proceed with regular usage. ## 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` From dcfcf222238dc40893fc836b9100c4507007397c Mon Sep 17 00:00:00 2001 From: Hong Sheng Date: Sat, 25 Mar 2023 16:57:25 +0800 Subject: [PATCH 3/6] Fix UG --- docs/UserGuide.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 76f1fe61ea7..de4d814bbdd 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,7 +1,5 @@ # User Guide ---- - * Quick Start * Features * Viewing help: `help` @@ -25,7 +23,7 @@ 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. +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. --- From be61f5bb7b0741d37b338cb8623eea501d918b1d Mon Sep 17 00:00:00 2001 From: Hong Sheng Date: Sat, 25 Mar 2023 17:00:27 +0800 Subject: [PATCH 4/6] Fix Userguide --- docs/UserGuide.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index de4d814bbdd..2cd089db7c2 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -30,13 +30,13 @@ Experienced users can delete the sample data and proceed with regular usage. ## Features -## Help command: `help` +### 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` +### Add user contacts: `add` Format: `add [name] [year/course] [phone number] [email] [address]` Optional to add: `t/TAGS` @@ -58,7 +58,7 @@ Tags are categorised according to tag colors: * Module tags: `Dark green` * General tags: `default blue` -## Delete user contacts: `delete` +### Delete user contacts: `delete` Delete a contact. Format: `delete INDEX` @@ -122,7 +122,7 @@ 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. From 88ffb901d79d14c36b2c4a3af0170b89e34fdb0a Mon Sep 17 00:00:00 2001 From: Hong Sheng Date: Sat, 25 Mar 2023 23:33:55 +0800 Subject: [PATCH 5/6] Implement find to search by prefix and keyword pairs Find command searches only by name. The user might want to search and filter by other means, such as by the list of people taking a particular class, but the user is limited to only filtering the list of contacts by their name. Let's change the existing find command to search via different prefixes provided by the user. These prefixes used for the find command will be similar to the ones used by the add command. This will allow users to filter their contacts list in a manner more specific to their requirements. UG will also be updated to reflect these changes to the find command. --- docs/UserGuide.md | 34 +++++--- .../address/commons/util/StringUtil.java | 5 +- .../address/logic/commands/FindCommand.java | 32 +++---- .../logic/parser/ArgumentMultimap.java | 1 - .../logic/parser/FindCommandParser.java | 84 +++++++++++++------ .../AddressContainsKeywordsPredicate.java | 30 +++++++ .../EmailContainsKeywordsPredicate.java | 30 +++++++ .../NameContainsKeywordsPredicate.java | 16 +++- .../PhoneContainsKeywordsPredicate.java | 8 +- .../StatusContainsKeywordsPredicate.java | 20 +++++ .../TagContainsKeywordsPredicate.java | 34 ++++++++ .../address/model/tag/CommitmentTag.java | 7 -- .../seedu/address/model/tag/ModuleTag.java | 7 -- .../java/seedu/address/model/tag/Tag.java | 4 +- .../logic/commands/CommandTestUtil.java | 2 +- .../logic/commands/FindCommandTest.java | 2 +- .../logic/parser/AddressBookParserTest.java | 2 +- .../logic/parser/FindCommandParserTest.java | 2 +- .../seedu/address/model/ModelManagerTest.java | 2 +- .../NameContainsKeywordsPredicateTest.java | 1 + 20 files changed, 240 insertions(+), 83 deletions(-) create mode 100644 src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.java create mode 100644 src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java rename src/main/java/seedu/address/model/person/{ => predicates}/NameContainsKeywordsPredicate.java (59%) rename src/main/java/seedu/address/model/person/{ => predicates}/PhoneContainsKeywordsPredicate.java (75%) create mode 100644 src/main/java/seedu/address/model/person/predicates/StatusContainsKeywordsPredicate.java create mode 100644 src/main/java/seedu/address/model/person/predicates/TagContainsKeywordsPredicate.java diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 2cd089db7c2..d7e8e2fceaa 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -6,7 +6,7 @@ * Adding a person: `add` * Deleting a person: `delete` * Listing all contacts: `list` - * Locating persons by name/tags: `find` + * Locating persons by keywords: `find` * Add an image for contacts: `add-image` * Delete an image for contacts: `delete-image` * Quick import admin contacts: `import` @@ -76,18 +76,32 @@ List all contacts in the address book. Format: `list` -### Locating persons by name/tags: `find` +### Locating persons by keywords: `find` -Finds persons whose names contain any of the given keywords. +Finds persons whose contact details contain any of the given keywords based on the +prefix specified. -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 @@ -108,7 +122,7 @@ Examples: * `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 an Image for contacts +### Delete an Image for contacts Delete the image of a contact. 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..403f80c3fe8 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -4,8 +4,9 @@ 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; + +import java.util.function.Predicate; /** * Finds and lists all persons in address book whose name contains any of the argument keywords. @@ -15,25 +16,18 @@ 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" + + "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/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java index c1eb40c8eef..239736df093 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java @@ -56,7 +56,6 @@ public List getAllValues(Prefix prefix) { if (!argMultimap.containsKey(prefix)) { return new ArrayList<>(); } - System.out.println(argMultimap.get(prefix)); return new ArrayList<>(argMultimap.get(prefix)); } diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 5d3c9371375..7741a464805 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,19 +1,30 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.*; -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.*; +import seedu.address.model.person.predicates.*; /** * 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 +32,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 NameContainsKeywordsPredicate(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 EmailContainsKeywordsPredicate(arg); + } else if (p == PREFIX_ADDRESS) { + currentPredicate = new AddressContainsKeywordsPredicate(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/predicates/AddressContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.java new file mode 100644 index 00000000000..7ab28211eb9 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.java @@ -0,0 +1,30 @@ +package seedu.address.model.person.predicates; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class AddressContainsKeywordsPredicate implements Predicate { + private final String keyword; + + public AddressContainsKeywordsPredicate(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 AddressContainsKeywordsPredicate // instanceof handles nulls + && keyword.equals(((AddressContainsKeywordsPredicate) other).keyword)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java new file mode 100644 index 00000000000..b10a77128e0 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java @@ -0,0 +1,30 @@ +package seedu.address.model.person.predicates; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class EmailContainsKeywordsPredicate implements Predicate { + private final String keyword; + + public EmailContainsKeywordsPredicate(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 EmailContainsKeywordsPredicate // instanceof handles nulls + && keyword.equals(((EmailContainsKeywordsPredicate) other).keyword)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java similarity index 59% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java index c9b5868427c..e9e6761e02a 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java @@ -1,24 +1,34 @@ -package seedu.address.model.person; +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 NameContainsKeywordsPredicate implements Predicate { private final List keywords; + private final String keyword; public NameContainsKeywordsPredicate(List keywords) { this.keywords = keywords; + this.keyword = null; + } + public NameContainsKeywordsPredicate(String keyword) { + this.keywords = null; + this.keyword = keyword; } @Override public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + if (keywords != null) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + } + return StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword); } @Override 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 75% 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..cff6ca0828f 100644 --- a/src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java @@ -1,4 +1,8 @@ -package seedu.address.model.person; +package seedu.address.model.person.predicates; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + import java.util.function.Predicate; @@ -12,7 +16,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..7d2739eba16 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/StatusContainsKeywordsPredicate.java @@ -0,0 +1,20 @@ +package seedu.address.model.person.predicates; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +import java.util.function.Predicate; + +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..5050ea73f0d --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/TagContainsKeywordsPredicate.java @@ -0,0 +1,34 @@ +package seedu.address.model.person.predicates; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +import java.util.Set; +import java.util.function.Predicate; + +/** + * 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 132f200822a..604d96314f6 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 04e670f50e7..822fd83597d 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -1,5 +1,7 @@ package seedu.address.model.tag; +import java.util.Locale; + import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; @@ -43,7 +45,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/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 3933135a71b..5cdc6c708cb 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -18,7 +18,7 @@ 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.predicates.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; import seedu.address.testutil.EditPersonDescriptorBuilder; diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java index 9b15db28bbb..806ea48548b 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.NameContainsKeywordsPredicate; /** * Contains integration tests (interaction with the Model) for {@code FindCommand}. diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index 1c548c3f854..34a00ff771f 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -23,7 +23,7 @@ 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.predicates.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; import seedu.address.testutil.EditPersonDescriptorBuilder; import seedu.address.testutil.PersonBuilder; diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java index 9db96f64a67..29eb34af8b6 100644 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test; import seedu.address.logic.commands.FindCommand; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.predicates.NameContainsKeywordsPredicate; public class FindCommandParserTest { diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 2cf1418d116..3feea2394bf 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.NameContainsKeywordsPredicate; import seedu.address.testutil.AddressBookBuilder; public class ModelManagerTest { diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java index f136664e017..7ffd6b30492 100644 --- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java +++ b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; +import seedu.address.model.person.predicates.NameContainsKeywordsPredicate; import seedu.address.testutil.PersonBuilder; public class NameContainsKeywordsPredicateTest { From 0b16f859944e71c01cc9a78b46fb2b2dd6c1aa88 Mon Sep 17 00:00:00 2001 From: Hong Sheng Date: Sun, 26 Mar 2023 00:34:37 +0800 Subject: [PATCH 6/6] Fix checkstyle errors --- .../address/logic/commands/FindCommand.java | 9 +++-- .../logic/parser/FindCommandParser.java | 34 ++++++++++++------- ...a => AddressContainsKeywordPredicate.java} | 14 ++++---- ...ava => EmailContainsKeywordPredicate.java} | 14 ++++---- ...java => NameContainsKeywordPredicate.java} | 19 ++++++++--- .../PhoneContainsKeywordsPredicate.java | 7 ++-- .../StatusContainsKeywordsPredicate.java | 7 ++-- .../TagContainsKeywordsPredicate.java | 6 ++-- .../java/seedu/address/model/tag/Tag.java | 2 -- .../java/seedu/address/ui/HelpWindow.java | 3 +- .../address/commons/util/StringUtilTest.java | 2 +- .../logic/commands/CommandTestUtil.java | 4 +-- .../logic/commands/FindCommandTest.java | 20 +++++------ .../logic/parser/AddressBookParserTest.java | 12 +++---- .../logic/parser/FindCommandParserTest.java | 15 -------- .../seedu/address/model/ModelManagerTest.java | 4 +-- .../NameContainsKeywordPredicateTest.java} | 32 ++++++++--------- 17 files changed, 103 insertions(+), 101 deletions(-) rename src/main/java/seedu/address/model/person/predicates/{AddressContainsKeywordsPredicate.java => AddressContainsKeywordPredicate.java} (56%) rename src/main/java/seedu/address/model/person/predicates/{EmailContainsKeywordsPredicate.java => EmailContainsKeywordPredicate.java} (56%) rename src/main/java/seedu/address/model/person/predicates/{NameContainsKeywordsPredicate.java => NameContainsKeywordPredicate.java} (59%) rename src/test/java/seedu/address/model/person/{NameContainsKeywordsPredicateTest.java => predicates/NameContainsKeywordPredicateTest.java} (55%) diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 403f80c3fe8..5556101dc07 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -2,14 +2,15 @@ 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.Person; -import java.util.function.Predicate; - /** - * 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 { @@ -21,6 +22,8 @@ public class FindCommand extends Command { + "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"; diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 7741a464805..5a9da390d29 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,15 +1,25 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.*; +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.List; import java.util.function.Predicate; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.*; -import seedu.address.model.person.predicates.*; +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 @@ -17,12 +27,12 @@ public class FindCommandParser implements Parser { private Prefix[] possiblePrefixes = { - PREFIX_NAME, - PREFIX_STATUS, - PREFIX_PHONE, - PREFIX_EMAIL, - PREFIX_ADDRESS, - PREFIX_TAG + PREFIX_NAME, + PREFIX_STATUS, + PREFIX_PHONE, + PREFIX_EMAIL, + PREFIX_ADDRESS, + PREFIX_TAG }; /** @@ -51,15 +61,15 @@ public FindCommand parse(String args) throws ParseException { } Predicate currentPredicate = null; if (p == PREFIX_NAME) { - currentPredicate = new NameContainsKeywordsPredicate(arg); + 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 EmailContainsKeywordsPredicate(arg); + currentPredicate = new EmailContainsKeywordPredicate(arg); } else if (p == PREFIX_ADDRESS) { - currentPredicate = new AddressContainsKeywordsPredicate(arg); + currentPredicate = new AddressContainsKeywordPredicate(arg); } else if (p == PREFIX_TAG) { currentPredicate = new TagContainsKeywordsPredicate(arg); } diff --git a/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordPredicate.java similarity index 56% rename from src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordPredicate.java index 7ab28211eb9..6eccf6ff369 100644 --- a/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordPredicate.java @@ -1,17 +1,17 @@ package seedu.address.model.person.predicates; +import java.util.function.Predicate; + import seedu.address.commons.util.StringUtil; import seedu.address.model.person.Person; -import java.util.function.Predicate; - /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Person}'s {@code Address} matches any of the keywords given. */ -public class AddressContainsKeywordsPredicate implements Predicate { +public class AddressContainsKeywordPredicate implements Predicate { private final String keyword; - public AddressContainsKeywordsPredicate(String keyword) { + public AddressContainsKeywordPredicate(String keyword) { this.keyword = keyword; } @@ -23,8 +23,8 @@ public boolean test(Person person) { @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof AddressContainsKeywordsPredicate // instanceof handles nulls - && keyword.equals(((AddressContainsKeywordsPredicate) other).keyword)); // state check + || (other instanceof AddressContainsKeywordPredicate // instanceof handles nulls + && keyword.equals(((AddressContainsKeywordPredicate) other).keyword)); // state check } } diff --git a/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordPredicate.java similarity index 56% rename from src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordPredicate.java index b10a77128e0..cce124dc277 100644 --- a/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordPredicate.java @@ -1,17 +1,17 @@ package seedu.address.model.person.predicates; +import java.util.function.Predicate; + import seedu.address.commons.util.StringUtil; import seedu.address.model.person.Person; -import java.util.function.Predicate; - /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Person}'s {@code Email} matches any of the keywords given. */ -public class EmailContainsKeywordsPredicate implements Predicate { +public class EmailContainsKeywordPredicate implements Predicate { private final String keyword; - public EmailContainsKeywordsPredicate(String keyword) { + public EmailContainsKeywordPredicate(String keyword) { this.keyword = keyword; } @@ -23,8 +23,8 @@ public boolean test(Person person) { @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof EmailContainsKeywordsPredicate // instanceof handles nulls - && keyword.equals(((EmailContainsKeywordsPredicate) other).keyword)); // state check + || (other instanceof EmailContainsKeywordPredicate // instanceof handles nulls + && keyword.equals(((EmailContainsKeywordPredicate) other).keyword)); // state check } } diff --git a/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordPredicate.java similarity index 59% rename from src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/person/predicates/NameContainsKeywordPredicate.java index e9e6761e02a..5b63118688f 100644 --- a/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordPredicate.java @@ -9,15 +9,24 @@ /** * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class NameContainsKeywordPredicate implements Predicate { private final List keywords; private final String keyword; - public NameContainsKeywordsPredicate(List keywords) { + /** + * 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; } - public NameContainsKeywordsPredicate(String keyword) { + + /** + * 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; } @@ -34,8 +43,8 @@ public boolean test(Person person) { @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 + || (other instanceof NameContainsKeywordPredicate // instanceof handles nulls + && keyword.equals(((NameContainsKeywordPredicate) other).keyword)); // state check } } diff --git a/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java index cff6ca0828f..49bf727b3d9 100644 --- a/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java @@ -1,13 +1,12 @@ package seedu.address.model.person.predicates; +import java.util.function.Predicate; + import seedu.address.commons.util.StringUtil; import seedu.address.model.person.Person; - -import java.util.function.Predicate; - /** - * 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; diff --git a/src/main/java/seedu/address/model/person/predicates/StatusContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/StatusContainsKeywordsPredicate.java index 7d2739eba16..8251cb7ed4f 100644 --- a/src/main/java/seedu/address/model/person/predicates/StatusContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/StatusContainsKeywordsPredicate.java @@ -1,10 +1,13 @@ package seedu.address.model.person.predicates; +import java.util.function.Predicate; + import seedu.address.commons.util.StringUtil; import seedu.address.model.person.Person; -import java.util.function.Predicate; - +/** + * Tests that a {@code Person}'s {@code Status} matches any of the keywords given. + */ public class StatusContainsKeywordsPredicate implements Predicate { private final String 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 index 5050ea73f0d..778643dd69d 100644 --- a/src/main/java/seedu/address/model/person/predicates/TagContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/TagContainsKeywordsPredicate.java @@ -1,12 +1,12 @@ 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; -import java.util.Set; -import java.util.function.Predicate; - /** * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. */ diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index 822fd83597d..ff9c2da4c40 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -1,7 +1,5 @@ package seedu.address.model.tag; -import java.util.Locale; - import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; 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 5cdc6c708cb..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.predicates.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 806ea48548b..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.predicates.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 34a00ff771f..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.predicates.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 29eb34af8b6..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.predicates.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 3feea2394bf..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.predicates.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 55% rename from src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java rename to src/test/java/seedu/address/model/person/predicates/NameContainsKeywordPredicateTest.java index 7ffd6b30492..c3f72781110 100644 --- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java +++ b/src/test/java/seedu/address/model/person/predicates/NameContainsKeywordPredicateTest.java @@ -1,32 +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.model.person.predicates.NameContainsKeywordsPredicate; 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 @@ -36,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())); }