Repository: AY2425S2-CS2103T-T14-3/tp
Pull request #68: Edit find feature v1.4
Author: login: simenggg
Created: 2025-03-27T04:00:46Z | Merged: 2025-03-27T12:48:51Z

User can find a contact by using keywords in student name or using student id

Fix issue #67 


Diff:
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
index 72b9eddd3a7..53856533278 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -6,30 +6,55 @@
 import seedu.address.logic.Messages;
 import seedu.address.model.Model;
 import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.StudentIdMatchPredicate;
 
 /**
  * Finds and lists all persons in address book whose name contains any of the argument keywords.
- * Keyword matching is case insensitive.
+ * 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"
+            + "the specified keywords (case-insensitive)"
+            + "or match the given student number"
+            + "and displays them as a list with index numbers.\n"
             + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
             + "Example: " + COMMAND_WORD + " alice bob charlie";
 
-    private final NameContainsKeywordsPredicate predicate;
+    private final NameContainsKeywordsPredicate name_predicate;
+
+    private final StudentIdMatchPredicate id_predicate;
+
+    /**
+     * @param name_predicate predicate regarding the keywords in a student's name the user wants to find
+     * @param id_predicate predicate regarding the studentId the user wants to find
+     */
+    public FindCommand(NameContainsKeywordsPredicate name_predicate, StudentIdMatchPredicate id_predicate) {
+        if (name_predicate == null && id_predicate == null) {
+            throw new IllegalArgumentException("At least one predicate must be provided.");
+        }
+        this.name_predicate = name_predicate;
+        this.id_predicate = id_predicate;
+    }
 
     public FindCommand(NameContainsKeywordsPredicate predicate) {
-        this.predicate = predicate;
+        this(predicate, null);
+    }
+
+    public FindCommand(StudentIdMatchPredicate predicate) {
+        this(null, predicate);
     }
 
     @Override
     public CommandResult execute(Model model) {
         requireNonNull(model);
-        model.updateFilteredPersonList(predicate);
+        if (id_predicate == null) {
+            model.updateFilteredPersonList(name_predicate);
+        } else {
+            model.updateFilteredPersonList(id_predicate);
+        }
         return new CommandResult(
                 String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
     }
@@ -46,13 +71,24 @@ public boolean equals(Object other) {
         }
 
         FindCommand otherFindCommand = (FindCommand) other;
-        return predicate.equals(otherFindCommand.predicate);
+        if (name_predicate == null) {
+            return otherFindCommand.name_predicate == null && otherFindCommand.id_predicate.equals(id_predicate);
+        } else {
+            return otherFindCommand.id_predicate == null && name_predicate.equals(otherFindCommand.name_predicate);
+        }
+
     }
 
     @Override
     public String toString() {
-        return new ToStringBuilder(this)
-                .add("predicate", predicate)
-                .toString();
+        if (id_predicate == null) {
+            return new ToStringBuilder(this)
+                    .add("name predicate", name_predicate)
+                    .toString();
+        } else {
+            return new ToStringBuilder(this)
+                    .add("id predicate", id_predicate)
+                    .toString();
+        }
     }
 }
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
index 2867bde857b..7b0b06134a9 100644
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
@@ -7,6 +7,8 @@
 import seedu.address.logic.commands.FindCommand;
 import seedu.address.logic.parser.exceptions.ParseException;
 import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.StudentId;
+import seedu.address.model.person.StudentIdMatchPredicate;
 
 /**
  * Parses input arguments and creates a new FindCommand object
@@ -25,9 +27,12 @@ public FindCommand parse(String args) throws ParseException {
                     String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
         }
 
-        String[] nameKeywords = trimmedArgs.split("\\s+");
-
-        return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+        if (trimmedArgs.matches("A\\d{7}[A-Z]")) {
+            StudentId studentId = new StudentId(trimmedArgs);
+            return new FindCommand(new StudentIdMatchPredicate(studentId));
+        } else {
+            String[] nameKeywords = trimmedArgs.split("\\s+");
+            return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+        }
     }
-
 }
diff --git a/src/main/java/seedu/address/model/person/StudentIdMatchPredicate.java b/src/main/java/seedu/address/model/person/StudentIdMatchPredicate.java
new file mode 100644
index 00000000000..ab0e8ddf20b
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/StudentIdMatchPredicate.java
@@ -0,0 +1,42 @@
+package seedu.address.model.person;
+
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.ToStringBuilder;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ */
+public class StudentIdMatchPredicate implements Predicate<Person> {
+    private final StudentId studentId;
+
+    public StudentIdMatchPredicate(StudentId studentId) {
+        this.studentId = studentId;
+    }
+
+    @Override
+    public boolean test(Person person) {
+        return person.getStudentId().equals(studentId);
+
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == this) {
+            return true;
+        }
+
+        // instanceof handles nulls
+        if (!(other instanceof NameContainsKeywordsPredicate)) {
+            return false;
+        }
+
+        StudentIdMatchPredicate otherStudentIdMatchPredicate = (StudentIdMatchPredicate) other;
+        return studentId.equals(otherStudentIdMatchPredicate.studentId);
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this).add("studentId", studentId).toString();
+    }
+}
diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
index b8b7dbba91a..908844a2595 100644
--- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
@@ -6,6 +6,7 @@
 import static seedu.address.logic.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
 import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
 import static seedu.address.testutil.TypicalPersons.CARL;
+import static seedu.address.testutil.TypicalPersons.DANIEL;
 import static seedu.address.testutil.TypicalPersons.ELLE;
 import static seedu.address.testutil.TypicalPersons.FIONA;
 import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
@@ -19,6 +20,8 @@
 import seedu.address.model.ModelManager;
 import seedu.address.model.UserPrefs;
 import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.StudentId;
+import seedu.address.model.person.StudentIdMatchPredicate;
 
 /**
  * Contains integration tests (interaction with the Model) for {@code FindCommand}.
@@ -74,11 +77,31 @@ public void execute_multipleKeywords_multiplePersonsFound() {
         assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList());
     }
 
+    @Test
+    public void execute_findByStudentId() {
+        String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1);
+        StudentIdMatchPredicate predicate = new StudentIdMatchPredicate(new StudentId("A1234567D"));
+        FindCommand command = new FindCommand(predicate);
+        expectedModel.updateFilteredPersonList(predicate);
+        assertCommandSuccess(command, model, expectedMessage, expectedModel);
+        assertEquals(Arrays.asList(DANIEL), model.getFilteredPersonList());
+
+    }
+
+
     @Test
     public void toStringMethod() {
         NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Arrays.asList("keyword"));
         FindCommand findCommand = new FindCommand(predicate);
-        String expected = FindCommand.class.getCanonicalName() + "{predicate=" + predicate + "}";
+        String expected = FindCommand.class.getCanonicalName() + "{name predicate=" + predicate + "}";
+        assertEquals(expected, findCommand.toString());
+    }
+
+    @Test
+    public void toStringMethod_id() {
+        StudentIdMatchPredicate predicate = new StudentIdMatchPredicate(new StudentId("A1112222P"));
+        FindCommand findCommand = new FindCommand(predicate);
+        String expected = FindCommand.class.getCanonicalName() + "{id predicate=" + predicate + "}";
         assertEquals(expected, findCommand.toString());
     }
 


Reviews:
user: login: laishuya
body: lgtm
state: APPROVED
author_association: NONE
submitted_at: 2025-03-27T12:48:23Z
user: login: TasmiaH0508
body: Reviewed code quality. Generally, code is readable but there are some code quality issues that can be fixed by next milestone. 
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T08:11:10Z

Review comments:
diff_hunk: @@ -46,13 +71,24 @@ public boolean equals(Object other) {
         }
 
         FindCommand otherFindCommand = (FindCommand) other;
-        return predicate.equals(otherFindCommand.predicate);
+        if (name_predicate == null) {
+            return otherFindCommand.name_predicate == null && otherFindCommand.id_predicate.equals(id_predicate);
+        } else {
+            return otherFindCommand.id_predicate == null && name_predicate.equals(otherFindCommand.name_predicate);
+        }
+
path: src/main/java/seedu/address/logic/commands/FindCommand.java
user: login: laishuya
body: The checks on whether the input is a name or student id look correct 👍 
created_at: 2025-03-27T12:43:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+package seedu.address.model.person;
+
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.ToStringBuilder;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ */
+public class StudentIdMatchPredicate implements Predicate<Person> {
+    private final StudentId studentId;
+
+    public StudentIdMatchPredicate(StudentId studentId) {
+        this.studentId = studentId;
+    }
+
+    @Override
+    public boolean test(Person person) {
+        return person.getStudentId().equals(studentId);
+
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == this) {
+            return true;
+        }
+
+        // instanceof handles nulls
+        if (!(other instanceof NameContainsKeywordsPredicate)) {
+            return false;
path: src/main/java/seedu/address/model/person/StudentIdMatchPredicate.java
user: login: laishuya
body: Not an important issue for v1.4, but a StudentIdMatchPredicateTest class can be added in the later iterations. You can refer to the NameContainsKeywordsPredicateTest and StudentHasSameIdPredicateTest classes for sample tests.
created_at: 2025-03-27T12:48:14Z
author_association: NONE
diff_hunk: @@ -46,13 +71,24 @@ public boolean equals(Object other) {
         }
 
         FindCommand otherFindCommand = (FindCommand) other;
-        return predicate.equals(otherFindCommand.predicate);
+        if (name_predicate == null) {
+            return otherFindCommand.name_predicate == null && otherFindCommand.id_predicate.equals(id_predicate);
+        } else {
path: src/main/java/seedu/address/logic/commands/FindCommand.java
user: login: TasmiaH0508
body: Variable names should be in camel case, in accordance with the course's coding standards. 
created_at: 2025-03-28T08:04:19Z
author_association: NONE
diff_hunk: @@ -6,30 +6,55 @@
 import seedu.address.logic.Messages;
 import seedu.address.model.Model;
 import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.StudentIdMatchPredicate;
 
 /**
  * Finds and lists all persons in address book whose name contains any of the argument keywords.
- * Keyword matching is case insensitive.
+ * 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"
+            + "the specified keywords (case-insensitive)"
+            + "or match the given student number"
+            + "and displays them as a list with index numbers.\n"
             + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
             + "Example: " + COMMAND_WORD + " alice bob charlie";
 
-    private final NameContainsKeywordsPredicate predicate;
+    private final NameContainsKeywordsPredicate name_predicate;
path: src/main/java/seedu/address/logic/commands/FindCommand.java
user: login: TasmiaH0508
body: Should use camel case for variable names, in accordance with coding standards. 
created_at: 2025-03-28T08:05:51Z
author_association: NONE
diff_hunk: @@ -74,11 +77,31 @@ public void execute_multipleKeywords_multiplePersonsFound() {
         assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList());
     }
 
+    @Test
+    public void execute_findByStudentId() {
+        String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1);
+        StudentIdMatchPredicate predicate = new StudentIdMatchPredicate(new StudentId("A1234567D"));
+        FindCommand command = new FindCommand(predicate);
+        expectedModel.updateFilteredPersonList(predicate);
+        assertCommandSuccess(command, model, expectedMessage, expectedModel);
+        assertEquals(Arrays.asList(DANIEL), model.getFilteredPersonList());
+
+    }
+
+
     @Test
     public void toStringMethod() {
         NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Arrays.asList("keyword"));
         FindCommand findCommand = new FindCommand(predicate);
-        String expected = FindCommand.class.getCanonicalName() + "{predicate=" + predicate + "}";
+        String expected = FindCommand.class.getCanonicalName() + "{name predicate=" + predicate + "}";
+        assertEquals(expected, findCommand.toString());
+    }
+
+    @Test
+    public void toStringMethod_id() {
+        StudentIdMatchPredicate predicate = new StudentIdMatchPredicate(new StudentId("A1112222P"));
+        FindCommand findCommand = new FindCommand(predicate);
+        String expected = FindCommand.class.getCanonicalName() + "{id predicate=" + predicate + "}";
path: src/test/java/seedu/address/logic/commands/FindCommandTest.java
user: login: TasmiaH0508
body: The code quality here can be improved, by not using magic literals. 

You may look at this section of the textbook: [https://nus-cs2103-ay2425s2.github.io/website/se-book-adapted/chapters/codeQuality.html#avoid-magic-numbers](url)
created_at: 2025-03-28T08:10:03Z
author_association: NONE

PR comments:
user: login: codecov[bot]
created_at: 2025-03-27T04:01:52Z
body: ## [Codecov](https://app.codecov.io/gh/AY2425S2-CS2103T-T14-3/tp/pull/68?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T14-3) Report
Attention: Patch coverage is `63.15789%` with `14 lines` in your changes missing coverage. Please review.
| [Files with missing lines](https://app.codecov.io/gh/AY2425S2-CS2103T-T14-3/tp/pull/68?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T14-3) | Patch % | Lines |
|---|---|---|
| [.../address/model/person/StudentIdMatchPredicate.java](https://app.codecov.io/gh/AY2425S2-CS2103T-T14-3/tp/pull/68?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T14-3#diff-c3JjL21haW4vamF2YS9zZWVkdS9hZGRyZXNzL21vZGVsL3BlcnNvbi9TdHVkZW50SWRNYXRjaFByZWRpY2F0ZS5qYXZh) | 45.45% | [6 Missing :warning: ](https://app.codecov.io/gh/AY2425S2-CS2103T-T14-3/tp/pull/68?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T14-3) |
| [...java/seedu/address/logic/commands/FindCommand.java](https://app.codecov.io/gh/AY2425S2-CS2103T-T14-3/tp/pull/68?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T14-3#diff-c3JjL21haW4vamF2YS9zZWVkdS9hZGRyZXNzL2xvZ2ljL2NvbW1hbmRzL0ZpbmRDb21tYW5kLmphdmE=) | 77.27% | [2 Missing and 3 partials :warning: ](https://app.codecov.io/gh/AY2425S2-CS2103T-T14-3/tp/pull/68?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T14-3) |
| [.../seedu/address/logic/parser/FindCommandParser.java](https://app.codecov.io/gh/AY2425S2-CS2103T-T14-3/tp/pull/68?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T14-3#diff-c3JjL21haW4vamF2YS9zZWVkdS9hZGRyZXNzL2xvZ2ljL3BhcnNlci9GaW5kQ29tbWFuZFBhcnNlci5qYXZh) | 40.00% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/AY2425S2-CS2103T-T14-3/tp/pull/68?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AY2425S2-CS2103T-T14-3) |

:loudspeaker: Thoughts on this report? [Let us know!](https://github.com/codecov/feedback/issues/255)
author_association: NONE