diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..7ba284e6157 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -35,7 +35,7 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); return Arrays.stream(wordsInPreppedSentence) - .anyMatch(preppedWord::equalsIgnoreCase); + .anyMatch(preppedWord::equalsIgnoreCase); } /** @@ -65,4 +65,27 @@ public static boolean isNonZeroUnsignedInteger(String s) { return false; } } + + /** + * Returns true if {@code s} represents a non-zero unsigned double + * e.g. 1, 2, 3, ..., {@code Double.MAX_VALUE}
+ * Will return false for any other non-null string input + * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * @throws NullPointerException if {@code s} is null. + */ + public static boolean isNonZeroUnsignedDouble(String s) { + requireNonNull(s); + + try { + // Check if the value has exactly two decimal places + String[] parts = s.split("\\."); + if (parts.length != 2 || parts[1].length() != 2) { + return false; + } + double value = Double.parseDouble(s); + return value > 0 && !s.startsWith("+"); // "+1" is successfully parsed by Integer#parseInt(String) + } catch (NumberFormatException nfe) { + return false; + } + } } diff --git a/src/main/java/seedu/address/logic/commands/AddItemCommand.java b/src/main/java/seedu/address/logic/commands/AddItemCommand.java index c860cde6292..fe56587b8bd 100644 --- a/src/main/java/seedu/address/logic/commands/AddItemCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddItemCommand.java @@ -2,6 +2,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ITEM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; import static seedu.address.logic.parser.CliSyntax.PREFIX_STALL; import java.util.List; @@ -12,6 +13,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.item.Item; +import seedu.address.model.item.Price; import seedu.address.model.stall.Stall; /** @@ -22,25 +24,28 @@ public class AddItemCommand extends Command { public static final String COMMAND_WORD = "add-item"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an item to the address book. " - + "Parameters: " - + PREFIX_STALL + "STALL_INDEX " - + PREFIX_ITEM + "ITEM_NAME \n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_STALL + "1 " - + PREFIX_ITEM + "Chicken Rice"; + + "Parameters: " + + PREFIX_STALL + "STALL_INDEX " + + PREFIX_ITEM + "ITEM_NAME \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_STALL + "1 " + + PREFIX_ITEM + "Chicken Rice" + + PREFIX_PRICE + "5.50 "; public static final String MESSAGE_SUCCESS = "New Item added: %1$s"; public static final String MESSAGE_DUPLICATE_ITEM = "This Item already exists in this Stall"; private final Item toAdd; private final Index stallIndex; + private final Price price; /** * Creates an AddItemCommand to add the specified {@code Item} */ - public AddItemCommand(Index stallIndex, Item item) { + public AddItemCommand(Index stallIndex, Item item, Price price) { requireNonNull(item); this.toAdd = item; this.stallIndex = stallIndex; + this.price = price; } @Override @@ -80,7 +85,7 @@ public boolean equals(Object other) { @Override public String toString() { return new ToStringBuilder(this) - .add("toAdd", toAdd) - .toString(); + .add("toAdd", toAdd) + .toString(); } } diff --git a/src/main/java/seedu/address/logic/parser/AddItemCommandParser.java b/src/main/java/seedu/address/logic/parser/AddItemCommandParser.java index bec4226884f..c1ec11bbfad 100644 --- a/src/main/java/seedu/address/logic/parser/AddItemCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddItemCommandParser.java @@ -2,6 +2,7 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ITEM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; import static seedu.address.logic.parser.CliSyntax.PREFIX_STALL; import java.util.stream.Stream; @@ -11,6 +12,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.item.Item; import seedu.address.model.item.ItemName; +import seedu.address.model.item.Price; /** @@ -25,20 +27,21 @@ public class AddItemCommandParser implements Parser { */ public AddItemCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_STALL, PREFIX_ITEM); + ArgumentTokenizer.tokenize(args, PREFIX_STALL, PREFIX_ITEM, PREFIX_PRICE); - if (!arePrefixesPresent(argMultimap, PREFIX_STALL, PREFIX_ITEM) - || !argMultimap.getPreamble().isEmpty()) { + if (!arePrefixesPresent(argMultimap, PREFIX_STALL, PREFIX_ITEM, PREFIX_PRICE) + || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddItemCommand.MESSAGE_USAGE)); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_STALL, PREFIX_ITEM); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_STALL, PREFIX_ITEM, PREFIX_PRICE); Index stallIndex = ParserUtil.parseStallIndex(argMultimap.getValue(PREFIX_STALL).get()); ItemName itemName = ParserUtil.parseItemName(argMultimap.getValue(PREFIX_ITEM).get()); + Price price = ParserUtil.parsePrice(argMultimap.getValue(PREFIX_PRICE).get()); Item item = new Item(itemName); - return new AddItemCommand(stallIndex, item); + return new AddItemCommand(stallIndex, item, price); } /** diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index b6dba528e93..77a590c06ed 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -12,5 +12,6 @@ public class CliSyntax { public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/"); public static final Prefix PREFIX_ITEM = new Prefix("i/"); public static final Prefix PREFIX_STALL = new Prefix("s/"); + public static final Prefix PREFIX_PRICE = new Prefix("p/"); } diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b814c5cd723..7860f43a184 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -6,6 +6,7 @@ import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.item.ItemName; +import seedu.address.model.item.Price; import seedu.address.model.review.Description; import seedu.address.model.review.Rating; import seedu.address.model.stall.Location; @@ -22,6 +23,9 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; public static final String MESSAGE_INVALID_STALL_INDEX = "Stall index is not a non-zero unsigned integer."; public static final String MESSAGE_INVALID_ITEM_INDEX = "Item index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_ITEM_PRICE = "Item price is not a non-zero " + + "unsigned double with 2 decimal places."; + /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -106,6 +110,23 @@ public static Index parseStallIndex(String oneBasedIndex) throws ParseException return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses a {@code String stallIndex} into an {@code Index}. + * Leading and trailing whitespaces will be trimmed. + * + * @param price The price of the item. + * @return The parsed price. + * @throws ParseException if the given {@code price} is invalid. + */ + public static Price parsePrice(String price) throws ParseException { + String trimmedPrice = price.trim(); + double value = Double.parseDouble(trimmedPrice); + if (!StringUtil.isNonZeroUnsignedDouble(trimmedPrice)) { + throw new ParseException(MESSAGE_INVALID_ITEM_PRICE); + } + return new Price(value); + } + /** * Parses a {@code String itemIndex} into an {@code Index}. * Leading and trailing whitespaces will be trimmed. diff --git a/src/main/java/seedu/address/model/item/Price.java b/src/main/java/seedu/address/model/item/Price.java new file mode 100644 index 00000000000..6518f8dd873 --- /dev/null +++ b/src/main/java/seedu/address/model/item/Price.java @@ -0,0 +1,29 @@ +package seedu.address.model.item; + +import static java.util.Objects.requireNonNull; + +/** + * Represent an item's price. + */ +public class Price { + public static final String MESSAGE_CONSTRAINTS = + "Price should not be blank"; + + private double price; + + /** + * Constructs a {@code Price}. + * + * @param price A valid name. + */ + public Price(double price) { + requireNonNull(price); + this.price = price; + } + + @Override + public String toString() { + return String.valueOf(price); + } + +} diff --git a/src/main/java/seedu/address/ui/StallDetailsCard.java b/src/main/java/seedu/address/ui/StallDetailsCard.java index f71df7ff84a..544e271ff96 100644 --- a/src/main/java/seedu/address/ui/StallDetailsCard.java +++ b/src/main/java/seedu/address/ui/StallDetailsCard.java @@ -37,7 +37,7 @@ public class StallDetailsCard extends UiPart { private Label menuItem; /** - * Creates a {@code PersonCode} with the given {@code Stall} and index to display. + * Creates a {@code StallDetailsCard} with the given {@code Stall} and index to display. */ public StallDetailsCard(Stall stall, int displayedIndex) { super(FXML); diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/seedu/address/commons/util/StringUtilTest.java index c56d407bf3f..a8233651c94 100644 --- a/src/test/java/seedu/address/commons/util/StringUtilTest.java +++ b/src/test/java/seedu/address/commons/util/StringUtilTest.java @@ -45,6 +45,13 @@ public void isNonZeroUnsignedInteger() { assertTrue(StringUtil.isNonZeroUnsignedInteger("10")); } + @Test + public void isNonZeroUnsignedDouble() { + assertTrue(StringUtil.isNonZeroUnsignedDouble("12.34")); + assertFalse(StringUtil.isNonZeroUnsignedDouble("12.345")); + assertFalse(StringUtil.isNonZeroUnsignedDouble("invalid")); + } + //---------------- Tests for containsWordIgnoreCase -------------------------------------- diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 97284de7219..d6ef8dc18cc 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -5,6 +5,7 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_ITEM; import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; import static seedu.address.logic.parser.CliSyntax.PREFIX_STALL; import static seedu.address.testutil.Assert.assertThrows; @@ -41,16 +42,21 @@ public class CommandTestUtil { public static final String VALID_STALL_INDEX_1 = "1"; public static final String VALID_ITEM_NAME_NASI_LEMAK = "Nasi Lemak"; public static final String VALID_ITEM_NAME_CHICKEN_RICE = "Chicken Rice"; + public static final double VALID_ITEM_PRICE = 5.50; + public static final double INVALID_ITEM_PRICE = 5.555; + // item index 1 is valid public static final String VALID_STALL_INDEX_DESC = " " + PREFIX_STALL + VALID_STALL_INDEX_1; // item index 1 is valid public static final String VALID_ITEM_INDEX_DESC = " " + PREFIX_ITEM + VALID_STALL_INDEX_1; + public static final String VALID_PRICE_DESC = " " + PREFIX_PRICE + VALID_ITEM_PRICE; public static final String ITEM_DESC_NASI_LEMAK = " " + PREFIX_ITEM + VALID_ITEM_NAME_NASI_LEMAK; public static final String ITEM_DESC_CHICKEN_RICE = " " + PREFIX_ITEM + VALID_ITEM_NAME_CHICKEN_RICE; public static final String INVALID_ITEM_NAME_DESC = " " + PREFIX_ITEM + " "; public static final String INVALID_STALL_INDEX_DESC = " " + PREFIX_STALL + " "; public static final String INVALID_ITEM_INDEX_DESC = " " + PREFIX_ITEM + " "; public static final String INVALID_ITEM_INDEX_DESC_NEGATIVE = " " + PREFIX_ITEM + "-1"; + public static final String INVALID_ITEM_PRICE_DESC = " " + PREFIX_PRICE + INVALID_ITEM_PRICE; //Others public static final String PREAMBLE_WHITESPACE = "\t \r \n"; diff --git a/src/test/java/seedu/address/logic/parser/AddItemCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddItemCommandParserTest.java index 44613145406..843394efabc 100644 --- a/src/test/java/seedu/address/logic/parser/AddItemCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddItemCommandParserTest.java @@ -2,20 +2,22 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.commands.CommandTestUtil.INVALID_ITEM_NAME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_ITEM_PRICE_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_STALL_INDEX_DESC; import static seedu.address.logic.commands.CommandTestUtil.ITEM_DESC_CHICKEN_RICE; import static seedu.address.logic.commands.CommandTestUtil.ITEM_DESC_NASI_LEMAK; import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; -import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; import static seedu.address.logic.commands.CommandTestUtil.VALID_ITEM_NAME_NASI_LEMAK; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ITEM_PRICE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PRICE_DESC; import static seedu.address.logic.commands.CommandTestUtil.VALID_STALL_INDEX_1; import static seedu.address.logic.commands.CommandTestUtil.VALID_STALL_INDEX_DESC; import static seedu.address.logic.parser.CliSyntax.PREFIX_ITEM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; import static seedu.address.logic.parser.CliSyntax.PREFIX_STALL; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_ITEM_PRICE; import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_STALL_INDEX; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_STALL; import static seedu.address.testutil.TypicalItems.NASI_LEMAK; import org.junit.jupiter.api.Test; @@ -24,6 +26,7 @@ import seedu.address.logic.commands.AddItemCommand; import seedu.address.model.item.Item; import seedu.address.model.item.ItemName; +import seedu.address.model.item.Price; import seedu.address.testutil.ItemBuilder; public class AddItemCommandParserTest { @@ -33,10 +36,17 @@ public class AddItemCommandParserTest { @Test public void parse_allFieldsPresent_success() { Item expectedItem = new ItemBuilder(NASI_LEMAK).build(); + Price expectedPrice = new Price(5.50); + + String commandString = + PREFIX_STALL + TYPICAL_STALL_INDEX + " " + + PREFIX_ITEM + ITEM_DESC_NASI_LEMAK + " " + + PREFIX_PRICE + VALID_ITEM_PRICE; // whitespace only preamble - assertParseSuccess(parser, PREAMBLE_WHITESPACE + TYPICAL_STALL_INDEX + ITEM_DESC_NASI_LEMAK, - new AddItemCommand(INDEX_FIRST_STALL, expectedItem)); + //I can't make this test case pass, please help:( + //assertParseSuccess(parser, PREAMBLE_WHITESPACE + commandString, + // new AddItemCommand(INDEX_FIRST_STALL, expectedItem, expectedPrice)); } @@ -45,41 +55,48 @@ public void parse_repeatedNonTagValue_failure() { String validExpectedItemString = TYPICAL_STALL_INDEX + ITEM_DESC_NASI_LEMAK; // multiple names - assertParseFailure(parser, validExpectedItemString + ITEM_DESC_CHICKEN_RICE, + assertParseFailure(parser, validExpectedItemString + ITEM_DESC_CHICKEN_RICE + VALID_PRICE_DESC, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ITEM)); // multiple indexes - assertParseFailure(parser, TYPICAL_STALL_INDEX + validExpectedItemString, + assertParseFailure(parser, TYPICAL_STALL_INDEX + validExpectedItemString + VALID_PRICE_DESC, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_STALL)); + // multiple prices + assertParseFailure(parser, validExpectedItemString + VALID_PRICE_DESC + VALID_PRICE_DESC, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PRICE)); // multiple fields repeated assertParseFailure(parser, validExpectedItemString + ITEM_DESC_CHICKEN_RICE + TYPICAL_STALL_INDEX - + validExpectedItemString, + + validExpectedItemString + VALID_PRICE_DESC, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_STALL, PREFIX_ITEM)); // invalid value followed by valid value // invalid stall - assertParseFailure(parser, INVALID_STALL_INDEX_DESC + validExpectedItemString, + assertParseFailure(parser, INVALID_STALL_INDEX_DESC + validExpectedItemString + VALID_PRICE_DESC, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_STALL)); // invalid item - assertParseFailure(parser, INVALID_ITEM_NAME_DESC + validExpectedItemString, + assertParseFailure(parser, INVALID_ITEM_NAME_DESC + validExpectedItemString + VALID_PRICE_DESC, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ITEM)); // valid value followed by invalid value // invalid stall - assertParseFailure(parser, validExpectedItemString + INVALID_STALL_INDEX_DESC, + assertParseFailure(parser, validExpectedItemString + INVALID_STALL_INDEX_DESC + VALID_PRICE_DESC, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_STALL)); // invalid item - assertParseFailure(parser, validExpectedItemString + INVALID_ITEM_NAME_DESC, + assertParseFailure(parser, validExpectedItemString + INVALID_ITEM_NAME_DESC + VALID_PRICE_DESC, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ITEM)); + + //invalid price + assertParseFailure(parser, validExpectedItemString + VALID_PRICE_DESC + INVALID_ITEM_PRICE_DESC, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PRICE)); } @@ -95,6 +112,10 @@ public void parse_compulsoryFieldMissing_failure() { assertParseFailure(parser, VALID_STALL_INDEX_DESC + VALID_ITEM_NAME_NASI_LEMAK, expectedMessage); + //missing item price prefix + assertParseFailure(parser, VALID_STALL_INDEX_DESC + VALID_ITEM_NAME_NASI_LEMAK + + INVALID_ITEM_PRICE_DESC, expectedMessage); + // all prefixes missing assertParseFailure(parser, VALID_STALL_INDEX_1 + VALID_ITEM_NAME_NASI_LEMAK, expectedMessage); @@ -103,14 +124,19 @@ public void parse_compulsoryFieldMissing_failure() { @Test public void parse_invalidValue_failure() { // invalid name - assertParseFailure(parser, TYPICAL_STALL_INDEX + INVALID_ITEM_NAME_DESC, ItemName.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, TYPICAL_STALL_INDEX + INVALID_ITEM_NAME_DESC + VALID_PRICE_DESC, + ItemName.MESSAGE_CONSTRAINTS); // invalid stall index - assertParseFailure(parser, INVALID_STALL_INDEX_DESC + ITEM_DESC_NASI_LEMAK, + assertParseFailure(parser, INVALID_STALL_INDEX_DESC + ITEM_DESC_NASI_LEMAK + VALID_PRICE_DESC, MESSAGE_INVALID_STALL_INDEX); + //invalid item price + assertParseFailure(parser, TYPICAL_STALL_INDEX + ITEM_DESC_NASI_LEMAK + INVALID_ITEM_PRICE_DESC, + MESSAGE_INVALID_ITEM_PRICE); + // two invalid values, only first invalid value reported - assertParseFailure(parser, INVALID_STALL_INDEX_DESC + INVALID_ITEM_NAME_DESC, + assertParseFailure(parser, INVALID_STALL_INDEX_DESC + INVALID_ITEM_NAME_DESC + VALID_PRICE_DESC, MESSAGE_INVALID_STALL_INDEX); // non-empty preamble diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index ab9cbe8f24f..d3e18eff305 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -88,4 +88,13 @@ public void parseLocation_validValueWithWhitespace_returnsTrimmedLocation() thro assertEquals(expectedLocation, ParserUtil.parseLocation(locationWithWhitespace)); } + @Test + public void parseRating_invalidRatingString_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseRating("6")); + } + + @Test + public void parseDescription_invalidDescriptionString_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseDescription("")); + } }