
com.github.rinde.rinsim.pdptw.common.RouteFollowingVehicle Maven / Gradle / Ivy
/*
* Copyright (C) 2011-2016 Rinde van Lon, iMinds-DistriNet, KU Leuven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.rinde.rinsim.pdptw.common;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;
import static com.google.common.collect.Lists.newLinkedList;
import static java.util.Collections.unmodifiableCollection;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import javax.annotation.Nullable;
import javax.measure.Measure;
import javax.measure.quantity.Duration;
import javax.measure.quantity.Length;
import javax.measure.quantity.Velocity;
import javax.measure.unit.Unit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.rinde.rinsim.core.model.pdp.Depot;
import com.github.rinde.rinsim.core.model.pdp.PDPModel;
import com.github.rinde.rinsim.core.model.pdp.PDPModel.ParcelState;
import com.github.rinde.rinsim.core.model.pdp.PDPModel.VehicleState;
import com.github.rinde.rinsim.core.model.pdp.Parcel;
import com.github.rinde.rinsim.core.model.pdp.Vehicle;
import com.github.rinde.rinsim.core.model.pdp.VehicleDTO;
import com.github.rinde.rinsim.core.model.road.RoadModel;
import com.github.rinde.rinsim.core.model.road.RoadModels;
import com.github.rinde.rinsim.core.model.time.TimeLapse;
import com.github.rinde.rinsim.event.Event;
import com.github.rinde.rinsim.event.Listener;
import com.github.rinde.rinsim.fsm.AbstractState;
import com.github.rinde.rinsim.fsm.StateMachine;
import com.github.rinde.rinsim.fsm.StateMachine.StateMachineEvent;
import com.github.rinde.rinsim.fsm.StateMachine.StateTransitionEvent;
import com.github.rinde.rinsim.geom.Point;
import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultiset;
import com.google.common.collect.Lists;
import com.google.common.collect.Multiset;
import com.google.common.collect.Sets;
import com.google.common.math.DoubleMath;
/**
* A simple vehicle implementation that follows a route comprised of
* {@link Parcel}s. At every stop in the route, the corresponding parcel is
* serviced (either picked up or delivered). The route can be set via
* {@link #setRoute(Iterable)}. The vehicle attempts route diversion when the
* underlying {@link PDPRoadModel} allows it, otherwise it will change its route
* at the next possible instant.
*
* This vehicle uses a strategy that postpones traveling towards a parcel such
* that any waiting time at the parcel's site is minimized.
*
* If it is the end of the day (as defined by {@link #isEndOfDay(TimeLapse)})
* and the route is empty, the vehicle will automatically return to the depot.
*
* Extension The behavior of this vehicle can be altered by modifying the
* state machine that is used internally. This can be done by overriding
* {@link #createStateMachine()}.
* @author Rinde van Lon
*/
public class RouteFollowingVehicle extends Vehicle {
private static final Logger LOGGER = LoggerFactory
.getLogger(RouteFollowingVehicle.class);
/**
* The state machine that defines the states and the allowed transitions
* between them.
*/
protected final StateMachine stateMachine;
/**
* The wait state: {@link Wait}.
*/
protected final Wait waitState;
/**
* The goto state: {@link Goto}.
*/
protected final Goto gotoState;
/**
* The wait at service state: {@link WaitAtService}.
*/
protected final WaitAtService waitForServiceState;
/**
* The service state: {@link Service}.
*/
protected final Service serviceState;
Queue route;
Optional extends Queue> newRoute;
Optional depot;
Optional currentTime;
boolean isDiversionAllowed;
private Optional> speed;
private final boolean allowDelayedRouteChanges;
private final RouteAdjuster routeAdjuster;
/**
* Initializes the vehicle.
* @param dto The {@link VehicleDTO} that defines this vehicle.
* @param allowDelayedRouteChanging This boolean changes the behavior of the
* {@link #setRoute(Iterable)} method.
*/
public RouteFollowingVehicle(VehicleDTO dto,
boolean allowDelayedRouteChanging) {
this(dto, allowDelayedRouteChanging, RouteAdjusters.NOP);
}
/**
* Initializes the vehicle.
* @param dto The {@link VehicleDTO} that defines this vehicle.
* @param allowDelayedRouteChanging This boolean changes the behavior of the
* {@link #setRoute(Iterable)} method.
* @param adjuster Allows to set a route adjuster to 'fix' routes if
* necessary.
*/
public RouteFollowingVehicle(VehicleDTO dto,
boolean allowDelayedRouteChanging, RouteAdjuster adjuster) {
super(dto);
depot = Optional.absent();
speed = Optional.absent();
route = newLinkedList();
newRoute = Optional.absent();
currentTime = Optional.absent();
allowDelayedRouteChanges = allowDelayedRouteChanging;
routeAdjuster = adjuster;
stateMachine = createStateMachine();
waitState = stateMachine.getStateOfType(Wait.class);
gotoState = stateMachine.getStateOfType(Goto.class);
waitForServiceState = stateMachine.getStateOfType(WaitAtService.class);
serviceState = stateMachine.getStateOfType(Service.class);
final String v = Integer.toHexString(hashCode());
stateMachine.getEventAPI().addListener(new Listener() {
@Override
public void handleEvent(Event e) {
verify(e instanceof StateTransitionEvent, ?>);
final StateTransitionEvent, ?> event = (StateTransitionEvent, ?>) e;
LOGGER.trace("vehicle({}) - {} + {} -> {}", v, event.previousState,
event.trigger, event.newState);
}
}, StateMachineEvent.STATE_TRANSITION);
}
/**
* @return true
if this vehicle is allowed to divert.
*/
public boolean isDiversionAllowed() {
return isDiversionAllowed;
}
/**
* Indicates whether delayed route changing is allowed for this vehicle, see
* {@link #setRoute(Iterable)} for more information about delayed route
* changing.
* @return true
if this vehicle allows delayed route changing,
* false
otherwise.
*/
public boolean isDelayedRouteChangingAllowed() {
return allowDelayedRouteChanges;
}
/**
* Change the route this vehicle is following. The route must adhere to the
* following requirements:
*
* - Parcels that have not yet been picked up can at maximum occur twice in
* the route.
* - Parcels that have been picked up can occur at maximum once in the
* route.
* - Parcels that are delivered may not occur in the route.
*
* These requirements are not checked defensively! It is the callers
* responsibility to make sure this is the case. Note that the underlying
* models normally should throw exceptions whenever a vehicle attempts
* to revisit an already delivered parcel.
*
* In some cases the models do not allow this vehicle to change its route
* immediately. If this is the case the route is changed the next time this
* vehicle enters its {@link #waitState}. If
* {@link #isDelayedRouteChangingAllowed()} is set to false
any
* attempts to do this will result in a runtime exception, in this case the
* caller must ensure that a route can be changed immediately or call this
* method at a later time. The situations when the route is changed
* immediately are:
*
* - If the vehicle is waiting.
* - If diversion is allowed and the vehicle is not currently servicing.
*
* - If the current route is empty.
* - If the first destination in the new route equals the first destination
* of the current route.
*
* @param r The route to set. The elements are copied from the
* {@link Iterable} using its iteration order.
*/
public void setRoute(Iterable extends Parcel> r) {
final Iterable adjustedRoute = routeAdjuster.adjust(r, this);
LOGGER.trace("{} setRoute {}", this, adjustedRoute);
// note: the following checks can not detect if a parcel has been set to
// multiple vehicles at the same time
final Multiset routeSet = LinkedHashMultiset.create(adjustedRoute);
for (final Parcel dp : routeSet.elementSet()) {
final ParcelState state = getPDPModel().getParcelState(dp);
checkArgument(
!state.isDelivered(),
"A parcel that is already delivered can not be part of a route. "
+ "Parcel %s in route %s.",
dp, adjustedRoute);
if (state.isTransitionState()) {
if (state == ParcelState.PICKING_UP) {
checkArgument(
getPDPModel().getVehicleState(this) == VehicleState.PICKING_UP,
"When a parcel in the route is in PICKING UP state the vehicle "
+ "must also be in that state, route: %s.",
adjustedRoute, getPDPModel().getVehicleState(this));
} else {
checkArgument(
getPDPModel().getVehicleState(this) == VehicleState.DELIVERING,
"When a parcel in the route is in DELIVERING state the vehicle"
+ " must also be in that state.");
}
checkArgument(
getPDPModel().getVehicleActionInfo(this).getParcel() == dp,
"A parcel in the route that is being serviced should be serviced by"
+ " this truck. This truck is servicing %s.",
getPDPModel().getVehicleActionInfo(this).getParcel());
}
final int frequency = routeSet.count(dp);
if (state.isPickedUp()) {
checkArgument(getPDPModel().getContents(this).contains(dp),
"A parcel that is in cargo state must be in cargo of this "
+ "vehicle.");
checkArgument(
frequency <= 1,
"A parcel that is in cargo may not occur more than once in a route,"
+ " found %s instance(s) of %s.",
frequency, dp, state);
} else {
checkArgument(
frequency <= 2,
"A parcel that is available may not occur more than twice in a "
+ "route, found %s instance(s) of %s (with state %s). Route: %s.",
frequency, dp, state, adjustedRoute);
}
}
final boolean firstEqualsFirst = firstEqualsFirstInRoute(adjustedRoute);
final boolean divertable = isDiversionAllowed
&& !stateMachine.stateIs(serviceState);
if (stateMachine.stateIs(waitState)
|| route.isEmpty()
|| divertable
|| firstEqualsFirst) {
route = newLinkedList(adjustedRoute);
newRoute = Optional.absent();
} else {
checkArgument(
allowDelayedRouteChanges,
"Diversion is not allowed in current state and delayed route changes "
+ "are also not allowed, rejected route: %s.",
adjustedRoute);
newRoute = Optional.of(newLinkedList(adjustedRoute));
}
}
boolean isPickingUp(Parcel p) {
return getPDPModel().getVehicleState(this) == VehicleState.PICKING_UP
&& getPDPModel().getVehicleActionInfo(this).getParcel().equals(p);
}
/**
* @return The route that is currently being followed.
*/
public Collection getRoute() {
return unmodifiableCollection(route);
}
/**
* Helper method for checking whether the first parcels in two routes are
* equal.
* @param r The route to compare with the current route in
* {@link RouteFollowingVehicle#getRoute()}.
* @return true
if the first item in r
equals the
* first item in {@link RouteFollowingVehicle#getRoute()}. If not
* equal or if either of the routes are empty false
is
* returned.
*/
protected final boolean firstEqualsFirstInRoute(
Iterable extends Parcel> r) {
return !Iterables.isEmpty(r) && !route.isEmpty()
&& r.iterator().next().equals(route.element());
}
@Override
public void initRoadPDP(RoadModel pRoadModel, PDPModel pPdpModel) {
super.initRoadPDP(pRoadModel, pPdpModel);
final Set depots = getRoadModel().getObjectsOfType(Depot.class);
checkArgument(depots.size() == 1,
"This vehicle requires exactly 1 depot, found %s depots.",
depots.size());
checkArgument(getRoadModel() instanceof PDPRoadModel,
"This vehicle requires the PDPRoadModel.");
isDiversionAllowed = ((PDPRoadModel) getRoadModel())
.isVehicleDiversionAllowed();
depot = Optional.of(depots.iterator().next());
speed = Optional.of(Measure.valueOf(getSpeed(), getRoadModel()
.getSpeedUnit()));
}
/**
* This method can optionally be overridden to change route of this vehicle by
* calling {@link #setRoute(Iterable)} from within this method.
* @param time The current time.
*/
protected void preTick(TimeLapse time) {}
@Override
protected final void tickImpl(TimeLapse time) {
currentTime = Optional.of(time);
preTick(time);
stateMachine.handle(this);
}
/**
* Check if leaving in the specified {@link TimeLapse} to the specified
* {@link Parcel} would mean a too early arrival time. When this method
* returns true
it is not necessary to leave already, when
* false
is returned the vehicle should leave as soon as
* possible.
*
* Calculates the latest time to leave (lttl) to be just in time at the parcel
* location. In case lttl is in this {@link TimeLapse} or has already passed,
* this method returns false
, returns true
* otherwise.
* @param p The parcel to travel to.
* @param time The current time.
* @return true
when leaving in this tick would mean arriving too
* early, false
otherwise.
*/
protected boolean isTooEarly(Parcel p, TimeLapse time) {
final ParcelState parcelState = getPDPModel().getParcelState(p);
checkArgument(
!parcelState.isTransitionState() && !parcelState.isDelivered(),
"Parcel state may not be a transition state nor may it be delivered, "
+ "it is %s.",
parcelState, parcelState.isTransitionState() ? getPDPModel()
.getVehicleActionInfo(this).timeNeeded() : null);
final boolean isPickup = !parcelState.isPickedUp();
// if it is available, we know we can't be too early
if (isPickup && parcelState == ParcelState.AVAILABLE) {
return false;
}
final Point loc = isPickup ? p.getDto().getPickupLocation() : p
.getDeliveryLocation();
final long travelTime = computeTravelTimeTo(loc, time.getTimeUnit());
final long openingTime = isPickup ? p.getPickupTimeWindow().begin() : p
.getDeliveryTimeWindow().begin();
final long latestTimeToLeave = openingTime - travelTime;
return latestTimeToLeave >= time.getEndTime();
}
/**
* Computes the travel time for this vehicle to any point.
* @param p The point to calculate travel time to.
* @param timeUnit The time unit used in the simulation.
* @return The travel time in the used time unit.
*/
protected long computeTravelTimeTo(Point p, Unit timeUnit) {
final Measure distance = Measure.valueOf(Point.distance(
getRoadModel().getPosition(this), p), getRoadModel()
.getDistanceUnit());
return DoubleMath.roundToLong(
RoadModels.computeTravelTime(speed.get(), distance, timeUnit),
RoundingMode.CEILING);
}
/**
* @param time The time to use as 'now'.
* @return true
if it is the end of the day or if this vehicle
* has to leave before the end of this tick to arrive back at the
* depot right before the end of the day, false
* otherwise.
*/
protected boolean isEndOfDay(TimeLapse time) {
final long travelTime = computeTravelTimeTo(
getRoadModel().getPosition(depot.get()), time.getTimeUnit());
return time.getEndTime() - 1 >= getAvailabilityTimeWindow().end()
- travelTime;
}
/**
* @return the depot
*/
protected Depot getDepot() {
return depot.get();
}
/**
* @return the currentTime
*/
protected TimeLapse getCurrentTimeLapse() {
return currentTime.get();
}
/**
* @return the current time.
*/
protected long getCurrentTime() {
if (currentTime.isPresent()) {
return currentTime.get().getTime();
}
return 0L;
}
/**
* Creates the {@link StateMachine} that is used in this vehicle. This method
* is (and should) called only once during the life time of a vehicle.
*
* ExtensionThis method can optionally be overridden to change the
* behavior of the vehicle. When overriding make sure that:
*
* - The resulting state machine contains at least four states of the
* following types: {@link Wait}, {@link Goto}, {@link WaitAtService} and
* {@link Service}. Subclasses are allowed, multiple instances of the same
* type may result in unexpected behavior.
* - This method does not have any side effects. It should not call any
* instance methods or set any global variables.
*
* @return A newly created {@link StateMachine} that controls this vehicle.
*/
protected StateMachine createStateMachine() {
final Wait wait = new Wait();
final Goto gotos = new Goto();
final WaitAtService waitAtService = new WaitAtService();
final Service service = new Service();
return StateMachine.create(wait)
.explicitRecursiveTransitions()
.addTransition(wait, DefaultEvent.GOTO, gotos)
.addTransition(gotos, DefaultEvent.NOGO, wait)
.addTransition(gotos, DefaultEvent.ARRIVED, waitAtService)
.addTransition(gotos, DefaultEvent.REROUTE, gotos)
.addTransition(waitAtService, DefaultEvent.REROUTE, gotos)
.addTransition(waitAtService, DefaultEvent.NOGO, wait)
.addTransition(waitAtService, DefaultEvent.READY_TO_SERVICE, service)
.addTransition(service, DefaultEvent.DONE, wait).build();
}
void checkCurrentParcelOwnership() {
checkState(
!getPDPModel().getParcelState(route.peek()).isTransitionState(),
"Parcel is already being serviced by another vehicle. Parcel state: %s",
getPDPModel().getParcelState(route.peek()));
}
/**
* Fixes routes that have become invalid due to natural progressing of time.
* Invalidation over time can for example occur in case of a real-time
* simulation where the route is calculated in a separate thread, in this case
* it is possible that the simulated world has already changed in such a way
* that the route is invalid. This method detects such changes and adapts the
* specified route to make it valid again. Note that this method assumes that
* the specified route was valid at some point in the past, if this isn't true
* this {@link RouteAdjuster} may yield unexpected results. If the route is
* valid at the time this adjuster is used the route will be left unchanged.
*
* There are four situations that are fixed by this adjuster:
*
* - A parcel that occurs in the route has been delivered, all occurrences
* of this parcel will be removed from the route.
* - A parcel that occurs in the route has been picked up by another vehicle
* (or is in the process of being picked up), all occurrences of this parcel
* will be removed from the route.
* - A parcel that occurs twice in the route but has already been picked up
* by this vehicle, the first occurrence in the route will be removed.
* - A parcel that no longer occurs in the route but is in the cargo of the
* vehicle, the parcel will be added at the end of the route.
*
* @return The route adjuster.
*/
public static RouteAdjuster delayAdjuster() {
return RouteAdjusters.DELAY_ADJUSTER;
}
/**
* @return A no-operation {@link RouteAdjuster}.
*/
public static RouteAdjuster nopAdjuster() {
return RouteAdjusters.NOP;
}
/**
* Marker interface for events. When defining new events simply implement this
* interface.
* @author Rinde van Lon
*/
protected interface StateEvent {}
/**
* The default event types of the state machine.
* @author Rinde van Lon
*/
protected enum DefaultEvent implements StateEvent {
/**
* Indicates that waiting is over, the vehicle is going to a parcel.
*/
GOTO,
/**
* Indicates that the vehicle no longer has a destination.
*/
NOGO,
/**
* Indicates that the vehicle has arrived at a service location.
*/
ARRIVED,
/**
* Indicates that the vehicle is at a service location and that the vehicle
* and the parcel are both ready to start the servicing.
*/
READY_TO_SERVICE,
/**
* Indicates that the vehicle is going to a new destination. This event only
* occurs when the vehicle was previously waiting at a service point.
*/
REROUTE,
/**
* Indicates that servicing is finished.
*/
DONE;
}
/**
* Base state class, can be subclassed to define custom states.
* @author Rinde van Lon
*/
protected abstract class AbstractTruckState extends
AbstractState {
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
/**
* Implementation of waiting state, is also responsible for driving back to
* the depot.
* @author Rinde van Lon
*/
protected class Wait extends AbstractTruckState {
/**
* New instance.
*/
protected Wait() {}
@SuppressWarnings("synthetic-access")
@Override
public void onEntry(StateEvent event, RouteFollowingVehicle context) {
checkState(
getPDPModel().getVehicleState(context) == VehicleState.IDLE,
"We can only be in Wait state when the vehicle is idle, "
+ "vehicle is %s.",
getPDPModel().getVehicleState(context));
if (event == DefaultEvent.NOGO) {
checkArgument(isDiversionAllowed);
}
if (context.newRoute.isPresent()) {
context.setRoute(context.newRoute.get());
}
}
@SuppressWarnings("synthetic-access")
@Nullable
@Override
public DefaultEvent handle(@Nullable StateEvent event,
RouteFollowingVehicle context) {
if (!route.isEmpty()) {
checkCurrentParcelOwnership();
if (!isTooEarly(route.peek(), currentTime.get())) {
return DefaultEvent.GOTO;
}
// else it is too early, and we do nothing
} else if (currentTime.get().hasTimeLeft()
&& isEndOfDay(currentTime.get())
&& !getRoadModel().equalPosition(context, depot.get())) {
// check if it is time to go back to the depot
getRoadModel().moveTo(context, depot.get(), currentTime.get());
}
currentTime.get().consumeAll();
return null;
}
}
/**
* State responsible for moving to a service location.
* @author Rinde van Lon
*/
protected class Goto extends AbstractTruckState {
/**
* Field for storing the destination.
*/
protected Optional destination;
/**
* Field for storing the previous destination.
*/
protected Optional prevDestination;
/**
* New instance.
*/
protected Goto() {
destination = Optional.absent();
prevDestination = Optional.absent();
}
@Override
public void onEntry(StateEvent event, RouteFollowingVehicle context) {
if (event == DefaultEvent.REROUTE) {
checkArgument(isDiversionAllowed);
}
checkCurrentParcelOwnership();
destination = Optional.of(route.element());
}
@SuppressWarnings("synthetic-access")
@Nullable
@Override
public DefaultEvent handle(@Nullable StateEvent event,
RouteFollowingVehicle context) {
if (route.isEmpty()) {
return DefaultEvent.NOGO;
} else if (destination.get() != route.element()) {
return DefaultEvent.REROUTE;
}
final Parcel cur = route.element();
if (getRoadModel().equalPosition(context, cur)) {
return DefaultEvent.ARRIVED;
}
getRoadModel().moveTo(context, cur, currentTime.get());
if (getRoadModel().equalPosition(context, cur)
&& currentTime.get().hasTimeLeft()) {
return DefaultEvent.ARRIVED;
}
return null;
}
@Override
public void onExit(StateEvent event, RouteFollowingVehicle context) {
prevDestination = destination;
destination = Optional.absent();
}
/**
* @return The destination of the vehicle.
* @throws IllegalStateException if there is no destination.
*/
public Parcel getDestination() {
return destination.get();
}
/**
* @return The previous destination of the vehicle.
* @throws IllegalStateException if there is no previous destination.
*/
public Parcel getPreviousDestination() {
return prevDestination.get();
}
}
/**
* State responsible for waiting at a service location to become available.
* @author Rinde van Lon
*/
protected class WaitAtService extends AbstractTruckState {
/**
* New instance.
*/
protected WaitAtService() {}
@SuppressWarnings("synthetic-access")
@Nullable
@Override
public DefaultEvent handle(@Nullable StateEvent event,
RouteFollowingVehicle context) {
// the route has changed (there is no destination anymore)
if (route.isEmpty()) {
return DefaultEvent.NOGO;
}
checkCurrentParcelOwnership();
final PDPModel pm = getPDPModel();
final TimeLapse time = currentTime.get();
final Parcel cur = route.element();
// we are not at the parcel's position, this means the next parcel has
// changed in the mean time, so we have to reroute.
if (!getRoadModel().equalPosition(context, cur)) {
return DefaultEvent.REROUTE;
}
// if parcel is not ready yet, wait
final boolean pickup = !pm.getContents(context).contains(cur);
final long timeUntilReady = (pickup
? cur.getDto().getPickupTimeWindow().begin()
: cur.getDto().getDeliveryTimeWindow().begin())
- time.getTime();
if (timeUntilReady > 0) {
if (time.getTimeLeft() < timeUntilReady) {
// in this case we can not yet start servicing
time.consumeAll();
return null;
} else {
time.consume(timeUntilReady);
}
}
if (time.hasTimeLeft()) {
return DefaultEvent.READY_TO_SERVICE;
} else {
return null;
}
}
}
/**
* State responsible for servicing a parcel.
* @author Rinde van Lon
*/
protected class Service extends AbstractTruckState {
/**
* New instance.
*/
protected Service() {}
@SuppressWarnings("synthetic-access")
@Override
public void onEntry(StateEvent event, RouteFollowingVehicle context) {
getPDPModel().service(context, route.peek(), currentTime.get());
}
@SuppressWarnings("synthetic-access")
@Nullable
@Override
public DefaultEvent handle(@Nullable StateEvent event,
RouteFollowingVehicle context) {
if (getPDPModel().getVehicleState(context) == VehicleState.IDLE) {
route.remove();
return DefaultEvent.DONE;
}
return null;
}
}
/**
* Implementations can modify the incoming route and return the modified
* version.
* @author Rinde van Lon
*/
public interface RouteAdjuster {
/**
* Adjusts the route. Implementations should be pure functions, i.e. have no
* side-effects.
* @param route The incoming route.
* @param vehicle The vehicle which is owner of the route.
* @return The outgoing possibly modified route
*/
Iterable adjust(Iterable extends Parcel> route,
RouteFollowingVehicle vehicle);
}
enum RouteAdjusters implements RouteAdjuster {
NOP {
@SuppressWarnings("unchecked")
@Override
public Iterable adjust(Iterable extends Parcel> r,
RouteFollowingVehicle vehicle) {
return (Iterable) r;
}
},
DELAY_ADJUSTER {
@SuppressWarnings("synthetic-access")
@Override
public Iterable adjust(Iterable extends Parcel> r,
RouteFollowingVehicle vehicle) {
final List routeList = Lists.newArrayList(r);
final Multiset routeSet = LinkedHashMultiset.create(r);
// removals
for (final Parcel p : routeSet.elementSet()) {
final ParcelState state = vehicle.getPDPModel().getParcelState(p);
if (state.isDelivered()) {
// remove all occurrences
routeList.removeAll(Collections.singleton(p));
} else if (state.isPickedUp()) {
// in this case the parcel is either: IN_CARGO or DELIVERING
if (!vehicle.getPDPModel().getContents(vehicle).contains(p)) {
// if we don't carry the parcel it must have been picked up by
// someone else, remove it from our route
routeList.removeAll(Collections.singleton(p));
} else if (routeSet.count(p) == 2) {
// in this case it is in cargo and it occurs twice in our route
// (which is one too many), therefore first occurrence is removed
routeList.remove(p);
}
} else if (state == ParcelState.PICKING_UP
&& !vehicle.isPickingUp(p)) {
// in this case the parcel is being picked up by another vehicle
routeList.removeAll(Collections.singleton(p));
}
}
// additions
// loop through cargo, add all parcels that are not in the list
final Set contents = vehicle.getPDPModel().getContents(vehicle);
routeList.addAll(Sets.difference(contents, routeSet.elementSet()));
return routeList;
}
@Override
public String toString() {
return RouteFollowingVehicle.class.getSimpleName() + ".delayAdjuster()";
}
}
}
}