From d80e315f6e4552cd854cceb1b3da2032a6070d9c Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:50:41 +0800 Subject: [PATCH 01/14] Add IncomeComparisonPredicate --- .../predicates/IncomeComparisonPredicate.java | 71 +++++++++++++++++++ .../IncomeContainsSubstringPredicate.java | 40 ----------- 2 files changed, 71 insertions(+), 40 deletions(-) create mode 100644 src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java delete mode 100644 src/main/java/seedu/address/model/person/predicates/IncomeContainsSubstringPredicate.java diff --git a/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java b/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java new file mode 100644 index 00000000000..1d1000a173e --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java @@ -0,0 +1,71 @@ +package seedu.address.model.person.predicates; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.Person; +import seedu.address.model.util.IncomeComparisonOperator; + +/** + * Predicate that compares a {@code Person}'s income against a threshold using a specified comparison operator. + */ +public class IncomeComparisonPredicate implements Predicate { + private final int incomeThreshold; + private final IncomeComparisonOperator incomeComparisonOperator; + + /** + * Constructs an {@code IncomeComparisonPredicate}. + * + * @param incomeThreshold The threshold income to compare against. + * @param incomeComparisonOperator The operator used to compare the person's income with the threshold. + */ + public IncomeComparisonPredicate(int incomeThreshold, IncomeComparisonOperator incomeComparisonOperator) { + requireNonNull(incomeComparisonOperator); + this.incomeThreshold = incomeThreshold; + this.incomeComparisonOperator = incomeComparisonOperator; + } + + @Override + public boolean test(Person person) { + int personIncome = person.getIncome().value; + + switch (incomeComparisonOperator.comparisonOperator) { + case "=": + return personIncome == incomeThreshold; + case ">": + return personIncome > incomeThreshold; + case "<": + return personIncome < incomeThreshold; + default: + return false; + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof IncomeComparisonPredicate)) { + return false; + } + + IncomeComparisonPredicate otherIncomeComparisonPredicate = + (IncomeComparisonPredicate) other; + return incomeThreshold == otherIncomeComparisonPredicate.incomeThreshold + && incomeComparisonOperator.equals(otherIncomeComparisonPredicate.incomeComparisonOperator); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("incomeThreshold", incomeThreshold) + .add("incomeComparisonOperator", incomeComparisonOperator) + .toString(); + } +} + diff --git a/src/main/java/seedu/address/model/person/predicates/IncomeContainsSubstringPredicate.java b/src/main/java/seedu/address/model/person/predicates/IncomeContainsSubstringPredicate.java deleted file mode 100644 index 098e76836e0..00000000000 --- a/src/main/java/seedu/address/model/person/predicates/IncomeContainsSubstringPredicate.java +++ /dev/null @@ -1,40 +0,0 @@ -package seedu.address.model.person.predicates; - -import seedu.address.commons.util.StringUtil; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.person.Person; - -/** - * Tests that a {@code Person}'s {@code Income} contains a specified substring. - */ -public class IncomeContainsSubstringPredicate extends ContainsSubstringPredicate { - public IncomeContainsSubstringPredicate(String substring) { - super(substring); - } - - @Override - public boolean test(Person person) { - return StringUtil.containsSubstringIgnoreCase(String.valueOf(person.getIncome().value), substring); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof IncomeContainsSubstringPredicate)) { - return false; - } - - IncomeContainsSubstringPredicate otherIncomeContainsSubstringPredicate = - (IncomeContainsSubstringPredicate) other; - return substring.equals(otherIncomeContainsSubstringPredicate.substring); - } - - @Override - public String toString() { - return new ToStringBuilder(this).add("substring", substring).toString(); - } -} From 665ec37046b8c0cffff3b08ec1a6be73fc17eb58 Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:51:04 +0800 Subject: [PATCH 02/14] Add IncomeComparisonOperator --- .../model/util/IncomeComparisonOperator.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/main/java/seedu/address/model/util/IncomeComparisonOperator.java diff --git a/src/main/java/seedu/address/model/util/IncomeComparisonOperator.java b/src/main/java/seedu/address/model/util/IncomeComparisonOperator.java new file mode 100644 index 00000000000..ed865bee5e8 --- /dev/null +++ b/src/main/java/seedu/address/model/util/IncomeComparisonOperator.java @@ -0,0 +1,61 @@ +package seedu.address.model.util; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Objects; + +/** + * Represents a comparison operator used in income comparisons. + * The operator can only be one of '=', '>', or '<'. + * Guarantees: comparisonOperator is validated upon creation. + */ +public class IncomeComparisonOperator { + public static final String MESSAGE_CONSTRAINTS = "Income comparison operators can only be '=', '>' or '<'"; + + public final String comparisonOperator; + + /** + * Constructs a {@code IncomeComparisonOperator}. + * + * @param comparisonOperator A comparisonOperator + */ + public IncomeComparisonOperator(String comparisonOperator) { + requireNonNull(comparisonOperator); + checkArgument(isValidComparisonOperator(comparisonOperator), MESSAGE_CONSTRAINTS); + this.comparisonOperator = comparisonOperator; + } + + /** + * Returns true if a given string is a valid comparison operator. + */ + public static boolean isValidComparisonOperator(String test) { + if (test.trim().isEmpty()) { + return false; + } + + return (test.equals("=") || test.equals(">") || test.equals("<")); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof IncomeComparisonOperator) { + IncomeComparisonOperator otherIncomeComparisonOperator = (IncomeComparisonOperator) other; + return comparisonOperator.equals(otherIncomeComparisonOperator.comparisonOperator); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(comparisonOperator); + } + + @Override + public String toString() { + return comparisonOperator; + } +} From b5db600bcf2d48857a11ad1ccb96e740aa691c42 Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:51:48 +0800 Subject: [PATCH 03/14] Add income tag and search term parsing to FilterCommandParser --- .../address/logic/parser/FilterCommandParser.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java index 017eb981542..2beb73d8990 100644 --- a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java @@ -23,10 +23,12 @@ import seedu.address.model.person.predicates.AddressContainsSubstringPredicate; import seedu.address.model.person.predicates.CombinedPredicate; import seedu.address.model.person.predicates.EmailContainsSubstringPredicate; +import seedu.address.model.person.predicates.IncomeComparisonPredicate; import seedu.address.model.person.predicates.JobContainsSubstringPredicate; import seedu.address.model.person.predicates.NameContainsSubstringPredicate; import seedu.address.model.person.predicates.PhoneContainsSubstringPredicate; import seedu.address.model.person.predicates.RemarkContainsSubstringPredicate; +import seedu.address.model.util.IncomeComparisonOperator; /** * Parses input arguments and creates a new FilterCommand object @@ -109,6 +111,15 @@ private List> collectPredicates(ArgumentMultimap argMultimap) String substring = ParserUtil.parseJob(argMultimap.getValue(PREFIX_JOB).get()).value; predicates.add(new JobContainsSubstringPredicate(substring)); } + if (argMultimap.getValue(PREFIX_INCOME).isPresent()) { + String operatorAndIncome = argMultimap.getValue(PREFIX_INCOME).get(); + + IncomeComparisonOperator operator = ParserUtil.parseIncomeComparisonOperator( + operatorAndIncome.substring(0, 1)); + int income = ParserUtil.parseIncome(operatorAndIncome.substring(1)).value; + + predicates.add(new IncomeComparisonPredicate(income, operator)); + } if (argMultimap.getValue(PREFIX_REMARK).isPresent()) { String substring = ParserUtil.parseRemark(argMultimap.getValue(PREFIX_REMARK).get()).value; predicates.add(new RemarkContainsSubstringPredicate(substring)); From d8b4f8d5de398a46a92415b25276a5c38a3122fb Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:52:10 +0800 Subject: [PATCH 04/14] Add IncomeComparisonOperator parser in ParserUtil --- .../address/logic/parser/ParserUtil.java | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b1e78ad506a..61283eb8269 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -13,6 +13,7 @@ import seedu.address.model.person.Phone; import seedu.address.model.person.Remark; import seedu.address.model.tier.Tier; +import seedu.address.model.util.IncomeComparisonOperator; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -141,10 +142,25 @@ public static Remark parseRemark(String remark) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String remark} into a {@code Remark}. + * Leading and trailing whitespaces are trimmed. + * + * @throws ParseException if the given {@code remark} is invalid. + */ + public static Remark parseNewRemark(String remark) throws ParseException { + requireNonNull(remark); + String trimmedRemark = remark.trim(); + if (!Remark.isValidRemark(trimmedRemark)) { + throw new ParseException(Remark.MESSAGE_CONSTRAINTS); + } + return new Remark(trimmedRemark); + } + + /** + * Parses a {@code String tier} into a {@code Tier}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code tier} is invalid. */ public static Tier parseTier(String tag) throws ParseException { requireNonNull(tag); @@ -159,19 +175,19 @@ public static Tier parseTier(String tag) throws ParseException { } /** - * Parses a {@code String remark} into a {@code Remark}. - * Leading and trailing whitespaces are trimmed. + * Parses a {@code String operator} into a {@code IncomeComparisonOperator}. + * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code remark} is invalid. + * @throws ParseException if the given {@code operator} is invalid. */ - public static Remark parseNewRemark(String remark) throws ParseException { - requireNonNull(remark); - String trimmedRemark = remark.trim(); - if (!Remark.isValidRemark(trimmedRemark)) { - throw new ParseException(Remark.MESSAGE_CONSTRAINTS); + public static IncomeComparisonOperator parseIncomeComparisonOperator(String operator) throws ParseException { + requireNonNull(operator); + String trimmedOperator = operator.trim(); + if (!IncomeComparisonOperator.isValidComparisonOperator(trimmedOperator)) { + System.out.println(("HERE")); + throw new ParseException(IncomeComparisonOperator.MESSAGE_CONSTRAINTS); } - System.out.println(trimmedRemark); - return new Remark(trimmedRemark); + return new IncomeComparisonOperator(trimmedOperator); } } From 8b925a0061e7688b6afa79f705cb12f181a935fb Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:52:52 +0800 Subject: [PATCH 05/14] Add unit tests for IncomeComparisonOperator --- .../util/IncomeComparisonOperatorTest.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/test/java/seedu/address/model/util/IncomeComparisonOperatorTest.java diff --git a/src/test/java/seedu/address/model/util/IncomeComparisonOperatorTest.java b/src/test/java/seedu/address/model/util/IncomeComparisonOperatorTest.java new file mode 100644 index 00000000000..243920102a3 --- /dev/null +++ b/src/test/java/seedu/address/model/util/IncomeComparisonOperatorTest.java @@ -0,0 +1,89 @@ +package seedu.address.model.util; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class IncomeComparisonOperatorTest { + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new IncomeComparisonOperator(null)); + } + + @Test + public void constructor_invalidOperator_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new IncomeComparisonOperator("!")); + assertThrows(IllegalArgumentException.class, () -> new IncomeComparisonOperator("==")); + assertThrows(IllegalArgumentException.class, () -> new IncomeComparisonOperator("")); + } + + @Test + public void constructor_validOperator_success() { + assertDoesNotThrow(() -> new IncomeComparisonOperator("=")); + assertDoesNotThrow(() -> new IncomeComparisonOperator(">")); + assertDoesNotThrow(() -> new IncomeComparisonOperator("<")); + } + + @Test + public void isValidComparisonOperator() { + // Test valid operators + assertTrue(IncomeComparisonOperator.isValidComparisonOperator("=")); + assertTrue(IncomeComparisonOperator.isValidComparisonOperator(">")); + assertTrue(IncomeComparisonOperator.isValidComparisonOperator("<")); + + // Test invalid operators + assertFalse(IncomeComparisonOperator.isValidComparisonOperator("!")); + assertFalse(IncomeComparisonOperator.isValidComparisonOperator("==")); + assertFalse(IncomeComparisonOperator.isValidComparisonOperator(" ")); + assertFalse(IncomeComparisonOperator.isValidComparisonOperator("")); + assertFalse(IncomeComparisonOperator.isValidComparisonOperator(">=")); + } + + @Test + public void equals_sameObject_returnsTrue() { + IncomeComparisonOperator operator = new IncomeComparisonOperator("="); + assertEquals(operator, operator); + } + + @Test + public void equals_differentObjectsSameValue_returnsTrue() { + IncomeComparisonOperator operator1 = new IncomeComparisonOperator(">"); + IncomeComparisonOperator operator2 = new IncomeComparisonOperator(">"); + assertEquals(operator1, operator2); + } + + @Test + public void equals_differentObjectsDifferentValue_returnsFalse() { + IncomeComparisonOperator operator1 = new IncomeComparisonOperator(">"); + IncomeComparisonOperator operator2 = new IncomeComparisonOperator("<"); + assertNotEquals(operator1, operator2); + } + + @Test + public void hashCode_sameValue_returnsSameHashCode() { + // Test that objects with the same value return the same hash code + IncomeComparisonOperator operator1 = new IncomeComparisonOperator("<"); + IncomeComparisonOperator operator2 = new IncomeComparisonOperator("<"); + assertEquals(operator1.hashCode(), operator2.hashCode()); + } + + @Test + public void hashCode_differentValues_returnsDifferentHashCodes() { + // Test that objects with different values return different hash codes + IncomeComparisonOperator operator1 = new IncomeComparisonOperator("="); + IncomeComparisonOperator operator2 = new IncomeComparisonOperator(">"); + assertNotEquals(operator1.hashCode(), operator2.hashCode()); + } + + @Test + public void toString_returnsCorrectString() { + // Test that the toString method returns the correct string representation + IncomeComparisonOperator operator = new IncomeComparisonOperator("="); + assertEquals("=", operator.toString()); + } +} From 226f711af6df079223db3a52318e83a686f88fb0 Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:53:17 +0800 Subject: [PATCH 06/14] Add unit tests for parsing IncomeComparisonOperator --- .../address/logic/parser/ParserUtilTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 28f9d76d4ae..7586bcd0de8 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -10,9 +10,11 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.Income; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; import seedu.address.model.tier.Tier; +import seedu.address.model.util.IncomeComparisonOperator; public class ParserUtilTest { private static final String INVALID_NAME = "R@chel"; @@ -20,6 +22,8 @@ public class ParserUtilTest { private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; private static final String INVALID_TAG = "#friend"; + private static final String INVALID_INCOME_COMPARISON_OPERATOR_1 = "=="; + private static final String INVALID_INCOME_COMPARISON_OPERATOR_2 = "!"; private static final String VALID_NAME = "Rachel Walker"; private static final String VALID_PHONE = "91234567"; @@ -27,6 +31,9 @@ public class ParserUtilTest { private static final String VALID_EMAIL = "rachel@example.com"; private static final String VALID_TAG_1 = "BRONZE"; private static final String VALID_TAG_2 = "SILVER"; + private static final String VALID_INCOME_COMPARISON_OPERATOR_EQUAL = ">"; + private static final String VALID_INCOME_COMPARISON_OPERATOR_GREATER_THAN = ">"; + private static final String VALID_INCOME_COMPARISON_OPERATOR_LESS_THAN = "<"; private static final String WHITESPACE = " \t\r\n"; @@ -165,4 +172,53 @@ public void parseTier_validValueWithWhitespace_returnsTrimmedTag() throws Except assertEquals(expectedTier, ParserUtil.parseTier(tagWithWhitespace)); } + @Test + public void parseIncomeComparisonOperator_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseIncomeComparisonOperator(null)); + } + + @Test + public void parseIncomeComparisonOperator_invalidValue_throwsParseException() { + assertThrows(ParseException.class, + () -> ParserUtil.parseIncomeComparisonOperator(INVALID_INCOME_COMPARISON_OPERATOR_1)); + assertThrows(ParseException.class, + () -> ParserUtil.parseIncomeComparisonOperator(INVALID_INCOME_COMPARISON_OPERATOR_2)); + } + + @Test + public void parseIncomeComparisonOperator_validValueWithoutWhitespace_returnsIncomeComparisonOperator() + throws Exception { + IncomeComparisonOperator equalOperator = new IncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_EQUAL); + IncomeComparisonOperator greaterThanOperator = new IncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_GREATER_THAN); + IncomeComparisonOperator lessThanOperator = new IncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_LESS_THAN); + + assertEquals(equalOperator, ParserUtil.parseIncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_EQUAL)); + assertEquals(greaterThanOperator, ParserUtil.parseIncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_GREATER_THAN)); + assertEquals(lessThanOperator, ParserUtil.parseIncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_LESS_THAN)); + } + + @Test + public void parseIncomeComparisonOperator_validValueWithWhitespace_returnsIncomeComparisonOperator() + throws Exception { + IncomeComparisonOperator equalOperator = new IncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_EQUAL); + IncomeComparisonOperator greaterThanOperator = new IncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_GREATER_THAN); + IncomeComparisonOperator lessThanOperator = new IncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_LESS_THAN); + + assertEquals(equalOperator, ParserUtil.parseIncomeComparisonOperator( + WHITESPACE + VALID_INCOME_COMPARISON_OPERATOR_EQUAL + WHITESPACE)); + assertEquals(greaterThanOperator, ParserUtil.parseIncomeComparisonOperator( + WHITESPACE + VALID_INCOME_COMPARISON_OPERATOR_GREATER_THAN + WHITESPACE)); + assertEquals(lessThanOperator, ParserUtil.parseIncomeComparisonOperator( + WHITESPACE + VALID_INCOME_COMPARISON_OPERATOR_LESS_THAN + WHITESPACE)); + } + } From b8925f9e2cb6f450761ddceef02db1a46c922fe3 Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:54:01 +0800 Subject: [PATCH 07/14] Add unit tests for IncomeComparisonPredicate --- .../IncomeComparisonPredicateTest.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/test/java/seedu/address/model/person/predicates/IncomeComparisonPredicateTest.java diff --git a/src/test/java/seedu/address/model/person/predicates/IncomeComparisonPredicateTest.java b/src/test/java/seedu/address/model/person/predicates/IncomeComparisonPredicateTest.java new file mode 100644 index 00000000000..e3911007db8 --- /dev/null +++ b/src/test/java/seedu/address/model/person/predicates/IncomeComparisonPredicateTest.java @@ -0,0 +1,88 @@ +package seedu.address.model.person.predicates; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.util.IncomeComparisonOperator; +import seedu.address.testutil.PersonBuilder; + +public class IncomeComparisonPredicateTest { + + @Test + public void equals() { + IncomeComparisonOperator operatorEqual = new IncomeComparisonOperator("="); + IncomeComparisonOperator operatorGreater = new IncomeComparisonOperator(">"); + + IncomeComparisonPredicate firstPredicate = new IncomeComparisonPredicate(5000, operatorEqual); + IncomeComparisonPredicate secondPredicate = new IncomeComparisonPredicate(10000, operatorGreater); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + IncomeComparisonPredicate firstPredicateCopy = new IncomeComparisonPredicate(5000, operatorEqual); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different predicate -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_incomeEqualComparison_returnsTrue() { + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, new IncomeComparisonOperator("=")); + assertTrue(predicate.test(new PersonBuilder().withIncome(5000).build())); + } + + @Test + public void test_incomeEqualComparison_returnsFalse() { + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, new IncomeComparisonOperator("=")); + assertFalse(predicate.test(new PersonBuilder().withIncome(6000).build())); + assertFalse(predicate.test(new PersonBuilder().withIncome(4000).build())); + } + + @Test + public void test_incomeGreaterThanComparison_returnsTrue() { + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, new IncomeComparisonOperator(">")); + assertTrue(predicate.test(new PersonBuilder().withIncome(6000).build())); + } + + @Test + public void test_incomeGreaterThanComparison_returnsFalse() { + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, new IncomeComparisonOperator(">")); + assertFalse(predicate.test(new PersonBuilder().withIncome(4000).build())); + assertFalse(predicate.test(new PersonBuilder().withIncome(5000).build())); + } + + @Test + public void test_incomeLessThanComparison_returnsTrue() { + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, new IncomeComparisonOperator("<")); + assertTrue(predicate.test(new PersonBuilder().withIncome(4000).build())); + } + + @Test + public void test_incomeLessThanComparison_returnsFalse() { + // Income is not less than the threshold + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, new IncomeComparisonOperator("<")); + assertFalse(predicate.test(new PersonBuilder().withIncome(6000).build())); + assertFalse(predicate.test(new PersonBuilder().withIncome(5000).build())); + } + + @Test + public void toStringMethod() { + IncomeComparisonOperator operator = new IncomeComparisonOperator("="); + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, operator); + + String expected = IncomeComparisonPredicate.class.getCanonicalName() + + "{incomeThreshold=5000, incomeComparisonOperator==}"; + assertEquals(expected, predicate.toString()); + } +} From b1035fbf76f6d06afc82319631d2295a274f1afd Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:07:39 +0800 Subject: [PATCH 08/14] Change order of fields in constructor of IncomeComparisonPredicate --- .../seedu/address/logic/parser/FilterCommandParser.java | 6 +++--- .../model/person/predicates/IncomeComparisonPredicate.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java index 2beb73d8990..f1a01508803 100644 --- a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java @@ -114,11 +114,11 @@ private List> collectPredicates(ArgumentMultimap argMultimap) if (argMultimap.getValue(PREFIX_INCOME).isPresent()) { String operatorAndIncome = argMultimap.getValue(PREFIX_INCOME).get(); - IncomeComparisonOperator operator = ParserUtil.parseIncomeComparisonOperator( - operatorAndIncome.substring(0, 1)); + IncomeComparisonOperator operator = + ParserUtil.parseIncomeComparisonOperator(operatorAndIncome.substring(0, 1)); int income = ParserUtil.parseIncome(operatorAndIncome.substring(1)).value; - predicates.add(new IncomeComparisonPredicate(income, operator)); + predicates.add(new IncomeComparisonPredicate(operator, income)); } if (argMultimap.getValue(PREFIX_REMARK).isPresent()) { String substring = ParserUtil.parseRemark(argMultimap.getValue(PREFIX_REMARK).get()).value; diff --git a/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java b/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java index 1d1000a173e..fef36f881ca 100644 --- a/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java @@ -18,10 +18,10 @@ public class IncomeComparisonPredicate implements Predicate { /** * Constructs an {@code IncomeComparisonPredicate}. * - * @param incomeThreshold The threshold income to compare against. * @param incomeComparisonOperator The operator used to compare the person's income with the threshold. + * @param incomeThreshold The threshold income to compare against. */ - public IncomeComparisonPredicate(int incomeThreshold, IncomeComparisonOperator incomeComparisonOperator) { + public IncomeComparisonPredicate(IncomeComparisonOperator incomeComparisonOperator, int incomeThreshold) { requireNonNull(incomeComparisonOperator); this.incomeThreshold = incomeThreshold; this.incomeComparisonOperator = incomeComparisonOperator; From d0a70e9981820b19fb0fbc8d49ed955e23d20063 Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:08:31 +0800 Subject: [PATCH 09/14] Update tests to match change in constructor of IncomeComparisonPredicate --- .../IncomeComparisonPredicateTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/test/java/seedu/address/model/person/predicates/IncomeComparisonPredicateTest.java b/src/test/java/seedu/address/model/person/predicates/IncomeComparisonPredicateTest.java index e3911007db8..a4028c79f26 100644 --- a/src/test/java/seedu/address/model/person/predicates/IncomeComparisonPredicateTest.java +++ b/src/test/java/seedu/address/model/person/predicates/IncomeComparisonPredicateTest.java @@ -16,14 +16,14 @@ public void equals() { IncomeComparisonOperator operatorEqual = new IncomeComparisonOperator("="); IncomeComparisonOperator operatorGreater = new IncomeComparisonOperator(">"); - IncomeComparisonPredicate firstPredicate = new IncomeComparisonPredicate(5000, operatorEqual); - IncomeComparisonPredicate secondPredicate = new IncomeComparisonPredicate(10000, operatorGreater); + IncomeComparisonPredicate firstPredicate = new IncomeComparisonPredicate(operatorEqual, 5000); + IncomeComparisonPredicate secondPredicate = new IncomeComparisonPredicate(operatorGreater, 10000); // same object -> returns true assertTrue(firstPredicate.equals(firstPredicate)); // same values -> returns true - IncomeComparisonPredicate firstPredicateCopy = new IncomeComparisonPredicate(5000, operatorEqual); + IncomeComparisonPredicate firstPredicateCopy = new IncomeComparisonPredicate(operatorEqual, 5000); assertTrue(firstPredicate.equals(firstPredicateCopy)); // different types -> returns false @@ -38,40 +38,40 @@ public void equals() { @Test public void test_incomeEqualComparison_returnsTrue() { - IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, new IncomeComparisonOperator("=")); + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(new IncomeComparisonOperator("="), 5000); assertTrue(predicate.test(new PersonBuilder().withIncome(5000).build())); } @Test public void test_incomeEqualComparison_returnsFalse() { - IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, new IncomeComparisonOperator("=")); + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(new IncomeComparisonOperator("="), 5000); assertFalse(predicate.test(new PersonBuilder().withIncome(6000).build())); assertFalse(predicate.test(new PersonBuilder().withIncome(4000).build())); } @Test public void test_incomeGreaterThanComparison_returnsTrue() { - IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, new IncomeComparisonOperator(">")); + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(new IncomeComparisonOperator(">"), 5000); assertTrue(predicate.test(new PersonBuilder().withIncome(6000).build())); } @Test public void test_incomeGreaterThanComparison_returnsFalse() { - IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, new IncomeComparisonOperator(">")); + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(new IncomeComparisonOperator(">"), 5000); assertFalse(predicate.test(new PersonBuilder().withIncome(4000).build())); assertFalse(predicate.test(new PersonBuilder().withIncome(5000).build())); } @Test public void test_incomeLessThanComparison_returnsTrue() { - IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, new IncomeComparisonOperator("<")); + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(new IncomeComparisonOperator("<"), 5000); assertTrue(predicate.test(new PersonBuilder().withIncome(4000).build())); } @Test public void test_incomeLessThanComparison_returnsFalse() { // Income is not less than the threshold - IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, new IncomeComparisonOperator("<")); + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(new IncomeComparisonOperator("<"), 5000); assertFalse(predicate.test(new PersonBuilder().withIncome(6000).build())); assertFalse(predicate.test(new PersonBuilder().withIncome(5000).build())); } @@ -79,7 +79,7 @@ public void test_incomeLessThanComparison_returnsFalse() { @Test public void toStringMethod() { IncomeComparisonOperator operator = new IncomeComparisonOperator("="); - IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(5000, operator); + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(operator, 5000); String expected = IncomeComparisonPredicate.class.getCanonicalName() + "{incomeThreshold=5000, incomeComparisonOperator==}"; From 1be73c996f3a1c0848efce2b3f348b4b5eefdece Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:39:15 +0800 Subject: [PATCH 10/14] Add unit tests for income filter parsing --- .../logic/parser/FilterCommandParserTest.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java index d38081c6357..878a9a9e2d8 100644 --- a/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java @@ -15,10 +15,12 @@ import seedu.address.model.person.predicates.AddressContainsSubstringPredicate; import seedu.address.model.person.predicates.CombinedPredicate; import seedu.address.model.person.predicates.EmailContainsSubstringPredicate; +import seedu.address.model.person.predicates.IncomeComparisonPredicate; import seedu.address.model.person.predicates.JobContainsSubstringPredicate; import seedu.address.model.person.predicates.NameContainsSubstringPredicate; import seedu.address.model.person.predicates.PhoneContainsSubstringPredicate; import seedu.address.model.person.predicates.RemarkContainsSubstringPredicate; +import seedu.address.model.util.IncomeComparisonOperator; public class FilterCommandParserTest { @@ -57,13 +59,24 @@ public void parse_emailFlag_returnsEmailFilterCommand() { assertParseSuccess(parser, " e/ alice@hello.com", expectedFilterCommand); } + @Test + public void parse_incomeFlag_returnsIncomeFilterCommand() { + List> expectedPredicates = new ArrayList<>(); + expectedPredicates.add(new EmailContainsSubstringPredicate("alice@hello.com")); + FilterCommand expectedFilterCommand = new FilterCommand(new CombinedPredicate(expectedPredicates)); + + assertParseSuccess(parser, " e/ alice@hello.com", expectedFilterCommand); + } + @Test public void parse_jobFlag_returnsRemarkFilterCommand() { List> expectedPredicates = new ArrayList<>(); - expectedPredicates.add(new JobContainsSubstringPredicate("Software Engineer")); + IncomeComparisonOperator operator = new IncomeComparisonOperator(">"); + expectedPredicates.add(new IncomeComparisonPredicate(operator, 5000)); + FilterCommand expectedFilterCommand = new FilterCommand(new CombinedPredicate(expectedPredicates)); - assertParseSuccess(parser, " j/ Software Engineer", expectedFilterCommand); + assertParseSuccess(parser, " i/ >5000", expectedFilterCommand); } @Test @@ -102,12 +115,14 @@ public void parse_validMultipleArgs_returnsFilterCommand() { expectedPredicates.add(new EmailContainsSubstringPredicate("alice@example.com")); expectedPredicates.add(new AddressContainsSubstringPredicate("Block 123")); expectedPredicates.add(new JobContainsSubstringPredicate("Software Engineer")); + IncomeComparisonOperator operator = new IncomeComparisonOperator(">"); + expectedPredicates.add(new IncomeComparisonPredicate(operator, 5000)); expectedPredicates.add(new RemarkContainsSubstringPredicate("is a celebrity")); FilterCommand expectedFilterCommand = new FilterCommand(new CombinedPredicate(expectedPredicates)); assertParseSuccess(parser, " n/ Alice p/ 91112222 e/ alice@example.com a/ Block 123 " - + "j/ Software Engineer r/ is a celebrity", expectedFilterCommand); + + "j/ Software Engineer i/ >5000 r/ is a celebrity", expectedFilterCommand); } @Test From 31c2b0c6537677670183c998b0f19f615c011d90 Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:49:55 +0800 Subject: [PATCH 11/14] Add unit tests for parsing income --- .../address/logic/parser/ParserUtilTest.java | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 7586bcd0de8..b88d22d1e59 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -21,6 +21,7 @@ public class ParserUtilTest { private static final String INVALID_PHONE = "+651234"; private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; + private static final String INVALID_INCOME = "one thousand"; private static final String INVALID_TAG = "#friend"; private static final String INVALID_INCOME_COMPARISON_OPERATOR_1 = "=="; private static final String INVALID_INCOME_COMPARISON_OPERATOR_2 = "!"; @@ -29,6 +30,7 @@ public class ParserUtilTest { private static final String VALID_PHONE = "91234567"; private static final String VALID_ADDRESS = "123 Main Street #0505"; private static final String VALID_EMAIL = "rachel@example.com"; + private static final String VALID_INCOME = "1000"; private static final String VALID_TAG_1 = "BRONZE"; private static final String VALID_TAG_2 = "SILVER"; private static final String VALID_INCOME_COMPARISON_OPERATOR_EQUAL = ">"; @@ -149,6 +151,29 @@ public void parseEmail_validValueWithWhitespace_returnsTrimmedEmail() throws Exc assertEquals(expectedEmail, ParserUtil.parseEmail(emailWithWhitespace)); } + @Test + public void parseIncome_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseIncome((String) null)); + } + + @Test + public void parseIncome_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseIncome(INVALID_INCOME)); + } + + @Test + public void parseIncome_validValueWithoutWhitespace_returnsEmail() throws Exception { + Income expectedIncome = new Income(Integer.parseInt(VALID_INCOME)); + assertEquals(expectedIncome, ParserUtil.parseIncome(VALID_INCOME)); + } + + @Test + public void parseIncome_validValueWithWhitespace_returnsTrimmedEmail() throws Exception { + String incomeWithWhitespace = WHITESPACE + VALID_INCOME + WHITESPACE; + Income expectedIncome = new Income(Integer.parseInt(VALID_INCOME)); + assertEquals(expectedIncome, ParserUtil.parseEmail(incomeWithWhitespace)); + } + @Test public void parseTier_null_throwsNullPointerException() { assertThrows(NullPointerException.class, () -> ParserUtil.parseTier(null)); @@ -179,10 +204,10 @@ public void parseIncomeComparisonOperator_null_throwsNullPointerException() { @Test public void parseIncomeComparisonOperator_invalidValue_throwsParseException() { - assertThrows(ParseException.class, - () -> ParserUtil.parseIncomeComparisonOperator(INVALID_INCOME_COMPARISON_OPERATOR_1)); - assertThrows(ParseException.class, - () -> ParserUtil.parseIncomeComparisonOperator(INVALID_INCOME_COMPARISON_OPERATOR_2)); + assertThrows(ParseException.class, () -> + ParserUtil.parseIncomeComparisonOperator(INVALID_INCOME_COMPARISON_OPERATOR_1)); + assertThrows(ParseException.class, () -> + ParserUtil.parseIncomeComparisonOperator(INVALID_INCOME_COMPARISON_OPERATOR_2)); } @Test From 846c2c68b4509e39486dbab57d4b75edbf677d2b Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:51:18 +0800 Subject: [PATCH 12/14] Fix unit tests for parsing income --- src/test/java/seedu/address/logic/parser/ParserUtilTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index b88d22d1e59..4e3eab7c986 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -168,10 +168,10 @@ public void parseIncome_validValueWithoutWhitespace_returnsEmail() throws Except } @Test - public void parseIncome_validValueWithWhitespace_returnsTrimmedEmail() throws Exception { + public void parseIncome_validValueWithWhitespace_returnsTrimmedIncome() throws Exception { String incomeWithWhitespace = WHITESPACE + VALID_INCOME + WHITESPACE; Income expectedIncome = new Income(Integer.parseInt(VALID_INCOME)); - assertEquals(expectedIncome, ParserUtil.parseEmail(incomeWithWhitespace)); + assertEquals(expectedIncome, ParserUtil.parseIncome(incomeWithWhitespace)); } @Test From b7448fb96dff580ddde62ad949b544ab3b41c87e Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Fri, 18 Oct 2024 00:09:07 +0800 Subject: [PATCH 13/14] Update UG to reflect filter by income --- docs/UserGuide.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 251a5944745..3cdf1f48280 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -265,8 +265,9 @@ This feature allows users to update the details of an existing customer in the d This feature allows users to search for customers by specific details such as name, address, email, phone number, job title, or remarks. **How to Use It:** -To perform a search, use the `filter` command followed by one or more flags (indicating the fields to search) and the corresponding search terms. - Searches are **case-insensitive** and use [**substring-matching**](#substring-matching), **except for Tier**, which must start with the specified substring. +To perform a search, use the `filter` command followed by one or more flags (indicating the fields to search) and the corresponding search terms. + +Searches are **case-insensitive** and use [**substring-matching**](#substring-matching), **except for [Tier](#filtering-by-tier) and [Income](#filtering-by-income)**, which have their own specific matching criteria detailed below. - **Command Format:** ``` @@ -288,10 +289,10 @@ To perform a search, use the `filter` command followed by one or more flags (ind #### Parameters -| Parameter | Expected Format | Explanation | -|-------------|--------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| -| FLAG | Refer to the list of supported flags detailed below. | Identifies the field to search (e.g., `n/` for name, `j/` for job). | | -| SEARCH TERM | Refer to the syntax constraints in the [parameter subsection of the `add` command](#add-command-parameters). | The value to search for in the specified field (e.g., "doctor" for job, "TAN LESHEW" for name). | +| Parameter | Expected Format | Explanation | +|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| FLAG | Refer to the list of supported flags detailed below. | Identifies the field to search.

e.g., `n/` for name, `j/` for job. | | +| SEARCH TERM | Follows the syntax for [each field's expected input](#add-command-parameters).

**Income** requires a numeric value with a comparison operator (`=`, `>`, `<`), while **Tier** allows for partial (prefix) matching. Other fields follow substring matching. | The value to search for in the specified field.

e.g., `doctor` for job, `>5000` for income). | #### Supported flags: - `n/` for Name @@ -306,6 +307,19 @@ To perform a search, use the `filter` command followed by one or more flags (ind - Substring matching is used for searches, meaning that the search term must match a part of the field in the same order as it appears in the customer record. - For instance, if a customer’s name is `Gordon Moore`, the search term `Gordon`, `Moore`, or `Gordon Moore` will match, but `Moore Gordon` will not. +#### Filtering By Tier +- **Prefix Matching:** Tier searches use **prefix matching**, meaning the search term must match the beginning of the tier exactly. + - If a customer has a tier labeled `Gold`, a search for `t/ G` or `t/ Gold` will match, but `t/ ld` or `t/ Gold Premium` will not. + +#### Filtering By Income +- **Comparison Operators:** Filtering by income allows numeric comparisons using operators `=`, `>`, or `<` to find customers whose income meets certain criteria. +- **Equal to (`=`):** Use `=` to find customers with a specific income. + - `i/ =5000` will match customers with an income of exactly 5000. +- **Greater than (`>`):** Use `>` to find customers with an income higher than the specified threshold. + - `i/ >5000` will match customers with incomes greater than 5000. +- **Less than (`<`):** Use `<` to find customers with an income lower than the specified threshold. + - `i/ <5000` will match customers with incomes below 5000. + #### What to Expect - **If Successful:** - Message: "`x` person listed!", where `x` is the number of matching results. From a7cf4e87edfc45b2a2fd6439ad6ed2c9aa20a2de Mon Sep 17 00:00:00 2001 From: leyew <102467346+itsme-zeix@users.noreply.github.com> Date: Sun, 20 Oct 2024 16:44:16 +0800 Subject: [PATCH 14/14] Add check for positive income threshold --- .../predicates/IncomeComparisonPredicate.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java b/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java index fef36f881ca..37f2485d5be 100644 --- a/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java @@ -23,6 +23,7 @@ public class IncomeComparisonPredicate implements Predicate { */ public IncomeComparisonPredicate(IncomeComparisonOperator incomeComparisonOperator, int incomeThreshold) { requireNonNull(incomeComparisonOperator); + checkPositiveIncomeThreshold(incomeThreshold); this.incomeThreshold = incomeThreshold; this.incomeComparisonOperator = incomeComparisonOperator; } @@ -67,5 +68,17 @@ public String toString() { .add("incomeComparisonOperator", incomeComparisonOperator) .toString(); } + + /** + * Ensures that the income threshold is positive. + * + * @param incomeThreshold The threshold to check. + * @throws IllegalArgumentException if {@code incomeThreshold} is not greater than 1. + */ + private void checkPositiveIncomeThreshold(int incomeThreshold) { + if (incomeThreshold < 0) { + throw new IllegalArgumentException("Income threshold cannot be less than 0."); + } + } }