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

Persistent search #130

Merged
merged 30 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
201c24e
Add InitSearchMode command
jan-kai1 Oct 27, 2024
2754c7d
Add PredicateOrCombiner and AndCombiner
jan-kai1 Oct 27, 2024
a3dadfb
Remove Predicatecombiner classes
jan-kai1 Oct 27, 2024
58548c0
Change matching test for FieldContainsKeywordsPredicate
jan-kai1 Oct 29, 2024
b91d0ae
Add implementation of searchmodesearch
jan-kai1 Oct 29, 2024
331b36e
Fix logic error in LogicManager
jan-kai1 Oct 29, 2024
de3197c
Change how predicates are stored in SearchModeSearchCommand
jan-kai1 Oct 29, 2024
22e6404
Resolve merge with master
jan-kai1 Oct 29, 2024
2c73c1e
Checkstyle fix
jan-kai1 Oct 29, 2024
eae8ff6
Bug fix for wrong search command called
jan-kai1 Oct 29, 2024
a1a82cf
Fix Checkstyle errors
jan-kai1 Oct 29, 2024
a9846ef
Merge pull request #129 from jan-kai1/PersistentSearch
jan-kai1 Oct 29, 2024
ba75067
Simplify predicates used for Searchmodesearchcommand
jan-kai1 Nov 1, 2024
b34a112
Add testcases for new Predicate Classes
jan-kai1 Nov 1, 2024
b8f5432
Fix bug with empty inputs
jan-kai1 Nov 1, 2024
5d4d14d
Add test case for Searchmodesearchcommandparser
jan-kai1 Nov 1, 2024
b2d7dba
Fix checkstyle errors
jan-kai1 Nov 1, 2024
9bb1b6a
Add testcase for searchmodecommandparser and remove wrapper class for…
jan-kai1 Nov 1, 2024
7f89cdc
Merge pull request #139 from jan-kai1/PersistentSearch
jan-kai1 Nov 1, 2024
c271274
Resolve merge conflict
jan-kai1 Nov 1, 2024
88fddfa
Merge pull request #140 from jan-kai1/PersistentSearch
jan-kai1 Nov 1, 2024
35f6098
Add additional test cases for addressbookparser
jan-kai1 Nov 1, 2024
e7c376d
Add testcase for exit search mode
jan-kai1 Nov 1, 2024
e9e99dd
Fix checkstyle errors
jan-kai1 Nov 1, 2024
1fd0beb
Add test case coverage
jan-kai1 Nov 1, 2024
aacfe05
Add additional coverage
jan-kai1 Nov 1, 2024
d256bce
Merge pull request #141 from jan-kai1/PersistentSearch
jan-kai1 Nov 1, 2024
70a747f
Resolve merge conflict with master branch
jan-kai1 Nov 4, 2024
a5ab090
Refactor SearchModeSearchCommandParser to improve readability
jan-kai1 Nov 4, 2024
185126e
Merge pull request #162 from jan-kai1/PersistentSearch
jan-kai1 Nov 4, 2024
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: 10 additions & 1 deletion src/main/java/seedu/address/logic/LogicManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

private final EventManager eventManager;


/**
* Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}.
*/
Expand All @@ -52,7 +53,15 @@
logger.info("----------------[USER COMMAND][" + commandText + "]");

CommandResult commandResult;
Command contactCommand = addressBookParser.parseCommand(commandText);
Command contactCommand;
if (model.getSearchMode()) {
logger.info("Searchmode command detected");
contactCommand = addressBookParser.parseSearchCommand(commandText);

Check warning on line 59 in src/main/java/seedu/address/logic/LogicManager.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/LogicManager.java#L58-L59

Added lines #L58 - L59 were not covered by tests

} else {
contactCommand = addressBookParser.parseCommand(commandText);

}
commandResult = contactCommand.execute(model, eventManager);

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import seedu.address.logic.commands.CommandResult;
import seedu.address.model.Model;
import seedu.address.model.event.EventManager;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.predicates.NameContainsKeywordsPredicate;

/**
* Finds and lists all persons in address book whose name contains any of the argument keywords.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import seedu.address.logic.commands.CommandResult;
import seedu.address.model.Model;
import seedu.address.model.event.EventManager;
import seedu.address.model.person.PersonIsRolePredicate;
import seedu.address.model.person.predicates.PersonIsRolePredicate;

/**
* Finds and lists all persons in address book who has any role equal to any of the role keywords.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package seedu.address.logic.commands.searchmode;

import static java.util.Objects.requireNonNull;

import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.CommandResult;
import seedu.address.model.Model;
import seedu.address.model.event.EventManager;


/**
* Exits search mode.
*/
public class ExitSearchModeCommand extends Command {
public static final String COMMAND_WORD = "exitsearchmode";

public static final String MESSAGE_USAGE = COMMAND_WORD + ": Exits search mode.\n"
+ "Example: " + COMMAND_WORD;

public static final String MESSAGE_SUCCESS = "Exited search mode.";

public ExitSearchModeCommand() {
}
@Override
public CommandResult execute(Model model, EventManager eventManager) {
requireNonNull(model);
model.setSearchMode(false);
model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
return new CommandResult(MESSAGE_SUCCESS);
}

@Override
public boolean equals(Object other) {
return other == this // short circuit if same object
|| other instanceof ExitSearchModeCommand; // instanceof handles nulls
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package seedu.address.logic.commands.searchmode;

import static java.util.Objects.requireNonNull;

import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.CommandResult;
import seedu.address.model.Model;
import seedu.address.model.event.EventManager;


/**
* Enters search mode.
*/
public class InitSearchModeCommand extends Command {
public static final String COMMAND_WORD = "searchmode";

public static final String MESSAGE_USAGE = COMMAND_WORD + ": Enters search mode.\n"
+ "Example: " + COMMAND_WORD;

public static final String MESSAGE_SUCCESS = "Entered search mode.";

public InitSearchModeCommand() {
}

Check warning on line 23 in src/main/java/seedu/address/logic/commands/searchmode/InitSearchModeCommand.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/commands/searchmode/InitSearchModeCommand.java#L22-L23

Added lines #L22 - L23 were not covered by tests
@Override
public CommandResult execute(Model model, EventManager eventManager) {
requireNonNull(model);
model.setSearchMode(true);

Check warning on line 27 in src/main/java/seedu/address/logic/commands/searchmode/InitSearchModeCommand.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/commands/searchmode/InitSearchModeCommand.java#L26-L27

Added lines #L26 - L27 were not covered by tests
// empties the current displayed list
model.updateFilteredPersonList(person -> false);
return new CommandResult(MESSAGE_SUCCESS);

Check warning on line 30 in src/main/java/seedu/address/logic/commands/searchmode/InitSearchModeCommand.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/commands/searchmode/InitSearchModeCommand.java#L29-L30

Added lines #L29 - L30 were not covered by tests
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package seedu.address.logic.commands.searchmode;

import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;

import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.CommandResult;
import seedu.address.model.Model;
import seedu.address.model.event.EventManager;
import seedu.address.model.person.Person;


/**
* Searches for all persons in the address book whose names contain all the specified keywords.
*/
public class SearchModeSearchCommand extends Command {
public static final String COMMAND_WORD = "search";

public static final String MESSAGE_USAGE = COMMAND_WORD
+ ": Searches for all persons whose fields contain the specified keywords (case-insensitive) "
+ "and displays them as a list with index numbers.\n"
+ "Parameters: [Flag] [MORE_KEYWORDS]...\n"
+ "Example: " + COMMAND_WORD + " n/Amy Bob Charlie";

public static final String MESSAGE_SUCCESS = "Added all Persons who fit search parameter";

//maintains a set of predicates, reducing them to get the final predicate in execute
private final Set<Predicate<Person>> predicates = new HashSet<>();

public SearchModeSearchCommand(Predicate<Person> predicate) {
this.predicates.add(predicate);
}

public SearchModeSearchCommand(Set<Predicate<Person>> predicates) {
this.predicates.addAll(predicates);
}

@Override
public CommandResult execute(Model model, EventManager eventManager) {
// combine the new predicates with the old ones
Predicate<Person> currPredicate = model.getLastPredicate();
Predicate<Person> predicate = predicates.stream().reduce(Predicate::and).orElse(x -> true);
Predicate<Person> newPredicate = currPredicate.or(predicate);
model.updateFilteredPersonList(newPredicate);
return new CommandResult(MESSAGE_SUCCESS);
}

public Set<Predicate<Person>> getPredicates() {
return predicates;

Check warning on line 50 in src/main/java/seedu/address/logic/commands/searchmode/SearchModeSearchCommand.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/commands/searchmode/SearchModeSearchCommand.java#L50

Added line #L50 was not covered by tests
}

@Override
public boolean equals(Object other) {
return other == this // short circuit if same object
|| (other instanceof SearchModeSearchCommand // instanceof handles nulls
&& predicates.equals(((SearchModeSearchCommand) other).predicates));
}
}
38 changes: 38 additions & 0 deletions src/main/java/seedu/address/logic/parser/AddressBookParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import seedu.address.logic.commands.event.commands.AddPersonToEventCommand;
import seedu.address.logic.commands.event.commands.RemovePersonFromEventCommand;
import seedu.address.logic.commands.event.commands.ViewEventCommand;
import seedu.address.logic.commands.searchmode.ExitSearchModeCommand;
import seedu.address.logic.commands.searchmode.InitSearchModeCommand;
import seedu.address.logic.commands.searchmode.SearchModeSearchCommand;
import seedu.address.logic.parser.contact.parser.AddCommandParser;
import seedu.address.logic.parser.contact.parser.DeleteCommandParser;
import seedu.address.logic.parser.contact.parser.EditCommandParser;
Expand Down Expand Up @@ -97,6 +100,9 @@
case AddEventCommand.COMMAND_WORD:
return new NewEventCommandParser().parse(arguments);

case InitSearchModeCommand.COMMAND_WORD:
return new InitSearchModeCommand();

Check warning on line 104 in src/main/java/seedu/address/logic/parser/AddressBookParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/AddressBookParser.java#L104

Added line #L104 was not covered by tests

case ViewEventCommand.COMMAND_WORD:
return new ViewEventCommandParser().parse(arguments);

Expand All @@ -112,4 +118,36 @@
}
}

/**
* Parses user input into command for execution in search mode.
*
* @param userInput full user input string
* @return the command based on the user input
* @throws ParseException if the user input does not conform the expected format
*/
public Command parseSearchCommand(String userInput) throws ParseException {
final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
if (!matcher.matches()) {
logger.warning("This user input caused a ParseException: " + userInput);
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
}

final String commandWord = matcher.group("commandWord");
final String arguments = matcher.group("arguments");

// Note to developers: Change the log level in config.json to enable lower level (i.e., FINE, FINER and lower)
// log messages such as the one below.
// Lower level log messages are used sparingly to minimize noise in the code.
logger.fine("Using SearchCommandParser to parse user input: " + userInput);
logger.fine("Command word: " + commandWord + "; Arguments: " + arguments);
switch (commandWord) {
case ExitSearchModeCommand.COMMAND_WORD:
return new ExitSearchModeCommand();
case SearchModeSearchCommand.COMMAND_WORD:
return new SearchModeSearchCommandParser().parse(arguments);
default:
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);

Check warning on line 149 in src/main/java/seedu/address/logic/parser/AddressBookParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/AddressBookParser.java#L149

Added line #L149 was not covered by tests
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package seedu.address.logic.parser;


import static java.util.Objects.requireNonNull;
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_ROLE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import seedu.address.logic.Messages;
import seedu.address.logic.commands.searchmode.SearchModeSearchCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Person;
import seedu.address.model.person.predicates.AddressContainsKeywordsPredicate;
import seedu.address.model.person.predicates.EmailContainsKeywordsPredicate;
import seedu.address.model.person.predicates.NameContainsKeywordsPredicate;
import seedu.address.model.person.predicates.PersonIsRolePredicate;
import seedu.address.model.person.predicates.PhoneNumberContainsKeywordPredicate;
import seedu.address.model.person.predicates.TelegramContainsKeywordsPredicate;
import seedu.address.model.role.Role;

/**
* Parses input arguments and creates a new SearchModeSearchCommand object
*/
public class SearchModeSearchCommandParser implements Parser<SearchModeSearchCommand> {
private static final Logger logger = Logger.getLogger(SearchModeSearchCommandParser.class.getName());
@Override
public SearchModeSearchCommand parse(String args) throws ParseException {
requireNonNull(args);
ArgumentMultimap argMultimap =
ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
PREFIX_TELEGRAM, PREFIX_ROLE);
argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
PREFIX_TELEGRAM, PREFIX_ROLE);
//for each prefix, if present, parse the value and create a predicate...
// combine the predicates using AND
// return the search command with the predicate
// original predicate just show all
Set<Predicate<Person>> predicates = new HashSet<>();
// if a field is present, AND with the predicate for that field
if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
Predicate<Person> namePred = createPersonPredicate(argMultimap);
predicates.add(namePred);
}
if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
Predicate<Person> phonePred = createPhonePredicate(argMultimap);
predicates.add(phonePred);
}
if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) {
Predicate<Person> emailPred = createEmailPredicate(argMultimap);
predicates.add(emailPred);
}
if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
Predicate<Person> addressPred = createAddressPredicate(argMultimap);

predicates.add(addressPred);
}
if (argMultimap.getValue(PREFIX_TELEGRAM).isPresent()) {
Predicate<Person> telegramPred = createTelegramPredicate(argMultimap);
predicates.add(telegramPred);

Check warning on line 70 in src/main/java/seedu/address/logic/parser/SearchModeSearchCommandParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/SearchModeSearchCommandParser.java#L69-L70

Added lines #L69 - L70 were not covered by tests
}
//role have to use separate predicate
if (argMultimap.getValue(PREFIX_ROLE).isPresent()) {
Predicate<Person> rolePred = createRolePredicate(argMultimap);
predicates.add(rolePred);
}
if (predicates.isEmpty()) {
throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT,
SearchModeSearchCommand.MESSAGE_USAGE));
}
return new SearchModeSearchCommand(predicates);
}

private static Predicate<Person> createRolePredicate(ArgumentMultimap argMultimap) throws ParseException {
String roles = argMultimap.getValue(PREFIX_ROLE).get().trim();
// map each word in String roles to a Role object


Set<Role> roleSet = ParserUtil.parseRoles(argMultimap.getAllValues(PREFIX_ROLE));
List<Role> roleList = roleSet.stream().collect(Collectors.toList());

Predicate<Person> rolePred = new PersonIsRolePredicate(roleList);
return rolePred;
}

private static Predicate<Person> createTelegramPredicate(ArgumentMultimap argMultimap) {
String telegram = argMultimap.getValue(PREFIX_TELEGRAM).get().trim();
String[] telegramKeywords = telegram.split("\\s+");
Predicate<Person> telegramPred = new TelegramContainsKeywordsPredicate(
Arrays.stream(telegramKeywords).toList());
return telegramPred;

Check warning on line 101 in src/main/java/seedu/address/logic/parser/SearchModeSearchCommandParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/SearchModeSearchCommandParser.java#L97-L101

Added lines #L97 - L101 were not covered by tests
}

private static Predicate<Person> createAddressPredicate(ArgumentMultimap argMultimap) {
String address = argMultimap.getValue(PREFIX_ADDRESS).get().trim();
String[] addressKeywords = address.split("\\s+");
Predicate<Person> addressPred = new AddressContainsKeywordsPredicate(
Arrays.stream(addressKeywords).toList());
return addressPred;
}

private static Predicate<Person> createEmailPredicate(ArgumentMultimap argMultimap) {
String email = argMultimap.getValue(PREFIX_EMAIL).get().trim();
String[] emailKeywords = email.split("\\s+");

Predicate<Person> emailPred = new EmailContainsKeywordsPredicate(
Arrays.stream(emailKeywords).toList());
return emailPred;
}

private static Predicate<Person> createPhonePredicate(ArgumentMultimap argMultimap) {
String phone = argMultimap.getValue(PREFIX_PHONE).get().trim();
String[] phoneKeywords = phone.split("\\s+");

Predicate<Person> phonePred = new PhoneNumberContainsKeywordPredicate(
Arrays.stream(phoneKeywords).toList());
return phonePred;
}

private static Predicate<Person> createPersonPredicate(ArgumentMultimap argMultimap) {
String name = argMultimap.getValue(PREFIX_NAME).get().trim();

String[] nameKeywords = name.split("\\s+");

Predicate<Person> namePred = new NameContainsKeywordsPredicate(
Arrays.stream(nameKeywords).toList());
return namePred;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import seedu.address.logic.commands.contact.commands.FindCommand;
import seedu.address.logic.parser.Parser;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.predicates.NameContainsKeywordsPredicate;

/**
* Parses input arguments and creates a new FindCommand object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import seedu.address.logic.commands.contact.commands.SearchCommand;
import seedu.address.logic.parser.Parser;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.PersonIsRolePredicate;
import seedu.address.model.person.predicates.PersonIsRolePredicate;
import seedu.address.model.role.Role;


Expand Down
Loading