Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update FindCommand to handle prefixes #68

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,20 @@ task coverage(type: JacocoReport) {

dependencies {
String jUnitVersion = '5.4.0'
String javaFxVersion = '17.0.7'

// JavaFX dependencies for specific platforms
String javaFxVersion = '17.0.7'
implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux'
implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac-aarch64' // Changed to mac-aarch64 for Apple Silicon
implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux'
implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac-aarch64' // Changed to mac-aarch64 for Apple Silicon
implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux'
implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac-aarch64' // Changed to mac-aarch64 for Apple Silicon
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac-aarch64' // Changed to mac-aarch64 for Apple Silicon

// Other dependencies
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0'
Expand Down
41 changes: 12 additions & 29 deletions src/main/java/seedu/address/logic/commands/FindCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,25 @@

import java.util.function.Predicate;

import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
import seedu.address.model.Model;
import seedu.address.model.student.Student;

/**
* Finds and lists all persons in address book whose attribute satisfies the given predicate.
* Finds and lists all persons in address book whose name contains any of the argument keywords.
* Keyword matching is case insensitive.
*/
public abstract class FindCommand extends Command {
public class FindCommand extends Command {

public static final String COMMAND_WORD = "find";

public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons with the specified attribute "
+ "containing any of the specified keywords (case-insensitive)"
+ "and displays them as a list with index numbers.\n"
+ "Parameters: PREFIX/KEYWORD [PREFIX/MORE_KEYWORDS]...\n"
+ "Example: " + COMMAND_WORD + " n/alice n/bob";
public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose attributes satisfy "
+ "all of the specified search criteria and displays them as a list with index numbers.\n"
+ "Parameters: [n/NAME] [c/COURSE]...\n"
+ "Example: " + COMMAND_WORD + " n/alice c/CS2103T";

protected final Predicate<Student> predicate;
private final Predicate predicate;

public FindCommand(Predicate<Student> predicate) {
public FindCommand(Predicate predicate) {
this.predicate = predicate;
}

Expand All @@ -38,23 +36,8 @@ public CommandResult execute(Model model) {

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

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

FindCommand otherFindCommand = (FindCommand) other;
return predicate.equals(otherFindCommand.predicate);
}

@Override
public String toString() {
return new ToStringBuilder(this)
.add("predicate", predicate)
.toString();
return other == this // short circuit if same object
|| (other instanceof FindCommand // instanceof handles nulls
&& predicate.equals(((FindCommand) other).predicate)); // state check
}
}
19 changes: 0 additions & 19 deletions src/main/java/seedu/address/logic/commands/FindNameCommand.java

This file was deleted.

43 changes: 27 additions & 16 deletions src/main/java/seedu/address/logic/parser/FindCommandParser.java
Original file line number Diff line number Diff line change
@@ -1,47 +1,58 @@
package seedu.address.logic.parser;

import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_COURSE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;

import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.FindNameCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.student.CourseContainsKeywordsPredicate;
import seedu.address.model.student.NameContainsKeywordsPredicate;
import seedu.address.model.student.Student;

/**
* Parses input arguments and creates a new FindNameCommand object
* Parses input arguments and creates a new FindCommand object
*/
public class FindCommandParser implements Parser<FindCommand> {

/**
* Parses the given {@code String} of arguments in the context of the FindNameCommand
* and returns a FindNameCommand object for execution.
* Parses the given {@code String} of arguments in the context of the FindCommand
* and returns a FindCommand object for execution.
* @throws ParseException if the user input does not conform the expected format
*/
public FindCommand parse(String args) throws ParseException {
ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME);
ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_COURSE);

if (!argMultimap.getPreamble().isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindNameCommand.MESSAGE_USAGE));
if (argMultimap.getPreamble().isEmpty()
&& (!arePrefixesPresent(argMultimap, PREFIX_NAME)
&& !arePrefixesPresent(argMultimap, PREFIX_COURSE))) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}

if (!arePrefixesPresent(argMultimap, PREFIX_NAME)) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindNameCommand.MESSAGE_USAGE));
List<Predicate<Student>> predicates = new ArrayList<>();

if (arePrefixesPresent(argMultimap, PREFIX_NAME)) {
NameContainsKeywordsPredicate namePredicate =
new NameContainsKeywordsPredicate(argMultimap.getAllValues(PREFIX_NAME));
predicates.add(namePredicate);
}

List<String> nameKeywords = argMultimap.getAllValues(PREFIX_NAME);
if (arePrefixesPresent(argMultimap, PREFIX_COURSE)) {
CourseContainsKeywordsPredicate coursePredicate =
new CourseContainsKeywordsPredicate(argMultimap.getAllValues(PREFIX_COURSE));
predicates.add(coursePredicate);
}

return new FindNameCommand(new NameContainsKeywordsPredicate(nameKeywords));
Predicate<Student> finalPredicate = predicates.stream().reduce(x -> true, Predicate::and);
return new FindCommand(finalPredicate);
}

/**
* Returns true if none of the prefixes contains empty {@code Optional} values in the given
* {@code ArgumentMultimap}.
*/
private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package seedu.address.model.student;

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

import seedu.address.commons.util.StringUtil;

/**
* Tests that a {@code Course}'s {@code courseCode} matches any of the keywords given.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want course matching to be exact and case-insensitive, because a tutor for CS2040 may want to exclude students from CS2040S for example.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've worked on this, see #71.

*/
public class CourseContainsKeywordsPredicate implements Predicate<Student> {
private final List<String> keywords;

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

@Override
public boolean test(Student student) {
return keywords.stream()
.anyMatch(keyword ->
student.getCourses().stream()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this works, but maybe for the sake of readability / slap / less nesting etc, maybe a separate function to get a student's courses as a List<String> could be used? (Then we can do .anyMatch here.)

.anyMatch(course ->
StringUtil.containsWordIgnoreCase(course.courseCode, keyword)));
}

@Override
public boolean equals(Object other) {
return other == this // short circuit if same object
|| (other instanceof CourseContainsKeywordsPredicate // instanceof handles nulls
&& keywords.equals(((CourseContainsKeywordsPredicate) other).keywords)); // state check
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,9 @@ public boolean test(Student person) {

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

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

NameContainsKeywordsPredicate otherNameContainsKeywordsPredicate = (NameContainsKeywordsPredicate) other;
return keywords.equals(otherNameContainsKeywordsPredicate.keywords);
return other == this // short circuit if same object
|| (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls
&& keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,49 @@
"phone" : "94351253",
"email" : "alice@example.com",
"address" : "123, Jurong West Ave 6, #08-111",
"tags" : [ "friends" ]
"tags" : [ "friends" ],
"courses" : [ ]
}, {
"name" : "Benson Meier",
"phone" : "98765432",
"email" : "johnd@example.com",
"address" : "311, Clementi Ave 2, #02-25",
"tags" : [ "owesMoney", "friends" ]
"tags" : [ "owesMoney", "friends" ],
"courses" : [ ]
}, {
"name" : "Carl Kurz",
"phone" : "95352563",
"email" : "heinz@example.com",
"address" : "wall street",
"tags" : [ ]
"tags" : [ ],
"courses" : [ "CS2103T" ]
}, {
"name" : "Daniel Meier",
"phone" : "87652533",
"email" : "cornelia@example.com",
"address" : "10th street",
"tags" : [ "friends" ]
"tags" : [ "friends" ],
"courses" : [ ]
}, {
"name" : "Elle Meyer",
"phone" : "9482224",
"email" : "werner@example.com",
"address" : "michegan ave",
"tags" : [ ]
"tags" : [ ],
"courses" : [ ]
}, {
"name" : "Fiona Kunz",
"phone" : "9482427",
"email" : "lydia@example.com",
"address" : "little tokyo",
"tags" : [ ]
"tags" : [ ],
"courses" : [ ]
}, {
"name" : "George Best",
"phone" : "9482442",
"email" : "anna@example.com",
"address" : "4th street",
"tags" : [ ]
"tags" : [ ],
"courses" : [ ]
} ]
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@

import java.util.Arrays;
import java.util.Collections;
import java.util.function.Predicate;

import org.junit.jupiter.api.Test;

import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
import seedu.address.model.student.CourseContainsKeywordsPredicate;
import seedu.address.model.student.NameContainsKeywordsPredicate;
import seedu.address.model.student.Student;

/**
* Contains integration tests (interaction with the Model) for {@code FindNameCommand}.
* Contains integration tests (interaction with the Model) for {@code FindCommand}.
*/
public class FindNameCommandTest {
public class FindCommandTest {
private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());

Expand All @@ -34,14 +37,14 @@ public void equals() {
NameContainsKeywordsPredicate secondPredicate =
new NameContainsKeywordsPredicate(Collections.singletonList("second"));

FindNameCommand findFirstCommand = new FindNameCommand(firstPredicate);
FindNameCommand findSecondCommand = new FindNameCommand(secondPredicate);
FindCommand findFirstCommand = new FindCommand(firstPredicate);
FindCommand findSecondCommand = new FindCommand(secondPredicate);

// same object -> returns true
assertTrue(findFirstCommand.equals(findFirstCommand));

// same values -> returns true
FindNameCommand findFirstCommandCopy = new FindNameCommand(firstPredicate);
FindCommand findFirstCommandCopy = new FindCommand(firstPredicate);
assertTrue(findFirstCommand.equals(findFirstCommandCopy));

// different types -> returns false
Expand All @@ -58,7 +61,7 @@ public void equals() {
public void execute_zeroKeywords_noPersonFound() {
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
NameContainsKeywordsPredicate predicate = preparePredicate(" ");
FindNameCommand command = new FindNameCommand(predicate);
FindCommand command = new FindCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(Collections.emptyList(), model.getFilteredPersonList());
Expand All @@ -67,13 +70,44 @@ 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");
FindNameCommand command = new FindNameCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
NameContainsKeywordsPredicate namePredicate = new NameContainsKeywordsPredicate(
Arrays.asList("Kurz", "Elle", "Kunz"));
FindCommand command = new FindCommand(namePredicate);
expectedModel.updateFilteredPersonList(namePredicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList());
}

@Test
public void toStringMethod() {
NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Arrays.asList("keyword"));
// FindCommand findCommand = new FindCommand(predicate);
String result = FindCommand.class.getCanonicalName() + "{predicate=" + predicate + "}";

// Check if the result contains the essential parts
assertTrue(result.contains("FindCommand"));
assertTrue(result.contains("predicate"));
assertTrue(result.contains(predicate.toString()));
}


@Test
public void execute_nameAndCourseCriteria_personFound() {
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1);
NameContainsKeywordsPredicate namePredicate = new NameContainsKeywordsPredicate(Arrays.asList("Kurz"));
CourseContainsKeywordsPredicate coursePredicate = new CourseContainsKeywordsPredicate(Arrays.asList("CS2103T"));
Predicate<Student> combinedPredicate = namePredicate.and(coursePredicate);
FindCommand command = new FindCommand(combinedPredicate);
expectedModel.updateFilteredPersonList(combinedPredicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);

assertEquals(1, model.getFilteredPersonList().size());
Student foundStudent = model.getFilteredPersonList().get(0);
assertTrue(foundStudent.getName().fullName.toLowerCase().contains("kurz"));
assertTrue(foundStudent.getCourses().stream()
.anyMatch(course -> course.courseCode.equalsIgnoreCase("CS2103T")));
}

/**
* Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}.
*/
Expand Down
Loading
Loading