@Override
  public void drop(Vehicle vehicle, Parcel parcel, TimeLapse time) {
    synchronized (this) {
      /* 1 */ checkVehicleInRoadModel(vehicle);
      /* 2 */ checkArgument(
          vehicleState.get(vehicle).equals(VehicleState.IDLE),
          "vehicle must be idle but is: %s ",
          vehicleState.get(vehicle));
      /* 3 */ checkArgument(
          containerContents.get(vehicle).contains(parcel), "vehicle does not contain parcel");

      eventDispatcher.dispatchEvent(
          new PDPModelEvent(
              PDPModelEventType.START_DELIVERY, self, time.getTime(), parcel, vehicle));
      if (time.getTimeLeft() < parcel.getDeliveryDuration()) {
        vehicleState.put(vehicle, VehicleState.DELIVERING);
        parcelState.put(ParcelState.DELIVERING, parcel);
        pendingVehicleActions.put(
            vehicle,
            new DropAction(
                this, vehicle, parcel, parcel.getDeliveryDuration() - time.getTimeLeft()));
        time.consumeAll();
      } else {
        time.consume(parcel.getDeliveryDuration());
        doDrop(vehicle, parcel, time.getTime());
      }
    }
  }
  @Override
  public void addParcelIn(Container container, Parcel parcel) {
    synchronized (this) {
      /* 1 */ checkArgument(
          !roadModel.get().containsObject(parcel), "this parcel is already added to the roadmodel");
      /* 2 */ checkArgument(
          parcelState.getKeys(parcel) == ParcelState.AVAILABLE,
          "parcel must be registered and in AVAILABLE state, current state: %s",
          parcelState.getKeys(parcel));
      /* 3 */ checkArgument(
          containerCapacities.containsKey(container), "the parcel container is not registered");
      /* 4 */ checkArgument(
          roadModel.get().containsObject(container),
          "the parcel container is not on the roadmodel");
      final double newSize = containerContentsSize.get(container) + parcel.getMagnitude();
      /* 5 */ checkArgument(
          newSize <= containerCapacities.get(container),
          "parcel does not fit in container. Capacity is %s, current content size is %s, new parcel size is %s",
          containerCapacities.get(container),
          containerContentsSize.get(container),
          parcel.getMagnitude());

      containerContents.put(container, parcel);
      containerContentsSize.put(container, newSize);
      parcelState.put(ParcelState.IN_CARGO, parcel);
    }
  }
 /**
  * The actual dropping of the specified {@link rinde.sim.core.model.pdp.Parcel} by the specified
  * {@link rinde.sim.core.model.pdp.Vehicle}.
  *
  * @param vehicle The {@link rinde.sim.core.model.pdp.Vehicle} that performs the dropping.
  * @param parcel The {@link rinde.sim.core.model.pdp.Parcel} that is dropped.
  * @param time The current time.
  */
 protected void doDrop(Vehicle vehicle, Parcel parcel, long time) {
   synchronized (this) {
     containerContents.remove(vehicle, parcel);
     containerContentsSize.put(
         vehicle, containerContentsSize.get(vehicle) - parcel.getMagnitude());
     roadModel.get().addObjectAtSamePosition(parcel, vehicle);
     parcelState.put(ParcelState.AVAILABLE, parcel);
     LOGGER.info("{} dropped {} by {}", time, parcel, vehicle);
     eventDispatcher.dispatchEvent(
         new PDPModelEvent(PDPModelEventType.PARCEL_AVAILABLE, self, time, parcel, null));
   }
 }
  /**
   * The actual delivery of the specified {@link rinde.sim.core.model.pdp.Parcel} by the specified
   * {@link rinde.sim.core.model.pdp.Vehicle}.
   *
   * @param vehicle The {@link rinde.sim.core.model.pdp.Vehicle} that performs the delivery.
   * @param parcel The {@link rinde.sim.core.model.pdp.Parcel} that is delivered.
   * @param time The current time.
   */
  protected void doDeliver(Vehicle vehicle, Parcel parcel, long time) {
    synchronized (this) {
      containerContents.remove(vehicle, parcel);
      containerContentsSize.put(
          vehicle, containerContentsSize.get(vehicle) - parcel.getMagnitude());

      parcelState.put(ParcelState.DELIVERED, parcel);
      LOGGER.info("{} end delivery of {} by {}", time, parcel, vehicle);
      eventDispatcher.dispatchEvent(
          new PDPModelEvent(PDPModelEventType.END_DELIVERY, self, time, parcel, vehicle));
    }
  }
  /**
   * Actual pickup, updates the {@link rinde.sim.core.model.pdp.Vehicle} contents.
   *
   * @param vehicle The {@link rinde.sim.core.model.pdp.Vehicle} that performs the pickup.
   * @param parcel The {@link rinde.sim.core.model.pdp.Parcel} that is picked up.
   * @param time The current time.
   * @see #pickup(rinde.sim.core.model.pdp.Vehicle, rinde.sim.core.model.pdp.Parcel,
   *     rinde.sim.core.TimeLapse)
   */
  protected void doPickup(Vehicle vehicle, Parcel parcel, long time) {
    synchronized (this) {
      containerContents.put(vehicle, parcel);
      containerContentsSize.put(
          vehicle, containerContentsSize.get(vehicle) + parcel.getMagnitude());

      parcelState.put(ParcelState.IN_CARGO, parcel);
      LOGGER.info("{} end pickup of {} by {}", time, parcel, vehicle);
      eventDispatcher.dispatchEvent(
          new PDPModelEvent(PDPModelEventType.END_PICKUP, self, time, parcel, vehicle));
    }
  }
 @Override
 public void tick(TimeLapse timeLapse) {
   synchronized (this) {
     // TODO this can be optimized by scheduling events upon registering
     currentTime = timeLapse.getStartTime();
     final Collection<Parcel> parcels = parcelState.get(ParcelState.ANNOUNCED);
     final List<Parcel> newAvailables = newArrayList();
     for (final Parcel p : parcels) {
       if (timeLapse.getStartTime() >= p.getPickupTimeWindow().begin) {
         newAvailables.add(p);
       }
     }
     for (final Parcel p : newAvailables) {
       parcelState.put(ParcelState.AVAILABLE, p);
       eventDispatcher.dispatchEvent(
           new PDPModelEvent(PDPModelEventType.PARCEL_AVAILABLE, self, currentTime, p, null));
     }
   }
 }
  @Override
  public void deliver(Vehicle vehicle, Parcel parcel, TimeLapse time) {
    synchronized (this) {
      /* 1 */ checkVehicleInRoadModel(vehicle);
      /* 2 */ checkArgument(
          vehicleState.get(vehicle).equals(VehicleState.IDLE),
          "vehicle must be idle but is: %s ",
          vehicleState.get(vehicle));
      /* 3 */ checkArgument(
          containerContents.get(vehicle).contains(parcel), "vehicle does not contain parcel");
      /* 4 */ checkArgument(
          parcel.getDestination().equals(roadModel.get().getPosition(vehicle)),
          "parcel must be delivered at its destination, vehicle should move there first");

      checkArgument(
          timeWindowPolicy.canDeliver(
              parcel.getDeliveryTimeWindow(), time.getTime(), parcel.getDeliveryDuration()),
          "parcel delivery is not allowed at this time (%s) according to the time window policy: %s",
          time.getTime(),
          timeWindowPolicy);

      checkArgument(
          parcel.canBeDelivered(vehicle, time.getTime()),
          "the parcel does not allow a delivery now");

      eventDispatcher.dispatchEvent(
          new PDPModelEvent(
              PDPModelEventType.START_DELIVERY, self, time.getTime(), parcel, vehicle));
      if (time.getTimeLeft() < parcel.getDeliveryDuration()) {
        vehicleState.put(vehicle, VehicleState.DELIVERING);
        parcelState.put(ParcelState.DELIVERING, parcel);
        pendingVehicleActions.put(
            vehicle,
            new DeliverAction(
                this, vehicle, parcel, parcel.getDeliveryDuration() - time.getTimeLeft()));
        time.consumeAll();
      } else {
        time.consume(parcel.getDeliveryDuration());
        doDeliver(vehicle, parcel, time.getTime());
      }
    }
  }
  @Override
  protected boolean doRegister(PDPObject element) {
    synchronized (this) {
      LOGGER.info("{} register {}", currentTime, element);
      if (element.getType() == PDPType.PARCEL) {
        checkArgument(!parcelState.containsValue(element));
        final Parcel p = (Parcel) element;
        final ParcelState state =
            currentTime < p.getPickupTimeWindow().begin
                ? ParcelState.ANNOUNCED
                : ParcelState.AVAILABLE;
        parcelState.put(state, (Parcel) element);
        eventDispatcher.dispatchEvent(
            new PDPModelEvent(PDPModelEventType.NEW_PARCEL, self, currentTime, p, null));
        // if the parcel is immediately available, we send this event as
        // well
        if (state == ParcelState.AVAILABLE) {
          eventDispatcher.dispatchEvent(
              new PDPModelEvent(PDPModelEventType.PARCEL_AVAILABLE, self, currentTime, p, null));
        }
      } else {
        // it is a vehicle or a depot
        final Container container = (Container) element;
        checkArgument(!containerCapacities.containsKey(container));
        containerCapacities.put(container, container.getCapacity());
        containerContentsSize.put(container, 0d);

        if (element.getType() == PDPType.VEHICLE) {
          final Vehicle v = (Vehicle) element;
          vehicleState.put(v, VehicleState.IDLE);
          eventDispatcher.dispatchEvent(
              new PDPModelEvent(PDPModelEventType.NEW_VEHICLE, self, currentTime, null, v));
        }
      }
      element.initPDPObject(self);

      return true;
    }
  }
  @Override
  public void pickup(Vehicle vehicle, Parcel parcel, TimeLapse time) {
    synchronized (this) {
      /* 1 */ checkVehicleInRoadModel(vehicle);
      /* 2 */ checkArgument(
          roadModel.get().containsObject(parcel), "parcel does not exist in RoadModel");
      final ParcelState ps = parcelState.getKeys(parcel);
      /* 3 */ checkArgument(
          ps == ParcelState.AVAILABLE || ps == ParcelState.ANNOUNCED,
          "Parcel must be registered and must be either ANNOUNCED or AVAILABE, it is: %s. Parcel: %s.",
          ps,
          parcel);
      /* 4 */ checkArgument(
          vehicleState.get(vehicle) == VehicleState.IDLE,
          "vehicle must be registered and must be available");
      /* 5 */ checkArgument(
          roadModel.get().equalPosition(vehicle, parcel),
          "vehicle must be at the same location as the parcel it wishes to pickup");
      final double newSize = containerContentsSize.get(vehicle) + parcel.getMagnitude();
      /* 6 */ checkArgument(
          newSize <= containerCapacities.get(vehicle),
          "parcel does not fit in vehicle. Parcel size: %s, current contents size: %s, capacity: %s.",
          parcel.getMagnitude(),
          containerContentsSize.get(vehicle),
          containerCapacities.get(vehicle));

      checkArgument(
          timeWindowPolicy.canPickup(
              parcel.getPickupTimeWindow(), time.getTime(), parcel.getPickupDuration()),
          "parcel pickup is not allowed according to the time window policy: %s, current time: %s, time window %s.",
          timeWindowPolicy,
          time.getTime(),
          parcel.getPickupTimeWindow());

      checkArgument(
          parcel.canBePickedUp(vehicle, time.getTime()), "the parcel does not allow pickup now");

      eventDispatcher.dispatchEvent(
          new PDPModelEvent(PDPModelEventType.START_PICKUP, self, time.getTime(), parcel, vehicle));

      // remove the parcel such that no other attempts to pickup can be
      // made
      roadModel.get().removeObject(parcel);

      // in this case we know we cannot finish this action with the
      // available time. We must continue in the nextDeliverable tick.
      if (time.getTimeLeft() < parcel.getPickupDuration()) {
        vehicleState.put(vehicle, VehicleState.PICKING_UP);
        parcelState.put(ParcelState.PICKING_UP, parcel);

        pendingVehicleActions.put(
            vehicle,
            new PickupAction(
                this, vehicle, parcel, parcel.getPickupDuration() - time.getTimeLeft()));
        time.consumeAll();
      } else {
        time.consume(parcel.getPickupDuration());
        doPickup(vehicle, parcel, time.getTime());
      }
    }
  }