diff --git a/docs/staticpages/DisplayRoutes.html b/docs/staticpages/DisplayRoutes.html index 961c584aa267..fde437173dac 100644 --- a/docs/staticpages/DisplayRoutes.html +++ b/docs/staticpages/DisplayRoutes.html @@ -28,8 +28,6 @@ var tempObject = {}; var keyList = []; if (queryString) { - // stuff after # is not part of query string, so get rid of it - queryString = queryString.split('#')[0]; // split our query string into its component parts var arr = queryString.split('&'); for (var i = 0; i < arr.length; i++) { diff --git a/src/main/java/seedu/equipment/logic/commands/CommandResult.java b/src/main/java/seedu/equipment/logic/commands/CommandResult.java index d5a33e56bbb6..77cb04cedb87 100644 --- a/src/main/java/seedu/equipment/logic/commands/CommandResult.java +++ b/src/main/java/seedu/equipment/logic/commands/CommandResult.java @@ -4,6 +4,8 @@ import java.util.Objects; +import seedu.equipment.model.equipment.Address; + /** * Represents the result of a command execution. */ @@ -20,6 +22,12 @@ public class CommandResult { /** The application should show map on right hand side with equipment locations. */ private final boolean displayMap; + /** The application should show map on right hand side with route to equipments. */ + private final boolean route; + + /** The address send back to UI to display route. */ + private final Address routeAddress; + /** * Constructs a {@code CommandResult} with the specified fields. */ @@ -40,10 +48,21 @@ public CommandResult(String feedbackToUser) { * and other fields set to their default value. */ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, boolean displayMap) { + this(feedbackToUser, showHelp, exit, displayMap, false, null); + } + + /** + * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, + * and other fields set to their default value. + */ + public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, boolean displayMap, + boolean route, Address routeAddress) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; this.displayMap = displayMap; + this.route = route; + this.routeAddress = routeAddress; } public String getFeedbackToUser() { @@ -62,6 +81,14 @@ public boolean isDisplayMap() { return displayMap; } + public boolean isRoute() { + return route; + } + + public Address getRouteAddress() { + return routeAddress; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -77,12 +104,14 @@ public boolean equals(Object other) { return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp && exit == otherCommandResult.exit - && displayMap == otherCommandResult.displayMap; + && displayMap == otherCommandResult.displayMap + && route == otherCommandResult.route + && ((routeAddress == null && otherCommandResult.routeAddress == null) + || (routeAddress != null && routeAddress.equals(otherCommandResult.routeAddress))); } - @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit, displayMap); + return Objects.hash(feedbackToUser, showHelp, exit, displayMap, route, routeAddress); } } diff --git a/src/main/java/seedu/equipment/logic/commands/RouteCommand.java b/src/main/java/seedu/equipment/logic/commands/RouteCommand.java new file mode 100644 index 000000000000..6a57f30c49c0 --- /dev/null +++ b/src/main/java/seedu/equipment/logic/commands/RouteCommand.java @@ -0,0 +1,63 @@ +package seedu.equipment.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.equipment.logic.CommandHistory; +import seedu.equipment.logic.commands.exceptions.CommandException; +import seedu.equipment.model.Model; +import seedu.equipment.model.equipment.Address; +import seedu.equipment.model.equipment.Equipment; + +/** + * Selects an equipment identified using it's displayed index from the equipment manager. + */ +public class RouteCommand extends Command { + + public static final String COMMAND_WORD = "route"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Produce a optimized route from current location to the displayed equipment list.\n" + + "Parameters: CURRENT ADDRESS (must be a valid address with postal code and country name)\n" + + "Example: " + COMMAND_WORD + " School of Computing, NUS, Singapore 117417"; + + public static final String MESSAGE_ROUTE_EQUIPMENT_SUCCESS = "Routes displayed on map."; + + public static final String MESSAGE_NO_EQUIPMENTS_TO_ROUTE = "At least 1 equipment in the equipment panel " + + " is required to use route command."; + + public static final String MESSAGE_TOO_MANY_EQUIPMENTS_TO_ROUTE = "At most 15 equipment in the equipment panel " + + " to use route command."; + + private final Address startendAddress; + + public RouteCommand(Address startendAddress) { + this.startendAddress = startendAddress; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + List filteredEquipmentList = model.getFilteredPersonList(); + + if (filteredEquipmentList.size() < 1) { + throw new CommandException(MESSAGE_NO_EQUIPMENTS_TO_ROUTE); + } + + if (filteredEquipmentList.size() > 15) { + throw new CommandException(MESSAGE_TOO_MANY_EQUIPMENTS_TO_ROUTE); + } + + return new CommandResult(MESSAGE_ROUTE_EQUIPMENT_SUCCESS, false, false, false, true, startendAddress); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RouteCommand // instanceof handles nulls + && startendAddress.equals(((RouteCommand) other).startendAddress)); // state check + } +} diff --git a/src/main/java/seedu/equipment/logic/parser/EquipmentManagerParser.java b/src/main/java/seedu/equipment/logic/parser/EquipmentManagerParser.java index 2f9c19ff3caa..ceffa7c38a8f 100644 --- a/src/main/java/seedu/equipment/logic/parser/EquipmentManagerParser.java +++ b/src/main/java/seedu/equipment/logic/parser/EquipmentManagerParser.java @@ -22,6 +22,7 @@ import seedu.equipment.logic.commands.ListWorkListCommand; import seedu.equipment.logic.commands.PutCommand; import seedu.equipment.logic.commands.RedoCommand; +import seedu.equipment.logic.commands.RouteCommand; import seedu.equipment.logic.commands.SelectCommand; import seedu.equipment.logic.commands.SortCommand; import seedu.equipment.logic.commands.UndoCommand; @@ -114,6 +115,9 @@ public Command parseCommand(String userInput) throws ParseException { case DisplayCommand.COMMAND_WORD: return new DisplayCommand(); + case RouteCommand.COMMAND_WORD: + return new RouteCommandParser().parse(arguments); + default: throw new ParseException(Messages.MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/equipment/logic/parser/RouteCommandParser.java b/src/main/java/seedu/equipment/logic/parser/RouteCommandParser.java new file mode 100644 index 000000000000..e33021e5b4c8 --- /dev/null +++ b/src/main/java/seedu/equipment/logic/parser/RouteCommandParser.java @@ -0,0 +1,27 @@ +package seedu.equipment.logic.parser; + +import seedu.equipment.commons.core.Messages; +import seedu.equipment.logic.commands.RouteCommand; +import seedu.equipment.logic.parser.exceptions.ParseException; +import seedu.equipment.model.equipment.Address; + +/** + * Parses input arguments and creates a new RouteCommand object + */ +public class RouteCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RoutetCommand + * and returns an RouteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RouteCommand parse(String args) throws ParseException { + try { + Address address = ParserUtil.parseAddress(args); + return new RouteCommand(address); + } catch (ParseException pe) { + throw new ParseException( + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, RouteCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/equipment/ui/BrowserPanel.java b/src/main/java/seedu/equipment/ui/BrowserPanel.java index a975f982615e..909e765e8d91 100644 --- a/src/main/java/seedu/equipment/ui/BrowserPanel.java +++ b/src/main/java/seedu/equipment/ui/BrowserPanel.java @@ -23,6 +23,7 @@ public class BrowserPanel extends UiPart { public static final String MAP_PAGE_BASE_URL = "https://cs2103-ay1819s2-w10-3.github.io/" + "main/DisplayEquipmentDetail"; public static final String MAP_MULTIPLE_POINT_BASE_URL = "https://cs2103-ay1819s2-w10-3.github.io/main/DisplayGmap"; + public static final String MAP_ROUTE_BASE_URL = "https://cs2103-ay1819s2-w10-3.github.io/main/DisplayRoutes"; public static final URL DEFAULT_PAGE = processDefaultPage(MAP_MULTIPLE_POINT_BASE_URL); private static final String FXML = "BrowserPanel.fxml"; diff --git a/src/main/java/seedu/equipment/ui/MainWindow.java b/src/main/java/seedu/equipment/ui/MainWindow.java index ca7106add56b..e93cffa3f875 100644 --- a/src/main/java/seedu/equipment/ui/MainWindow.java +++ b/src/main/java/seedu/equipment/ui/MainWindow.java @@ -17,6 +17,7 @@ import seedu.equipment.logic.commands.CommandResult; import seedu.equipment.logic.commands.exceptions.CommandException; import seedu.equipment.logic.parser.exceptions.ParseException; +import seedu.equipment.model.equipment.Address; import seedu.equipment.model.equipment.Equipment; /** @@ -206,6 +207,25 @@ public void handleDisplayMap() { browserPanel.loadPage(url); } + /** + * Opens the map window and display the route on it. + */ + @FXML + public void handleRoute(Address startendAddress) { + List equipmentList = logic.getFilteredEquipment(); + String addressString = "["; + for (Equipment equipment:equipmentList) { + Address address = equipment.getAddress(); + addressString += "\"" + address.toString() + "\","; + } + addressString = addressString.replaceAll(",$", ""); + addressString += "]"; + String url = BrowserPanel.MAP_ROUTE_BASE_URL + "?address=" + addressString + "&start=\"" + + startendAddress.toString() + "\"&end=\"" + startendAddress.toString() + "\""; + System.out.println("Loading page: " + url); + browserPanel.loadPage(url); + } + /** * Executes the command and returns the result. * @@ -230,6 +250,10 @@ private CommandResult executeCommand(String commandText) throws CommandException handleDisplayMap(); } + if (commandResult.isRoute()) { + handleRoute(commandResult.getRouteAddress()); + } + return commandResult; } catch (CommandException | ParseException e) { logger.info("Invalid command: " + commandText); diff --git a/src/test/java/seedu/equipment/logic/commands/RouteCommandTest.java b/src/test/java/seedu/equipment/logic/commands/RouteCommandTest.java new file mode 100644 index 000000000000..00662147539a --- /dev/null +++ b/src/test/java/seedu/equipment/logic/commands/RouteCommandTest.java @@ -0,0 +1,109 @@ +package seedu.equipment.logic.commands; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static seedu.equipment.testutil.TypicalEquipments.getTypicalAddressBook; + +import org.junit.Before; +import org.junit.Test; + +import seedu.equipment.logic.CommandHistory; +import seedu.equipment.logic.commands.exceptions.CommandException; +import seedu.equipment.logic.parser.RouteCommandParser; +import seedu.equipment.logic.parser.exceptions.ParseException; +import seedu.equipment.model.Model; +import seedu.equipment.model.ModelManager; +import seedu.equipment.model.UserPrefs; +import seedu.equipment.model.equipment.Address; +import seedu.equipment.model.equipment.Equipment; +import seedu.equipment.testutil.EquipmentBuilder; + +public class RouteCommandTest { + + private Model model; + private Model expectedModel; + private Model emptyModel; + private CommandHistory commandHistory = new CommandHistory(); + + @Before + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + emptyModel = new ModelManager(); + expectedModel = new ModelManager(model.getEquipmentManager(), new UserPrefs()); + } + + @Test + public void execute_routeCommandExecuteSuccessful() { + try { + CommandHistory expectedCommandHistory = new CommandHistory(commandHistory); + Address testAddress = new Address("School of computing, NUS, Singapore"); + CommandResult result = new RouteCommand(testAddress).execute(model, commandHistory); + CommandResult expectedCommandResult = new CommandResult( + RouteCommand.MESSAGE_ROUTE_EQUIPMENT_SUCCESS, false, false, false, true, testAddress); + assertEquals(expectedCommandResult.toString(), result.toString()); + assertEquals(expectedModel, model); + assertEquals(expectedCommandHistory, commandHistory); + } catch (CommandException ce) { + fail("NO invalid command exception should be thrown."); + } + } + + @Test + public void execute_routeCommandExecuteNoEquipmentExceptions() { + try { + Address testAddress = new Address("School of computing, NUS, Singapore"); + CommandResult result = new RouteCommand(testAddress).execute(emptyModel, commandHistory); + fail("Routing with empty equipment list should raise exception."); + } catch (CommandException ce) { + if (ce.getMessage() != RouteCommand.MESSAGE_NO_EQUIPMENTS_TO_ROUTE) { + fail("Should display no equipment message to user."); + } + } + } + + @Test + public void execute_routeCommandExecuteTooManyEquipmentExceptions() { + try { + Address testAddress = new Address("School of computing, NUS, Singapore"); + for (int i = 0; i < 16; i++) { + Equipment newEquipment = new EquipmentBuilder().withName("PGPR" + i).withPhone("8482131") + .withDate("10-05-2019").withAddress("PGPR, NUS, SINGAPORE") + .withSerialNumber(String.valueOf(i)).build(); + model.addEquipment(newEquipment); + } + CommandResult result = new RouteCommand(testAddress).execute(model, commandHistory); + fail("Routing with 16 equipment list should raise exception."); + } catch (CommandException ce) { + if (ce.getMessage() != RouteCommand.MESSAGE_TOO_MANY_EQUIPMENTS_TO_ROUTE) { + fail("Should display too many equipments message to user."); + } + } + } + + @Test + public void execute_routeCommandParserExecuteInvalidAddressExceptions() { + try { + CommandResult result = new RouteCommandParser().parse("").execute(model, commandHistory); + fail("Routing with invalid starting address should raise exception."); + } catch (CommandException ce) { + fail("ParseException should be thrown but CommandException is thrown."); + } catch (ParseException pe) { + if (pe.getMessage() == null) { + fail("Should display some error message."); + } + } + } + + @Test + public void execute_routeCommandEqualsTest() { + try { + RouteCommand command1 = new RouteCommandParser().parse("ABC"); + RouteCommand command2 = new RouteCommandParser().parse("ABC"); + if (!command1.equals(command2)) { + fail("Route commands with same address should be equal."); + } + } catch (ParseException pe) { + fail("No ParseException should be thrown."); + } + } +} diff --git a/src/test/java/systemtests/EquipmentManagerSystemTest.java b/src/test/java/systemtests/EquipmentManagerSystemTest.java index a26b69e1543f..ce77af3eaad4 100644 --- a/src/test/java/systemtests/EquipmentManagerSystemTest.java +++ b/src/test/java/systemtests/EquipmentManagerSystemTest.java @@ -280,6 +280,14 @@ protected void assertSelectedCardUnchanged() { assertFalse(getPersonListPanel().isSelectedPersonCardChanged()); } + /** + * Asserts that the selected card in the equipment list panel remain unchanged. + * @see EquipmentListPanelHandle#isSelectedPersonCardChanged() + */ + protected void assertSelectedPersonCardUnchanged() { + assertFalse(getPersonListPanel().isSelectedPersonCardChanged()); + } + /** * Asserts that the command box's shows the default style. */ diff --git a/src/test/java/systemtests/RouteCommandSystemTest.java b/src/test/java/systemtests/RouteCommandSystemTest.java new file mode 100644 index 000000000000..2074632c89fc --- /dev/null +++ b/src/test/java/systemtests/RouteCommandSystemTest.java @@ -0,0 +1,49 @@ +package systemtests; + +import static org.junit.Assert.assertNotEquals; + +import java.net.URL; + +import org.junit.Test; + +import seedu.equipment.logic.commands.RouteCommand; +import seedu.equipment.model.Model; +import seedu.equipment.ui.BrowserPanel; + +public class RouteCommandSystemTest extends EquipmentManagerSystemTest { + + @Test + public void route() { + /* Case: display route of equipments equipments on map + * -> Equipments showed on map! + */ + String command = "" + RouteCommand.COMMAND_WORD + " School of Computing, NUS, Singapore 117417"; + Model expectedModel = getModel(); + assertCommandSuccess(command, expectedModel); + + } + + /** + * Executes {@code command} and verifies that the command box displays an empty string, the result display + * box displays {@code Messages#MESSAGE_EQUIPMENTS_LISTED_OVERVIEW} with the number of people in the filtered list, + * and the model related components equal to {@code expectedModel}. + * These verifications are done by + * {@code EquipmentManagerSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
+ * Also verifies that the status bar remains unchanged, and the command box has the default style class, and the + * selected card updated accordingly, depending on {@code cardStatus}. + * @see EquipmentManagerSystemTest#assertApplicationDisplaysExpected(String, String, Model) + */ + private void assertCommandSuccess(String command, Model expectedModel) { + String expectedResultMessage = RouteCommand.MESSAGE_ROUTE_EQUIPMENT_SUCCESS; + URL oldUrl = getBrowserPanel().getLoadedUrl(); + executeCommand(command); + assertApplicationDisplaysExpected("", expectedResultMessage, expectedModel); + assertCommandBoxShowsDefaultStyle(); + assertStatusBarUnchanged(); + assertSelectedPersonCardUnchanged(); + assertNotEquals(oldUrl.toString(), getBrowserPanel().getLoadedUrl().toString()); + assertNotEquals(BrowserPanel.DEFAULT_PAGE, getBrowserPanel().getLoadedUrl().toString()); + assertNotEquals(BrowserPanel.MAP_MULTIPLE_POINT_BASE_URL, getBrowserPanel().getLoadedUrl().toString()); + assertNotEquals(BrowserPanel.MAP_ROUTE_BASE_URL, getBrowserPanel().getLoadedUrl().toString()); + } +}