Skip to content

Commit

Permalink
Add SubString Search add more restrictions and formatting updates
Browse files Browse the repository at this point in the history
  • Loading branch information
maze508 committed Mar 12, 2024
1 parent 97a0f14 commit 6297eb7
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 83 deletions.
20 changes: 20 additions & 0 deletions src/main/java/seedu/address/commons/util/StringUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ public static boolean containsWordIgnoreCase(String sentence, String word) {
.anyMatch(preppedWord::equalsIgnoreCase);
}

/**
* Returns true if the {@code sentence} contains the {@code query} as a substring, ignoring case.
* <br>examples:<pre>
* containsSubstringIgnoreCase("Alexander Yeoh", "Ale") == true
* containsSubstringIgnoreCase("Alexander Yeoh", "ye") == true
* containsSubstringIgnoreCase("Alex Yeoh", "zander") == false
* </pre>
* @param sentence cannot be null
* @param query cannot be null, cannot be empty
*/
public static boolean containsSubstringIgnoreCase(String sentence, String query) {
requireNonNull(sentence);
requireNonNull(query);

String preppedQuery = query.trim();
checkArgument(!preppedQuery.isEmpty(), "Query parameter cannot be empty");

return sentence.toLowerCase().contains(preppedQuery.toLowerCase());
}

/**
* Returns a detailed message of the t, including the stack trace.
*/
Expand Down
9 changes: 5 additions & 4 deletions src/main/java/seedu/address/logic/commands/FindCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@

/**
* 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"
+ "Parameters: /TYPE KEYWORD [MORE_KEYWORDS]... /TYPE KEYWORD [MORE_KEYWORDS] ...\n"
+ "Where TYPE can be /n or /t to specify name or tag respectively.\n"
+ "Example: " + COMMAND_WORD + "/n alice bob /t friends owesMoney";
+ "Parameters: TYPE/ KEYWORD [MORE_KEYWORDS]... TYPE/ KEYWORD [MORE_KEYWORDS] ...\n"
+ "Where TYPE can be n/ or t/ to specify name or tag respectively.\n"
+ "Example: " + COMMAND_WORD + "n/ alice n/ bob t/ friends t/ owesMoney \n"
+ "Please Refer to User Guide for more details.";

private final NameAndTagContainsKeywordsPredicate predicate;

Expand Down
73 changes: 44 additions & 29 deletions src/main/java/seedu/address/logic/parser/FindCommandParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@

import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.parser.exceptions.ParseException;
Expand All @@ -23,44 +20,62 @@ public class FindCommandParser implements Parser<FindCommand> {
* @throws ParseException if the user input does not conform the expected format.
*/
public FindCommand parse(String args) throws ParseException {
String trimmedArgs = args.trim();
// Enforces /n and /t prefixes
if (trimmedArgs.isEmpty() || (!trimmedArgs.startsWith("/n ") && !trimmedArgs.startsWith("/t "))) {
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
if (args.trim().isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}

String[] splitArgs = trimmedArgs.split("/");
Map<String, List<String>> keywordsMap = new HashMap<>();
List<String> nameKeywords = new ArrayList<>();
List<String> tagKeywords = new ArrayList<>();

for (String arg : splitArgs) {
String[] keyValue = arg.trim().split(" ", 2);
if (keyValue.length < 2) {
continue;
// Initial split by the prefixes " n/" and " t/" to handle names with spaces
String[] parts = args.trim().split("(?= n/)|(?= t/)");

for (String part : parts) {

String[] splitParts = part.trim().split("\\s+", 2);
String prefix = splitParts[0];

if (splitParts.length < 2 || splitParts[1].trim().isEmpty()) {
// If there is no keyword after the prefix OR it is empty
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
String keyword = splitParts[1].trim();

String key = keyValue[0].trim();
String[] values = keyValue[1].trim().split("\\s+");

// Check for duplicate prefixes
if (keywordsMap.containsKey(key)) {
throw new ParseException("Duplicate prefix /" + key + " is not allowed. \n"
+ "If you want to pass multiple values for a single-valued field, "
+ "please separate them with spaces."
);
if (keyword.contains("/")) {
throw new ParseException("Keywords must NOT contain `/`. \n"

Check warning on line 46 in src/main/java/seedu/address/logic/parser/FindCommandParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/FindCommandParser.java#L46

Added line #L46 was not covered by tests
+ "'/' is a special character reserved for commands. \n");
}

keywordsMap.put(key, Arrays.asList(values));
}
if (prefix.equals("n/")) {
if (keyword.contains(" ")) {
throw new ParseException("Names should not contain spaces. Use 'n/' prefix for EACH name.");

Check warning on line 52 in src/main/java/seedu/address/logic/parser/FindCommandParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/FindCommandParser.java#L52

Added line #L52 was not covered by tests
}

if (!keyword.matches("[a-zA-Z]+")) {
throw new ParseException("Name keywords must consist of only alphabets.");

Check warning on line 56 in src/main/java/seedu/address/logic/parser/FindCommandParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/FindCommandParser.java#L56

Added line #L56 was not covered by tests
}

List<String> nameKeywords = keywordsMap.getOrDefault("n", Collections.emptyList());
List<String> tagKeywords = keywordsMap.getOrDefault("t", Collections.emptyList());
nameKeywords.add(keyword);
} else if (prefix.equals("t/")) {
if (keyword.contains(" ")) {
throw new ParseException("Tags should not contain spaces. Use 't/' prefix for EACH tag.");

Check warning on line 62 in src/main/java/seedu/address/logic/parser/FindCommandParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/FindCommandParser.java#L62

Added line #L62 was not covered by tests
}

tagKeywords.add(keyword);
} else {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));

Check warning on line 67 in src/main/java/seedu/address/logic/parser/FindCommandParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/FindCommandParser.java#L67

Added line #L67 was not covered by tests
}
}

if (nameKeywords.isEmpty() && tagKeywords.isEmpty()) {
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));

Check warning on line 72 in src/main/java/seedu/address/logic/parser/FindCommandParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/FindCommandParser.java#L72

Added line #L72 was not covered by tests
}

System.out.println(nameKeywords);
System.out.println(tagKeywords);

return new FindCommand(new NameAndTagContainsKeywordsPredicate(nameKeywords, tagKeywords));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,11 @@ public NameAndTagContainsKeywordsPredicate(List<String> nameKeywords, List<Strin

@Override
public boolean test(Person person) {
boolean matchesName = true;
boolean matchesTags = true;

// If name keywords are provided, check for name match
if (!nameKeywords.isEmpty()) {
matchesName = nameKeywords.stream()
.anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
}

// If tag keywords are provided, check for tag match
if (!tagKeywords.isEmpty()) {
matchesTags = tagKeywords.stream()
.anyMatch(keyword -> person.getTags().stream()
.anyMatch(tag -> StringUtil.containsWordIgnoreCase(tag.tagName, keyword)));
}

boolean matchesName = nameKeywords.stream()
.allMatch(keyword -> StringUtil.containsSubstringIgnoreCase(person.getName().fullName, keyword));
boolean matchesTags = tagKeywords.stream()
.allMatch(keyword -> person.getTags().stream()
.anyMatch(tag -> StringUtil.containsSubstringIgnoreCase(tag.tagName, keyword)));
return matchesName && matchesTags;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ public void parseCommand_find() throws Exception {
List<String> nameKeywords = Arrays.asList("foo", "bar");
List<String> tagKeywords = Arrays.asList("friend", "colleague");
FindCommand command = (FindCommand) parser.parseCommand(
FindCommand.COMMAND_WORD + " /n " + String.join(" ", nameKeywords)
+ " /t " + String.join(" ", tagKeywords));
FindCommand.COMMAND_WORD + " n/ " + String.join(" n/ ", nameKeywords)
+ " t/ " + String.join(" t/ ", tagKeywords));
assertEquals(new FindCommand(new NameAndTagContainsKeywordsPredicate(nameKeywords, tagKeywords)), command);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public void parse_validArgs_returnsFindCommand() {
FindCommand expectedFindCommandForName = new FindCommand(predicateForNameSearch);

// Test parsing for name search
assertParseSuccess(parser, "/n Alice Bob", expectedFindCommandForName);
assertParseSuccess(parser, "n/ Alice n/ Bob", expectedFindCommandForName);

// Test for searching tags using /t prefix
List<String> tagKeywords = Arrays.asList("friend", "colleague");
Expand All @@ -42,55 +42,30 @@ public void parse_validArgs_returnsFindCommand() {
FindCommand expectedFindCommandForTag = new FindCommand(predicateForTagSearch);

// Test parsing for tag search
assertParseSuccess(parser, "/t friend colleague", expectedFindCommandForTag);
assertParseSuccess(parser, "t/ friend t/ colleague", expectedFindCommandForTag);

// Test for searching both names and tags
NameAndTagContainsKeywordsPredicate predicateForBothSearch =
new NameAndTagContainsKeywordsPredicate(nameKeywords, tagKeywords);
FindCommand expectedFindCommandForBoth = new FindCommand(predicateForBothSearch);

// Test parsing for both names and tags search
assertParseSuccess(parser, "/n Alice Bob /t friend colleague", expectedFindCommandForBoth);
assertParseSuccess(parser, "n/ Alice n/ Bob t/ friend t/ colleague", expectedFindCommandForBoth);
}

@Test
public void parse_noKeywordsProvided_throwsParseException() {
// Test case with no keywords provided for both /n and /t prefixes
assertParseFailure(parser, "/n /t", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
assertParseFailure(parser, "n/ t/", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));

// Test case with /n prefix but no name keywords
assertParseFailure(parser, "/n ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
assertParseFailure(parser, "n/ ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));

// Test case with /t prefix but no tag keywords
assertParseFailure(parser, "/t ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
assertParseFailure(parser, "t/ ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));

// Test case with both /n and /t prefixes but no keywords
assertParseFailure(parser, "/n /t ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
assertParseFailure(parser, "n/ t/ ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}

@Test
public void parse_duplicateNamePrefix_throwsParseException() {
// Test case with duplicate /n prefixes
assertParseFailure(parser, "/n Alice /n Bob", "Duplicate prefix /n is not allowed. \n"
+ "If you want to pass multiple values for a single-valued field, "
+ "please separate them with spaces.");
}

@Test
public void parse_duplicateTagPrefix_throwsParseException() {
// Test case with duplicate /t prefixes
assertParseFailure(parser, "/t friend /t colleague", "Duplicate prefix /t is not allowed. \n"
+ "If you want to pass multiple values for a single-valued field, "
+ "please separate them with spaces.");
}

@Test
public void parse_duplicateNameAndTagPrefixes_throwsParseException() {
// Test case with both /n and /t prefixes duplicated
assertParseFailure(parser, "/n Alice /n Bob /t friend /t colleague", "Duplicate prefix /n is not allowed. \n"
+ "If you want to pass multiple values for a single-valued field, "
+ "please separate them with spaces.");
}


}

0 comments on commit 6297eb7

Please sign in to comment.