1
0
Fork 0
mirror of https://github.com/graphhopper/jsprit.git synced 2020-01-24 07:45:05 +01:00

Merge pull request #351 from balage1551/master

Migration process started
This commit is contained in:
Stefan Schröder 2017-08-07 07:11:08 +02:00 committed by GitHub
commit 71d97f2234
232 changed files with 7263 additions and 8482 deletions

3
.gitignore vendored
View file

@ -11,4 +11,5 @@
# Eclipse
.project
.classpath
.classpath
jsprit-core/src/main/java/com/graphhopper/jsprit/core/reporting/SolutionPrinter_depr.java

View file

@ -0,0 +1,52 @@
# Introduction to Custom Jobs
In the previous version of Jsprit, users had the choice to pick one of the four, predefined job types: Services, Pickups, Deliveries or Shipments. Life is much more complex and these limited palette of basic ingredients often proved to be not enough, forcing the users to make complex constraints.
Internally, the jobs were broken down into steps called *activities*, but the parameters of these activities were configured and stored in the Job.
Jsprit 2 uses a completely different approach. Even if you are experienced with Jsprit, it is vital to understand the new concept.
What is a job? A sequence of tasks (*activities*) someone has to fulfill. In Jsprit 2, a `Job` is nothing more than a container which contains one or more `Activities`. The algorithm will ensure that these activities
* are executed on the same route
* either all or none of them is included in the solution
* their order is kept as they were defined.
The activities became the main building blocks of the problems. They have their own
* capacity impact (size dimension change)
* time windows
* operation time
* location.
There are for type of abilities, differentiated by their impact on vehicle load:
| Type | Capacity impact |
| -------- | ---------------------------------------- |
| Service | No change in vehicle load. |
| Pickup | Some space is allocated in one or more dimensions. |
| Delivery | Some space is freed in one or more dimensions. |
| Exchange | Some dimensions may be increased other may be reduced: a combination of pickup and delivery. |
> *Note: This is only a convenient partitioning, because each other types could be interpreted as a special case of exchange.*
On the other hand, the user has the freedom to build up Jobs from these activities, combining any kind and any number of them.
**<u>Example:</u>**
A classical case when there is a shipment from the warehouse to the customer, but when delivering the order, the vehicle has to pick up some backhaul cargo at the same time and take it back to the warehouse. (Such as empty crates.)
Earlier this required to define two shipments and ensure that (1) both jobs are done by the same vehicle; (2) the delivery of the first job is immediately preceding the pickup of the second. These rules had to be ensured by complex hard constraints and even the ruin strategy had to be altered.
With Jsprit 2, one can simply define a new `CustomJob` with three activities:
```java
new CustomJob.Builder(id)
.addPickup(...)
.addExchange(...)
.addDelivery(...)
.build();
```
All constraints a taken care by the algorithm.
> For convenience and backward compatibility, the old Job types (Service, Pickup, Delivery and Shipment) are kept, but most of their methods are deprecated. Because the vast structural change of the Job/Activity architecture, there are some legacy code breaking changes. See XXX for migration guide!

View file

@ -0,0 +1,92 @@
# Migration checklist
### Task 1: Capacity to SizeDimension
**<u>Nature:</u>**
`Capacity` became deprecated but kept, it is fully compatible with the `SizeDimension` class, which is a subclass of `Capacity`.
**<u>Decision:</u>**
Make `Capacity` *deprecated*. Keep both classes.
**<u>State:</u>**
Ready.
**<u>Migration task:</u>**
None required. Optionally, but strongly recommended to replace all `Capacity` references by `SizeDimension`. (Only a search and replace.)
### Task 2: The Job interface
**<u>Nature:</u>**
The `Job` interface was extended. This would break any third party implementation of this interface (or the extension of the `AbstractJob` abstract class).
**<u>Decision:</u>**
There are several ways to overcome on this compatibility conflict:
1. Keep it as it is and force the implementators to extend its implementation or to migrate to `CustomJob`.
2. Restore the original interface and create an extended one as a descendant of this. The `AbstractJob` is the only one implementing this interface, so it can simply implement the extended one.
Option 1 is a compatibility breaker, but doesn't pollute the code unnecessary and urges the users to switch to new job structure. With Option 2, we are keeping compatibility, but introducing an unnecessary level in the class hierarchy and finding a good name for the extended interface would be hard, because this is only created for the sake of compatibility.
**<u>State:</u>**
(!) Waiting for decision.
**<u>Migration task:</u>**
*Write down detailed description of all mandatory and optional migration tasks.*
### Task 3: The old Job types
**<u>Nature:</u>**
There are old job types (`Service`, `Pickup`, `Delivery` and `Shipment`) with deprecated methods. With the new `CustomJob` and the special functions (`addService`, `addPickup`, etc.) of its builder, these old job types are redundant and they are only special cases for the more general `CustomJob`. However, they are the central building classes for Jsprit, so keeping and supporting them in some way is vital for backward compatibility.
**<u>Decision:</u>**
There are several ways to overcome on this compatibility conflict:
1. Remove these classes and guide the users to migrate to the methods in `CustomJob.Builder`.
2. There are already implementations for these classes in new version, but they are not compatible with the old once. Help the users to migrate to these classes.
3. Create façade classes. These classes offer the same interface as the old ones, but extends `CustomJob` and delegates the calls to the new structure.
The first lefts the least of the garbage in the code base, but seems to be too drastic and puts too much migration work on the old users. The second is the way how now the new version works, so here mostly documentation tasks are necessary. The third one would provide an *almost* 100% compatibility, but for a cost of one more redirection, which may have (although possibly minor) performance impact on Jsprit. The *almost* is there, because there may be a few method name collisions where both `CustomJob` and the old classes have the same method, but with different behavior.
My proposal is the 3rd one.
**<u>State:</u>**
(!) Waiting for decision.
**<u>Migration task:</u>**
*Write down detailed description of all mandatory and optional migration tasks.*
----
### Task X:
**<u>Nature:</u>**
*Write down the nature of the change and how it affects the compatibility.*
**<u>Decision:</u>**
*Write down how we plan to solve the conflict.*
**<u>State:</u>**
*Write down the state of the task.*
**<u>Migration task:</u>**
*Write down detailed description of all mandatory and optional migration tasks.*

129
docs/2.0/MigrationGuide.md Normal file
View file

@ -0,0 +1,129 @@
# Migration guide
The new version of the Jsprit library made deep changes on the key structure. Although we made effort to keep as much as of these changes under the hood, there are some code breaking changes in the new version.
This guide helps you to migrate your code to the new version. The first part of the guide helps you make your code run again as small an effort and as quick as it is possible, without migrating to the new data structure. The second part gives you some hints, how to move your code and your data structure to meet the new version. Although you can get a running code without this migration, the legacy classes backing this partial solution are deprecated and are going to be removed in future versions.
## Chapter 1: The quick guide to make your code run again
To help the third party developers to quickly and painlessly migrate to the new version, the current version of the library contains several legacy classes. They has the same (or as close to the original as it was possible to achieve) API as the ones in the previous version. These classes are now marked as deprecated and mostly facades over the new structure.
> **Although by completing these migration steps, you are likely to get a running code, keep in mind that these legacy implementations are in the library only temporally and are going to be removed in some future version.**
### Relocation of files
Some of the classes are relocated. Although most of the modern IDEs would simply relocate the import references, here is the list of relocated classes:
| Class | Old package | New package |
| ----------- | ----------------------------------- | --------------------------------------- |
| AbstractJob | com.graphhopper.jsprit.core.problem | com.graphhopper.jsprit.core.problem.job |
### Using the new structure for own job implementations
If you defined your own implementation of Job, you may run into a few incompatibilities. Before these problems are solved, you have to choose which path you take.
If the reason of your own job implementation is to create jobs with more activities, the new `CustomJob` may render your class deprecated and you can now use the `CustomJob` instead. It brings benefits of being general, and you don't have to create the constraints to keep the activities together (on the same route and either all of them or none).
If your reason to extends any of the old job types was to add user data to it, it is now better to use the `userData` field of the `CustomJob`.
If, after taking account all the above, you still can't avoid to use your implementation, you have to be aware the structural changes and make your implementation compatible with it. Because there is no defined way how your implementation extends the API, it is impossible to give a step by step guide. However, here are the most important changes your implementation must follow:
- The activities now have fixed set of types (Service, Pickup, Exchange, Delivery) and you have to map your activities to these.
- Some of the parameters which was on job level are moved to activity level, because they are associated to them: time windows, operation times, size requirements and changes, location.
- The AbstractJob abstract class is extended with some new abstract methods which should be implemented in your class.
- The builder mechanism is made inheritance friendly and it is recommended to migrate your one to it. (See the JavaDoc for details!)
## Chapter 2: Prepare for the future
In this step, we give you guided help how to completely get rid of the legacy classes and move
### Capacity to SizeDimension
The `Capacity` class is renamed to `SizeDimension`. For backward compatibility, the `SizeDimension` class extends the now deprecated `Capacity` class. This let you use the `Capacity` class as variable type anywhere the value is read out. Also, the `Capacity.Builder` creates a `SizeDimension` class under the hood, so when a `Capacity` object is created it is really a `SizeDimension`.
This makes this rename transparent as far as code correctness goes. However, the `Capacity` class may be removed in the future, so it is strongly recommended to rename all references to `Capacity` to `SizeDimension`.
### Using CustomJob instead of legacy Job types
The old job types (`Service`, `Pickup`, `Delivery`, `Shipment`) are obsolete. However, they can easily be replaced with the new `CustomJob`, by using its Builder methods.
#### Transforming single-activity jobs
The `Service`, `Pickup`, `Delivery` jobs contain only one activity. They can be replaced by the corresponding addXXX() methods (XXX stands for the name of the old job) in `CustomJob.Builder`.
These methods comes with four different flavors:
```
addService(Location location)
addService(Location location, SizeDimension size)
addService(Location location, SizeDimension size, double operationTime)
addService(Location location, SizeDimension size, double operationTime, TimeWindow tw)
```
These methods let's you create jobs with a location, size, operation time and a single time window.
**<u>Example 1:</u>**
If you have a Service declaration:
```java
Service s1 = new Service.Builder("s1").setLocation(Location.newInstance(10, 0)).build();
```
In this example, only the location is set, so you can replace it to the following code snippet:
```java
CustomJob s1 = new CustomJob.Builder("s1")
.addService(Location.newInstance(10, 0))
.build();
```
**<u>Example 2:</u>**
When you have to set the time window, but neither the size or the operation time, there are common defaults for these values to use. This code
```java
Service service = new Service.Builder("s").setLocation(Location.newInstance(20, 0))
.setTimeWindow(TimeWindow.newInstance(40, 50)).build();
```
may be converted to
```java
CustomJob service = new CustomJob.Builder("s")
.addService(Location.newInstance(20, 0), SizeDimension.EMPTY, 0,
TimeWindow.newInstance(40, 50))
.build();
```
**<u>Example 3:</u>**
When you need even more than these convenient methods offer (more time windows, name the activities, skills), you have to do some additional work. First you have to create a `BuilderActivityInfo`:
```
BuilderActivityInfo activityInfo = new BuilderActivityInfo(ActivityType.SERVICE,
Location.newInstance(20, 0));
```
Then set the required values on it:
```java
activityInfo.withName("activity name");
activityInfo.withOperationTime(10);
activityInfo.withSize(SizeDimension.Builder.newInstance()
.addDimension(0, 1)
.addDimension(1, 2)
.build());
activityInfo.withTimeWindows(TimeWindow.newInstance(40, 50), TimeWindow.newInstance(70, 80));
```
Finally, you can configure the CustomJob.Builder and create the job:
```java
CustomJob.Builder customJobBuilder = new CustomJob.Builder("id");
customJobBuilder.addActivity(activityInfo)
.addAllRequiredSkills(Skills.Builder.newInstance().addSkill("skill").build())
.setPriority(5)
.build();
```

View file

@ -27,7 +27,7 @@ import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.job.Break;
import com.graphhopper.jsprit.core.problem.job.CustomJob;
import com.graphhopper.jsprit.core.problem.job.Shipment;
import com.graphhopper.jsprit.core.problem.job.ShipmentJob;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.problem.vehicle.Vehicle;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl;
@ -47,7 +47,7 @@ public class GraphStreamViewerTest {
Vehicle vehicle = VehicleImpl.Builder.newInstance("vehicle").setType(type).setBreak(Break.Builder.newInstance("myBreak").addTimeWindow(5, 10).build())
.setStartLocation(Location.newInstance(0, 0))
.build();
CustomJob cj = CustomJob.Builder.newInstance("job")
CustomJob cj = new CustomJob.Builder("job")
.addPickup(Location.newInstance(10, 0), SizeDimension.Builder.newInstance().addDimension(0, 1).build())
.addPickup(Location.newInstance(5, 0), SizeDimension.Builder.newInstance().addDimension(0, 2).build())
.addDelivery(Location.newInstance(20, 0), SizeDimension.Builder.newInstance().addDimension(0, 3).build())
@ -63,7 +63,7 @@ public class GraphStreamViewerTest {
VehicleType type = VehicleTypeImpl.Builder.newInstance("type").addCapacityDimension(0, 3).build();
Vehicle vehicle = VehicleImpl.Builder.newInstance("vehicle").setStartLocation(Location.newInstance(0, 0))
.setType(type).build();
CustomJob cj = CustomJob.Builder.newInstance("job")
CustomJob cj = new CustomJob.Builder("job")
.addPickup(Location.newInstance(10, 0), SizeDimension.Builder.newInstance().addDimension(0, 1).build())
.addPickup(Location.newInstance(-5, 4), SizeDimension.Builder.newInstance().addDimension(0, 2).build())
.addDelivery(Location.newInstance(20, 10), SizeDimension.Builder.newInstance().addDimension(0, 3).build())
@ -78,7 +78,7 @@ public class GraphStreamViewerTest {
VehicleType type = VehicleTypeImpl.Builder.newInstance("type").addCapacityDimension(0, 3).addCapacityDimension(1, 3).build();
Vehicle vehicle = VehicleImpl.Builder.newInstance("vehicle").setStartLocation(Location.newInstance(0, 0))
.setType(type).build();
CustomJob cj = CustomJob.Builder.newInstance("job")
CustomJob cj = new CustomJob.Builder("job")
.addPickup(Location.newInstance(10, 0), SizeDimension.Builder.newInstance().addDimension(0, 1).addDimension(1, 1).build())
.addExchange(Location.newInstance(-5, 4), SizeDimension.Builder.newInstance().addDimension(0, -1).addDimension(1, 1).build())
.addDelivery(Location.newInstance(20, 10), SizeDimension.Builder.newInstance().addDimension(0, 3).build())
@ -94,7 +94,7 @@ public class GraphStreamViewerTest {
VehicleType type = VehicleTypeImpl.Builder.newInstance("type").addCapacityDimension(0, 3).build();
Vehicle vehicle = VehicleImpl.Builder.newInstance("vehicle").setStartLocation(Location.newInstance(0, 0))
.setType(type).build();
Shipment shipment = Shipment.Builder.newInstance("shipment").setPickupLocation(Location.newInstance(-5, 4))
ShipmentJob shipment = new ShipmentJob.Builder("shipment").setPickupLocation(Location.newInstance(-5, 4))
.addSizeDimension(0, 2).setDeliveryLocation(Location.newInstance(20, 10)).build();
VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance().addJob(shipment).addVehicle(vehicle).build();
VehicleRoutingProblemSolution solution = Solutions.bestOf(Jsprit.createAlgorithm(vrp).searchSolutions());

View file

@ -26,7 +26,7 @@ import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.job.CustomJob;
import com.graphhopper.jsprit.core.problem.job.Shipment;
import com.graphhopper.jsprit.core.problem.job.ShipmentJob;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.problem.vehicle.Vehicle;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl;
@ -45,7 +45,7 @@ public class PlotterTest {
Vehicle vehicle = VehicleImpl.Builder.newInstance("vehicle").setStartLocation(Location.newInstance(0, 0))
.build();
CustomJob cj = CustomJob.Builder.newInstance("job")
CustomJob cj = new CustomJob.Builder("job")
.addPickup(Location.newInstance(10, 0), SizeDimension.Builder.newInstance().addDimension(0, 1).build())
.addPickup(Location.newInstance(5, 0), SizeDimension.Builder.newInstance().addDimension(0, 2).build())
.addDelivery(Location.newInstance(20, 00), SizeDimension.Builder.newInstance().addDimension(0, 3).build())
@ -59,7 +59,7 @@ public class PlotterTest {
VehicleType type = VehicleTypeImpl.Builder.newInstance("type").addCapacityDimension(0, 3).build();
Vehicle vehicle = VehicleImpl.Builder.newInstance("vehicle").setStartLocation(Location.newInstance(0, 0))
.setType(type).build();
CustomJob cj = CustomJob.Builder.newInstance("job")
CustomJob cj = new CustomJob.Builder("job")
.addPickup(Location.newInstance(10, 0), SizeDimension.Builder.newInstance().addDimension(0, 1).build())
.addPickup(Location.newInstance(-5, 4), SizeDimension.Builder.newInstance().addDimension(0, 2).build())
.addDelivery(Location.newInstance(20, 10), SizeDimension.Builder.newInstance().addDimension(0, 3).build())
@ -74,7 +74,7 @@ public class PlotterTest {
VehicleType type = VehicleTypeImpl.Builder.newInstance("type").addCapacityDimension(0, 3).addCapacityDimension(1, 3).build();
Vehicle vehicle = VehicleImpl.Builder.newInstance("vehicle").setStartLocation(Location.newInstance(0, 0))
.setType(type).build();
CustomJob cj = CustomJob.Builder.newInstance("job")
CustomJob cj = new CustomJob.Builder("job")
.addPickup(Location.newInstance(10, 0), SizeDimension.Builder.newInstance().addDimension(0, 1).addDimension(1, 1).build())
.addExchange(Location.newInstance(-5, 4), SizeDimension.Builder.newInstance().addDimension(0, -1).addDimension(1, 1).build())
.addDelivery(Location.newInstance(20, 10), SizeDimension.Builder.newInstance().addDimension(0, 3).build())
@ -90,7 +90,7 @@ public class PlotterTest {
VehicleType type = VehicleTypeImpl.Builder.newInstance("type").addCapacityDimension(0, 3).build();
Vehicle vehicle = VehicleImpl.Builder.newInstance("vehicle").setStartLocation(Location.newInstance(0, 0))
.setType(type).build();
Shipment shipment = Shipment.Builder.newInstance("shipment").setPickupLocation(Location.newInstance(-5, 4))
ShipmentJob shipment = new ShipmentJob.Builder("shipment").setPickupLocation(Location.newInstance(-5, 4))
.addSizeDimension(0, 2).setDeliveryLocation(Location.newInstance(20, 10)).build();
VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance().addJob(shipment).addVehicle(vehicle).build();
VehicleRoutingProblemSolution solution = Solutions.bestOf(Jsprit.createAlgorithm(vrp).searchSolutions());

View file

@ -47,6 +47,11 @@
<artifactId>commons-csv</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>hu.vissy.plain-text-table</groupId>
<artifactId>ptt-core</artifactId>
<version>${ptt.version}</version>
</dependency>
</dependencies>
</project>

View file

@ -22,10 +22,10 @@ import java.util.Collection;
import com.graphhopper.jsprit.core.algorithm.recreate.listener.InsertionStartsListener;
import com.graphhopper.jsprit.core.algorithm.recreate.listener.JobInsertedListener;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.job.Delivery;
import com.graphhopper.jsprit.core.problem.job.DeliveryJob;
import com.graphhopper.jsprit.core.problem.job.Job;
import com.graphhopper.jsprit.core.problem.job.Pickup;
import com.graphhopper.jsprit.core.problem.job.Service;
import com.graphhopper.jsprit.core.problem.job.PickupJob;
import com.graphhopper.jsprit.core.problem.job.ServiceJob;
import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute;
import com.graphhopper.jsprit.core.problem.solution.route.activity.ActivityVisitor;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
@ -84,9 +84,9 @@ class UpdateLoads implements ActivityVisitor, StateUpdater, InsertionStartsListe
SizeDimension loadAtDepot = SizeDimension.Builder.newInstance().build();
SizeDimension loadAtEnd = SizeDimension.Builder.newInstance().build();
for (Job j : route.getTourActivities().getJobs()) {
if (j instanceof Delivery) {
if (j instanceof DeliveryJob) {
loadAtDepot = loadAtDepot.add(j.getSize());
} else if (j instanceof Pickup || j instanceof Service) {
} else if (j instanceof PickupJob || j instanceof ServiceJob) {
loadAtEnd = loadAtEnd.add(j.getSize());
}
}
@ -103,14 +103,14 @@ class UpdateLoads implements ActivityVisitor, StateUpdater, InsertionStartsListe
@Override
public void informJobInserted(Job job2insert, VehicleRoute inRoute, double additionalCosts, double additionalTime) {
if (job2insert instanceof Delivery) {
if (job2insert instanceof DeliveryJob) {
SizeDimension loadAtDepot = stateManager.getRouteState(inRoute, InternalStates.LOAD_AT_BEGINNING, SizeDimension.class);
if (loadAtDepot == null) {
loadAtDepot = defaultValue;
}
stateManager.putTypedInternalRouteState(inRoute, InternalStates.LOAD_AT_BEGINNING,
loadAtDepot.add(job2insert.getSize()));
} else if (job2insert instanceof Pickup || job2insert instanceof Service) {
} else if (job2insert instanceof PickupJob || job2insert instanceof ServiceJob) {
SizeDimension loadAtEnd = stateManager.getRouteState(inRoute, InternalStates.LOAD_AT_END, SizeDimension.class);
if (loadAtEnd == null) {
loadAtEnd = defaultValue;

View file

@ -225,7 +225,7 @@ public class VehicleRoutingProblem {
if (tentativeJobs.containsKey(job.getId())) {
throw new IllegalArgumentException("vehicle routing problem already contains a service or shipment with id " + job.getId() + ". make sure you use unique ids for all services and shipments");
}
job.setIndex(jobIndexCounter);
job.impl_setIndex(jobIndexCounter);
incJobIndexCounter();
tentativeJobs.put(job.getId(), job);
addLocationToTentativeLocations(job);

View file

@ -17,8 +17,8 @@
*/
package com.graphhopper.jsprit.core.problem.constraint;
import com.graphhopper.jsprit.core.problem.job.Service;
import com.graphhopper.jsprit.core.problem.job.Shipment;
import com.graphhopper.jsprit.core.problem.job.ServiceJob;
import com.graphhopper.jsprit.core.problem.job.ShipmentJob;
import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext;
import com.graphhopper.jsprit.core.problem.solution.route.activity.*;
@ -67,11 +67,11 @@ public class ServiceDeliveriesFirstConstraint implements HardActivityConstraint
}
protected boolean isShipment(TourActivity newAct) {
return newAct instanceof JobActivity && ((JobActivity) newAct).getJob() instanceof Shipment;
return newAct instanceof JobActivity && ((JobActivity) newAct).getJob() instanceof ShipmentJob;
}
protected boolean isService(TourActivity newAct) {
return newAct instanceof JobActivity && ((JobActivity) newAct).getJob() instanceof Service;
return newAct instanceof JobActivity && ((JobActivity) newAct).getJob() instanceof ServiceJob;
}
// protected ConstraintsStatus old(TourActivity prevAct, TourActivity

View file

@ -18,59 +18,62 @@
package com.graphhopper.jsprit.core.problem.job;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.Skills;
import com.graphhopper.jsprit.core.problem.solution.route.activity.JobActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
import java.util.*;
/**
* Abstract base class for all Job implementations.
* <p>
* See {@linkplain JobBuilder} for detailed instruction how to implement your
* Job.
* </p>
* <p>
* Created by schroeder on 14.07.14.
* </p>
*
* @author schroeder
* @author balage
*
* @see JobBuilder
*/
public abstract class AbstractJob implements Job {
/**
* Base builder for all direct descendants.
* <p>
* The is an abstract implementation of the builder pattern providing the
* base functionality for inheritence. When you create a new AbstractJob
* This is an abstract implementation of the builder pattern providing the
* base functionality for inheritance. When you create a new AbstractJob
* implementation and would like to provide builder for it follow the
* guidlines below:
* guidelines below:
* </p>
* <p>
* First of all, you have to decide whether you would like to create a final
* class (no further inheritence from it) or not. If you decide to make your
* class (no further inheritance from it) or not. If you decide to make your
* implementation <code>final</code> you can make your concrete builder in
* one step, but make the Job class final to emphasize this fact.
* one step, but make the class final to emphasize this fact.
* </p>
* <p>
* If you wish to allow your Job implementation to be extended, first create
* your own abstract Builder class. The signature of your abstract builder
* should be something like this (<i>self referencing generics</i>):
* <p>
*
* <pre>
* public static abstract class BuilderBase&lt;T extends MyJob, B extends BuilderBase&lt;T, B>>
* extends JobBuilder&lt;T, B> {
* extends AbstractJob.JobBuilder&lt;T, B> {
* }
* </pre>
* <p>
* This implenetation should contain all new fields, the new setters
* This implementation should contain all new fields, the new setters
* following the pattern:
* <p>
*
* <pre>
* &#64;SuppressWarnings("unchecked")
* public B setField(FieldType field) {
@ -85,8 +88,8 @@ public abstract class AbstractJob implements Job {
* getters are provided for the fields as well.
* </p>
* <p>
* This BuilderBase class is for the new descendents to base their Builder
* on. If you don't need to refere to this class outside the descedents,
* This BuilderBase class is for the new descendants to base their Builder
* on. If you don't need to refer to this class outside the descendants,
* make it protected.
* </p>
* <p>
@ -94,8 +97,9 @@ public abstract class AbstractJob implements Job {
* complex generic pattern and makes it safe (see <a href=
* "http://stackoverflow.com/questions/7354740/is-there-a-way-to-refer-to-the-current-type-with-a-type-variable">
* the answer of this topic</a> for more information about the pitfalls of
* the self-refering generics pattern):
* the self-referring generic pattern):
* <p>
*
* <pre>
* public static class Builder extends BuilderBase&lt;MyJob, Builder> {
* public Builder(String id) {
@ -127,50 +131,42 @@ public abstract class AbstractJob implements Job {
protected int priority = 2;
protected Object userData;
public JobBuilder(String id) {
if (id == null) {
if (id == null)
throw new IllegalArgumentException("id must not be null");
}
this.id = id;
}
/**
* Adds capacity dimension.
*
* @param dimensionIndex the dimension index of the capacity value
* @param dimensionValue the capacity value
* @param dimensionIndex
* the dimension index of the capacity value
* @param dimensionValue
* the capacity value
* @return the builder
* @throws IllegalArgumentException if dimensionValue < 0
* @throws IllegalArgumentException
* if dimensionValue < 0
*/
@SuppressWarnings("unchecked")
public B addSizeDimension(int dimensionIndex, int dimensionValue) {
if (dimensionValue < 0) {
if (dimensionValue < 0)
throw new IllegalArgumentException("capacity value cannot be negative");
}
capacityBuilder.addDimension(dimensionIndex, dimensionValue);
return (B) this;
}
@SuppressWarnings("unchecked")
public B addRequiredSkill(String skill) {
skillBuilder.addSkill(skill);
return (B) this;
}
@SuppressWarnings("unchecked")
public B setName(String name) {
this.name = name;
return (B) this;
}
@SuppressWarnings("unchecked")
public B addAllRequiredSkills(Skills skills) {
for (String s : skills.values()) {
skillBuilder.addSkill(s);
}
return (B) this;
}
/**
* Clones a size dimension structures by adding all dimensions to the
* job.
*
* @param size
* The size dimensions to clone.
* @return the builder
*/
@SuppressWarnings("unchecked")
public B addAllSizeDimensions(SizeDimension size) {
for (int i = 0; i < size.getNuOfDimensions(); i++) {
@ -180,20 +176,80 @@ public abstract class AbstractJob implements Job {
}
/**
* Set priority to service. Only 1 = high priority, 2 = medium and 3 =
* low are allowed.
* Adds a user data object to the job.
*
* <p>
* Default is 2 = medium.
* This object can be any valid Java object and is a black box for the
* API. With the user object, the job van be decorated and associated
* with any custom information. This information is available anywhere
* the job is available (most probably in constraints).
* </p>
*
* @param userData
* The data to associate.
* @return the builder
*/
@SuppressWarnings("unchecked")
protected B addUserData(Object userData) {
this.userData = userData;
return (B) this;
}
/**
* Adds a required skill to the job.
*
* @param skill
* The skill to add.
* @return the builder
*/
@SuppressWarnings("unchecked")
public B addRequiredSkill(String skill) {
skillBuilder.addSkill(skill);
return (B) this;
}
/**
* Clones all skills and adds them to the job.
*
* @param skills
* The skill set to clone.
* @return the builder
*/
@SuppressWarnings("unchecked")
public B addAllRequiredSkills(Skills skills) {
for (String s : skills.values()) {
skillBuilder.addSkill(s);
}
return (B) this;
}
/**
* Sets the name of the job.
*
* @param name
* The name of the job.
* @return the builder
*/
@SuppressWarnings("unchecked")
public B setName(String name) {
this.name = name;
return (B) this;
}
/**
* Set priority to service. Only 1 (very high) to 10 (very low) are
* allowed.
* <p>
* Default is 2.
*
* @param priority
* @return builder
*/
@SuppressWarnings("unchecked")
public B setPriority(int priority) {
if (priority < 1 || priority > 3) {
if (priority < 1 || priority > 10)
throw new IllegalArgumentException(
"incorrect priority. only 1 = high, 2 = medium and 3 = low is allowed");
}
"incorrect priority. only priority values from 1 to 10 are allowed where 1 = high and 10 is low");
this.priority = priority;
return (B) this;
}
@ -201,15 +257,13 @@ public abstract class AbstractJob implements Job {
/**
* Builds the job.
* <p>
* <p>
* You never has to override this method. Override the
* <b> You never has to override this method. Override the
* {@linkplain #validate()} and {@linkplain #createInstance()} methods
* instead. (See for detailed implementation guidlines at
* {@linkplain JobBuilder}!)
* instead. (See for detailed implementation guidelines at
* {@linkplain JobBuilder}!) </b>
* </p>
*
* @return {@link T} The new implementation of the corresponding Job.
* @author balage
* @see JobBuilder
*/
public final T build() {
@ -219,30 +273,66 @@ public abstract class AbstractJob implements Job {
return job;
}
/**
* Validates the settings. The implementation should throw exception
* when the values are inconsistent.
*/
protected abstract void validate();
/**
* Creates a new job instance.
* <p>
* This method is rarely overridden in the abstract base
* implementations, but in the concrete Builder classes. (See for
* detailed implementation guidelines at {@linkplain JobBuilder}!)
* </p>
*
* @return The new job instance.
*/
protected abstract T createInstance();
/**
* @return The constructed size dimension object.
*/
public SizeDimension getCapacity() {
return capacityBuilder.build();
}
/**
* @return The required skill set.
*/
public Skills getSkills() {
return skillBuilder.build();
}
/**
* @return The unique id of the job.
*/
public String getId() {
return id;
}
/**
* @return The (optional) name of the task.
*/
public String getName() {
return name;
}
/**
* @return The priority value of the task.
*/
public int getPriority() {
return priority;
}
/**
* @return The asssociated user data object.
*/
public Object getUserData() {
return userData;
}
}
private int index;
@ -255,31 +345,39 @@ public abstract class AbstractJob implements Job {
private int priority;
protected List<Location> allLocations;
private List<Location> allLocations;
private JobActivityList activityList;
protected Set<TimeWindow> allTimeWindows;
private Set<TimeWindow> allTimeWindows;
private SizeDimension sizeAtStart;
private SizeDimension sizeAtEnd;
private Object userData;
/**
* Builder based constructor.
*
* @param builder The builder instance.
* @param builder
* The builder instance.
* @see JobBuilder
*/
protected AbstractJob(JobBuilder<?, ?> builder) {
super();
activityList = new SequentialJobActivityList(this);
id = builder.getId();
skills = builder.getSkills();
name = builder.getName();
priority = builder.getPriority();
userData = builder.getUserData();
}
/**
* This package local constructor is for legacy job implementations.
*/
@Deprecated
AbstractJob() {
}
@Override
@ -287,10 +385,25 @@ public abstract class AbstractJob implements Job {
return index;
}
public void setIndex(int index) {
/**
* Sets the index of the job within the problem.
* <p>
* <b>This method isn't part of the public API and should not be called!</b>
* </p>
*
* @param index
* The index.
*/
public void impl_setIndex(int index) {
this.index = index;
}
/**
* @return User-specific domain data associated with the job
*/
public Object getUserData() {
return userData;
}
private void addLocation(Location location) {
if (location != null) {
@ -303,6 +416,17 @@ public abstract class AbstractJob implements Job {
return allLocations;
}
/**
* This method prepares the caches, such as collected location and time
* window collections, and calculates the size at start and at the end.
*
* <p>
* Most of the time, you won't need to call this function directly, because
* it is called when the activities are created. However, you may override
* this method if you have your own caches to initialize in your Job
* implementation, but don't forget to call the super method.
* </p>
*/
protected void prepareCaches() {
allLocations = new ArrayList<>();
allTimeWindows = new LinkedHashSet<>();
@ -319,8 +443,10 @@ public abstract class AbstractJob implements Job {
for (JobActivity act : activityList.getAll()) {
size = size.add(act.getLoadChange());
}
if (start) return size.getNegativeDimensions().abs();
else return size.getPositiveDimensions();
if (start)
return size.getNegativeDimensions().abs();
else
return size.getPositiveDimensions();
}
private void addTimeWindows(Collection<TimeWindow> timeWindows) {
@ -329,10 +455,16 @@ public abstract class AbstractJob implements Job {
}
}
/**
* @return The size dimension at the start.
*/
public SizeDimension getSizeAtStart() {
return sizeAtStart;
}
/**
* @return The size dimension at the end.
*/
public SizeDimension getSizeAtEnd() {
return sizeAtEnd;
}
@ -343,10 +475,10 @@ public abstract class AbstractJob implements Job {
* <p>
* This functions contract specifies that the implementation has to call
* {@linkplain #prepareCaches()} function at the end, after all activities
* are added.
* are added or call the {@linkplain #setActivities(JobActivityList)} method
* which calls the above method implicitlely.
* </p>
*/
// protected abstract void createActivities();
protected abstract void createActivities(JobBuilder<? extends AbstractJob, ?> jobBuilder);
@Override
@ -358,32 +490,37 @@ public abstract class AbstractJob implements Job {
}
/**
* Two shipments are equal if they have the same id.
* Two jobs are equal if they have the same id.
*
* @return true if shipments are equal (have the same id)
* @return true if the jobs are equal (have the same id)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
if (this == obj)
return true;
}
if (obj == null) {
if (obj == null)
return false;
}
if (getClass() != obj.getClass()) {
if (getClass() != obj.getClass())
return false;
}
AbstractJob other = (AbstractJob) obj;
if (id == null) {
if (other.id != null) {
if (other.id != null)
return false;
}
} else if (!id.equals(other.id)) {
} else if (!id.equals(other.id))
return false;
}
return true;
}
/**
* Sets the activity list.
*
* <p>
* This method calls the {@linkplain #prepareCaches()} function.
* </p>
*
* @param list
* The activity list
*/
protected void setActivities(JobActivityList list) {
activityList = list;
prepareCaches();
@ -394,9 +531,8 @@ public abstract class AbstractJob implements Job {
return activityList;
}
@Override
public Set<TimeWindow> getTimeWindows() {
public Collection<TimeWindow> getTimeWindows() {
return allTimeWindows;
}
@ -420,6 +556,4 @@ public abstract class AbstractJob implements Job {
return priority;
}
}

View file

@ -34,7 +34,7 @@ public abstract class AbstractListBackedJobActivityList extends JobActivityList
validateActivity(activity);
if (!_activities.contains(activity)) {
_activities.add(activity);
activity.setOrderNumber(_activities.size());
activity.impl_setOrderNumber(_activities.size());
}
}

View file

@ -29,21 +29,38 @@ import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowsIm
/**
* Service implementation of a job.
* <p>
* Note that two services are equal if they have the same id.
* </p>
* <h3>Warning!</h3>
* <p>
* <p>Note that two services are equal if they have the same id.
* This class and all its subclasses ({@linkplain ServiceJob},
* {@linkplain PickupJob}, {@linkplain DeliveryJob} and
* {@linkplain ShipmentJob}) are here for convenience. Most of the time using
* them is unnecessary and using the {@linkplain CustomJob} is a better choice.
* </p>
* <p>
* For most of the use cases, the {@linkplain CustomJob} offers sufficient
* functionality, so before you decide to implement your own Job implementation
* think over if you really need one. If after the above considerations, you
* still choose to make your own implementation, it is strongly recommended to
* base your implementation on {@linkplain AbstractJob} instead of this class.
* It offers little and this class and all its subclasses may most likely be
* deprecated and be removed in the future.
* </p>
*
* @author schroeder
* @author Balage
*/
public abstract class AbstractSingleActivityJob<A extends JobActivity> extends AbstractJob {
/**
* Builder that builds a service.
* Builder base that builds a job with a single activity.
*
* @author schroeder
* @author Balage
*/
public static abstract class BuilderBase<T extends AbstractSingleActivityJob<?>, B extends BuilderBase<T, B>>
extends JobBuilder<T, B> {
protected String type = "service";
protected double serviceTime;
@ -52,6 +69,12 @@ public abstract class AbstractSingleActivityJob<A extends JobActivity> extends A
protected TimeWindowsImpl timeWindows;
/**
* The constructor of the builder.
*
* @param id
* The unique id of the job.
*/
public BuilderBase(String id) {
super(id);
this.id = id;
@ -61,9 +84,12 @@ public abstract class AbstractSingleActivityJob<A extends JobActivity> extends A
/**
* Protected method to set the type-name of the service.
* <p>
* <p>Currently there are {@link AbstractSingleActivityJob}, {@link Pickup} and {@link Delivery}.
* <p>
* Currently there are {@link ServiceJob}, {@link PickupJob} and
* {@link DeliveryJob}.
*
* @param name the name of service
* @param name
* the name of service
* @return the builder
*/
@SuppressWarnings("unchecked")
@ -96,9 +122,8 @@ public abstract class AbstractSingleActivityJob<A extends JobActivity> extends A
*/
@SuppressWarnings("unchecked")
public B setServiceTime(double serviceTime) {
if (serviceTime < 0) {
if (serviceTime < 0)
throw new IllegalArgumentException("serviceTime must be greater than or equal to zero");
}
this.serviceTime = serviceTime;
return (B) this;
}
@ -114,42 +139,88 @@ public abstract class AbstractSingleActivityJob<A extends JobActivity> extends A
@Override
@SuppressWarnings("unchecked")
public B addSizeDimension(int dimensionIndex, int dimensionValue) {
if (dimensionValue < 0) {
if (dimensionValue < 0)
throw new IllegalArgumentException("capacity value cannot be negative");
}
capacityBuilder.addDimension(dimensionIndex, dimensionValue);
return (B) this;
}
/**
* Sets a single time window.
* <p>
* This method clears any previously set time windows. Use
* {@linkplain #addTimeWindow(TimeWindow)} to add an additional one,
* instead of replacing the already set ones.
* </p>
*
* @param tw
* The time window to set.
* @return the builder
* @throws IllegalArgumentException
* If the time window is null.
*/
@SuppressWarnings("unchecked")
public B setTimeWindow(TimeWindow tw) {
if (tw == null) {
if (tw == null)
throw new IllegalArgumentException("time-window arg must not be null");
}
timeWindows = new TimeWindowsImpl();
timeWindows.add(tw);
return (B) this;
}
/**
* adds a single time window
* <p>
* This method adds the time window to the previously set time windows.
* </p>
*
* @param timeWindow
* The time window to set.
* @return the builder
* @throws IllegalArgumentException
* If the time window is null.
*/
@SuppressWarnings("unchecked")
public B addTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null) {
if (timeWindow == null)
throw new IllegalArgumentException("time-window arg must not be null");
}
timeWindows.add(timeWindow);
return (B) this;
}
/**
* Clones the time windows from an existing time window set.
*
* @param timeWindows
* The time window set.
* @return the build
*/
public B addTimeWindows(TimeWindows timeWindows) {
return addTimeWindows(timeWindows.getTimeWindows());
}
/**
* Adds a collection of time windows.
*
* @param timeWindows
* The time windows to add.
* @return the build
*/
@SuppressWarnings("unchecked")
public B addTimeWindows(Collection<TimeWindow> timeWindows) {
timeWindows.forEach(t -> addTimeWindow(t));
return (B) this;
}
/**
* Constructs and adds a time window.
*
* @param earliest
* The earliest start.
* @param latest
* The latest start.
* @return the builder
*/
public B addTimeWindow(double earliest, double latest) {
return addTimeWindow(TimeWindow.newInstance(earliest, latest));
}
@ -179,26 +250,37 @@ public abstract class AbstractSingleActivityJob<A extends JobActivity> extends A
@Override
protected void validate() {
if (location == null) {
if (location == null)
throw new IllegalArgumentException("location is missing");
}
if (timeWindows.isEmpty()) {
timeWindows.add(TimeWindow.ETERNITY);
}
}
/**
* @return The type code.
*/
public String getType() {
return type;
}
/**
* @return The operation time.
*/
public double getServiceTime() {
return serviceTime;
}
/**
* @return The location of the job.
*/
public Location getLocation() {
return location;
}
/**
* @return The time window set.
*/
public TimeWindowsImpl getTimeWindows() {
return timeWindows;
}
@ -211,10 +293,27 @@ public abstract class AbstractSingleActivityJob<A extends JobActivity> extends A
type = builder.type;
}
protected abstract A createActivity(
BuilderBase<? extends AbstractSingleActivityJob<?>, ?> builder);
/**
* Creates the single activity.
*
* @param builder
* the builder object
* @return The created activity.
*/
protected abstract A createActivity(BuilderBase<? extends AbstractSingleActivityJob<?>, ?> builder);
/**
* Creates the activity.
* <p>
* It creates an activity list, adds the single activity produces by
* {@linkplain #createActivity(BuilderBase)} and sets the activity list on
* the job.
* </p>
*
* @param builder
* the builder object.
*/
@Override
protected final void createActivities(JobBuilder<?, ?> builder) {
@SuppressWarnings("unchecked")
@ -224,49 +323,14 @@ public abstract class AbstractSingleActivityJob<A extends JobActivity> extends A
setActivities(list);
}
/**
* @return The single activity assigned to the job.
*/
@SuppressWarnings("unchecked")
public A getActivity() {
return (A) getActivityList().getAll().get(0);
}
// /**
// * Returns location.
// *
// * @return location
// */
// @Deprecated
// public Location getLocation() {
// return getActivity().getLocation();
// }
//
//
// /**
// * Returns the service-time/duration a service takes at service-location.
// *
// * @return service duration
// */
// @Deprecated
// public double getServiceDuration() {
// return getActivity().getOperationTime();
// }
//
// /**
// * Returns the time-window a service(-operation) is allowed to start.
// * It is recommended to use getTimeWindows() instead. If you still use
// this, it returns the first time window of getTimeWindows() collection.
// *
// * @return time window
// */
// @Deprecated
// public TimeWindow getTimeWindow() {
// return getTimeWindows().iterator().next();
// }
//
// @Deprecated
// public Collection<TimeWindow> getServiceTimeWindows() {
// return getActivity().getTimeWindows();
// }
/**
* @return the name
*/
@ -282,7 +346,7 @@ public abstract class AbstractSingleActivityJob<A extends JobActivity> extends A
@Override
public String toString() {
return "[id=" + getId() + "][name=" + getName() + "][type=" + type + "][activity="
+ getActivity() + "]";
+ getActivity() + "]";
}
@ -293,6 +357,6 @@ public abstract class AbstractSingleActivityJob<A extends JobActivity> extends A
}
public abstract <X extends BuilderBase<AbstractSingleActivityJob<? extends A>, ? extends BuilderBase<AbstractSingleActivityJob<? extends A>, ?>>> X getBuilder(
String id);
String id);
}

View file

@ -28,7 +28,7 @@ import com.graphhopper.jsprit.core.problem.solution.route.activity.BreakActivity
*/
public class Break extends AbstractSingleActivityJob<BreakActivity> implements InternalJobMarker {
public static final class Builder extends Service.BuilderBase<Break, Builder> {
public static final class Builder extends ServiceJob.BuilderBase<Break, Builder> {
private static final Location VARIABLE_LOCATION = Location
.newInstance("@@@VARIABLE_LOCATION");

View file

@ -35,6 +35,18 @@ import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindows;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowsImpl;
/**
* This is a general purpose, highly configurable job.
*
* <p>
* This job offers enough flexibility for most of the problems. It could hold
* any number of sequential activities. With the <code>userData</code> field,
* any associated business data can be linked to the job.
* </p>
*
* <p>
* For details see its {@linkplain Builder}.
* </p>
*
* Created by schroeder on 16/11/16.
*
* @author schroeder
@ -42,45 +54,101 @@ import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowsIm
*/
public class CustomJob extends AbstractJob {
public static abstract class BuilderBase<T extends CustomJob, B extends CustomJob.BuilderBase<T, B>>
/**
* Protected base builder class for {@linkplain CustomJob}.
* <p>
* The class is the protected part of the inheritable builder pattern. For
* more information, see {@linkplain AbstractJob.JobBuilder}.
* </p>
*
* @author Balage
*
* @param <T>
* The type of the job it creates.
* @param <B>
* Self-refering generic value.
*/
protected static abstract class BuilderBase<T extends CustomJob, B extends CustomJob.BuilderBase<T, B>>
extends JobBuilder<T, B> {
/**
* The possible activity types.
*
* <p>
* Note, that the set of activity types are final.
* </p>
*
* @author Balage
*
*/
public enum ActivityType {
/**
* Service activity type.
* <p>
* The service activity type represents an activity with no cargo
* change (nothing is loaded or unloaded).
* </p>
*/
SERVICE {
@Override
public JobActivity create(CustomJob job, BuilderActivityInfo info) {
protected JobActivity create(CustomJob job, BuilderActivityInfo info) {
return new ServiceActivity(job, info.getName() == null ? name().toLowerCase() : info.getName(),
info.getLocation(), info.getOperationTime(), info.getSize(), prepareTimeWindows(info));
info.getLocation(), info.getOperationTime(), info.getSize(), prepareTimeWindows(info));
}
},
/**
* Pickup activity type.
* <p>
* The pickup activity type represents an activity where something
* is picked up (loaded). It has a positive impact on the cargo
* size.
* </p>
*/
PICKUP {
@Override
public JobActivity create(CustomJob job, BuilderActivityInfo info) {
protected JobActivity create(CustomJob job, BuilderActivityInfo info) {
return new PickupActivity(job, info.getName() == null ? name().toLowerCase() : info.getName(),
info.getLocation(), info.getOperationTime(), info.getSize(), prepareTimeWindows(info));
info.getLocation(), info.getOperationTime(), info.getSize(), prepareTimeWindows(info));
}
},
/**
* Delivery activity type.
* <p>
* The delivery activity type represents an activity where something
* is delivered (unloaded). It has a negative impact on the cargo
* size.
* </p>
*/
DELIVERY {
@Override
public JobActivity create(CustomJob job, BuilderActivityInfo info) {
protected JobActivity create(CustomJob job, BuilderActivityInfo info) {
return new DeliveryActivity(job, info.getName() == null ? name().toLowerCase() : info.getName(),
info.getLocation(), info.getOperationTime(), info.getSize(), prepareTimeWindows(info));
info.getLocation(), info.getOperationTime(), info.getSize(), prepareTimeWindows(info));
}
},
/**
* Exchange activity type.
* <p>
* The exchange activity type represents an activity where something
* is delivered and something else is picked up at the same time.
* (loaded and unloaded). It has a mixed (may be even zero) impact
* on the cargo size. It may increase one dimension and reduce
* another one.
* </p>
*/
EXCHANGE {
@Override
public JobActivity create(CustomJob job, BuilderActivityInfo info) {
protected JobActivity create(CustomJob job, BuilderActivityInfo info) {
return new ExchangeActivity(job, info.getName() == null ? name().toLowerCase() : info.getName(),
info.getLocation(), info.getOperationTime(), info.getSize(), prepareTimeWindows(info));
info.getLocation(), info.getOperationTime(), info.getSize(), prepareTimeWindows(info));
}
};
public abstract JobActivity create(CustomJob job, BuilderActivityInfo builderActivityInfo);
protected abstract JobActivity create(CustomJob job, BuilderActivityInfo builderActivityInfo);
private static Collection<TimeWindow> prepareTimeWindows(BuilderActivityInfo info) {
TimeWindows tws = info.getTimeWindows();
@ -92,6 +160,18 @@ public class CustomJob extends AbstractJob {
}
/**
* Class for defining custom activities when the standard methods of
* {@linkplain Builder} are not enough. The class applies the fluent API
* pattern.
* <p>
* Note that this class is <b>NOT</b> immutable, so always create a new
* instance for each activity!
* </p>
*
* @author Balage
*
*/
public static class BuilderActivityInfo {
private ActivityType type;
private Location locs;
@ -101,81 +181,179 @@ public class CustomJob extends AbstractJob {
private TimeWindowsImpl timeWindows = new TimeWindowsImpl();
/**
* Constructs a new instance.
*
* @param type
* The type of the activity.
* @param locs
* The location of the activity.
*/
public BuilderActivityInfo(ActivityType type, Location locs) {
super();
this.type = type;
this.locs = locs;
}
/**
* @return The type of the activity.
*/
public ActivityType getType() {
return type;
}
/**
* @return The location of the activity.
*/
public Location getLocation() {
return locs;
}
/**
* @return The size dimensions (cargo change) of the activity.
*/
public SizeDimension getSize() {
return size;
}
/**
* Sets the size dimensions (cargo change) of the activity.
*
* @param size
* The size dimensions. (May be negative.)
* @return The info object.
*/
public BuilderActivityInfo withSize(SizeDimension size) {
this.size = size;
return this;
}
/**
* @return The name of the activity (for debug and reporting).
*/
public String getName() {
return name;
}
/**
* Sets the name of the activity for debugging and reporting
* purpose.
*
* @param name
* The name.
* @return The info object.
*/
public BuilderActivityInfo withName(String name) {
this.name = name;
return this;
}
/**
* @return The time windows of the activity.
*/
public TimeWindows getTimeWindows() {
return timeWindows;
}
/**
* Adds a time window to the activity.
*
* @param timeWindow
* A time window.
* @return The info object.
*/
public BuilderActivityInfo withTimeWindow(TimeWindow timeWindow) {
timeWindows.add(timeWindow);
return this;
}
public BuilderActivityInfo withTimeWindows(TimeWindow... tws) {
timeWindows.addAll(tws);
/**
* Adds several time windows to the activity.
*
* @param timeWindows
* The list of time windows.
* @return The info object.
*/
public BuilderActivityInfo withTimeWindows(TimeWindow... timeWindows) {
this.timeWindows.addAll(timeWindows);
return this;
}
/**
* Adds several time windows.
*
* @param tws
* The collection of time windows.
* @return The info object.
*/
public BuilderActivityInfo withTimeWindows(Collection<TimeWindow> tws) {
timeWindows.addAll(tws);
return this;
}
/**
* @return The operation time (time taken to fulfill the activity at
* the location) of the activity.
*/
public double getOperationTime() {
return operationTime;
}
/**
* Sets the operation time (time taken to fulfill the activity at
* the location).
*
* @param operationTime
* The operation time.
* @return The info object.
*/
public BuilderActivityInfo withOperationTime(double operationTime) {
this.operationTime = operationTime;
return this;
}
}
List<BuilderActivityInfo> acts = new ArrayList<>();
private List<BuilderActivityInfo> acts = new ArrayList<>();
/**
* Constructor.
*
* @param id
* The id of the job. Should be unique within the problem.
*/
public BuilderBase(String id) {
super(id);
}
public BuilderBase<T, B> addActivity(BuilderActivityInfo act) {
@SuppressWarnings("unchecked")
public B addActivity(BuilderActivityInfo act) {
acts.add(act);
return this;
return (B) this;
}
/**
* General activity add method.
* <p>
* It constructs a {@linkplain BuilderActivityInfo} objects and calls
* the {@linkplain #addActivity(BuilderActivityInfo)} function.
* </p>
*
* @param type
* The type of the activity.
* @param location
* The location of the activity.
* @param operationTime
* The operation time of the activity.
* @param size
* The cargo change of the activity. May be null.
* @param name
* The name of the activity. May be null.
* @param timeWindows
* The time windows of the activity. May be null.
*/
private void add(ActivityType type, Location location, double operationTime, SizeDimension size, String name,
Collection<TimeWindow> tws) {
Collection<TimeWindow> timeWindows) {
BuilderActivityInfo builderActivityInfo = new BuilderActivityInfo(type, location);
builderActivityInfo.withOperationTime(operationTime);
if (name != null) {
@ -184,8 +362,8 @@ public class CustomJob extends AbstractJob {
if (size != null) {
builderActivityInfo.withSize(size);
}
if (tws != null) {
builderActivityInfo.withTimeWindows(tws);
if (timeWindows != null) {
builderActivityInfo.withTimeWindows(timeWindows);
}
acts.add(builderActivityInfo);
@ -193,116 +371,341 @@ public class CustomJob extends AbstractJob {
// Service
public CustomJob.BuilderBase<T, B> addService(Location location) {
/**
* Adds a {@linkplain ActivityType#SERVICE} activity to the job with 0
* operation time, without time windows and name.
*
* @param location
* The location of the activity.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addService(Location location) {
add(ActivityType.SERVICE, location, 0d, null, null, null);
return this;
return (B) this;
}
public CustomJob.BuilderBase<T, B> addService(Location location, SizeDimension size) {
add(ActivityType.SERVICE, location, 0d, size, null, null);
return this;
/**
* Adds a {@linkplain ActivityType#SERVICE} activity to the job without
* time windows and name.
*
* @param location
* The location of the activity.
* @param operationTime
* The operation time of the activity.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addService(Location location, double operationTime) {
add(ActivityType.SERVICE, location, operationTime, null, null, null);
return (B) this;
}
public CustomJob.BuilderBase<T, B> addService(Location location, SizeDimension size, double operationTime) {
add(ActivityType.SERVICE, location, operationTime, size, null, null);
return this;
}
public CustomJob.BuilderBase<T, B> addService(Location location, SizeDimension size, double operationTime,
TimeWindow tw) {
add(ActivityType.SERVICE, location, operationTime, size, null, Collections.singleton(tw));
return this;
/**
* Adds a {@linkplain ActivityType#SERVICE} activity to the job without
* name and with a single time window.
*
* @param location
* The location of the activity.
* @param operationTime
* The operation time of the activity.
* @param timeWindow
* The time window of the activity.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addService(Location location, double operationTime, TimeWindow timeWindow) {
add(ActivityType.SERVICE, location, operationTime, null, null, Collections.singleton(timeWindow));
return (B) this;
}
// Pickup
public CustomJob.BuilderBase<T, B> addPickup(Location location) {
/**
* Adds a {@linkplain ActivityType#PICKUP} activity to the job with 0
* operation time, without cargo change, time windows and name.
*
* @param location
* The location of the activity.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addPickup(Location location) {
add(ActivityType.PICKUP, location, 0d, null, null, null);
return this;
return (B) this;
}
public CustomJob.BuilderBase<T, B> addPickup(Location location, SizeDimension size) {
/**
* Adds a {@linkplain ActivityType#PICKUP} activity to the job with 0
* operation time, without time windows and name.
*
* @param location
* The location of the activity.
* @param size
* The cargo change of the pickup. Should be positive.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addPickup(Location location, SizeDimension size) {
add(ActivityType.PICKUP, location, 0d, size, null, null);
return this;
return (B) this;
}
public CustomJob.BuilderBase<T, B> addPickup(Location location, SizeDimension size, double operationTime) {
/**
* Adds a {@linkplain ActivityType#PICKUP} activity to the job without
* time windows and name.
*
* @param location
* The location of the activity.
* @param size
* The cargo change of the pickup. Should be positive.
* @param operationTime
* The operation time of the activity.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addPickup(Location location, SizeDimension size, double operationTime) {
add(ActivityType.PICKUP, location, operationTime, size, null, null);
return this;
return (B) this;
}
public CustomJob.BuilderBase<T, B> addPickup(Location location, SizeDimension size, double operationTime,
TimeWindow tw) {
add(ActivityType.PICKUP, location, operationTime, size, null, Collections.singleton(tw));
return this;
/**
* Adds a {@linkplain ActivityType#PICKUP} activity to the job without
* name and with a single time window.
*
* @param location
* The location of the activity.
* @param size
* The cargo change of the pickup. Should be positive.
* @param operationTime
* The operation time of the activity.
* @param timeWindow
* The time window of the activity.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addPickup(Location location, SizeDimension size, double operationTime,
TimeWindow timeWindow) {
add(ActivityType.PICKUP, location, operationTime, size, null, Collections.singleton(timeWindow));
return (B) this;
}
// Delivery
public CustomJob.BuilderBase<T, B> addDelivery(Location location) {
/**
* Adds a {@linkplain ActivityType#DELIVERY} activity to the job with 0
* operation time, without cargo change, time windows and name.
*
* @param location
* The location of the activity.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addDelivery(Location location) {
add(ActivityType.DELIVERY, location, 0d, null, null, null);
return this;
return (B) this;
}
public CustomJob.BuilderBase<T, B> addDelivery(Location location, SizeDimension size) {
/**
* Adds a {@linkplain ActivityType#DELIVERY} activity to the job with 0
* operation time, without time windows and name.
*
* @param location
* The location of the activity.
* @param size
* The cargo change of the delivery. Should be negative.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addDelivery(Location location, SizeDimension size) {
add(ActivityType.DELIVERY, location, 0d, size, null, null);
return this;
return (B) this;
}
public CustomJob.BuilderBase<T, B> addDelivery(Location location, SizeDimension size, double operationTime) {
/**
* Adds a {@linkplain ActivityType#DELIVERY} activity to the job without
* time windows and name.
*
* @param location
* The location of the activity.
* @param size
* The cargo change of the delivery. Should be negative.
* @param operationTime
* The operation time of the activity.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addDelivery(Location location, SizeDimension size, double operationTime) {
add(ActivityType.DELIVERY, location, operationTime, size, null, null);
return this;
return (B) this;
}
public CustomJob.BuilderBase<T, B> addDelivery(Location location, SizeDimension size, double operationTime,
TimeWindow tw) {
/**
* Adds a {@linkplain ActivityType#DELIVERY} activity to the job without
* name and with a single time window.
*
* @param location
* The location of the activity.
* @param size
* The cargo change of the delivery. Should be negative.
* @param operationTime
* The operation time of the activity.
* @param timeWindow
* The time window of the activity.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addDelivery(Location location, SizeDimension size, double operationTime,
TimeWindow tw) {
add(ActivityType.DELIVERY, location, operationTime, size, null, Collections.singleton(tw));
return this;
return (B) this;
}
// Exchange
public CustomJob.BuilderBase<T, B> addExchange(Location location) {
/**
* Adds a {@linkplain ActivityType#EXCHANGE} activity to the job with 0
* operation time, without cargo change, time windows and name.
*
* @param location
* The location of the activity.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addExchange(Location location) {
add(ActivityType.EXCHANGE, location, 0d, null, null, null);
return this;
return (B) this;
}
public CustomJob.BuilderBase<T, B> addExchange(Location location, SizeDimension size) {
/**
* Adds a {@linkplain ActivityType#EXCHANGE} activity to the job with 0
* operation time, without time windows and name.
*
* @param location
* The location of the activity.
* @param size
* The cargo change of the exchange. May be negative,
* positive or mixed.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addExchange(Location location, SizeDimension size) {
add(ActivityType.EXCHANGE, location, 0d, size, null, null);
return this;
return (B) this;
}
public CustomJob.BuilderBase<T, B> addExchange(Location location, SizeDimension size, double operationTime) {
/**
* Adds a {@linkplain ActivityType#EXCHANGE} activity to the job without
* time windows and name.
*
* @param location
* The location of the activity.
* @param size
* The cargo change of the exchange. May be negative,
* positive or mixed.
* @param operationTime
* The operation time of the activity.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addExchange(Location location, SizeDimension size, double operationTime) {
add(ActivityType.EXCHANGE, location, operationTime, size, null, null);
return this;
return (B) this;
}
public CustomJob.BuilderBase<T, B> addExchange(Location location, SizeDimension size, double operationTime,
TimeWindow tw) {
/**
* Adds a {@linkplain ActivityType#EXCHANGE} activity to the job without
* name and with a single time window.
*
* @param location
* The location of the activity.
* @param size
* The cargo change of the exchange. May be negative,
* positive or mixed.
* @param operationTime
* The operation time of the activity.
* @param timeWindow
* The time window of the activity.
* @return The builder instance.
*/
@SuppressWarnings("unchecked")
public B addExchange(Location location, SizeDimension size, double operationTime,
TimeWindow tw) {
add(ActivityType.EXCHANGE, location, operationTime, size, null, Collections.singleton(tw));
return this;
return (B) this;
}
@Override
protected void validate() {
if (acts.isEmpty()) {
if (acts.isEmpty())
throw new IllegalStateException("There is no activities defined on this job.");
}
}
public List<BuilderActivityInfo> getActs() {
public List<BuilderActivityInfo> getActivities() {
return Collections.unmodifiableList(acts);
}
}
/**
* This is the builder of the {@linkplain CustomJob}.
*
* <p>
* A CustomJob is a job with any number of activities of any type. These
* activities will be executed by the same vehicle and on the same route.
* They will keep they order and either all of them or none of them will be
* included into the solution.
* </p>
* <p>
* The main difference between the jobs and activities known from version 1
* and 2 is the bias shift from job to activity. Before version 2 the jobs
* has holden most of the business information and activities were
* second-class entities, meanwhile the algorithm worked on activities. This
* has driven to a state where the code had no indication which job field
* belonged to which activities.
* </p>
* <p>
* In the new concept a stronger encapsulation ensures the right behavior.
* This led to most of the business data to move from job to activity. These
* are:
* <ul>
* <li>Load change (how the cargo size change (increase or decrease) on the
* vehicle)</li>
* <li>Location (where the activity should be executed)</li>
* <li>Time windows (when the activity should be performed)</li>
* <li>Operation time (how much time it takes to fulfill the activity)</li>
* </ul>
* These parameters are now defined per activity.
* </p>
* <p>
* Some information has left in the scope of the job, because they affects
* the whole job:
* <ul>
* <li>Required skills</li>
* <li>Priority</li>
* </ul>
* </p>
* <p>
* The builder contains methods for simply configuring basic activities.
* They are the counterparts of the version 1 job builders. If more control
* is needed on the activity creation, an {@linkplain BuilderActivityInfo}
* record has to be created and passed to the builder. (<i>This indirection
* is required to keep immutable behavior of a job and its activities after
* creation.</i>)
* </p>
*
* @author Balage
*
*/
public static final class Builder extends CustomJob.BuilderBase<CustomJob, CustomJob.Builder> {
public static CustomJob.Builder newInstance(String id) {
return new CustomJob.Builder(id);
}
/**
* Constructor.
*
* @param id
* The id of the job. Should be unique within a problem.
*/
public Builder(String id) {
super(id);
}
@ -334,7 +737,7 @@ public class CustomJob extends AbstractJob {
protected void createActivities(JobBuilder<? extends AbstractJob, ?> jobBuilder) {
CustomJob.Builder builder = (CustomJob.Builder) jobBuilder;
JobActivityList list = new SequentialJobActivityList(this);
for (CustomJob.Builder.BuilderActivityInfo info : builder.getActs()) {
for (CustomJob.Builder.BuilderActivityInfo info : builder.getActivities()) {
JobActivity act = info.getType().create(this, info);
list.addActivity(act);
}

View file

@ -17,57 +17,52 @@
*/
package com.graphhopper.jsprit.core.problem.job;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.solution.route.activity.DeliveryActivity;
/**
* Delivery extends Service and is intended to model a Service where smth is UNLOADED (i.e. delivered) from a transport unit.
*
* @author schroeder
*/
public class Delivery extends AbstractSingleActivityJob<DeliveryActivity> {
public class Delivery extends Service {
public static final class Builder
extends AbstractSingleActivityJob.BuilderBase<Delivery, Builder> {
public Builder(String id) {
super(id);
setType("delivery");
}
public static class Builder extends Service.Builder<Delivery> {
/**
* Returns a new instance of builder that builds a delivery.
*
* @param id the id of the delivery
* @return the builder
*/
public static Builder newInstance(String id) {
return new Builder(id);
}
Builder(String id) {
super(id);
}
/**
* Builds Delivery.
*
* @return delivery
* @throws IllegalArgumentException if neither locationId nor coord is set
*/
@Override
protected Delivery createInstance() {
@SuppressWarnings("deprecation")
public Delivery build() {
if (location == null) throw new IllegalArgumentException("location is missing");
this.setType("delivery");
super.capacity = super.capacityBuilder.build();
super.skills = super.skillBuilder.build();
return new Delivery(this);
}
}
Delivery(BuilderBase<? extends Delivery, ?> builder) {
@SuppressWarnings("deprecation")
Delivery(Builder builder) {
super(builder);
}
@Override
protected DeliveryActivity createActivity(
BuilderBase<? extends AbstractSingleActivityJob<?>, ?> builder) {
return new DeliveryActivity(this, builder.type, builder.location,
builder.serviceTime,
builder.getCapacity().invert(), builder.timeWindows.getTimeWindows());
}
@Override
@Deprecated
public SizeDimension getSize() {
return super.getSize().abs();
}
@SuppressWarnings("unchecked")
@Override
public Builder getBuilder(String id) {
return Builder.newInstance(id);
}
}

View file

@ -0,0 +1,98 @@
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you 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.graphhopper.jsprit.core.problem.job;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.solution.route.activity.DeliveryActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
/**
* Delivery job implementation.
* <p>
* Delivery is intend to represent a kind of job where something is unloaded.
* </p>
*
* <h3>Warning!</h3>
* <p>
* This class and are here for convenience. Most of the time using the
* {@linkplain CustomJob} is a better choice. Note that this class may most
* likely be deprecated and be removed in the future.
* </p>
*
* @author schroeder
* @author Balage
*
* @see {@linkplain CustomJob.BuilderBase#addDelivery(Location)}
* @see {@linkplain CustomJob.BuilderBase#addDelivery(Location, SizeDimension)}
* @see {@linkplain CustomJob.BuilderBase#addDelivery(Location, SizeDimension, double)}
* @see {@linkplain CustomJob.BuilderBase#addDelivery(Location, SizeDimension, double, TimeWindow)}
*/
public class DeliveryJob extends AbstractSingleActivityJob<DeliveryActivity> {
/**
* Builder for {@linkplain PickupJob}.
*
* @author Balage
*/
public static final class Builder
extends AbstractSingleActivityJob.BuilderBase<DeliveryJob, Builder> {
/**
* Constructor.
*
* @param id
* The unique id.
*/
public Builder(String id) {
super(id);
setType("delivery");
}
@Override
protected DeliveryJob createInstance() {
return new DeliveryJob(this);
}
}
private DeliveryJob(BuilderBase<? extends DeliveryJob, ?> builder) {
super(builder);
}
@Override
protected DeliveryActivity createActivity(
BuilderBase<? extends AbstractSingleActivityJob<?>, ?> builder) {
return new DeliveryActivity(this, builder.type, builder.location,
builder.serviceTime,
builder.getCapacity().invert(), builder.timeWindows.getTimeWindows());
}
@Override
@Deprecated
public SizeDimension getSize() {
return super.getSize().abs();
}
@SuppressWarnings("unchecked")
@Override
public Builder getBuilder(String id) {
return new Builder(id);
}
}

View file

@ -18,8 +18,8 @@
package com.graphhopper.jsprit.core.problem.job;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import com.graphhopper.jsprit.core.problem.HasId;
import com.graphhopper.jsprit.core.problem.HasIndex;
@ -52,6 +52,9 @@ public interface Job extends HasId, HasIndex {
@Deprecated
public SizeDimension getSize();
/**
* @return Returns the required skill set.
*/
public Skills getRequiredSkills();
/**
@ -86,6 +89,6 @@ public interface Job extends HasId, HasIndex {
/**
* @return All operation time windows
*/
public Set<TimeWindow> getTimeWindows();
public Collection<TimeWindow> getTimeWindows();
}

View file

@ -17,48 +17,53 @@
*/
package com.graphhopper.jsprit.core.problem.job;
import com.graphhopper.jsprit.core.problem.solution.route.activity.PickupActivity;
/**
* Pickup extends Service and is intended to model a Service where smth is LOADED (i.e. picked up) to a transport unit.
*
* @author schroeder
*/
public class Pickup extends AbstractSingleActivityJob<PickupActivity> {
public class Pickup extends Service {
public static final class Builder
extends AbstractSingleActivityJob.BuilderBase<Pickup, Builder> {
public Builder(String id) {
super(id);
setType("pickup");
}
public static class Builder extends Service.Builder<Pickup> {
/**
* Returns a new instance of builder that builds a pickup.
*
* @param id the id of the pickup
* @return the builder
*/
public static Builder newInstance(String id) {
return new Builder(id);
}
Builder(String id) {
super(id);
}
/**
* Builds Pickup.
* <p>
* <p>Pickup type is "pickup"
*
* @return pickup
* @throws IllegalArgumentException if neither locationId nor coordinate has been set
*/
@Override
protected Pickup createInstance() {
@SuppressWarnings("deprecation")
public Pickup build() {
if (location == null) throw new IllegalArgumentException("location is missing");
this.setType("pickup");
super.capacity = super.capacityBuilder.build();
super.skills = super.skillBuilder.build();
return new Pickup(this);
}
}
@SuppressWarnings("deprecation")
Pickup(Builder builder) {
super(builder);
}
@Override
protected PickupActivity createActivity(
AbstractSingleActivityJob.BuilderBase<? extends AbstractSingleActivityJob<?>, ?> builder) {
return new PickupActivity(this, builder.type, builder.location, builder.serviceTime,
builder.getCapacity(), builder.timeWindows.getTimeWindows());
}
@SuppressWarnings("unchecked")
@Override
public Builder getBuilder(String id) {
return Builder.newInstance(id);
}
}

View file

@ -0,0 +1,89 @@
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you 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.graphhopper.jsprit.core.problem.job;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.solution.route.activity.PickupActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
/**
* Pickup job implementation.
* <p>
* Pickup is intend to represent a kind of job where something is loaded.
* </p>
*
* <h3>Warning!</h3>
* <p>
* This class and are here for convenience. Most of the time using the
* {@linkplain CustomJob} is a better choice. Note that this class may most
* likely be deprecated and be removed in the future.
* </p>
*
* @author schroeder
* @author Balage
*
* @see {@linkplain CustomJob.BuilderBase#addPickup(Location)}
* @see {@linkplain CustomJob.BuilderBase#addPickup(Location, SizeDimension)}
* @see {@linkplain CustomJob.BuilderBase#addPickup(Location, SizeDimension, double)}
* @see {@linkplain CustomJob.BuilderBase#addPickup(Location, SizeDimension, double, TimeWindow)}
*/
public class PickupJob extends AbstractSingleActivityJob<PickupActivity> {
/**
* Builder for {@linkplain PickupJob}.
*
* @author Balage
*/
public static final class Builder
extends AbstractSingleActivityJob.BuilderBase<PickupJob, Builder> {
/**
* Constructor.
*
* @param id
* The unique id.
*/
public Builder(String id) {
super(id);
setType("pickup");
}
@Override
protected PickupJob createInstance() {
return new PickupJob(this);
}
}
private PickupJob(Builder builder) {
super(builder);
}
@Override
protected PickupActivity createActivity(
AbstractSingleActivityJob.BuilderBase<? extends AbstractSingleActivityJob<?>, ?> builder) {
return new PickupActivity(this, builder.type, builder.location, builder.serviceTime,
builder.getCapacity(), builder.timeWindows.getTimeWindows());
}
@SuppressWarnings("unchecked")
@Override
public Builder getBuilder(String id) {
return new Builder(id);
}
}

View file

@ -1,243 +0,0 @@
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you 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.graphhopper.jsprit.core.problem.job;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.solution.route.activity.DeliveryActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.ExchangeActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.PickupActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowsImpl;
/**
* Shipment is an implementation of Job and consists of a backhaul and exchange
* and an delivery
*
* @author balage
*/
public final class ReturnedShipment extends Shipment {
public static final String BACKHAUL_ACTIVITY_NAME = "backhaul";
/**
* Builder that builds the shipment.
*
* @author schroeder
*/
public static final class Builder extends Shipment.BuilderBase<ReturnedShipment, Builder> {
private double backhaulServiceTime = 0.0;
private Location backhaulLocation;
protected TimeWindowsImpl backhaulTimeWindows = new TimeWindowsImpl();
protected SizeDimension.Builder backhaulCapacityBuilder = SizeDimension.Builder.newInstance();
/**
* Returns new instance of this builder.
*
* @param id the id of the shipment which must be a unique identifier
* among all jobs
* @return the builder
*/
public Builder(String id) {
super(id);
backhaulTimeWindows = new TimeWindowsImpl();
}
/**
* Sets backhaul location.
*
* @param backhaulLocation backhaul location
* @return builder
*/
public Builder setBackhaulLocation(Location backhaulLocation) {
this.backhaulLocation = backhaulLocation;
return this;
}
/**
* Sets backhaulServiceTime.
* <p>
* <p>
* ServiceTime is intended to be the time the implied activity takes at
* the backhaul-location.
*
* @param serviceTime the service time / duration the backhaul of the associated
* shipment takes
* @return builder
* @throws IllegalArgumentException if servicTime < 0.0
*/
public Builder setBackhaulServiceTime(double serviceTime) {
if (serviceTime < 0.0) {
throw new IllegalArgumentException("serviceTime must not be < 0.0");
}
backhaulServiceTime = serviceTime;
return this;
}
/**
* Sets the timeWindow for the backhaul, i.e. the time-period in which a
* backhaul operation is allowed to START.
* <p>
* <p>
* By default timeWindow is [0.0, Double.MAX_VALUE}
*
* @param timeWindow the time window within the backhaul operation/activity can
* START
* @return builder
* @throws IllegalArgumentException if timeWindow is null
*/
public Builder setBackhaulTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null) {
throw new IllegalArgumentException("backhaul time-window must not be null");
}
backhaulTimeWindows.clear();
backhaulTimeWindows.add(timeWindow);
return this;
}
public Builder addBackhaulTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null) {
throw new IllegalArgumentException("time-window arg must not be null");
}
backhaulTimeWindows.add(timeWindow);
return this;
}
public Builder addBackhaulTimeWindow(double earliest, double latest) {
addBackhaulTimeWindow(TimeWindow.newInstance(earliest, latest));
return this;
}
public Builder addBackhaulSizeDimension(int dimensionIndex, int dimensionValue) {
if (dimensionValue < 0) {
throw new IllegalArgumentException("capacity value cannot be negative");
}
backhaulCapacityBuilder.addDimension(dimensionIndex, dimensionValue);
return this;
}
public Builder addAllBackhaulSizeDimensions(SizeDimension size) {
for (int i = 0; i < size.getNuOfDimensions(); i++) {
backhaulCapacityBuilder.addDimension(i, size.get(i));
}
return this;
}
@Override
protected void validate() {
super.validate();
if (backhaulLocation == null) {
backhaulLocation = getPickupLocation();
}
if (backhaulTimeWindows.isEmpty()) {
backhaulTimeWindows.add(TimeWindow.ETERNITY);
}
}
private double getBackhaulServiceTime() {
return backhaulServiceTime;
}
private Location getBackhaulLocation() {
return backhaulLocation;
}
private TimeWindowsImpl getBackhaulTimeWindows() {
return backhaulTimeWindows;
}
private SizeDimension getBackhaulCapacity() {
SizeDimension backhaulCapacity = backhaulCapacityBuilder.build();
// If no capacity is specified, the backhaul capacity will be the
// same as the picking one.
if (backhaulCapacity.getNuOfDimensions() == 0) {
backhaulCapacity = getCapacity();
}
return backhaulCapacity;
}
public static Builder newInstance(String id) {
return new Builder(id);
}
@Override
protected ReturnedShipment createInstance() {
return new ReturnedShipment(this);
}
}
ReturnedShipment(BuilderBase<? extends ReturnedShipment, ?> builder) {
super(builder);
}
@Override
protected void createActivities(JobBuilder<?, ?> builder) {
Builder shipmentBuilder = (Builder) builder;
JobActivityList list = new SequentialJobActivityList(this);
list.addActivity(new PickupActivity(this, PICKUP_ACTIVITY_NAME,
shipmentBuilder.getPickupLocation(),
shipmentBuilder.getPickupServiceTime(), shipmentBuilder.getCapacity(),
shipmentBuilder.getPickupTimeWindows().getTimeWindows()));
list.addActivity(new ExchangeActivity(this, DELIVERY_ACTIVITY_NAME,
shipmentBuilder.getDeliveryLocation(),
shipmentBuilder.getDeliveryServiceTime(),
shipmentBuilder.getBackhaulCapacity()
.subtract(shipmentBuilder.getCapacity()),
shipmentBuilder.getDeliveryTimeWindows().getTimeWindows()));
list.addActivity(new DeliveryActivity(this, BACKHAUL_ACTIVITY_NAME,
shipmentBuilder.getBackhaulLocation(),
shipmentBuilder.getBackhaulServiceTime(),
shipmentBuilder.getBackhaulCapacity(),
shipmentBuilder.getBackhaulTimeWindows().getTimeWindows()));
setActivities(list);
}
@Override
public PickupActivity getPickupActivity() {
return (PickupActivity) getActivityList()
.findByType(PICKUP_ACTIVITY_NAME)
.get();
}
public ExchangeActivity getExchangeActivity() {
return (ExchangeActivity) getActivityList()
.findByType(DELIVERY_ACTIVITY_NAME)
.get();
}
public DeliveryActivity getBackhaulActivity() {
return (DeliveryActivity) getActivityList()
.findByType(BACKHAUL_ACTIVITY_NAME)
.get();
}
}

View file

@ -17,56 +17,477 @@
*/
package com.graphhopper.jsprit.core.problem.job;
import java.util.Collection;
import java.util.List;
import com.graphhopper.jsprit.core.problem.Capacity;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.Skills;
import com.graphhopper.jsprit.core.problem.job.CustomJob.BuilderBase.ActivityType;
import com.graphhopper.jsprit.core.problem.job.CustomJob.BuilderBase.BuilderActivityInfo;
import com.graphhopper.jsprit.core.problem.solution.route.activity.ServiceActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowsImpl;
import com.graphhopper.jsprit.core.util.Coordinate;
/**
* Service implementation of a job.
* <p>
* <p>
* <p>Note that two services are equal if they have the same id.
*
* @deprecated Use {@linkplain CustomJob} instead
*
*
* <p>
* <h1><em>Warning!</em></h1>
* </p>
*
* <p>
* <strong>This class is deprecated and only available for backward
* compatibility and for easier migration.</strong>
* </p>
* <p>
* This class wraps a new CustomJob instance and delegates its
* values and the values from its sole activity. It is strongly
* recommended to switch to the {@linkplain CustomJob} and use one
* of the following functions of its builder to add the service
* activity:
*
* <ul>
* <li>{@linkplain CustomJob.Builder#addService(Location)}</li>
* <li>{@linkplain CustomJob.Builder#addService(Location, double)}
* </li>
* <li>
* {@linkplain CustomJob.Builder#addService(Location, double, TimeWindow)}
* </li>
* </ul>
*
* or if you need more control on the activity, use the
* {@linkplain CustomJob.Builder#addActivity(BuilderActivityInfo)}
* function:
*
* <pre>
* BuilderActivityInfo activityInfo = new BuilderActivityInfo(ActivityType.SERVICE, <i>location</i>);
activityInfo.withName(<i>activity name</i>);
activityInfo.withOperationTime(<i>serviceTime</i>);
activityInfo.withSize((SizeDimension) <i>capacity</i>);
activityInfo.withTimeWindows(<i>timeWindows</i>);
activityInfo.withTimeWindow(<i>timeWindow</i>);
CustomJob.Builder customJobBuilder = new CustomJob.Builder(<i>id</i>);
customJobBuilder
.addActivity(activityInfo)
.addAllRequiredSkills(<i>skills<i>)
.setName(<i>job name</i>)
.setPriority(<i>priority</i>);
job = customJobBuilder.build();
* </pre>
*
* </p>
*
* @author schroeder
* @author Balage
*
* @see {@linkplain CustomJob}
* @see {@linkplain CustomJob.Builder}
* @see {@linkplain CustomJob.BuilderBase.BuilderActivityInfo}
*/
public class Service extends AbstractSingleActivityJob<ServiceActivity> {
@Deprecated
public class Service extends AbstractJob {
/**
* Builder that builds a service.
*
* @deprecated Use {@linkplain CustomJob.Builder} instead
*
* @author schroeder
*/
@Deprecated
public static class Builder<T extends Service> {
public static final class Builder
extends AbstractSingleActivityJob.BuilderBase<Service, Builder> {
public Builder(String id) {
super(id);
setType("pickup");
}
/**
* Returns a new instance of builder that builds a service.
*
* @param id the id of the service
* @return the builder
*/
public static Builder newInstance(String id) {
return new Builder(id);
}
@Override
protected Service createInstance() {
return new Service(this);
private String id;
protected String locationId;
private String type = "service";
protected Coordinate coord;
protected double serviceTime;
protected TimeWindow timeWindow = TimeWindow.newInstance(0.0, Double.MAX_VALUE);
protected Capacity.Builder capacityBuilder = Capacity.Builder.newInstance();
protected Capacity capacity;
protected Skills.Builder skillBuilder = Skills.Builder.newInstance();
protected Skills skills;
private String name = "no-name";
protected Location location;
protected TimeWindowsImpl timeWindows;
private boolean twAdded = false;
private int priority = 2;
protected Object userData;
Builder(String id){
this.id = id;
timeWindows = new TimeWindowsImpl();
timeWindows.add(timeWindow);
}
/**
* Protected method to set the type-name of the service.
* <p>
* <p>Currently there are {@link Service}, {@link Pickup} and {@link Delivery}.
*
* @param name the name of service
* @return the builder
*/
protected Builder<T> setType(String name) {
this.type = name;
return this;
}
/**
* Sets location
*
* @param location location
* @return builder
*/
public Builder<T> setLocation(Location location) {
this.location = location;
return this;
}
/**
* Sets the serviceTime of this service.
* <p>
* <p>It is understood as time that a service or its implied activity takes at the service-location, for instance
* to unload goods.
*
* @param serviceTime the service time / duration of service to be set
* @return builder
* @throws IllegalArgumentException if serviceTime < 0
*/
public Builder<T> setServiceTime(double serviceTime) {
if (serviceTime < 0)
throw new IllegalArgumentException("serviceTime must be greater than or equal to zero");
this.serviceTime = serviceTime;
return this;
}
/**
* Sets user specific domain data associated with the object.
*
* <p>
* The user data is a black box for the framework, it only stores it,
* but never interacts with it in any way.
* </p>
*
* @param userData
* any object holding the domain specific user data
* associated with the object.
* @return builder
*/
public Builder<T> setUserData(Object userData) {
this.userData = userData;
return this;
}
/**
* Adds capacity dimension.
*
* @param dimensionIndex the dimension index of the capacity value
* @param dimensionValue the capacity value
* @return the builder
* @throws IllegalArgumentException if dimensionValue < 0
*/
public Builder<T> addSizeDimension(int dimensionIndex, int dimensionValue) {
if (dimensionValue < 0) throw new IllegalArgumentException("capacity value cannot be negative");
capacityBuilder.addDimension(dimensionIndex, dimensionValue);
return this;
}
public Builder<T> setTimeWindow(TimeWindow tw){
if(tw == null) throw new IllegalArgumentException("time-window arg must not be null");
this.timeWindow = tw;
this.timeWindows = new TimeWindowsImpl();
timeWindows.add(tw);
return this;
}
public Builder<T> addTimeWindow(TimeWindow timeWindow) {
if(timeWindow == null) throw new IllegalArgumentException("time-window arg must not be null");
if(!twAdded){
timeWindows = new TimeWindowsImpl();
twAdded = true;
}
timeWindows.add(timeWindow);
return this;
}
public Builder<T> addTimeWindow(double earliest, double latest) {
return addTimeWindow(TimeWindow.newInstance(earliest, latest));
}
/**
* Builds the service.
*
* @return {@link Service}
* @throws IllegalArgumentException if neither locationId nor coordinate is set.
*/
public T build() {
if (location == null) throw new IllegalArgumentException("location is missing");
this.setType("service");
capacity = capacityBuilder.build();
skills = skillBuilder.build();
return (T) new Service(this);
}
public Builder<T> addRequiredSkill(String skill) {
skillBuilder.addSkill(skill);
return this;
}
public Builder<T> setName(String name) {
this.name = name;
return this;
}
public Builder<T> addAllRequiredSkills(Skills skills){
for(String s : skills.values()){
skillBuilder.addSkill(s);
}
return this;
}
public Builder<T> addAllSizeDimensions(Capacity size){
for(int i=0;i<size.getNuOfDimensions();i++){
capacityBuilder.addDimension(i,size.get(i));
}
return this;
}
/**
* Set priority to service. Only 1 (very high) to 10 (very low) are allowed.
* <p>
* Default is 2.
*
* @param priority
* @return builder
*/
public Builder<T> setPriority(int priority) {
if (priority < 1 || priority > 10)
throw new IllegalArgumentException("incorrect priority. only priority values from 1 to 10 are allowed where 1 = high and 10 is low");
this.priority = priority;
return this;
}
}
Service(Builder builder) {
super(builder);
private CustomJob theRealJob;
private ServiceActivity theRealActivity;
Service(Builder<?> builder) {
BuilderActivityInfo activityInfo = new BuilderActivityInfo(ActivityType.SERVICE,
builder.location);
activityInfo.withName(builder.name);
activityInfo.withOperationTime(builder.serviceTime);
// Safe cast because SizeDimension is the only implementation of
// Capacity
activityInfo.withSize((SizeDimension) builder.capacity);
activityInfo.withTimeWindows(builder.timeWindows.getTimeWindows());
CustomJob.Builder customJobBuilder = new CustomJob.Builder(builder.id);
customJobBuilder.addActivity(activityInfo).addAllRequiredSkills(builder.skills)
.setName(builder.name)
.addUserData(builder.userData)
.setPriority(builder.priority);
theRealJob = customJobBuilder.build();
theRealActivity = (ServiceActivity) theRealJob.getActivityList().getAll().get(0);
}
@Override
protected ServiceActivity createActivity(
AbstractSingleActivityJob.BuilderBase<? extends AbstractSingleActivityJob<?>, ?> builder) {
return new ServiceActivity(this, builder.type,
builder.location, builder.serviceTime, builder.getCapacity(),
builder.timeWindows.getTimeWindows());
// return new PickupActivityNEW(this, builder.type, builder.location,
// builder.serviceTime,
// builder.getCapacity(), builder.timeWindows.getTimeWindows());
public Collection<TimeWindow> getTimeWindows() {
return theRealJob.getTimeWindows();
}
@SuppressWarnings("unchecked")
@Override
public Builder getBuilder(String id) {
return Builder.newInstance(id);
public String getId() {
return theRealJob.getId();
}
/**
* Returns location.
*
* @return location
*/
public Location getLocation() {
return theRealActivity.getLocation();
}
/**
* Returns the service-time/duration a service takes at service-location.
*
* @return service duration
*/
public double getServiceDuration() {
return theRealActivity.getOperationTime();
}
/**
* Returns the time-window a service(-operation) is allowed to start.
* It is recommended to use getTimeWindows() instead. If you still use this, it returns the first time window of getTimeWindows() collection.
*
* @return time window
*
*/
public TimeWindow getTimeWindow() {
return theRealActivity.getSingleTimeWindow();
}
/**
* @return the name
*/
public String getType() {
return "service";
}
/**
* Returns a string with the service's attributes.
* <p>
* <p>String is built as follows: [attr1=val1][attr2=val2]...
*/
@Override
public String toString() {
return "[id=" + getId() + "][name=" + getName() + "][type=" + getType() + "][location="
+ getLocation() + "][capacity=" + getSize() + "][serviceTime="
+ getServiceDuration() + "][timeWindow=" + getTimeWindow() + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
return result;
}
/**
* Two services are equal if they have the same id.
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Service other = (Service) obj;
if (getId() == null) {
if (other.getId() != null)
return false;
} else if (!getId().equals(other.getId()))
return false;
return true;
}
@Override
public SizeDimension getSize() {
return theRealActivity.getLoadSize();
}
@Override
public Skills getRequiredSkills() {
return theRealJob.getRequiredSkills();
}
@Override
public String getName() {
return theRealJob.getName();
}
/**
* Get priority of service. Only 1 = high priority, 2 = medium and 3 = low are allowed.
* <p>
* Default is 2 = medium.
*
* @return priority
*/
@Override
public int getPriority() {
return theRealJob.getPriority();
}
@Override
public Object getUserData() {
return theRealJob.getUserData();
}
@Override
protected void createActivities(JobBuilder<? extends AbstractJob, ?> jobBuilder) {
// This is unused being a legacy implementation
}
@Override
public int getIndex() {
return theRealJob.getIndex();
}
@Override
public void impl_setIndex(int index) {
theRealJob.impl_setIndex(index);
}
@Override
public List<Location> getAllLocations() {
return theRealJob.getAllLocations();
}
@Override
public SizeDimension getSizeAtStart() {
return theRealJob.getSizeAtStart();
}
@Override
public SizeDimension getSizeAtEnd() {
return theRealJob.getSizeAtEnd();
}
@Override
public JobActivityList getActivityList() {
return theRealJob.getActivityList();
}
public CustomJob getTheRealJob() {
return theRealJob;
}
public ServiceActivity getTheRealActivity() {
return theRealActivity;
}
}

View file

@ -0,0 +1,85 @@
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you 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.graphhopper.jsprit.core.problem.job;
import com.graphhopper.jsprit.core.problem.solution.route.activity.ServiceActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
/**
* Service implementation of a job.
*
* <h3>Warning!</h3>
* <p>
* This class and are here for convenience. Most of the time using the
* {@linkplain CustomJob} is a better choice. Note that this class may most
* likely be deprecated and be removed in the future.
* </p>
*
* @author schroeder
* @author Balage
*
* @see {@linkplain CustomJob.BuilderBase#addService(Location)}
* @see {@linkplain CustomJob.BuilderBase#addService(Location, double)}
* @see {@linkplain CustomJob.BuilderBase#addService(Location, double, TimeWindow)}
*/
public class ServiceJob extends AbstractSingleActivityJob<ServiceActivity> {
/**
* Builder for {@linkplain ServiceJob}.
*
* @author Balage
*/
public static final class Builder
extends AbstractSingleActivityJob.BuilderBase<ServiceJob, Builder> {
/**
* Constructor.
*
* @param id
* The unique id.
*/
public Builder(String id) {
super(id);
setType("pickup");
}
@Override
protected ServiceJob createInstance() {
return new ServiceJob(this);
}
}
private ServiceJob(Builder builder) {
super(builder);
}
@Override
protected ServiceActivity createActivity(
AbstractSingleActivityJob.BuilderBase<? extends AbstractSingleActivityJob<?>, ?> builder) {
return new ServiceActivity(this, builder.type,
builder.location, builder.serviceTime, builder.getCapacity(),
builder.timeWindows.getTimeWindows());
}
@SuppressWarnings("unchecked")
@Override
public Builder getBuilder(String id) {
return new Builder(id);
}
}

View file

@ -17,8 +17,15 @@
*/
package com.graphhopper.jsprit.core.problem.job;
import java.util.Collection;
import java.util.List;
import com.graphhopper.jsprit.core.problem.Capacity;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.Skills;
import com.graphhopper.jsprit.core.problem.job.CustomJob.BuilderBase.ActivityType;
import com.graphhopper.jsprit.core.problem.job.CustomJob.BuilderBase.BuilderActivityInfo;
import com.graphhopper.jsprit.core.problem.solution.route.activity.DeliveryActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.PickupActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
@ -48,348 +55,550 @@ import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowsIm
* <p>
* Note that two shipments are equal if they have the same id.
*
* @deprecated Use {@linkplain CustomJob} instead
*
*
* <p>
* <h1><em>Warning!</em></h1>
* </p>
*
* <p>
* <strong>This class is deprecated and only available for backward
* compatibility and for easier migration.</strong>
* </p>
* <p>
* This class wraps a new CustomJob instance and delegates its
* values and the values from its sole activity. It is strongly
* recommended to switch to the {@linkplain CustomJob} and use one
* of the following functions of its builder to add the service
* activity:
*
* <ul>
* <li>{@linkplain CustomJob.Builder#addService(Location)}</li>
* <li>
* {@linkplain CustomJob.Builder#addService(Location, double, TimeWindow)}
* </li>
* </ul>
*
* or if you need more control on the activity, use the
* {@linkplain CustomJob.Builder#addActivity(BuilderActivityInfo)}
* function:
*
* <pre>
* BuilderActivityInfo activityInfo = new BuilderActivityInfo(ActivityType.SERVICE, <i>location</i>);
activityInfo.withName(<i>activity name</i>);
activityInfo.withOperationTime(<i>serviceTime</i>);
activityInfo.withSize((SizeDimension) <i>capacity</i>);
activityInfo.withTimeWindows(<i>timeWindows</i>);
activityInfo.withTimeWindow(<i>timeWindow</i>);
CustomJob.Builder customJobBuilder = new CustomJob.Builder(<i>id</i>);
customJobBuilder
.addActivity(activityInfo)
.addAllRequiredSkills(<i>skills<i>)
.setName(<i>job name</i>)
.setPriority(<i>priority</i>);
job = customJobBuilder.build();
* </pre>
*
* </p>
*
* @author schroeder
* @author Balage
*
* @see {@linkplain CustomJob}
* @see {@linkplain CustomJob.Builder}
* @see {@linkplain CustomJob.BuilderBase.BuilderActivityInfo}
*
*
* @author schroeder
*/
@Deprecated
public class Shipment extends AbstractJob {
public static final String DELIVERY_ACTIVITY_NAME = "deliverShipment";
public static final String PICKUP_ACTIVITY_NAME = "pickupShipment";
/**
* Builder that builds the shipment.
*
* @deprecated Use {@linkplain CustomJob.Builder} instead
* @author schroeder
*/
public static abstract class BuilderBase<T extends Shipment, B extends BuilderBase<T, B>>
extends JobBuilder<T, B> {
@Deprecated
public static class Builder {
private String id;
private double pickupServiceTime = 0.0;
private double deliveryServiceTime = 0.0;
private Location pickupLocation;
private TimeWindow deliveryTimeWindow = TimeWindow.newInstance(0.0, Double.MAX_VALUE);
private Location deliveryLocation;
private TimeWindow pickupTimeWindow = TimeWindow.newInstance(0.0, Double.MAX_VALUE);
protected TimeWindowsImpl deliveryTimeWindows = new TimeWindowsImpl();
private Capacity.Builder capacityBuilder = Capacity.Builder.newInstance();
private TimeWindowsImpl pickupTimeWindows = new TimeWindowsImpl();
private Capacity capacity;
private Skills.Builder skillBuilder = Skills.Builder.newInstance();
private Skills skills;
private String name = "no-name";
private Location pickupLocation_;
private Location deliveryLocation_;
protected TimeWindowsImpl deliveryTimeWindows;
private boolean deliveryTimeWindowAdded = false;
private boolean pickupTimeWindowAdded = false;
private TimeWindowsImpl pickupTimeWindows;
private int priority = 2;
public Object userData;
/**
* Returns new instance of this builder.
*
* @param id the id of the shipment which must be a unique identifier
* among all jobs
* @param id the id of the shipment which must be a unique identifier among all jobs
* @return the builder
*/
public static Builder newInstance(String id) {
return new Builder(id);
}
public BuilderBase(String id) {
super(id);
Builder(String id) {
if (id == null) throw new IllegalArgumentException("id must not be null");
this.id = id;
pickupTimeWindows = new TimeWindowsImpl();
pickupTimeWindows.add(pickupTimeWindow);
deliveryTimeWindows = new TimeWindowsImpl();
deliveryTimeWindows.add(deliveryTimeWindow);
}
/**
* Sets user specific domain data associated with the object.
*
* <p>
* The user data is a black box for the framework, it only stores it,
* but never interacts with it in any way.
* </p>
*
* @param userData
* any object holding the domain specific user data
* associated with the object.
* @return builder
*/
public Builder setUserData(Object userData) {
this.userData = userData;
return this;
}
/**
* Sets pickup location.
*
* @param pickupLocation pickup location
* @param pickupLocation
* pickup location
* @return builder
*/
@SuppressWarnings("unchecked")
public B setPickupLocation(Location pickupLocation) {
this.pickupLocation = pickupLocation;
return (B) this;
public Builder setPickupLocation(Location pickupLocation) {
this.pickupLocation_ = pickupLocation;
return this;
}
/**
* Sets pickupServiceTime.
* <p>
* <p>
* ServiceTime is intended to be the time the implied activity takes at
* the pickup-location.
* <p>ServiceTime is intended to be the time the implied activity takes at the pickup-location.
*
* @param serviceTime the service time / duration the pickup of the associated
* shipment takes
* @param serviceTime the service time / duration the pickup of the associated shipment takes
* @return builder
* @throws IllegalArgumentException if servicTime < 0.0
*/
@SuppressWarnings("unchecked")
public B setPickupServiceTime(double serviceTime) {
if (serviceTime < 0.0) {
throw new IllegalArgumentException("serviceTime must not be < 0.0");
}
pickupServiceTime = serviceTime;
return (B) this;
public Builder setPickupServiceTime(double serviceTime) {
if (serviceTime < 0.0) throw new IllegalArgumentException("serviceTime must not be < 0.0");
this.pickupServiceTime = serviceTime;
return this;
}
/**
* Sets the timeWindow for the pickup, i.e. the time-period in which a
* pickup operation is allowed to START.
* Sets the timeWindow for the pickup, i.e. the time-period in which a pickup operation is
* allowed to START.
* <p>
* <p>
* By default timeWindow is [0.0, Double.MAX_VALUE}
* <p>By default timeWindow is [0.0, Double.MAX_VALUE}
*
* @param timeWindow the time window within the pickup operation/activity can
* START
* @param timeWindow the time window within the pickup operation/activity can START
* @return builder
* @throws IllegalArgumentException if timeWindow is null
*/
@SuppressWarnings("unchecked")
public B setPickupTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null) {
throw new IllegalArgumentException("pickup time-window must not be null");
}
pickupTimeWindows.clear();
pickupTimeWindows.add(timeWindow);
return (B) this;
public Builder setPickupTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null) throw new IllegalArgumentException("delivery time-window must not be null");
this.pickupTimeWindow = timeWindow;
this.pickupTimeWindows = new TimeWindowsImpl();
this.pickupTimeWindows.add(timeWindow);
return this;
}
/**
* Sets delivery location.
*
* @param deliveryLocation delivery location
* @return builder
*/
@SuppressWarnings("unchecked")
public B setDeliveryLocation(Location deliveryLocation) {
this.deliveryLocation = deliveryLocation;
return (B) this;
public Builder setDeliveryLocation(Location deliveryLocation) {
this.deliveryLocation_ = deliveryLocation;
return this;
}
/**
* Sets the delivery service-time.
* <p>
* <p>
* ServiceTime is intended to be the time the implied activity takes at
* the delivery-location.
* <p>ServiceTime is intended to be the time the implied activity takes at the delivery-location.
*
* @param deliveryServiceTime the service time / duration of shipment's delivery
* @return builder
* @throws IllegalArgumentException if serviceTime < 0.0
*/
@SuppressWarnings("unchecked")
public B setDeliveryServiceTime(double deliveryServiceTime) {
if (deliveryServiceTime < 0.0) {
throw new IllegalArgumentException("deliveryServiceTime must not be < 0.0");
}
public Builder setDeliveryServiceTime(double deliveryServiceTime) {
if (deliveryServiceTime < 0.0) throw new IllegalArgumentException("deliveryServiceTime must not be < 0.0");
this.deliveryServiceTime = deliveryServiceTime;
return (B) this;
return this;
}
/**
* Sets the timeWindow for the delivery, i.e. the time-period in which a
* delivery operation is allowed to start.
* Sets the timeWindow for the delivery, i.e. the time-period in which a delivery operation is
* allowed to start.
* <p>
* <p>
* By default timeWindow is [0.0, Double.MAX_VALUE}
* <p>By default timeWindow is [0.0, Double.MAX_VALUE}
*
* @param timeWindow the time window within the associated delivery is allowed
* to START
* @param timeWindow the time window within the associated delivery is allowed to START
* @return builder
* @throws IllegalArgumentException if timeWindow is null
*/
@SuppressWarnings("unchecked")
public B setDeliveryTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null) {
throw new IllegalArgumentException("delivery time-window must not be null");
}
deliveryTimeWindows.clear();
deliveryTimeWindows.add(timeWindow);
return (B) this;
public Builder setDeliveryTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null) throw new IllegalArgumentException("delivery time-window must not be null");
this.deliveryTimeWindow = timeWindow;
this.deliveryTimeWindows = new TimeWindowsImpl();
this.deliveryTimeWindows.add(timeWindow);
return this;
}
@SuppressWarnings("unchecked")
public B addDeliveryTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null) {
throw new IllegalArgumentException("time-window arg must not be null");
}
deliveryTimeWindows.add(timeWindow);
return (B) this;
/**
* Adds capacity dimension.
*
* @param dimensionIndex the dimension index of the corresponding capacity value
* @param dimensionValue the capacity value
* @return builder
* @throws IllegalArgumentException if dimVal < 0
*/
public Builder addSizeDimension(int dimensionIndex, int dimensionValue) {
if (dimensionValue < 0) throw new IllegalArgumentException("capacity value cannot be negative");
capacityBuilder.addDimension(dimensionIndex, dimensionValue);
return this;
}
@SuppressWarnings("unchecked")
public B addDeliveryTimeWindow(double earliest, double latest) {
public Builder addRequiredSkill(String skill) {
skillBuilder.addSkill(skill);
return this;
}
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder addDeliveryTimeWindow(TimeWindow timeWindow) {
if(timeWindow == null) throw new IllegalArgumentException("time-window arg must not be null");
if(!deliveryTimeWindowAdded){
deliveryTimeWindows = new TimeWindowsImpl();
deliveryTimeWindowAdded = true;
}
deliveryTimeWindows.add(timeWindow);
return this;
}
public Builder addDeliveryTimeWindow(double earliest, double latest) {
addDeliveryTimeWindow(TimeWindow.newInstance(earliest, latest));
return (B) this;
return this;
}
@SuppressWarnings("unchecked")
public B addPickupTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null) {
throw new IllegalArgumentException("time-window arg must not be null");
public Builder addPickupTimeWindow(TimeWindow timeWindow) {
if(timeWindow == null) throw new IllegalArgumentException("time-window arg must not be null");
if(!pickupTimeWindowAdded){
pickupTimeWindows = new TimeWindowsImpl();
pickupTimeWindowAdded = true;
}
pickupTimeWindows.add(timeWindow);
return (B) this;
return this;
}
@SuppressWarnings("unchecked")
public B addPickupTimeWindow(double earliest, double latest) {
addPickupTimeWindow(TimeWindow.newInstance(earliest, latest));
return (B) this;
public Builder addPickupTimeWindow(double earliest, double latest) {
return addPickupTimeWindow(TimeWindow.newInstance(earliest, latest));
}
@Override
protected void validate() {
if (pickupLocation == null) {
/**
* Set priority to shipment. Only 1 (high) to 10 (low) are allowed.
* <p>
* Default is 2 = medium.
*
* @param priority
* @return builder
*/
public Builder setPriority(int priority) {
if (priority < 1 || priority > 10)
throw new IllegalArgumentException("incorrect priority. only 1 (very high) to 10 (very low) are allowed");
this.priority = priority;
return this;
}
/**
* Builds the shipment.
*
* @return shipment
* @throws IllegalArgumentException
* if neither pickup-location nor pickup-coord is set or if
* neither delivery-location nor delivery-coord is set
*/
public Shipment build() {
if (pickupLocation_ == null)
throw new IllegalArgumentException("pickup location is missing");
}
if (deliveryLocation == null) {
if (deliveryLocation_ == null)
throw new IllegalArgumentException("delivery location is missing");
}
if (pickupTimeWindows.isEmpty()) {
pickupTimeWindows.add(TimeWindow.ETERNITY);
}
if (deliveryTimeWindows.isEmpty()) {
deliveryTimeWindows.add(TimeWindow.ETERNITY);
}
}
// ---- Refactor test
public double getPickupServiceTime() {
return pickupServiceTime;
}
public double getDeliveryServiceTime() {
return deliveryServiceTime;
}
public Location getPickupLocation() {
return pickupLocation;
}
public Location getDeliveryLocation() {
return deliveryLocation;
}
public TimeWindowsImpl getDeliveryTimeWindows() {
return deliveryTimeWindows;
}
public TimeWindowsImpl getPickupTimeWindows() {
return pickupTimeWindows;
}
}
public static final class Builder extends BuilderBase<Shipment, Builder> {
public static Builder newInstance(String id) {
return new Builder(id);
}
public Builder(String id) {
super(id);
}
@Override
protected Shipment createInstance() {
capacity = capacityBuilder.build();
skills = skillBuilder.build();
return new Shipment(this);
}
}
private CustomJob theRealJob;
private PickupActivity theRealPickupActivity;
private DeliveryActivity theRealDeliveryActivity;
Shipment(BuilderBase<? extends Shipment, ?> builder) {
super(builder);
Shipment(Builder builder) {
BuilderActivityInfo pickupActivityInfo = new BuilderActivityInfo(ActivityType.PICKUP,
builder.pickupLocation_);
pickupActivityInfo.withName(builder.name == null ? null : builder.name + ".pickup");
pickupActivityInfo.withOperationTime(builder.pickupServiceTime);
// Safe cast because SizeDimension is the only implementation of
// Capacity
pickupActivityInfo.withSize((SizeDimension) builder.capacity);
pickupActivityInfo.withTimeWindows(builder.pickupTimeWindows.getTimeWindows());
BuilderActivityInfo deliveryActivityInfo = new BuilderActivityInfo(ActivityType.DELIVERY,
builder.deliveryLocation_);
deliveryActivityInfo.withName(builder.name == null ? null : builder.name + ".delivery");
deliveryActivityInfo.withOperationTime(builder.deliveryServiceTime);
// Safe cast because SizeDimension is the only implementation of
// Capacity
deliveryActivityInfo.withSize((SizeDimension) builder.capacity);
deliveryActivityInfo.withTimeWindows(builder.deliveryTimeWindows.getTimeWindows());
CustomJob.Builder customJobBuilder = new CustomJob.Builder(builder.id);
customJobBuilder
.addActivity(pickupActivityInfo)
.addActivity(deliveryActivityInfo)
.addAllRequiredSkills(builder.skills)
.setName(builder.name)
.addUserData(builder.userData)
.setPriority(builder.priority);
theRealJob = customJobBuilder.build();
theRealPickupActivity = (PickupActivity) theRealJob.getActivityList().getAll().get(0);
theRealDeliveryActivity = (DeliveryActivity) theRealJob.getActivityList().getAll().get(1);
}
@Override
protected void createActivities(JobBuilder<?, ?> builder) {
Builder shipmentBuilder = (Builder) builder;
JobActivityList list = new SequentialJobActivityList(this);
list.addActivity(new PickupActivity(this, PICKUP_ACTIVITY_NAME,
shipmentBuilder.getPickupLocation(),
shipmentBuilder.getPickupServiceTime(), shipmentBuilder.getCapacity(),
shipmentBuilder.getPickupTimeWindows().getTimeWindows()));
list.addActivity(new DeliveryActivity(this, DELIVERY_ACTIVITY_NAME,
shipmentBuilder.getDeliveryLocation(),
shipmentBuilder.getDeliveryServiceTime(),
shipmentBuilder.getCapacity().invert(),
shipmentBuilder.getDeliveryTimeWindows().getTimeWindows()));
setActivities(list);
public String getId() {
return theRealJob.getId();
}
public PickupActivity getPickupActivity() {
return (PickupActivity) getActivityList().findByType(PICKUP_ACTIVITY_NAME).get();
public Location getPickupLocation() {
return theRealPickupActivity.getLocation();
}
public DeliveryActivity getDeliveryActivity() {
return (DeliveryActivity) getActivityList().findByType(DELIVERY_ACTIVITY_NAME).get();
/**
* Returns the pickup service-time.
* <p>
* <p>By default service-time is 0.0.
*
* @return service-time
*/
public double getPickupServiceTime() {
return theRealPickupActivity.getOperationTime();
}
// =================== DEPRECATED GETTERS
public Location getDeliveryLocation() {
return theRealDeliveryActivity.getLocation();
}
// @Deprecated
// public Location getPickupLocation() {
// return getPickupActivity().getLocation();
// }
//
// /**
// * Returns the pickup service-time.
// * <p>
// * <p>
// * By default service-time is 0.0.
// *
// * @return service-time
// */
// @Deprecated
// public double getPickupServiceTime() {
// return getPickupActivity().getOperationTime();
// }
//
// @Deprecated
// public Location getDeliveryLocation() {
// return getDeliveryActivity().getLocation();
// }
//
// /**
// * Returns service-time of delivery.
// *
// * @return service-time of delivery
// */
// @Deprecated
// public double getDeliveryServiceTime() {
// return getDeliveryActivity().getOperationTime();
// }
//
// /**
// * Returns the time-window of delivery.
// *
// * @return time-window of delivery
// */
// @Deprecated
// public TimeWindow getDeliveryTimeWindow() {
// return getDeliveryTimeWindows().iterator().next();
// }
//
// @Deprecated
// public Collection<TimeWindow> getDeliveryTimeWindows() {
// return getDeliveryActivity().getTimeWindows();
// }
//
// /**
// * Returns the time-window of pickup.
// *
// * @return time-window of pickup
// */
// @Deprecated
// public TimeWindow getPickupTimeWindow() {
// return getPickupTimeWindows().iterator().next();
// }
//
// @Deprecated
// public Collection<TimeWindow> getPickupTimeWindows() {
// return getPickupActivity().getTimeWindows();
// }
/**
* Returns service-time of delivery.
*
* @return service-time of delivery
*/
public double getDeliveryServiceTime() {
return theRealDeliveryActivity.getOperationTime();
}
/**
* Returns the time-window of delivery.
*
* @return time-window of delivery
*/
public TimeWindow getDeliveryTimeWindow() {
return theRealDeliveryActivity.getTimeWindows().iterator().next();
}
public Collection<TimeWindow> getDeliveryTimeWindows() {
return theRealDeliveryActivity.getTimeWindows();
}
/**
* Returns the time-window of pickup.
*
* @return time-window of pickup
*/
public TimeWindow getPickupTimeWindow() {
return theRealPickupActivity.getTimeWindows().iterator().next();
}
public Collection<TimeWindow> getPickupTimeWindows() {
return theRealPickupActivity.getTimeWindows();
}
@Override
@Deprecated
public SizeDimension getSize() {
return getPickupActivity().getLoadChange();
return theRealPickupActivity.getLoadSize();
}
@Override
public Skills getRequiredSkills() {
return theRealJob.getRequiredSkills();
}
@Override
public String getName() {
return theRealJob.getName();
}
/**
* Get priority of shipment. Only 1 = high priority, 2 = medium and 3 = low are allowed.
* <p>
* Default is 2 = medium.
*
* @return priority
*/
@Override
public int getPriority() {
return theRealJob.getPriority();
}
@Override
protected void createActivities(JobBuilder<? extends AbstractJob, ?> jobBuilder) {
// This is unused being a legacy implementation
}
@Override
public int getIndex() {
return theRealJob.getIndex();
}
@Override
public Object getUserData() {
return theRealJob.getUserData();
}
@Override
public List<Location> getAllLocations() {
return theRealJob.getAllLocations();
}
@Override
public SizeDimension getSizeAtStart() {
return theRealJob.getSizeAtStart();
}
@Override
public SizeDimension getSizeAtEnd() {
return theRealJob.getSizeAtEnd();
}
@Override
public JobActivityList getActivityList() {
return theRealJob.getActivityList();
}
@Override
public Collection<TimeWindow> getTimeWindows() {
return theRealJob.getTimeWindows();
}
@Override
public String toString() {
return theRealJob.toString();
}
@Override
public void impl_setIndex(int index) {
theRealJob.impl_setIndex(index);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
return result;
}
/**
* Two services are equal if they have the same id.
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Shipment other = (Shipment) obj;
if (getId() == null) {
if (other.getId() != null)
return false;
} else if (!getId().equals(other.getId()))
return false;
return true;
}
public CustomJob getTheRealJob() {
return theRealJob;
}
public PickupActivity getTheRealPickupActivity() {
return theRealPickupActivity;
}
public DeliveryActivity getTheRealDeliveryActivity() {
return theRealDeliveryActivity;
}
}

View file

@ -0,0 +1,384 @@
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you 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.graphhopper.jsprit.core.problem.job;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.solution.route.activity.DeliveryActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.PickupActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowsImpl;
/**
* Shipment is an implementation of Job and consists of a pickup and a delivery
* of something.
* <p>
* <h3>Warning!</h3>
* <p>
* This class and are here for convenience. Most of the time using the
* {@linkplain CustomJob} is a better choice. Note that this class may most
* likely be deprecated and be removed in the future.
* </p>
*
* @author schroeder
* @author Balage
*
* @see {@linkplain CustomJob.BuilderBase}
*/
public class ShipmentJob extends AbstractJob {
/**
* Name of the pickup activity in the shipment.
*/
public static final String DELIVERY_ACTIVITY_NAME = "deliverShipment";
/**
* Name of the delivery activity in the shipment.
*/
public static final String PICKUP_ACTIVITY_NAME = "pickupShipment";
/**
* Builder that builds the shipment.
*
* @author schroeder
* @author Balage
*/
protected static abstract class BuilderBase<T extends ShipmentJob, B extends BuilderBase<T, B>>
extends JobBuilder<T, B> {
private double pickupServiceTime = 0.0;
private double deliveryServiceTime = 0.0;
private Location pickupLocation;
private Location deliveryLocation;
private TimeWindowsImpl deliveryTimeWindows = new TimeWindowsImpl();
private TimeWindowsImpl pickupTimeWindows = new TimeWindowsImpl();
/**
* Constructor.
*
* @param id
* the id of the shipment which must be a unique identifier
* among all jobs
* @return the builder
*/
public BuilderBase(String id) {
super(id);
pickupTimeWindows = new TimeWindowsImpl();
deliveryTimeWindows = new TimeWindowsImpl();
}
/**
* Sets pickup location.
*
* @param pickupLocation pickup location
* @return builder
*/
@SuppressWarnings("unchecked")
public B setPickupLocation(Location pickupLocation) {
this.pickupLocation = pickupLocation;
return (B) this;
}
/**
* Sets pickupServiceTime.
* <p>
* <p>
* ServiceTime is intended to be the time the implied activity takes at
* the pickup-location.
*
* @param serviceTime the service time / duration the pickup of the associated
* shipment takes
* @return builder
* @throws IllegalArgumentException if servicTime < 0.0
*/
@SuppressWarnings("unchecked")
public B setPickupServiceTime(double serviceTime) {
if (serviceTime < 0.0)
throw new IllegalArgumentException("serviceTime must not be < 0.0");
pickupServiceTime = serviceTime;
return (B) this;
}
/**
* Sets delivery location.
*
* @param deliveryLocation delivery location
* @return builder
*/
@SuppressWarnings("unchecked")
public B setDeliveryLocation(Location deliveryLocation) {
this.deliveryLocation = deliveryLocation;
return (B) this;
}
/**
* Sets the delivery service-time.
* <p>
* <p>
* ServiceTime is intended to be the time the implied activity takes at
* the delivery-location.
*
* @param deliveryServiceTime the service time / duration of shipment's delivery
* @return builder
* @throws IllegalArgumentException if serviceTime < 0.0
*/
@SuppressWarnings("unchecked")
public B setDeliveryServiceTime(double deliveryServiceTime) {
if (deliveryServiceTime < 0.0)
throw new IllegalArgumentException("deliveryServiceTime must not be < 0.0");
this.deliveryServiceTime = deliveryServiceTime;
return (B) this;
}
/**
* Sets a single time window.
* <p>
* This method clears any previously set time windows. Use
* {@linkplain #addTimeWindow(TimeWindow)} to add an additional one,
* instead of replacing the already set ones.
* </p>
*
* @param timeWindow
* the time window within the associated delivery is allowed
* to start
* @return builder
* @throws IllegalArgumentException
* if timeWindow is null
*/
@SuppressWarnings("unchecked")
public B setDeliveryTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null)
throw new IllegalArgumentException("delivery time-window must not be null");
deliveryTimeWindows.clear();
deliveryTimeWindows.add(timeWindow);
return (B) this;
}
/**
* Adds a single time window to the delivery activity.
*
* @param timeWindow
* The time window to set.
* @return the builder
* @throws IllegalArgumentException
* If the time window is null.
*/
@SuppressWarnings("unchecked")
public B addDeliveryTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null)
throw new IllegalArgumentException("time-window arg must not be null");
deliveryTimeWindows.add(timeWindow);
return (B) this;
}
/**
* Constructs and adds a time window to the delivery activity.
*
* @param earliest
* The earliest start.
* @param latest
* The latest start.
* @return the builder
*/
@SuppressWarnings("unchecked")
public B addDeliveryTimeWindow(double earliest, double latest) {
addDeliveryTimeWindow(TimeWindow.newInstance(earliest, latest));
return (B) this;
}
/**
* Sets the timeWindow for the pickup, i.e. the time-period in which a
* pickup operation is allowed to START.
* <p>
* <p>
* By default timeWindow is [0.0, Double.MAX_VALUE}
*
* @param timeWindow
* the time window within the pickup operation/activity can
* START
* @return builder
* @throws IllegalArgumentException
* if timeWindow is null
*/
@SuppressWarnings("unchecked")
public B setPickupTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null)
throw new IllegalArgumentException("pickup time-window must not be null");
pickupTimeWindows.clear();
pickupTimeWindows.add(timeWindow);
return (B) this;
}
/**
* Adds a single time window to the pickup activity.
*
* @param timeWindow
* The time window to set.
* @return the builder
* @throws IllegalArgumentException
* If the time window is null.
*/
@SuppressWarnings("unchecked")
public B addPickupTimeWindow(TimeWindow timeWindow) {
if (timeWindow == null)
throw new IllegalArgumentException("time-window arg must not be null");
pickupTimeWindows.add(timeWindow);
return (B) this;
}
/**
* Constructs and adds a time window to the pickup activity.
*
* @param earliest
* The earliest start.
* @param latest
* The latest start.
* @return the builder
*/
@SuppressWarnings("unchecked")
public B addPickupTimeWindow(double earliest, double latest) {
addPickupTimeWindow(TimeWindow.newInstance(earliest, latest));
return (B) this;
}
@Override
protected void validate() {
if (pickupLocation == null)
throw new IllegalArgumentException("pickup location is missing");
if (deliveryLocation == null)
throw new IllegalArgumentException("delivery location is missing");
if (pickupTimeWindows.isEmpty()) {
pickupTimeWindows.add(TimeWindow.ETERNITY);
}
if (deliveryTimeWindows.isEmpty()) {
deliveryTimeWindows.add(TimeWindow.ETERNITY);
}
}
// ---- Refactor test
public double getPickupServiceTime() {
return pickupServiceTime;
}
public double getDeliveryServiceTime() {
return deliveryServiceTime;
}
public Location getPickupLocation() {
return pickupLocation;
}
public Location getDeliveryLocation() {
return deliveryLocation;
}
public TimeWindowsImpl getDeliveryTimeWindows() {
return deliveryTimeWindows;
}
public TimeWindowsImpl getPickupTimeWindows() {
return pickupTimeWindows;
}
}
/**
* The builder for {@linkplain ShipmentJob}.
*
* <h3>Warning!</h3>
* <p>
* This class and are here for convenience. Most of the time using the
* {@linkplain CustomJob} is a better choice. Note that this class may most
* likely be deprecated and be removed in the future.
* </p>
*
* @author Balage
*/
public static final class Builder extends BuilderBase<ShipmentJob, Builder> {
/**
* Constructor.
*
* @param id
* The unique id.
*/
public Builder(String id) {
super(id);
}
@Override
protected ShipmentJob createInstance() {
return new ShipmentJob(this);
}
}
private ShipmentJob(BuilderBase<? extends ShipmentJob, ?> builder) {
super(builder);
}
@Override
protected void createActivities(JobBuilder<?, ?> builder) {
Builder shipmentBuilder = (Builder) builder;
JobActivityList list = new SequentialJobActivityList(this);
list.addActivity(new PickupActivity(this, PICKUP_ACTIVITY_NAME,
shipmentBuilder.getPickupLocation(),
shipmentBuilder.getPickupServiceTime(), shipmentBuilder.getCapacity(),
shipmentBuilder.getPickupTimeWindows().getTimeWindows()));
list.addActivity(new DeliveryActivity(this, DELIVERY_ACTIVITY_NAME,
shipmentBuilder.getDeliveryLocation(),
shipmentBuilder.getDeliveryServiceTime(),
shipmentBuilder.getCapacity().invert(),
shipmentBuilder.getDeliveryTimeWindows().getTimeWindows()));
setActivities(list);
}
/**
* @return The pickup activity.
*/
public PickupActivity getPickupActivity() {
return (PickupActivity) getActivityList().findByType(PICKUP_ACTIVITY_NAME).get();
}
/**
* @return The delivery activity.
*/
public DeliveryActivity getDeliveryActivity() {
return (DeliveryActivity) getActivityList().findByType(DELIVERY_ACTIVITY_NAME).get();
}
@Override
@Deprecated
public SizeDimension getSize() {
return getPickupActivity().getLoadChange();
}
}

View file

@ -30,9 +30,9 @@ import com.graphhopper.jsprit.core.problem.driver.Driver;
import com.graphhopper.jsprit.core.problem.driver.DriverImpl;
import com.graphhopper.jsprit.core.problem.job.AbstractSingleActivityJob;
import com.graphhopper.jsprit.core.problem.job.Break;
import com.graphhopper.jsprit.core.problem.job.Delivery;
import com.graphhopper.jsprit.core.problem.job.Pickup;
import com.graphhopper.jsprit.core.problem.job.Shipment;
import com.graphhopper.jsprit.core.problem.job.DeliveryJob;
import com.graphhopper.jsprit.core.problem.job.PickupJob;
import com.graphhopper.jsprit.core.problem.job.ShipmentJob;
import com.graphhopper.jsprit.core.problem.solution.route.activity.End;
import com.graphhopper.jsprit.core.problem.solution.route.activity.JobActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.Start;
@ -86,7 +86,7 @@ public class VehicleRoute {
*/
public static class Builder {
private Map<Shipment, TourActivity> openActivities = new HashMap<>();
private Map<ShipmentJob, TourActivity> openActivities = new HashMap<>();
/**
* Returns new instance of this builder.
@ -162,7 +162,7 @@ public class VehicleRoute {
// private TourShipmentActivityFactory shipmentActivityFactory = new
// DefaultShipmentActivityFactory();
private Set<Shipment> openShipments = new HashSet<>();
private Set<ShipmentJob> openShipments = new HashSet<>();
private JobActivityFactory jobActivityFactory = new SimpleJobActivityFactory();
@ -279,21 +279,21 @@ public class VehicleRoute {
*
* <p>
* <i><b>Note: Using this method is not recommended. Use the
* {@linkplain #addPickup(Pickup, TimeWindow)} instead.</b></i>
* {@linkplain #addPickup(PickupJob, TimeWindow)} instead.</b></i>
* </p>
*
* @param pickup
* pickup to be added
* @return the builder
*/
public Builder addPickup(Pickup pickup) {
public Builder addPickup(PickupJob pickup) {
if (pickup == null) {
throw new IllegalArgumentException("pickup must not be null");
}
return addService(pickup);
}
public Builder addPickup(Pickup pickup, TimeWindow timeWindow) {
public Builder addPickup(PickupJob pickup, TimeWindow timeWindow) {
if (pickup == null) {
throw new IllegalArgumentException("pickup must not be null");
}
@ -305,7 +305,7 @@ public class VehicleRoute {
*
* <p>
* <i><b>Note: Using this method is not recommended. Use the
* {@linkplain #addDelivery(Delivery, TimeWindow)} instead.</b></i>
* {@linkplain #addDelivery(DeliveryJob, TimeWindow)} instead.</b></i>
* </p>
*
*
@ -313,14 +313,14 @@ public class VehicleRoute {
* delivery to be added
* @return the builder
*/
public Builder addDelivery(Delivery delivery) {
public Builder addDelivery(DeliveryJob delivery) {
if (delivery == null) {
throw new IllegalArgumentException("delivery must not be null");
}
return addService(delivery);
}
public Builder addDelivery(Delivery delivery, TimeWindow timeWindow) {
public Builder addDelivery(DeliveryJob delivery, TimeWindow timeWindow) {
if (delivery == null) {
throw new IllegalArgumentException("delivery must not be null");
}
@ -332,7 +332,7 @@ public class VehicleRoute {
*
* <p>
* <i><b>Note: Using this method is not recommended. Use the
* {@linkplain #addPickup(Shipment, TimeWindow)} instead.</b></i>
* {@linkplain #addPickup(ShipmentJob, TimeWindow)} instead.</b></i>
* </p>
*
* @param shipment
@ -342,12 +342,12 @@ public class VehicleRoute {
* if method has already been called with the specified
* shipment.
*/
public Builder addPickup(Shipment shipment) {
public Builder addPickup(ShipmentJob shipment) {
return addPickup(shipment,
shipment.getPickupActivity().getSingleTimeWindow());
}
public Builder addPickup(Shipment shipment, TimeWindow pickupTimeWindow) {
public Builder addPickup(ShipmentJob shipment, TimeWindow pickupTimeWindow) {
if (openShipments.contains(shipment)) {
throw new IllegalArgumentException("shipment has already been added. cannot add it twice.");
}
@ -367,7 +367,7 @@ public class VehicleRoute {
*
* <p>
* <i><b>Note: Using this method is not recommended. Use the
* {@linkplain #addDelivery(Shipment, TimeWindow)} instead.</b></i>
* {@linkplain #addDelivery(ShipmentJob, TimeWindow)} instead.</b></i>
* </p>
*
* @param shipment
@ -377,11 +377,11 @@ public class VehicleRoute {
* if specified shipment has not been picked up yet (i.e.
* method addPickup(shipment) has not been called yet).
*/
public Builder addDelivery(Shipment shipment) {
public Builder addDelivery(ShipmentJob shipment) {
return addDelivery(shipment, shipment.getDeliveryActivity().getSingleTimeWindow());
}
public Builder addDelivery(Shipment shipment, TimeWindow deliveryTimeWindow) {
public Builder addDelivery(ShipmentJob shipment, TimeWindow deliveryTimeWindow) {
if (openShipments.contains(shipment)) {
TourActivity act = openActivities.get(shipment);
act.setTheoreticalEarliestOperationStartTime(deliveryTimeWindow.getStart());

View file

@ -6,8 +6,26 @@ import java.lang.reflect.InvocationTargetException;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.job.Shipment;
import com.graphhopper.jsprit.core.problem.job.Job;
import com.graphhopper.jsprit.core.problem.job.ShipmentJob;
/**
* Abstract base class for all activities.
*
* <p>
* Activities are the atomic building blocks of a problem. Each activity has its
* type, location, duration (operation time), cargo change.
* </p>
* <p>
* There are internal activities, ones only the algorithm could create. These
* activities are marked by the {@linkplain InternalActivityMarker} marker
* interface. Activities may belong to a {@linkplain Job}, these activities are
* the descendants of the {@linkplain JobActivity} base class.
* </p>
*
* @author Balage
*
*/
public abstract class AbstractActivity implements TourActivity {
private int index;
@ -19,7 +37,16 @@ public abstract class AbstractActivity implements TourActivity {
protected String type;
protected Location location;
/**
* Constructor.
*
* @param type
* The type of the activity.
* @param location
* The location of the activity.
* @param loadChange
* The cargo change of the activity.
*/
public AbstractActivity(String type, Location location, SizeDimension loadChange) {
super();
this.loadChange = loadChange;
@ -28,6 +55,15 @@ public abstract class AbstractActivity implements TourActivity {
}
/**
* Copy constructor.
* <p>
* This makes a <b>shallow</b> copy of the <code>sourceActivity</code>.
* </p>
*
* @param sourceActivity
* The activity to copy.
*/
public AbstractActivity(AbstractActivity sourceActivity) {
arrTime = sourceActivity.getArrTime();
endTime = sourceActivity.getEndTime();
@ -111,9 +147,9 @@ public abstract class AbstractActivity implements TourActivity {
@Override
public String toString() {
return "[name=" + getName() + "][locationId=" + getLocation().getId()
+ "][size=" + getLoadChange().toString()
+ "][twStart=" + Activities.round(getTheoreticalEarliestOperationStartTime())
+ "][twEnd=" + Activities.round(getTheoreticalLatestOperationStartTime()) + "]";
+ "][size=" + getLoadChange().toString()
+ "][twStart=" + Activities.round(getTheoreticalEarliestOperationStartTime())
+ "][twEnd=" + Activities.round(getTheoreticalLatestOperationStartTime()) + "]";
}
@ -125,17 +161,18 @@ public abstract class AbstractActivity implements TourActivity {
Constructor<? extends AbstractActivity> constructor = getClass().getConstructor(getClass());
return constructor.newInstance(this);
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
| InvocationTargetException e) {
System.out.println(this.getClass().getCanonicalName() + " : " + this);
throw new IllegalStateException(e);
}
}
// Temporal solution unto eliminated dependency on job type
// TODO: remove
@Deprecated
public static boolean isShipment(TourActivity activity) {
return (activity instanceof JobActivity)
&& (((JobActivity) activity).getJob() instanceof Shipment);
&& (((JobActivity) activity).getJob() instanceof ShipmentJob);
}
}

View file

@ -25,24 +25,44 @@ import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import com.graphhopper.jsprit.core.problem.job.Break;
import com.graphhopper.jsprit.core.problem.job.Break.Builder;
/**
* An {@linkplain InternalJobActivity} marking the break time of the vehicle.
*
* @author Balage
*
*/
public class BreakActivity extends InternalJobActivity {
/**
* Creates a new Break activity instance.
*
* @param aBreak
* The {@linkplain Break} job instance to associate the activity
* with.
* @param builder
* The Break job builder.
* @return The new break instance.
*/
public static BreakActivity newInstance(Break aBreak, Builder builder) {
return new BreakActivity(aBreak, "break", builder.getLocation(), builder.getServiceTime(),
builder.getCapacity(), builder.getTimeWindows().getTimeWindows());
builder.getCapacity(), builder.getTimeWindows().getTimeWindows());
}
// protected BreakActivity(Break aBreak) {
// super(aBreak, "Break", aBreak.getLocation(), aBreak.getServiceDuration(),
// SizeDimension.createNullCapacity(aBreak.getSize()), aBreak.getTimeWindows());
// }
/**
* Copy constructor.
* <p>
* Makes a shallow copy.
* </p>
*
* @param breakActivity
* The activity to copy.
*/
public BreakActivity(BreakActivity breakActivity) {
super(breakActivity);
}
private BreakActivity(AbstractJob job, String name, Location location, double operationTime,
SizeDimension capacity, Collection<TimeWindow> timeWindows) {
SizeDimension capacity, Collection<TimeWindow> timeWindows) {
super(job, name, location, operationTime, capacity, timeWindows);
}
@ -67,31 +87,35 @@ public class BreakActivity extends InternalJobActivity {
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
if (this == obj)
return true;
}
if (obj == null) {
if (obj == null)
return false;
}
if (getClass() != obj.getClass()) {
if (getClass() != obj.getClass())
return false;
}
BreakActivity other = (BreakActivity) obj;
if (getJob() == null) {
if (other.getJob() != null) {
if (other.getJob() != null)
return false;
}
} else if (!getJob().equals(other.getJob())) {
} else if (!getJob().equals(other.getJob()))
return false;
}
return true;
}
/**
* Sets the location of the break.
*
* @param location
* The location.
*/
public void setLocation(Location breakLocation) {
location = breakLocation;
}
/**
* @return The time window of the break.
*/
public TimeWindow getTimeWindow() {
// Break has always a single time window
return getSingleTimeWindow();

View file

@ -24,15 +24,46 @@ import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.SizeDimension.SizeDimensionSign;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
/**
* A {@linkplain JobActivity} representing a activity where something is
* delivered (unloaded from the vehicle).
*
* @author Balage
*/
public class DeliveryActivity extends JobActivity {
/**
* Constructor.
*
* @param job
* The job the activity is part of.
* @param type
* The type of the activity.
* @param location
* The location of the activity.
* @param operationTime
* The duration of the activity.
* @param capacity
* The cargo change of the activity. If the value is positive, it
* is negated.
* @param timeWindows
* The time windows of the activity.
*/
public DeliveryActivity(AbstractJob job, String name, Location location,
double operationTime, SizeDimension capacity, Collection<TimeWindow> timeWindows) {
double operationTime, SizeDimension capacity, Collection<TimeWindow> timeWindows) {
super(job, name, location, operationTime, capacity.sign() == SizeDimensionSign.POSITIVE
? capacity.invert() : capacity, timeWindows);
? capacity.invert() : capacity, timeWindows);
}
/**
* Copy constructor.
* <p>
* This makes a <b>shallow</b> copy of the <code>sourceActivity</code>.
* </p>
*
* @param sourceActivity
* The activity to copy.
*/
public DeliveryActivity(DeliveryActivity sourceActivity) {
super(sourceActivity);
}

View file

@ -35,11 +35,29 @@
*/
package com.graphhopper.jsprit.core.problem.solution.route.activity;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
/**
* An {@linkplain InternalActivity} marking the end of a route.
*
* @author Balage
*
*/
public final class End extends InternalActivity {
/**
* Factory method to create a new End activity.
*
* @param locationId
* The location id (depo) of the end of the route.
* @param theoreticalStart
* The earliest possible start time of the activity.
* @param theoreticalEnd
* The latest possible start time of the activity.
* @return The new {@linkplain End} instance.
*/
public static End newInstance(String locationId, double theoreticalStart, double theoreticalEnd) {
Location loc = null;
if (locationId != null) {
@ -48,10 +66,28 @@ public final class End extends InternalActivity {
return new End(loc, theoreticalStart, theoreticalEnd);
}
/**
* Copies the the activity.
*
* @param start
* The activity to copy.
* @return The shallow copy of the activity.
*/
public static End copyOf(End end) {
return new End(end);
}
/**
* Constructor.
*
* @param locationId
* The location id (depo) of the end of the route.
* @param theoreticalStart
* The earliest possible start time of the activity.
* @param theoreticalEnd
* The latest possible start time of the activity.
* @return The new {@linkplain End} instance.
*/
public End(Location location, double theoreticalStart, double theoreticalEnd) {
super("end", location, SizeDimension.EMPTY);
setTheoreticalEarliestOperationStartTime(theoreticalStart);
@ -60,10 +96,25 @@ public final class End extends InternalActivity {
setIndex(-2);
}
/**
* Copy constructor.
* <p>
* Makes a shallow copy.
* </p>
*
* @param end
* The activity to copy.
*/
private End(End end) {
super(end);
}
/**
* Sets the end location.
*
* @param location
* The location.
*/
public void setLocation(Location location) {
this.location = location;
}
@ -76,89 +127,8 @@ public final class End extends InternalActivity {
@Override
public String toString() {
return "[type=" + getName() + "][location=" + location
+ "][twStart=" + Activities.round(getTheoreticalEarliestOperationStartTime())
+ "][twEnd=" + Activities.round(getTheoreticalLatestOperationStartTime()) + "]";
+ "][twStart=" + Activities.round(getTheoreticalEarliestOperationStartTime())
+ "][twEnd=" + Activities.round(getTheoreticalLatestOperationStartTime()) + "]";
}
}
/*
* package com.graphhopper.jsprit.core.problem.solution.route.activity;
*
* import com.graphhopper.jsprit.core.problem.SizeDimension; import com.graphhopper.jsprit.core.problem.AbstractActivityNEW;
* import com.graphhopper.jsprit.core.problem.Location;
*
* public final class End extends AbstractActivityNEW {
*
* public static End newInstance(String locationId, double earliestArrival, double latestArrival) { return new
* End(locationId, earliestArrival, latestArrival); }
*
* public static End copyOf(End end) { return new End(end); }
*
* private final static SizeDimension capacity = SizeDimension.Builder.newInstance().build();
*
*
* private double endTime = -1;
*
*
* private double theoretical_earliestOperationStartTime;
*
* private double theoretical_latestOperationStartTime;
*
* private double arrTime;
*
* private Location location;
*
* @Override public void setTheoreticalEarliestOperationStartTime(double theoreticalEarliestOperationStartTime) {
* theoretical_earliestOperationStartTime = theoreticalEarliestOperationStartTime; }
*
* @Override public void setTheoreticalLatestOperationStartTime(double theoreticalLatestOperationStartTime) {
* theoretical_latestOperationStartTime = theoreticalLatestOperationStartTime; }
*
* public End(Location location, double theoreticalStart, double theoreticalEnd) { super(); this.location = location;
* theoretical_earliestOperationStartTime = theoreticalStart; theoretical_latestOperationStartTime = theoreticalEnd;
* endTime = theoreticalEnd; setIndex(-2); }
*
* public End(String locationId, double theoreticalStart, double theoreticalEnd) { super(); if (locationId != null) {
* location = Location.Builder.newInstance().setId(locationId).build(); } theoretical_earliestOperationStartTime =
* theoreticalStart; theoretical_latestOperationStartTime = theoreticalEnd; endTime = theoreticalEnd; setIndex(-2); }
*
* public End(End end) { location = end.getLocation(); // this.locationId = end.getLocation().getId();
* theoretical_earliestOperationStartTime = end.getTheoreticalEarliestOperationStartTime();
* theoretical_latestOperationStartTime = end.getTheoreticalLatestOperationStartTime(); arrTime = end.getArrTime();
* endTime = end.getEndTime(); setIndex(-2); }
*
* @Override public double getTheoreticalEarliestOperationStartTime() { return theoretical_earliestOperationStartTime; }
*
* @Override public double getTheoreticalLatestOperationStartTime() { return theoretical_latestOperationStartTime; }
*
* @Override public double getEndTime() { return endTime; }
*
* @Override public void setEndTime(double endTime) { this.endTime = endTime; }
*
* public void setLocation(Location location) { this.location = location; }
*
* @Override public Location getLocation() { return location; }
*
* @Override public double getOperationTime() { return 0.0; }
*
*
* @Override public String toString() { return "[type=" + getName() + "][location=" + location + "][twStart=" +
* Activities.round(theoretical_earliestOperationStartTime) + "][twEnd=" +
* Activities.round(theoretical_latestOperationStartTime) + "]"; }
*
* @Override public String getName() { return "end"; }
*
* @Override public double getArrTime() { return arrTime; }
*
* @Override public void setArrTime(double arrTime) { this.arrTime = arrTime;
*
* }
*
* @Override public TourActivity duplicate() { return new End(this); }
*
* @Override public SizeDimension getSize() { return capacity; }
*
* }
*/

View file

@ -23,19 +23,77 @@ import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
/**
* A {@linkplain JobActivity} representing a activity where something is
* unloaded and something else is loaded at the same time. (For example, the
* cargo is loaded and the empty crates are picked up.) The size dimension may
* contain both positive and negative values.
*
* @author Balage
*/
public class ExchangeActivity extends JobActivity {
/**
* Constructor.
*
* @param job
* The job the activity is part of.
* @param type
* The type of the activity.
* @param location
* The location of the activity.
* @param operationTime
* The duration of the activity.
* @param capacity
* The cargo change of the activity. It may contain both positive
* and negative values.
* @param timeWindows
* The time windows of the activity.
*/
public ExchangeActivity(AbstractJob job, String name, Location location,
double operationTime, SizeDimension size, Collection<TimeWindow> timeWindows) {
double operationTime, SizeDimension size, Collection<TimeWindow> timeWindows) {
super(job, name, location, operationTime, size, timeWindows);
}
/**
* Constructor.
* <p>
* This calls the
* {@linkplain ExchangeActivity#ExchangeActivity(AbstractJob, String, Location, double, SizeDimension, Collection)}
* with the capacity value of
* <code>backhaulSize.subtract(deliverySize)</code>.
* </p>
*
* @param job
* The job the activity is part of.
* @param type
* The type of the activity.
* @param location
* The location of the activity.
* @param operationTime
* The duration of the activity.
* @param deliverySize
* The unsigned (positive) size of the unloaded cargo.
* @param backhaulSize
* The unsigned (positive) size of the picked up backhaul cargo.
* @param timeWindows
* The time windows of the activity.
*/
public ExchangeActivity(AbstractJob job, String name, Location location,
double operationTime, SizeDimension deliverySize, SizeDimension backhaulSize,
Collection<TimeWindow> timeWindows) {
double operationTime, SizeDimension deliverySize, SizeDimension backhaulSize,
Collection<TimeWindow> timeWindows) {
this(job, name, location, operationTime, backhaulSize.subtract(deliverySize), timeWindows);
}
/**
* Copy constructor.
* <p>
* This makes a <b>shallow</b> copy of the <code>sourceActivity</code>.
* </p>
*
* @param sourceActivity
* The activity to copy.
*/
public ExchangeActivity(ExchangeActivity sourceActivity) {
super(sourceActivity);
}

View file

@ -1,10 +1,10 @@
package com.graphhopper.jsprit.core.problem.solution.route.activity;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
/**
* Common ancesstor for non-job-based, internal activities
* Common ancestor for non-job-based, internal activities
*
* @author balage
*/

View file

@ -1,20 +1,20 @@
package com.graphhopper.jsprit.core.problem.solution.route.activity;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import java.util.Collection;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
/**
* Common ancesstor for job-based, internal activities
* Common ancestor for job-based, internal activities
*
* @author balage
*/
public abstract class InternalJobActivity extends JobActivity implements InternalActivityMarker {
public InternalJobActivity(AbstractJob job, String name, Location location,
double operationTime, SizeDimension capacity, Collection<TimeWindow> timeWindows) {
double operationTime, SizeDimension capacity, Collection<TimeWindow> timeWindows) {
super(job, name, location, operationTime, capacity, timeWindows);
}

View file

@ -9,12 +9,14 @@ import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import com.graphhopper.jsprit.core.problem.job.Job;
/**
* Basic interface of job-activies.
* Basic interface of job-related activies.
* <p>
* <p>
* A job activity is related to a {@link Job}.
* A job activity may have time windows, operation time and is related to a
* {@link Job}.
* </p>
*
* @author schroeder
* @author Balage
*/
public abstract class JobActivity extends AbstractActivity {
@ -26,14 +28,39 @@ public abstract class JobActivity extends AbstractActivity {
private int orderNumber;
/**
* Constructor.
*
* @param job
* The job the activity is part of.
* @param type
* The type of the activity.
* @param location
* The location of the activity.
* @param operationTime
* The duration of the activity.
* @param capacity
* The cargo change of the activity.
* @param timeWindows
* The time windows of the activity.
*/
public JobActivity(AbstractJob job, String type, Location location, double operationTime,
SizeDimension capacity, Collection<TimeWindow> timeWindows) {
SizeDimension capacity, Collection<TimeWindow> timeWindows) {
super(type, location, capacity);
this.job = job;
this.operationTime = operationTime;
this.timeWindows = timeWindows;
}
/**
* Copy constructor.
* <p>
* This makes a <b>shallow</b> copy of the <code>sourceActivity</code>.
* </p>
*
* @param sourceActivity
* The activity to copy.
*/
protected JobActivity(JobActivity sourceActivity) {
super(sourceActivity);
job = sourceActivity.getJob();
@ -46,6 +73,9 @@ public abstract class JobActivity extends AbstractActivity {
}
}
/**
* @return The job the activity is associated with.
*/
public AbstractJob getJob() {
return job;
}
@ -60,14 +90,23 @@ public abstract class JobActivity extends AbstractActivity {
return job.getId() + "." + getType();
}
/**
* @return The time windows.
*/
public Collection<TimeWindow> getTimeWindows() {
return timeWindows;
}
/**
* @return A single time window.
* @throws IllegalArgumentException
* When more than one time window exists.
*/
// TODO: Is it legacy code, should be removed later
@Deprecated
public TimeWindow getSingleTimeWindow() {
if (timeWindows.size() > 1) {
if (timeWindows.size() > 1)
throw new IllegalArgumentException("More than one time window in. " + this);
}
return timeWindows.iterator().next();
}
@ -82,34 +121,39 @@ public abstract class JobActivity extends AbstractActivity {
@Override
public boolean equals(Object obj) {
if (this == obj) {
if (this == obj)
return true;
}
if (obj == null) {
if (obj == null)
return false;
}
if (getClass() != obj.getClass()) {
if (getClass() != obj.getClass())
return false;
}
JobActivity other = (JobActivity) obj;
if (job == null) {
if (other.job != null) {
if (other.job != null)
return false;
}
} else if (!job.equals(other.job)) {
} else if (!job.equals(other.job))
return false;
}
if (orderNumber != other.orderNumber) {
if (orderNumber != other.orderNumber)
return false;
}
return true;
}
/**
* @return The order of the task within its job
*/
public int getOrderNumber() {
return orderNumber;
}
public void setOrderNumber(int orderNumber) {
/**
* Sets the order number of the activity within the job.
* <p>
* <b>Warning! This function is not part of the API.</b>
* </p>
*
* @param orderNumber
*/
public void impl_setOrderNumber(int orderNumber) {
this.orderNumber = orderNumber;
}

View file

@ -23,13 +23,44 @@ import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
/**
* A {@linkplain JobActivity} representing a activity where something is picked
* up (loaded to the vehicle).
*
* @author Balage
*/
public class PickupActivity extends JobActivity {
/**
* Constructor.
*
* @param job
* The job the activity is part of.
* @param type
* The type of the activity.
* @param location
* The location of the activity.
* @param operationTime
* The duration of the activity.
* @param capacity
* The cargo change of the activity.
* @param timeWindows
* The time windows of the activity.
*/
public PickupActivity(AbstractJob job, String name, Location location, double operationTime,
SizeDimension capacity, Collection<TimeWindow> timeWindows) {
SizeDimension capacity, Collection<TimeWindow> timeWindows) {
super(job, name, location, operationTime, capacity, timeWindows);
}
/**
* Copy constructor.
* <p>
* This makes a <b>shallow</b> copy of the <code>sourceActivity</code>.
* </p>
*
* @param sourceActivity
* The activity to copy.
*/
public PickupActivity(PickupActivity sourceActivity) {
super(sourceActivity);
}

View file

@ -23,16 +23,50 @@ import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
/**
* A {@linkplain JobActivity} representing a activity where something is served.
* Theoretically no cargo change is involved, although, for historical reason,
* the constructor allows to pass a capacity. When a non-empty capacity is
* passed the service acts as an {@linkplain ExchangeActivity}.
*
* @author Balage
*/
public class ServiceActivity extends JobActivity {
/**
* Constructor.
*
* @param job
* The job the activity is part of.
* @param type
* The type of the activity.
* @param location
* The location of the activity.
* @param operationTime
* The duration of the activity.
* @param capacity
* The cargo change of the activity. It should be null or
* {@linkplain SizeDimension#EMPTY}, although it is not enforced.
* @param timeWindows
* The time windows of the activity.
*/
public ServiceActivity(AbstractJob job, String type, Location location, double operationTime,
SizeDimension capacity, Collection<TimeWindow> timeWindows) {
SizeDimension capacity, Collection<TimeWindow> timeWindows) {
super(job, type, location, operationTime, capacity, timeWindows);
}
/**
* Copy constructor.
* <p>
* This makes a <b>shallow</b> copy of the <code>sourceActivity</code>.
* </p>
*
* @param sourceActivity
* The activity to copy.
*/
public ServiceActivity(ServiceActivity sourceActivity) {
super(sourceActivity);
}
super(sourceActivity);
}
}

View file

@ -17,11 +17,28 @@
*/
package com.graphhopper.jsprit.core.problem.solution.route.activity;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
/**
* An {@linkplain InternalActivity} marking the start of a route.
*
* @author Balage
*
*/
public final class Start extends InternalActivity {
/**
* Factory method to create a new Start activity.
*
* @param locationId
* The location id (depo) of the start of the route.
* @param theoreticalStart
* The earliest possible start time of the activity.
* @param theoreticalEnd
* The latest possible start time of the activity.
* @return The new {@linkplain Start} instance.
*/
public static Start newInstance(String locationId, double theoreticalStart, double theoreticalEnd) {
Location loc = null;
if (locationId != null) {
@ -30,10 +47,27 @@ public final class Start extends InternalActivity {
return new Start(loc, theoreticalStart, theoreticalEnd);
}
/**
* Copies the the activity.
*
* @param start
* The activity to copy.
* @return The shallow copy of the activity.
*/
public static Start copyOf(Start start) {
return new Start(start);
}
/**
* Constructor.
*
* @param locationId
* The location id (depo) of the start of the route.
* @param theoreticalStart
* The earliest possible start time of the activity.
* @param theoreticalEnd
* The latest possible start time of the activity.
*/
public Start(Location location, double theoreticalStart, double theoreticalEnd) {
super("start", location, SizeDimension.EMPTY);
setTheoreticalEarliestOperationStartTime(theoreticalStart);
@ -42,10 +76,25 @@ public final class Start extends InternalActivity {
setIndex(-1);
}
/**
* Copy constructor.
* <p>
* Makes a shallow copy.
* </p>
*
* @param start
* The activity to copy.
*/
private Start(Start start) {
super(start);
}
/**
* Sets the start location.
*
* @param location
* The location.
*/
public void setLocation(Location location) {
this.location = location;
}
@ -58,8 +107,8 @@ public final class Start extends InternalActivity {
@Override
public String toString() {
return "[type=" + getName() + "][location=" + location
+ "][twStart=" + Activities.round(getTheoreticalEarliestOperationStartTime())
+ "][twEnd=" + Activities.round(getTheoreticalLatestOperationStartTime()) + "]";
+ "][twStart=" + Activities.round(getTheoreticalEarliestOperationStartTime())
+ "][twEnd=" + Activities.round(getTheoreticalLatestOperationStartTime()) + "]";
}
}

View file

@ -17,12 +17,12 @@
*/
package com.graphhopper.jsprit.core.problem.solution.route.activity;
import com.graphhopper.jsprit.core.problem.job.Shipment;
import com.graphhopper.jsprit.core.problem.job.ShipmentJob;
public interface TourShipmentActivityFactory {
public AbstractActivity createPickup(Shipment shipment);
public AbstractActivity createPickup(ShipmentJob shipment);
public AbstractActivity createDelivery(Shipment shipment);
public AbstractActivity createDelivery(ShipmentJob shipment);
}

View file

@ -1,115 +0,0 @@
package com.graphhopper.jsprit.core.reporting;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition.Builder;
/**
* Abstract base class for column definitions.
*
* @author balage
*
* @param <C>
* The context the column works on
* @param <T>
* The type of the data it emits
* @param <A>
* The class itself (internal generic parameter: for inheritence and
* builder pattern)
*/
public abstract class AbstractPrinterColumn<C extends PrinterContext, T, A extends AbstractPrinterColumn<C, T, A>> {
// Decorator is a post creation callback to alter the behaviour of the
// column definition.
private Consumer<ColumnDefinition.Builder> decorator;
private boolean isDefaultTitle = true;
private String title;
/**
* Constructor.
*/
public AbstractPrinterColumn() {
this(null);
}
/**
* @param decorator
* Decorator is a post creation callback to alter the behaviour
* of the column definition.
*/
public AbstractPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super();
this.decorator = decorator;
}
/**
* Creates the column definition of the column.
*
* @return the decorated column definition.
*/
public ColumnDefinition getColumnDefinition() {
Builder builder = getColumnBuilder().withTitle(getTitle());
if (decorator != null) {
decorator.accept(builder);
}
return builder.build();
}
/**
* @return A title of the column.
*/
public String getTitle() {
return isDefaultTitle ? getDefaultTitle() : title;
}
/**
* @param title
* the title of the column
* @return The object itself (fluent api)
*/
@SuppressWarnings("unchecked")
public A withTitle(String title) {
this.title = title;
isDefaultTitle = false;
return (A) this;
}
/**
* Decorator is a post creation callback to alter the behaviour of the
* column definition.
*
* @param decorator
* The decorator.
* @return The object itself (fluent api)
*/
@SuppressWarnings("unchecked")
public A withDecorator(Consumer<ColumnDefinition.Builder> decorator) {
this.decorator = decorator;
return (A) this;
}
/**
* Returns the builder implementation of the corresponding column
* definition.
*
* @return The column definition builder.
*/
protected abstract ColumnDefinition.Builder getColumnBuilder();
/**
* Extracts the data from the context.
*
* @param context
* The context to process.
* @return The extracted data.
*/
public abstract T getData(C context);
/**
* @return the default title
*/
protected abstract String getDefaultTitle();
}

View file

@ -0,0 +1,79 @@
package com.graphhopper.jsprit.core.reporting;
import java.util.Collection;
import java.util.stream.Collectors;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
public abstract class ColumnConfigBase {
private HumanReadableTimeFormatter timeFormatter;
private HumanReadableDurationFormatter durationFormatter;
public HumanReadableTimeFormatter getTimeFormatter() {
return timeFormatter;
}
protected void setTimeFormatter(HumanReadableTimeFormatter timeFormatter) {
this.timeFormatter = timeFormatter;
}
public HumanReadableDurationFormatter getDurationFormatter() {
return durationFormatter;
}
protected void setDurationFormatter(HumanReadableDurationFormatter durationFormatter) {
this.durationFormatter = durationFormatter;
}
protected String formatTimeWindowsNumeric(Collection<TimeWindow> timeWindows) {
if (timeWindows == null || timeWindows.isEmpty())
return "";
return timeWindows.stream().map(tw -> formatTimeWindowNumeric(tw))
.collect(Collectors.joining());
}
protected String formatTimeWindowsHuman(Collection<TimeWindow> timeWindows) {
if (timeWindows == null || timeWindows.isEmpty())
return "";
return timeWindows.stream().map(tw -> formatTimeWindowHuman(tw))
.collect(Collectors.joining());
}
protected String formatTimeWindowNumeric(TimeWindow tw) {
String res = "";
if (tw != null) {
res = "[" + (long) tw.getStart() + "-";
if (tw.getEnd() == Double.MAX_VALUE) {
res += "";
} else {
res += (long) tw.getEnd();
}
res += "]";
}
return res;
}
protected String formatTimeWindowHuman(TimeWindow tw) {
String res = "";
if (tw != null) {
res = "[" + timeFormatter.convert((long) tw.getStart()) + "-";
if (tw.getEnd() == Double.MAX_VALUE) {
res += "";
} else {
res += timeFormatter.convert((long) tw.getEnd());
}
res += "]";
}
return res;
}
protected String formatDurationHuman(Long data) {
return durationFormatter.convert(data);
}
protected String formatTimeHuman(Long data) {
return timeFormatter.convert(data);
}
}

View file

@ -1,486 +0,0 @@
package com.graphhopper.jsprit.core.reporting;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.QuoteMode;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnAlignment;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* A text-base table formatter with extendible and configurable column set.
*
* @author balage
* @param <C>
* The context the table formatter operates. When a new row of data
* is being added, this context is passed to all column definitions
* to create cell information.
*/
public class ConfigurableTablePrinter<C extends PrinterContext> {
/**
* A row of the table.
*
* @author balage
*
*/
public class TableRow {
private String row[] = new String[tableDef.size()];
// Used by add() function to determine the next column index.
private int lastIndex = 0;
/**
* Constructor.
*/
public TableRow() {
super();
Arrays.fill(row, "");
}
/**
* Sets the value of a cell in the row.
*
* @param index
* The index of the cell.
* @param data
* The data to be formatted.
* @return The table row itself.
* @throws IndexOutOfBoundsException
* When the index is not valid.
* @throws ClassCastException
* When the column doesn't accept the data provided.
*/
public TableRow set(int index, Object data) {
if (index < 0 || index >= row.length) {
throw new IndexOutOfBoundsException("Invalid index: " + index);
}
if (data != null) {
if (!tableDef.getColumns().get(index).getType().accepts(data)) {
throw new ClassCastException("Cannot assign " + data.getClass().getSimpleName()
+ " to " + tableDef.getColumns().get(index).getType().getClass()
.getSimpleName()
+ "( index: " + index + ")");
}
}
String val = tableDef.getColumns().get(index).getType().convert(data);
row[index] = val;
return this;
}
/**
* Adds data for the next cell.
* <p>
* Note that calling the {@linkplain #set(int, Object)} doesn't alter
* the insertation point for this function.
* <p>
*
* @param data
* The data to add.
* @return The table row itself (fluent api).
* @throws IndexOutOfBoundsException
* When the index is not valid.
* @throws ClassCastException
* When the column doesn't accept the data provided.
*/
public TableRow add(Object data) {
return set(lastIndex++, data);
}
/**
* Adds data for the next cell.
* <p>
* Note that calling the {@linkplain #set(int, Object)} doesn't alter
* the insertation point for this function.
* <p>
*
* @param data
* The data to add.
* @return The table row itself (fluent api).
* @throws IndexOutOfBoundsException
* When the index is not valid.
* @throws ClassCastException
* When the column doesn't accept the data provided.
*/
public TableRow add(int data) {
return add(new Integer(data));
}
/**
* Adds data for the next cell.
* <p>
* Note that calling the {@linkplain #set(int, Object)} doesn't alter
* the insertation point for this function.
* <p>
*
* @param data
* The data to add.
* @return The table row itself (fluent api).
* @throws IndexOutOfBoundsException
* When the index is not valid.
* @throws ClassCastException
* When the column doesn't accept the data provided.
*/
public TableRow add(long data) {
return add(new Long(data));
}
/**
* Adds data for the next cell.
* <p>
* Note that calling the {@linkplain #set(int, Object)} doesn't alter
* the insertation point for this function.
* <p>
*
* @param data
* The data to add.
* @return The table row itself (fluent api).
* @throws IndexOutOfBoundsException
* When the index is not valid.
* @throws ClassCastException
* When the column doesn't accept the data provided.
*/
public TableRow add(double data) {
return add(new Double(data));
}
/**
* Adds data for the next cell.
* <p>
* Note that calling the {@linkplain #set(int, Object)} doesn't alter
* the insertation point for this function.
* <p>
*
* @param data
* The data to add.
* @return The table row itself (fluent api).
* @throws IndexOutOfBoundsException
* When the index is not valid.
* @throws ClassCastException
* When the column doesn't accept the data provided.
*/
public TableRow add(boolean data) {
return add(Boolean.valueOf(data));
}
/**
* Returns the value of a cell.
*
* @param index
* The index of the cell.
* @return The string representation of the cell.
* @throws IndexOutOfBoundsException
* When the index is not valid.
*/
public String get(int index) {
if (index < 0 || index >= row.length) {
throw new IndexOutOfBoundsException("Invalid index: " + index);
}
return row[index];
}
/**
* @return Returns the unmodifiable data of the complete row.
*/
public List<String> getAll() {
return Collections.unmodifiableList(Arrays.asList(row));
}
}
/**
* Marker row for in-table separator line.
*
* @author balage
*/
private class Separator extends TableRow {
}
// The column list
private PrinterColumnList<C> columnList;
// The table definition
private DynamicTableDefinition tableDef;
// The rows of the table
List<TableRow> rows = new ArrayList<>();
/**
* Constructor.
*
* @param columnList
* The list of the columns in the table.
*/
public ConfigurableTablePrinter(PrinterColumnList<C> columnList) {
super();
this.columnList = columnList;
tableDef = columnList.getTableDefinition();
}
/**
* Adds and populates a row.
*
* @param context
* The context to use for row cell population.
*/
public void addRow(C context) {
TableRow row = new TableRow();
columnList.populateRow(row, context);
rows.add(row);
}
/**
* Adds an in-table separator line.
*/
public void addSeparator() {
rows.add(new Separator());
}
/**
* Repeats <code>c</code> <code>w</code> times.
*
* @param c
* The character to repeat.
* @param w
* The number of occurencies to repeat.
* @return A <code>w</code> long string containing <code>c</code>
* characters.
*/
private String repeat(char c, int w) {
return CharBuffer.allocate(w).toString().replace('\0', c);
}
/**
* Prints the table into a string.
*
* @return The string representation of the table.
*/
public String print() {
StringBuilder sb = new StringBuilder();
// Calculating width of each column
int[] colWidth = calculateWidthInfo();
// The total width of the table: the sum of column width, plus the
// padding two times for each column, plus the vertical lines (column
// count plus one times)
int totalWidth = colWidth.length * (tableDef.getPadding() * 2 + 1) + 1;
for (int w : colWidth) {
totalWidth += w;
}
// Caching draw characters and padding size (for cleaner code)
char corner = tableDef.getCorner();
char horizontal = tableDef.getHorizontal();
char vertical = tableDef.getVertical();
int padding = tableDef.getPadding();
// Padding string
String paddingChars = repeat(' ', padding);
// Build the line for the separator rows
StringBuilder sbSep = new StringBuilder();
sbSep.append(corner);
for (int w : colWidth) {
sbSep.append(repeat(horizontal, w + 2 * padding)).append(corner);
}
sbSep.append("\n");
String separatorLine = sbSep.toString();
// Printing heading if defined
if (tableDef.getHeading() != null) {
sb.append(corner).append(repeat(horizontal, totalWidth - 2)).append(corner)
.append("\n");
sb.append(vertical).append(paddingChars)
.append(ColumnAlignment.LEFT.align(tableDef.getHeading(),
totalWidth - 2 * padding - 2))
.append(paddingChars)
.append(vertical)
.append("\n");
}
// Adding a separator line (either as the top line of the table or to
// separate heading)
sb.append(separatorLine);
// Printing header line
sb.append(vertical);
for (int i = 0; i < tableDef.size(); i++) {
ColumnDefinition cd = tableDef.getColumns().get(i);
sb.append(paddingChars).append(ColumnAlignment.LEFT.align(cd.getTitle(), colWidth[i]))
.append(paddingChars).append(vertical);
}
sb.append("\n");
sb.append(separatorLine);
for(TableRow row : rows) {
if (row instanceof ConfigurableTablePrinter.Separator) {
// Adding separator line
sb.append(separatorLine);
} else {
// Printing a line
sb.append(vertical);
for (int i = 0; i < tableDef.size(); i++) {
ColumnDefinition cd = tableDef.getColumns().get(i);
sb.append(paddingChars).append(cd.getAlignment().align(row.get(i), colWidth[i]))
.append(paddingChars).append(vertical);
}
sb.append("\n");
}
}
// Closing the table
sb.append(separatorLine);
return sb.toString();
}
/**
* Calculates width of each column.
*
* @return The width info for the table.
*/
private int[] calculateWidthInfo() {
int colWidth[] = new int[tableDef.size()];
// For each column
IntStream.range(0, tableDef.size()).forEach(i -> {
// Calculate maximum data width
int max = rows.stream()
.filter(r -> r instanceof ConfigurableTablePrinter.TableRow)
.map(r -> r.get(i))
.filter(d -> d != null)
.mapToInt(d -> d.length())
.max().orElse(0);
ColumnDefinition colDef = tableDef.getColumns().get(i);
// The width will be the max data or title with, bounded by the min
// and/or max column width constraints.
colWidth[i] = Math.max(colDef.getTitle().length(),
Math.max(colDef.getMinWidth(), Math.min(colDef.getMaxWidth(), max)));
});
return colWidth;
}
/**
* CSV export configuration.
*
* @author balage
*
*/
public static class CsvConfig {
private char delimiter = ';';
private char quote = '\"';
private char escape = '\\';
private boolean printHeader = true;
/**
* @return the delimeter character (cell separator)
*/
public char getDelimiter() {
return delimiter;
}
/**
* @param delimiter
* the delimeter character (cell separator)
* @return The config itself (fluent api)
*/
public CsvConfig withDelimiter(char delimiter) {
this.delimiter = delimiter;
return this;
}
/**
* @return the quote character
*/
public char getQuote() {
return quote;
}
/**
* @param quote
* the quote character
* @return The config itself (fluent api)
*/
public CsvConfig withQuote(char quote) {
this.quote = quote;
return this;
}
/**
* @return the escape character
*/
public char getEscape() {
return escape;
}
/**
* @param escape
* the escape character
* @return The config itself (fluent api)
*/
public CsvConfig withEscape(char escape) {
this.escape = escape;
return this;
}
/**
* @return whether to print header line
*/
public boolean isPrintHeader() {
return printHeader;
}
/**
* @param printHeader
* whether to print header line
* @return The config itself (fluent api)
*/
public CsvConfig withPrintHeader(boolean printHeader) {
this.printHeader = printHeader;
return this;
}
}
/**
* Exports the data of the table into a CSV formatted string
*
* @param config
* The configuration of the CSV formatting.
* @return The data in CSV format
*/
public String exportToCsv(CsvConfig config) {
CSVFormat format = CSVFormat.DEFAULT
.withDelimiter(config.delimiter)
.withQuote(config.quote)
.withQuoteMode(QuoteMode.NON_NUMERIC)
.withEscape(config.escape);
StringWriter sw = new StringWriter();
try (CSVPrinter printer = new CSVPrinter(sw, format)) {
if (config.isPrintHeader()) {
printer.printRecord(columnList.getColumns().stream()
.map(c -> c.getColumnDefinition().getTitle())
.collect(Collectors.toList()));
}
for(TableRow r : rows) {
if (!(r instanceof ConfigurableTablePrinter.Separator)) {
printer.printRecord(r.getAll());
}
}
} catch (IOException e) {
e.printStackTrace();
}
return sw.toString();
}
}

View file

@ -1,179 +0,0 @@
package com.graphhopper.jsprit.core.reporting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* Table definition form dynamic table printers (both implementations)
*
* @author balage
* @see {@linkplain DynamicTablePrinter}
* @see {@linkplain ConfigurableTablePrinter}
*/
public class DynamicTableDefinition {
/**
* Builder for the table definition.
*
* @author balage
*/
public static class Builder {
private char corner = '+';
private char vertical = '|';
private char horizontal = '-';
private String heading = null;
private List<ColumnDefinition> columns = new ArrayList<>();
private int padding = 1;
/**
* @param corner
* The corner (where vertical and horizontal lines meet)
* character.
* @return the builder
*/
public Builder withCorner(char corner) {
this.corner = corner;
return this;
}
/**
* @param vertical
* The vertical line character.
* @return the builder
*/
public Builder withVertical(char vertical) {
this.vertical = vertical;
return this;
}
/**
* @param horizontal
* The horizontal line character.
* @return the builder
*/
public Builder withHorizontal(char horizontal) {
this.horizontal = horizontal;
return this;
}
/**
* @param heading
* The heading text of the table. If not defined or null
* specified, no heading will be printed.
* @return the builder
*/
public Builder withHeading(String heading) {
this.heading = heading;
return this;
}
/**
* Adds a column for the table definition.
*
* @param column
* The column definition to add.
* @return the builder
*/
public Builder addColumn(ColumnDefinition column) {
columns.add(column);
return this;
}
/**
* @param padding
* The padding size of the table.
* @return the builder
*/
public Builder withPadding(int padding) {
this.padding = Math.max(0, padding);
return this;
}
/**
* @return The imutable table definition object.
*/
public DynamicTableDefinition build() {
return new DynamicTableDefinition(this);
}
}
private char corner = '+';
private char vertical = '|';
private char horizontal = '-';
private String heading = null;
private List<ColumnDefinition> columns = new ArrayList<>();
private int padding = 1;
/**
* Private constructor for builder.
*
* @param builder
* the builder to initialize from.
*/
private DynamicTableDefinition(Builder builder) {
corner = builder.corner;
vertical = builder.vertical;
horizontal = builder.horizontal;
heading = builder.heading;
columns = Collections.unmodifiableList(builder.columns);
padding = builder.padding;
}
/**
* @return the corner (where vertical and horizontal lines meet) character.
*/
public char getCorner() {
return corner;
}
/**
* @return the character for vertical line
*/
public char getVertical() {
return vertical;
}
/**
* @return the character for horizontal line
*/
public char getHorizontal() {
return horizontal;
}
/**
* @return the heading text
*/
public String getHeading() {
return heading;
}
/**
* @return the unmodifiable column list
*/
public List<ColumnDefinition> getColumns() {
return Collections.unmodifiableList(columns);
}
/**
* @return the padding size
*/
public int getPadding() {
return padding;
}
/**
* @return The number of columns.
*/
public int size() {
return columns.size();
}
}

View file

@ -1,210 +0,0 @@
package com.graphhopper.jsprit.core.reporting;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnAlignment;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.DoubleColumnType;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* @author balage
*/
public class DynamicTablePrinter {
public class TableRow {
private String row[] = new String[tableDef.size()];
private int lastIndex = 0;
public TableRow() {
super();
Arrays.fill(row, "");
}
public TableRow set(int index, Object data) {
if (index < 0 || index >= row.length) {
throw new IndexOutOfBoundsException("Invalid index: " + index);
}
if (data != null) {
if (!tableDef.getColumns().get(index).getType().accepts(data)) {
throw new ClassCastException("Cannot assign " + data.getClass().getSimpleName()
+ " to " + tableDef.getColumns().get(index).getType().getClass()
.getSimpleName()
+ "( index: " + index + ")");
}
}
String val = tableDef.getColumns().get(index).getType().convert(data);
row[index] = val;
return this;
}
public TableRow add(Object data) {
return set(lastIndex++, data);
}
public TableRow add(int data) {
return add(new Integer(data));
}
public TableRow add(long data) {
return add(new Long(data));
}
public TableRow add(double data) {
return add(new Double(data));
}
public TableRow add(boolean data) {
return add(Boolean.valueOf(data));
}
public String get(int index) {
if (index < 0 || index >= row.length) {
throw new IndexOutOfBoundsException("Invalid index: " + index);
}
return row[index];
}
}
private class Separator extends TableRow {
}
DynamicTableDefinition tableDef;
List<TableRow> rows = new ArrayList<>();
public DynamicTablePrinter(DynamicTableDefinition tableDef) {
this.tableDef = tableDef;
}
public TableRow addRow() {
TableRow row = new TableRow();
rows.add(row);
return row;
}
public void addSeparator() {
rows.add(new Separator());
}
private String repeat(char c, int w) {
return CharBuffer.allocate(w).toString().replace('\0', c);
}
public String print() {
StringBuilder sb = new StringBuilder();
int[] colWidth = calculateWidthInfo();
int totalWidth = colWidth.length * (tableDef.getPadding() * 2 + 1) + 1;
for (int w : colWidth) {
totalWidth += w;
}
char corner = tableDef.getCorner();
char horizontal = tableDef.getHorizontal();
char vertical = tableDef.getVertical();
int padding = tableDef.getPadding();
String paddingChars = repeat(' ', padding);
StringBuilder sbSep = new StringBuilder();
sbSep.append(corner);
for (int w : colWidth) {
sbSep.append(repeat(horizontal, w + 2 * padding)).append(corner);
}
sbSep.append("\n");
String separatorLine = sbSep.toString();
if (tableDef.getHeading() != null) {
sb.append(corner).append(repeat(horizontal, totalWidth - 2)).append(corner)
.append("\n");
sb.append(vertical).append(paddingChars)
.append(ColumnAlignment.LEFT.align(tableDef.getHeading(),
totalWidth - 2 * padding - 2))
.append(paddingChars)
.append(vertical)
.append("\n");
}
sb.append(separatorLine);
sb.append(vertical);
for (int i = 0; i < tableDef.size(); i++) {
ColumnDefinition cd = tableDef.getColumns().get(i);
sb.append(paddingChars).append(ColumnAlignment.LEFT.align(cd.getTitle(), colWidth[i]))
.append(paddingChars).append(vertical);
}
sb.append("\n");
sb.append(separatorLine);
for(TableRow row : rows) {
if (row instanceof Separator) {
sb.append(separatorLine);
} else {
sb.append(vertical);
for (int i = 0; i < tableDef.size(); i++) {
ColumnDefinition cd = tableDef.getColumns().get(i);
sb.append(paddingChars).append(cd.getAlignment().align(row.get(i), colWidth[i]))
.append(paddingChars).append(vertical);
}
sb.append("\n");
}
}
sb.append(separatorLine);
return sb.toString();
}
private int[] calculateWidthInfo() {
int colWidth[] = new int[tableDef.size()];
IntStream.range(0, tableDef.size()).forEach(i -> {
int max = rows.stream()
.filter(r -> r instanceof TableRow)
.map(r -> r.get(i))
.filter(d -> d != null)
.mapToInt(d -> d.length())
.max().orElse(0);
ColumnDefinition colDef = tableDef.getColumns().get(i);
colWidth[i] = Math.max(colDef.getTitle().length(),
Math.max(colDef.getMinWidth(), Math.min(colDef.getMaxWidth(), max)));
});
return colWidth;
}
public static void main(String[] args) {
DynamicTableDefinition td = new DynamicTableDefinition.Builder()
// .withHeading("Test")
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "string")
.build())
.addColumn(new ColumnDefinition.Builder(new StringColumnType(),
"right-string")
.withAlignment(ColumnAlignment.CENTER).build())
.addColumn(new ColumnDefinition.Builder(new DoubleColumnType(),
"double")
.withMinWidth(10)
.withAlignment(ColumnAlignment.RIGHT).build())
.build();
DynamicTablePrinter p = new DynamicTablePrinter(td);
TableRow r;
r = p.addRow();
r.add("apple");
r.add("one");
r.add(Math.PI);
r = p.addRow();
r.add("banana");
r.add("two");
r.add(2d);
p.addSeparator();
r = p.addRow();
r.add("cherry");
r.add("four");
System.out.println(p.print());
}
}

View file

@ -0,0 +1,142 @@
package com.graphhopper.jsprit.core.reporting;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import hu.vissy.texttable.dataconverter.DataConverter;
/**
* Duration formatter for human readable format.
* <p>
* The formatter uses the {@linkplain DateTimeFormatter} for time value to
* string formatting. The default format is the standard ISO time format (
* <code>"HH:mm:ss"</code>). If the input long value is X, the time value is
* calculated by adding X of the units to a predefined origin. The default unit
* is {@linkplain ChronoUnit#SECONDS}.
* </p>
*
* @author balage
*
*/
public class HumanReadableDurationFormatter implements DataConverter<Long> {
private static class UnitInfo {
private ChronoUnit unit;
private int exchange;
private String format;
private String prefix;
private String postfix;
public UnitInfo(ChronoUnit unit, int exchange, String format, String prefix,
String postfix) {
super();
this.unit = unit;
this.exchange = exchange;
this.format = format;
this.prefix = prefix;
this.postfix = postfix;
}
public ChronoUnit getUnit() {
return unit;
}
public int getExchange() {
return exchange;
}
public String getFormat() {
return format;
}
public String getPrefix() {
return prefix;
}
public String getPostfix() {
return postfix;
}
}
private static final List<UnitInfo> UNIT_INFO;
private static final Set<ChronoUnit> VALID_UNITS;
static {
UNIT_INFO = new ArrayList<>();
UNIT_INFO.add(new UnitInfo(ChronoUnit.SECONDS, 60, "%02d", ":", ""));
UNIT_INFO.add(new UnitInfo(ChronoUnit.MINUTES, 60, "%02d", ":", ""));
UNIT_INFO.add(new UnitInfo(ChronoUnit.HOURS, 24, "%02d", " ", ""));
UNIT_INFO.add(new UnitInfo(ChronoUnit.DAYS, Integer.MAX_VALUE, "%d", "", " d"));
VALID_UNITS = UNIT_INFO.stream().map(ui -> ui.getUnit()).collect(Collectors.toSet());
}
// The time unit
private ChronoUnit lowUnit = ChronoUnit.SECONDS;
// The highest unit
private ChronoUnit highUnit = ChronoUnit.DAYS;
/**
* Constructor with default settings. See
* {@linkplain HumanReadableTimeFormatter} for default values.
*/
public HumanReadableDurationFormatter() {
}
public HumanReadableDurationFormatter(ChronoUnit lowUnit, ChronoUnit highUnit) {
if (!VALID_UNITS.contains(lowUnit))
throw new IllegalArgumentException(
lowUnit + " is not allowed. Only: " + VALID_UNITS + " units allowed.");
if (!VALID_UNITS.contains(highUnit))
throw new IllegalArgumentException(
highUnit + " is not allowed. Only: " + VALID_UNITS + " units allowed.");
if (indexOf(lowUnit) > indexOf(highUnit))
throw new IllegalArgumentException(
lowUnit + " should be not higher than " + highUnit + ".");
this.lowUnit = lowUnit;
this.highUnit = highUnit;
}
private int indexOf(ChronoUnit unit) {
for (int i = 0; i < UNIT_INFO.size(); i++)
if (UNIT_INFO.get(i).getUnit().equals(unit))
return i;
throw new IllegalArgumentException("Unit " + unit + " is not valid");
}
@Override
public String convert(Long data) {
if (data == null)
return "";
else {
long val = data;
String res = "";
int i = indexOf(lowUnit);
int highIndex = indexOf(highUnit);
while (i <= highIndex) {
String s = "";
UnitInfo unitInfo = UNIT_INFO.get(i);
if (val >= unitInfo.getExchange() && i == highIndex) {
s = String.format("%d", val);
} else {
s = String.format(unitInfo.getFormat(), val % unitInfo.exchange);
}
s = s + unitInfo.getPostfix();
if (i != highIndex) {
s = unitInfo.getPrefix() + s;
}
res = s + res;
val = val / unitInfo.exchange;
i++;
}
return res;
}
}
}

View file

@ -1,4 +1,4 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
package com.graphhopper.jsprit.core.reporting;
import java.time.LocalDate;
import java.time.LocalDateTime;
@ -6,6 +6,8 @@ import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import hu.vissy.texttable.dataconverter.DataConverter;
/**
* Time value or duration formatter for human readable format.
* <p>
@ -21,7 +23,7 @@ import java.time.temporal.ChronoUnit;
* @author balage
*
*/
public class HumanReadableTimeFormatter {
public class HumanReadableTimeFormatter implements DataConverter<Long> {
// Default origin
public static final LocalDateTime DEFAULT_ORIGIN = LocalDateTime.of(LocalDate.now(), LocalTime.MIDNIGHT);
@ -91,10 +93,11 @@ public class HumanReadableTimeFormatter {
* The value to convert.
* @return The converted value.
*/
public String format(Long timeValue) {
if (timeValue == null) {
return null;
} else {
@Override
public String convert(Long timeValue) {
if (timeValue == null)
return "";
else {
LocalDateTime dt = origin.plus(timeValue, unit);
return dateFormatter.format(dt);
}

View file

@ -1,164 +0,0 @@
package com.graphhopper.jsprit.core.reporting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import com.graphhopper.jsprit.core.reporting.ConfigurableTablePrinter.TableRow;
import com.graphhopper.jsprit.core.reporting.DynamicTableDefinition.Builder;
/**
* The list of the printer columns. This helps the user to construct, manage and
* alter the column definitions. Also this function populates the
* {@linkplain TableRow}.
*
* @author balage
*
* @param <C>
* The context the colums
*/
public class PrinterColumnList<C extends PrinterContext> {
// The heading line
private String heading = null;
// The list of the columns
private List<AbstractPrinterColumn<C, ?, ?>> columns = new ArrayList<>();
/**
* The constructor to create a table without heading.
*/
public PrinterColumnList() {
super();
}
/**
* Constructor to create with heading text.
*
* @param heading
* The heading text.
*/
public PrinterColumnList(String heading) {
super();
this.heading = heading;
}
/**
* Adds a column to the column list.
*
* @param column
* The column to add.
* @return The object itself (fluent api)
*/
public PrinterColumnList<C> addColumn(AbstractPrinterColumn<C, ?,?> column) {
if (findByTitle(column.getTitle()).isPresent()) {
throw new IllegalArgumentException("Name is duplicated: " + column.getTitle());
} else {
columns.add(column);
}
return this;
}
/**
* Removes a column.
* <p>
* Requires the exact column instance that was added- Use the
* {@linkplain #findByClass(Class)} or {@linkplain #findByTitle(String)}
* functions to get the instance.
* </p>
*
* @param column
* the column to remove.
* @return true if the column was found and removed
*/
public boolean removeColumn(AbstractPrinterColumn<C, ?, ?> column) {
boolean res = columns.contains(column);
if (res) {
columns.remove(column);
}
return res;
}
/**
* Builds the table definition from the column list and other parameters.
*
* @return the table definition
*/
public DynamicTableDefinition getTableDefinition() {
Builder defBuilder = new DynamicTableDefinition.Builder();
columns.forEach(c -> defBuilder.addColumn(c.getColumnDefinition()));
defBuilder.withHeading(heading);
return defBuilder.build();
}
/**
* Populates a table row with the data extracted from the context and
* formatted by the column definition.
*
* @param row
* The row to populate. The row must match the column definition.
* @param context
* The context to work on
*/
void populateRow(ConfigurableTablePrinter<C>.TableRow row, C context) {
columns.forEach(c -> row.add(c.getData(context)));
}
/**
* @return unmodifiable list of columns
*/
public List<AbstractPrinterColumn<C, ?,?>> getColumns() {
return Collections.unmodifiableList(columns);
}
/**
* @return the heading text. Null means there will be no heading.
*/
public String getHeading() {
return heading;
}
/**
* @param heading
* The new heading text or null to remove heading.
* @return The object itself (fluent api)
*/
public PrinterColumnList<C> withHeading(String heading) {
this.heading = heading;
return this;
}
/**
* Finds the columns with the type given.
* <p>
* A table could contain more columns of the same type, so this function
* returns all matching columns.
* </p>
* <p>
* Note that this function intentially uses
* <code>getClass().equals(clazz)</code> instead of <code>instanceof</code>,
* so only the exact matches are returned. Columns of inherited classes are
* not returned.
*
* @param clazz
* The class to look for
* @return The list of all the columns with the type
*/
public List<AbstractPrinterColumn<C, ?, ?>> findByClass(Class<? extends AbstractPrinterColumn<C, ?,?>> clazz) {
return columns.stream().filter(c -> c.getClass().equals(clazz)).collect(Collectors.toList());
}
/**
* Returns the column with the title.
*
* @param title
* The title to look for
* @return The column definition if there is any match
*/
public Optional<AbstractPrinterColumn<C, ?, ?>> findByTitle(String title) {
return columns.stream().filter(c -> c.getTitle().equals(title)).findAny();
}
}

View file

@ -1,10 +0,0 @@
package com.graphhopper.jsprit.core.reporting;
/**
* Common marker interface of printer contexts
*
* @author balage
*
*/
public interface PrinterContext {
}

View file

@ -0,0 +1,122 @@
package com.graphhopper.jsprit.core.reporting;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute;
import com.graphhopper.jsprit.core.problem.solution.route.activity.JobActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
/**
* The context of the detailed route printer columns.
*
* <p>
* This is a imutable class.
* </p>
*
* @author balage
*
*/
public class RouteDeatailsRecord {
// The route itself
private VehicleRoute route;
// The current activity
private TourActivity activity;
// The problem
private VehicleRoutingProblem problem;
/**
* Constructor.
*
* @param routeNr
* route id
* @param route
* the route
* @param activity
* current activity
* @param problem
* problem
*/
public RouteDeatailsRecord(VehicleRoute route, TourActivity activity,
VehicleRoutingProblem problem) {
super();
this.route = route;
this.activity = activity;
this.problem = problem;
}
/**
* @return The route itself.
*/
public VehicleRoute getRoute() {
return route;
}
/**
* @return The current activity.
*/
public TourActivity getActivity() {
return activity;
}
/**
* @return The problem.
*/
public VehicleRoutingProblem getProblem() {
return problem;
}
public AbstractJob getJob() {
return (getActivity() instanceof JobActivity) ? ((JobActivity) getActivity()).getJob() : null;
}
public SizeDimension calculateInitialLoad() {
SizeDimension sd = SizeDimension.EMPTY;
for (TourActivity a : getRoute().getActivities()) {
sd = sd.add(a.getLoadChange());
}
sd = sd.getNegativeDimensions().abs();
return sd;
}
/**
* Returns the activity cost extracted from the context.
*
* @param context
* The context.
* @return The activity cost.
*/
double getActivityCost() {
return getProblem().getActivityCosts().getActivityCost(getActivity(),
getActivity().getArrTime(), getRoute().getDriver(), getRoute().getVehicle());
}
/**
* Returns the transport cost extracted from the
*
* @param context
* The
* @return The transport cost.
*/
double getTransportCost(TourActivity prevAct) {
return prevAct == null ? 0d
: getProblem().getTransportCosts().getTransportCost(prevAct.getLocation(),
getActivity().getLocation(), getActivity().getArrTime(),
getRoute().getDriver(), getRoute().getVehicle());
}
/**
* Returns the transport time extracted from the
*
* @param context
* The
* @return The transpoert time.
*/
double getTransportTime(TourActivity prevAct) {
return prevAct == null ? 0d
: getProblem().getTransportCosts().getTransportTime(prevAct.getLocation(),
getActivity().getLocation(), getActivity().getArrTime(),
getRoute().getDriver(), getRoute().getVehicle());
}
}

View file

@ -0,0 +1,596 @@
package com.graphhopper.jsprit.core.reporting;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import com.graphhopper.jsprit.core.problem.solution.route.activity.AbstractActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.End;
import com.graphhopper.jsprit.core.problem.solution.route.activity.JobActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.Start;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import hu.vissy.texttable.column.ColumnDefinition;
import hu.vissy.texttable.contentformatter.CellContentFormatter;
import hu.vissy.texttable.dataconverter.DataConverter;
import hu.vissy.texttable.dataconverter.NumberDataConverter;
import hu.vissy.texttable.dataextractor.StatefulDataExtractor;
public class RouteDetailsConfig extends ColumnConfigBase {
private static final String[] PRIORITY_NAMES = new String[] { "", /* 1 */ "highest",
/* 2 */ "very high", /* 3 */ "high", /* 4 */ "above medium", /* 5 */ "medium",
/* 6 */ "below medium", /* 7 */ "low", /* 8 */ "very low", /* 9 */ "extreme low",
/* 10 */ "lowest", };
private static class SizeDimensionAggregator {
SizeDimension size;
}
private static class PrevActivityHolder {
TourActivity prevAct;
}
private static class CostAggregator {
int cost;
TourActivity prevAct;
}
private static final DataConverter<SizeDimension> SIZE_DIMENSION_CONVERTER = sd -> {
if (sd != null)
return IntStream.range(0, sd.getNuOfDimensions()).mapToObj(i -> "" + sd.get(i))
.collect(Collectors.joining(", ", "[", "]"));
else
return null;
};
public enum DisplayMode {
NUMERIC {
@Override
List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> pickColumns(
ColumnDefinition<RouteDeatailsRecord, ?, ?> numeric,
ColumnDefinition<RouteDeatailsRecord, ?, ?> human) {
return Collections.singletonList(numeric);
}
},
HUMAN {
@Override
List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> pickColumns(
ColumnDefinition<RouteDeatailsRecord, ?, ?> numeric,
ColumnDefinition<RouteDeatailsRecord, ?, ?> human) {
return Collections.singletonList(human);
}
},
BOTH {
@Override
List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> pickColumns(
ColumnDefinition<RouteDeatailsRecord, ?, ?> numeric,
ColumnDefinition<RouteDeatailsRecord, ?, ?> human) {
List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> res = new ArrayList<>();
res.add(numeric);
res.add(human);
return res;
}
};
abstract List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> pickColumns(
ColumnDefinition<RouteDeatailsRecord, ?, ?> numeric,
ColumnDefinition<RouteDeatailsRecord, ?, ?> human);
}
public enum Column {
ROUTE_NUMBER {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, Integer>()
.withTitle("route")
.withDataExtractor(r -> r.getRoute().getId())
.withCellContentFormatter(CellContentFormatter.rightAlignedCell())
.withDataConverter(NumberDataConverter.defaultIntegerFormatter())
.build());
}
},
VEHICLE_NAME {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, String>()
.withTitle("vehicle")
.withDataExtractor(r -> r.getRoute().getVehicle().getId()).build());
}
},
ACTIVITY_TYPE {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, String>()
.withTitle("activity")
.withDataExtractor(
r -> ((AbstractActivity) r.getActivity()).getType())
.build());
}
},
JOB_NAME {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, String>()
.withTitle("job name").withDataExtractor(r -> {
AbstractJob job = r.getJob();
return job == null ? null : job.getId();
})
.build());
}
},
JOB_TYPE {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, String>()
.withTitle("job type").withDataExtractor(r -> {
AbstractJob job = r.getJob();
return job == null ? null : job.getClass().getSimpleName();
}).build());
}
},
JOB_PRIORITY {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
Function<RouteDeatailsRecord, Integer> dataExtractorCallback = r -> {
AbstractJob job = r.getJob();
return job == null ? null : job.getPriority();
};
return routeDetailsConfig.displayMode.pickColumns(
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, Integer>()
.withTitle("priority")
.withCellContentFormatter(CellContentFormatter.centeredCell())
.withDataExtractor(dataExtractorCallback)
.build(),
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, Integer>()
.withTitle("priority (HR)")
.withCellContentFormatter(CellContentFormatter.centeredCell())
.withDataConverter(data -> data == null ? ""
: PRIORITY_NAMES[data] + "(" + data + ")")
.withDataExtractor(dataExtractorCallback).build()
);
}
},
LOCATION {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, String>()
.withTitle("location").withDataExtractor(r -> {
TourActivity act = r.getActivity();
Location loc = act.getLocation();
return loc == null ? null : loc.getId();
}).build());
}
},
LOAD_CHANGE {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord,SizeDimension>()
.withTitle("load change")
.withDataConverter(SIZE_DIMENSION_CONVERTER)
.withDataExtractor(r -> {
TourActivity act = r.getActivity();
if (act instanceof Start)
return r.calculateInitialLoad();
else
return act.getLoadChange();
}).build());
}
},
ROUTE_LOAD {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return Collections.singletonList(
new ColumnDefinition.StatefulBuilder<RouteDeatailsRecord, SizeDimensionAggregator, SizeDimension>()
.withTitle("load").withDataConverter(SIZE_DIMENSION_CONVERTER)
.withDataExtractor(new StatefulDataExtractor<>((r, s) -> {
TourActivity act = r.getActivity();
if (act instanceof Start) {
s.size = r.calculateInitialLoad();
} else {
s.size = s.size.add(act.getLoadChange());
}
return s.size;
}, SizeDimensionAggregator::new, (k, s) -> null)).build());
}
},
TIME_WINDOWS {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
Function<RouteDeatailsRecord, Collection<TimeWindow>> dataExtractorCallback = r -> {
TourActivity act = r.getActivity();
if (act instanceof JobActivity)
return ((JobActivity) act).getTimeWindows();
else
return null;
};
return routeDetailsConfig.displayMode.pickColumns(
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, Collection<TimeWindow>>()
.withTitle("time windows").withDataConverter(
tws -> routeDetailsConfig.formatTimeWindowsNumeric(tws))
.withDataExtractor(dataExtractorCallback).build(),
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, Collection<TimeWindow>>()
.withTitle("time windows (HR)").withDataConverter(
tws -> routeDetailsConfig.formatTimeWindowsHuman(tws))
.withDataExtractor(dataExtractorCallback).build()
);
}
},
OPERATION_DURATION {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return createTimeColumns(routeDetailsConfig, "opTime",
routeDetailsConfig.getDurationFormatter(), r -> {
TourActivity act = r.getActivity();
return (long) act.getOperationTime();
});
}
},
TRAVEL_DURATION {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return createStatefulDurationColumns(routeDetailsConfig, "travel",
new StatefulDataExtractor<RouteDeatailsRecord, PrevActivityHolder, Long>(
(r, s) -> {
TourActivity act = r.getActivity();
if (act instanceof Start) {
s.prevAct = null;
}
long val = (long) (r
.getTransportTime(s.prevAct));
s.prevAct = act;
return val;
}, PrevActivityHolder::new, (k, s) -> null));
}
},
WAITING {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return createTimeColumns(routeDetailsConfig, "waitng",
routeDetailsConfig.getDurationFormatter(), (r) -> {
TourActivity act = r.getActivity();
if (act instanceof Start || act instanceof End)
return null;
else
return (long) (act.getEndTime() - act.getOperationTime()
- act.getArrTime());
});
}
},
ACTIVITY_DURATION {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return createStatefulDurationColumns(routeDetailsConfig, "duration",
new StatefulDataExtractor<RouteDeatailsRecord, PrevActivityHolder, Long>(
(r, s) -> {
TourActivity act = r.getActivity();
if (act instanceof Start) {
s.prevAct = null;
}
long val = (long) (r.getTransportTime(s.prevAct)
+ act.getOperationTime());
s.prevAct = act;
return val;
}, PrevActivityHolder::new, (k, s) -> null));
}
},
ARRIVAL_TIME {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return createTimeColumns(routeDetailsConfig, "arrival",
routeDetailsConfig.getTimeFormatter(), r -> {
TourActivity act = r.getActivity();
if (act instanceof Start)
return null;
else
return (long) act.getArrTime();
});
}
},
START_TIME {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return createTimeColumns(routeDetailsConfig, "start",
routeDetailsConfig.getTimeFormatter(), r -> {
TourActivity act = r.getActivity();
if (act instanceof End)
return null;
else
return (long) (act.getEndTime() - act.getOperationTime());
});
}
},
END_TIME {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return createTimeColumns(routeDetailsConfig, "end",
routeDetailsConfig.getTimeFormatter(), r -> {
TourActivity act = r.getActivity();
if (act instanceof End)
return null;
else
return (long) act.getEndTime();
});
}
},
SELECTED_TIME_WINDOW {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
Function<RouteDeatailsRecord, TimeWindow> dataExtractorCallback = r -> {
TourActivity act = r.getActivity();
if (act instanceof JobActivity) {
Optional<TimeWindow> optTw = ((JobActivity) act)
.getTimeWindows().stream()
.filter(tw -> tw.contains(
act.getEndTime() - act.getOperationTime()))
.findAny();
if (optTw.isPresent())
return optTw.get();
else
return null;
} else
return null;
};
return routeDetailsConfig.displayMode.pickColumns(
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, TimeWindow>()
.withTitle("selected tw")
.withDataConverter(
tw -> routeDetailsConfig.formatTimeWindowNumeric(tw))
.withDataExtractor(dataExtractorCallback).build(),
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, TimeWindow>()
.withTitle("selected tw (HR)")
.withDataConverter(
tw -> routeDetailsConfig.formatTimeWindowHuman(tw))
.withDataExtractor(dataExtractorCallback).build());
}
},
TRANSPORT_COST {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return createStatefulCostColumns(routeDetailsConfig, "transCost",
new StatefulDataExtractor<RouteDeatailsRecord, PrevActivityHolder, Integer>(
(r, s) -> {
TourActivity act = r.getActivity();
if (act instanceof Start) {
s.prevAct = null;
}
double res = r.getTransportCost(s.prevAct);
s.prevAct = act;
return (int) res;
}, PrevActivityHolder::new, (k, s) -> null));
}
},
ACTIVITY_COST {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord,Integer>()
.withTitle("actCost")
.withDataExtractor(r -> (int) r.getActivityCost()).build());
}
},
ROUTE_COST {
@Override
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig) {
return Collections.singletonList(
new ColumnDefinition.StatefulBuilder<RouteDeatailsRecord, CostAggregator, Integer>()
.withTitle("routeCost")
.withCellContentFormatter(CellContentFormatter.rightAlignedCell())
.withDataExtractor(new StatefulDataExtractor<RouteDeatailsRecord, CostAggregator, Integer>(
(r, s) -> {
TourActivity act = r.getActivity();
if (act instanceof Start) {
s.prevAct = null;
s.cost = 0;
}
Double trCost = r.getTransportCost(s.prevAct);
s.prevAct = act;
if (trCost != null) {
s.cost += trCost;
}
s.cost += r.getActivityCost();
return s.cost;
}, CostAggregator::new, (k, s) -> null)
).build());
}
},
;
public abstract List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createColumns(
RouteDetailsConfig routeDetailsConfig);
private static List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createTimeColumns(
RouteDetailsConfig routeDetailsConfig, String title, DataConverter<Long> converter,
Function<RouteDeatailsRecord, Long> getter) {
return routeDetailsConfig.displayMode.pickColumns(
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, Long>()
.withTitle(title).withDataExtractor(getter)
.withCellContentFormatter(CellContentFormatter.rightAlignedCell())
.build(),
new ColumnDefinition.StatelessBuilder<RouteDeatailsRecord, Long>()
.withTitle(title + " (HR)")
.withCellContentFormatter(CellContentFormatter.rightAlignedCell())
.withDataConverter(converter)
.withDataExtractor(getter).build());
}
private static List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createStatefulDurationColumns(
RouteDetailsConfig routeDetailsConfig, String title,
StatefulDataExtractor<RouteDeatailsRecord, PrevActivityHolder, Long> getter) {
return routeDetailsConfig.displayMode.pickColumns(
new ColumnDefinition.StatefulBuilder<RouteDeatailsRecord, PrevActivityHolder, Long>()
.withTitle(title)
.withCellContentFormatter(CellContentFormatter.rightAlignedCell())
.withDataExtractor(getter)
.build(),
new ColumnDefinition.StatefulBuilder<RouteDeatailsRecord, PrevActivityHolder, Long>()
.withTitle(title+" (HR)")
.withCellContentFormatter(CellContentFormatter.rightAlignedCell())
.withDataConverter(dur -> routeDetailsConfig.formatDurationHuman(dur))
.withDataExtractor(getter)
.build());
}
private static List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> createStatefulCostColumns(
RouteDetailsConfig routeDetailsConfig, String title,
StatefulDataExtractor<RouteDeatailsRecord, PrevActivityHolder, Integer> getter) {
return Collections.singletonList(
new ColumnDefinition.StatefulBuilder<RouteDeatailsRecord, PrevActivityHolder, Integer>()
.withTitle(title)
.withCellContentFormatter(CellContentFormatter.rightAlignedCell())
.withDataExtractor(getter).build());
}
}
public static class Builder {
private LocalDateTime humanReadableOrigin = LocalDateTime.of(LocalDate.now(),
LocalTime.MIDNIGHT);
private DisplayMode displayMode = DisplayMode.NUMERIC;
private List<Column> columns;
private ChronoUnit lowUnit = ChronoUnit.SECONDS;
private ChronoUnit highUnit = ChronoUnit.HOURS;
public Builder() {
this.columns = new ArrayList<>();
}
public Builder withHumanReadableOrigin(LocalDateTime humanReadableOrigin) {
this.humanReadableOrigin = humanReadableOrigin;
return this;
}
public Builder withTimeDisplayMode(DisplayMode displayMode) {
this.displayMode = displayMode;
return this;
}
public Builder withLowUnit(ChronoUnit lowUnit) {
this.lowUnit = lowUnit;
return this;
}
public Builder withHighUnit(ChronoUnit highUnit) {
this.highUnit = highUnit;
return this;
}
public Builder withColumn(Column columns) {
this.columns.add(columns);
return this;
}
public Builder withColumns(Column... columns) {
for (Column c : columns) {
withColumn(c);
}
return this;
}
public RouteDetailsConfig build() {
return new RouteDetailsConfig(this);
}
}
private DisplayMode displayMode;
private List<Column> columns;
private RouteDetailsConfig(Builder builder) {
this.displayMode = builder.displayMode;
this.columns = builder.columns;
setTimeFormatter(
new HumanReadableTimeFormatter(builder.humanReadableOrigin, builder.lowUnit));
setDurationFormatter(new HumanReadableDurationFormatter(builder.lowUnit, builder.highUnit));
}
public DisplayMode getDisplayMode() {
return displayMode;
}
public List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> getColumns() {
List<ColumnDefinition<RouteDeatailsRecord, ?, ?>> columns = new ArrayList<>();
this.columns.forEach(c -> columns.addAll(c.createColumns(this)));
return columns;
}
}

View file

@ -19,31 +19,18 @@ package com.graphhopper.jsprit.core.reporting;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.graphhopper.jsprit.core.algorithm.objectivefunction.ComponentValue;
import com.graphhopper.jsprit.core.algorithm.objectivefunction.RouteLevelComponentValue;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem.FleetSize;
import com.graphhopper.jsprit.core.problem.job.Break;
import com.graphhopper.jsprit.core.problem.job.Job;
import com.graphhopper.jsprit.core.problem.job.ServiceJob;
import com.graphhopper.jsprit.core.problem.job.ShipmentJob;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute;
import com.graphhopper.jsprit.core.problem.solution.route.activity.JobActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.ConfigurableTablePrinter.CsvConfig;
import com.graphhopper.jsprit.core.reporting.DynamicTableDefinition.Builder;
import com.graphhopper.jsprit.core.reporting.DynamicTablePrinter.TableRow;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnAlignment;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.DoubleColumnType;
import com.graphhopper.jsprit.core.reporting.columndefinition.SolutionPrintColumnLists;
import com.graphhopper.jsprit.core.reporting.columndefinition.SolutionPrintColumnLists.PredefinedList;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
import com.graphhopper.jsprit.core.reporting.route.RoutePrinterContext;
import com.graphhopper.jsprit.core.reporting.vehicle.VehicleSummaryContext;
/**
@ -69,6 +56,19 @@ public class SolutionPrinter {
CONCISE, VERBOSE
}
private static class Jobs {
int nServices;
int nShipments;
int nBreaks;
public Jobs(int nServices, int nShipments, int nBreaks) {
super();
this.nServices = nServices;
this.nShipments = nShipments;
this.nBreaks = nBreaks;
}
}
/**
* Prints costs and #vehicles to stdout (out.println).
@ -102,329 +102,123 @@ public class SolutionPrinter {
SYSTEM_OUT_AS_PRINT_WRITER.flush();
}
public static void print(PrintWriter out, VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution, Print print) {
print(out, problem, solution, print, SolutionPrintColumnLists.getNumeric(PredefinedList.DEFAULT));
}
public static void print(VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution,
PrinterColumnList<RoutePrinterContext> verbosePrintColumns) {
print(SYSTEM_OUT_AS_PRINT_WRITER, problem, solution, verbosePrintColumns);
SYSTEM_OUT_AS_PRINT_WRITER.flush();
}
public static void print(PrintWriter out, VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution,
PrinterColumnList<RoutePrinterContext> verbosePrintColumns) {
print(out, problem, solution, Print.VERBOSE, verbosePrintColumns);
}
/**
* Prints costs and #vehicles to the given writer
*
* @param out the destination writer
* @param solution the solution to be printed
*/
public static void print(PrintWriter out, VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution, Print print,
PrinterColumnList<RoutePrinterContext> verbosePrintColumns) {
public static void print(PrintWriter out, VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution, Print print) {
String leftAlign = "| %-13s | %-8s | %n";
DynamicTableDefinition problemTableDef = new DynamicTableDefinition.Builder()
.withHeading("Problem")
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "indicator")
.build())
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "value")
.build())
.build();
out.format("+--------------------------+%n");
out.printf("| problem |%n");
out.format("+---------------+----------+%n");
out.printf("| indicator | value |%n");
out.format("+---------------+----------+%n");
DynamicTablePrinter problemTablePrinter = new DynamicTablePrinter(problemTableDef);
problemTablePrinter.addRow().add("fleetsize").add(problem.getFleetSize());
problemTablePrinter.addSeparator();
problemTablePrinter.addRow().add("noJobs").add(problem.getJobs().values().size());
for (Entry<Class<? extends Job>, Long> jc : getNuOfJobs(problem).entrySet()) {
problemTablePrinter.addRow().add(" " + jc.getKey().getSimpleName())
.add(jc.getValue());
}
out.println(problemTablePrinter.print());
out.format(leftAlign, "noJobs", problem.getJobs().values().size());
Jobs jobs = getNuOfJobs(problem);
out.format(leftAlign, "noServices", jobs.nServices);
out.format(leftAlign, "noShipments", jobs.nShipments);
out.format(leftAlign, "noBreaks", jobs.nBreaks);
out.format(leftAlign, "fleetsize", problem.getFleetSize().toString());
out.format("+--------------------------+%n");
DynamicTableDefinition solutionTableDef = new DynamicTableDefinition.Builder()
.withHeading("Solution")
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "indicator")
.build())
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "value")
.build())
.build();
DynamicTablePrinter solutionTablePrinter = new DynamicTablePrinter(solutionTableDef);
solutionTablePrinter.addRow().add("costs")
.add(String.format("%6.2f", solution.getCost()).trim());
solutionTablePrinter.addRow().add("noVehicles").add(solution.getRoutes().size());
solutionTablePrinter.addRow().add("unassgndJobs").add(solution.getUnassignedJobs().size());
out.println(solutionTablePrinter.print());
String leftAlignSolution = "| %-13s | %-40s | %n";
out.format("+----------------------------------------------------------+%n");
out.printf("| solution |%n");
out.format("+---------------+------------------------------------------+%n");
out.printf("| indicator | value |%n");
out.format("+---------------+------------------------------------------+%n");
out.format(leftAlignSolution, "costs", solution.getCost());
out.format(leftAlignSolution, "noVehicles", solution.getRoutes().size());
out.format(leftAlignSolution, "unassgndJobs", solution.getUnassignedJobs().size());
out.format("+----------------------------------------------------------+%n");
if (print.equals(Print.VERBOSE)) {
printVerbose(out, problem, solution, verbosePrintColumns);
printVerbose(out, problem, solution);
}
}
private static void printVerbose(PrintWriter out, VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution,
PrinterColumnList<RoutePrinterContext> columns) {
ConfigurableTablePrinter<RoutePrinterContext> tablePrinter = buildRouteDetailsTable(problem, solution, columns);
out.println(tablePrinter.print());
if (!solution.getUnassignedJobs().isEmpty()) {
DynamicTableDefinition unassignedTableDef = new DynamicTableDefinition.Builder().withHeading("Unassigned jobs")
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "id").withMinWidth(10).build())
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "type").build()).build();
DynamicTablePrinter unassignedTablePrinter = new DynamicTablePrinter(unassignedTableDef);
for (Job j : solution.getUnassignedJobs()) {
unassignedTablePrinter.addRow().add(j.getId()).add(j.getClass().getSimpleName());
}
out.println(unassignedTablePrinter.print());
}
}
private static Map<Class<? extends Job>, Long> getNuOfJobs(VehicleRoutingProblem problem) {
return problem.getJobs().values().stream()
.map(j -> (Class<? extends Job>) j.getClass())
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}
// New print functions (TODO old ones should be migrated into these)
// ----------------------------------------------------------
/**
* Prints costs and #vehicles to the given writer
*
* @param out
* the destination writer
* @param solution
* the solution to be printed
* @return
*/
public static void printSummary(VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution) {
printSummary(SYSTEM_OUT_AS_PRINT_WRITER, problem, solution);
private static void printVerbose(VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution) {
printVerbose(SYSTEM_OUT_AS_PRINT_WRITER, problem, solution);
SYSTEM_OUT_AS_PRINT_WRITER.flush();
}
public static void printSummary(PrintWriter out, VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution) {
DynamicTableDefinition problemTableDef = new DynamicTableDefinition.Builder()
.withHeading("Problem")
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "indicator")
.build())
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "value")
.build())
.build();
DynamicTablePrinter problemTablePrinter = new DynamicTablePrinter(problemTableDef);
problemTablePrinter.addRow().add("fleetsize").add(problem.getFleetSize());
problemTablePrinter.addRow().add("maxNoVehicles")
.add(problem.getFleetSize() == FleetSize.FINITE ? problem.getVehicles().size() : "unlimited");
problemTablePrinter.addSeparator();
problemTablePrinter.addRow().add("noJobs").add(problem.getJobs().values().size());
for (Entry<Class<? extends Job>, Long> jc : getNuOfJobs(problem).entrySet()) {
problemTablePrinter.addRow().add(" " + jc.getKey().getSimpleName())
.add(jc.getValue());
}
out.println(problemTablePrinter.print());
DynamicTableDefinition solutionTableDef = new DynamicTableDefinition.Builder()
.withHeading("Solution")
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "indicator")
.build())
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "value")
.build())
.build();
DynamicTablePrinter solutionTablePrinter = new DynamicTablePrinter(solutionTableDef);
solutionTablePrinter.addRow().add("costs")
.add(String.format("%6.2f", solution.getCost()).trim());
solutionTablePrinter.addRow().add("noVehicles").add(solution.getRoutes().size());
solutionTablePrinter.addRow().add("unassignedJobs").add(solution.getUnassignedJobs().size());
out.println(solutionTablePrinter.print());
}
// ----------------------------------------------------------
public static void printCostDetails(VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution) {
printCostDetails(SYSTEM_OUT_AS_PRINT_WRITER, problem, solution);
SYSTEM_OUT_AS_PRINT_WRITER.flush();
}
public static void printCostDetails(PrintWriter out, VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution) {
if (solution.getDetailedCost() == null) {
out.println("No detailed cost info available.");
return;
}
DynamicTableDefinition compomentTableDef = new DynamicTableDefinition.Builder()
.withHeading("Cost components")
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "component id")
.build())
.addColumn(new ColumnDefinition.Builder(new DoubleColumnType(), "value")
.withAlignment(ColumnAlignment.RIGHT).build())
.addColumn(new ColumnDefinition.Builder(new DoubleColumnType(), "weight")
.withAlignment(ColumnAlignment.RIGHT).build())
.addColumn(new ColumnDefinition.Builder(new DoubleColumnType(), "weighted value")
.withAlignment(ColumnAlignment.RIGHT).build())
.build();
DynamicTablePrinter componentTablePrinter = new DynamicTablePrinter(compomentTableDef);
for (ComponentValue cv : solution.getDetailedCost()) {
componentTablePrinter.addRow().add(cv.getKey()).add(cv.getValue()).add(cv.getWeight()).add(cv.getWeightedValue());
}
out.println(componentTablePrinter.print());
Builder routeLevelTableDefBuilder = new DynamicTableDefinition.Builder()
.withHeading("Route level costs (weighted)")
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "Route")
.build());
DynamicTableDefinition routeLevelTableDef = routeLevelTableDefBuilder.build();
for (ComponentValue cv : solution.getDetailedCost()) {
if (cv instanceof RouteLevelComponentValue) {
routeLevelTableDefBuilder.addColumn(new ColumnDefinition.Builder(new DoubleColumnType(), cv.getKey())
.withAlignment(ColumnAlignment.RIGHT).build());
}
}
routeLevelTableDefBuilder.addColumn(new ColumnDefinition.Builder(new DoubleColumnType(), "Total").build());
DynamicTablePrinter routeLevelTablePrinter = new DynamicTablePrinter(routeLevelTableDef);
TableRow row;
// row = routeLevelTablePrinter.addRow().add("Weight");
// for (ComponentValue cv : solution.getDetailedCost()) {
// if (cv instanceof RouteLevelComponentValue) {
// row.add(cv.getWeight());
// }
// }
// routeLevelTablePrinter.addSeparator();
for (VehicleRoute r : solution.getRoutes()) {
row = routeLevelTablePrinter.addRow().add(r.getId());
double sum = 0d;
for (ComponentValue cv : solution.getDetailedCost()) {
if (cv instanceof RouteLevelComponentValue) {
Double val = ((RouteLevelComponentValue) cv).getRouteValue(r.getId()).orElse(null);
if (val != null) {
val *= cv.getWeight();
}
sum += val;
row.add(val);
}
}
row.add(sum);
}
routeLevelTablePrinter.addSeparator();
row = routeLevelTablePrinter.addRow().add("Total");
double sum = 0d;
for (ComponentValue cv : solution.getDetailedCost()) {
if (cv instanceof RouteLevelComponentValue) {
Double val = ((RouteLevelComponentValue) cv).getValue();
if (val != null) {
val *= cv.getWeight();
}
sum += val;
row.add(val);
}
}
row.add(sum);
out.println(routeLevelTablePrinter.print());
}
// ----------------------------------------------------------
public static void printRouteDetails(VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution,
PrinterColumnList<RoutePrinterContext> columns) {
printRouteDetails(SYSTEM_OUT_AS_PRINT_WRITER, problem, solution, columns);
SYSTEM_OUT_AS_PRINT_WRITER.flush();
}
public static void printRouteDetails(PrintWriter out, VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution,
PrinterColumnList<RoutePrinterContext> columns) {
ConfigurableTablePrinter<RoutePrinterContext> tablePrinter = buildRouteDetailsTable(problem, solution, columns);
out.println(tablePrinter.print());
if (!solution.getUnassignedJobs().isEmpty()) {
DynamicTableDefinition unassignedTableDef = new DynamicTableDefinition.Builder().withHeading("Unassigned jobs")
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "id").withMinWidth(10).build())
.addColumn(new ColumnDefinition.Builder(new StringColumnType(), "type").build()).build();
DynamicTablePrinter unassignedTablePrinter = new DynamicTablePrinter(unassignedTableDef);
for (Job j : solution.getUnassignedJobs()) {
unassignedTablePrinter.addRow().add(j.getId()).add(j.getClass().getSimpleName());
}
out.println(unassignedTablePrinter.print());
}
}
protected static ConfigurableTablePrinter<RoutePrinterContext> buildRouteDetailsTable(VehicleRoutingProblem problem,
VehicleRoutingProblemSolution solution, PrinterColumnList<RoutePrinterContext> columns) {
ConfigurableTablePrinter<RoutePrinterContext> tablePrinter = new ConfigurableTablePrinter<>(columns);
private static void printVerbose(PrintWriter out, VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution) {
String leftAlgin = "| %-7s | %-20s | %-21s | %-15s | %-15s | %-15s | %-15s |%n";
out.format("+--------------------------------------------------------------------------------------------------------------------------------+%n");
out.printf("| detailed solution |%n");
out.format("+---------+----------------------+-----------------------+-----------------+-----------------+-----------------+-----------------+%n");
out.printf("| route | vehicle | activity | job | arrTime | endTime | costs |%n");
int routeNu = 1;
List<VehicleRoute> list = new ArrayList<>(solution.getRoutes());
Collections.sort(list , new com.graphhopper.jsprit.core.util.VehicleIndexComparator());
for (VehicleRoute route : list) {
if (route.getId() != 1) {
tablePrinter.addSeparator();
}
RoutePrinterContext context = new RoutePrinterContext(route, route.getStart(), problem);
tablePrinter.addRow(context);
out.format("+---------+----------------------+-----------------------+-----------------+-----------------+-----------------+-----------------+%n");
double costs = 0;
out.format(leftAlgin, routeNu, getVehicleString(route), route.getStart().getName(), "-", "undef", Math.round(route.getStart().getEndTime()),
Math.round(costs));
TourActivity prevAct = route.getStart();
for (TourActivity act : route.getActivities()) {
context.setActivity(act);
tablePrinter.addRow(context);
String jobId;
if (act instanceof JobActivity) {
jobId = ((JobActivity) act).getJob().getId();
} else {
jobId = "-";
}
double c = problem.getTransportCosts().getTransportCost(prevAct.getLocation(), act.getLocation(), prevAct.getEndTime(), route.getDriver(),
route.getVehicle());
c += problem.getActivityCosts().getActivityCost(act, act.getArrTime(), route.getDriver(), route.getVehicle());
costs += c;
out.format(leftAlgin, routeNu, getVehicleString(route), act.getName(), jobId, Math.round(act.getArrTime()),
Math.round(act.getEndTime()), Math.round(costs));
prevAct = act;
}
context.setActivity(route.getEnd());
tablePrinter.addRow(context);
double c = problem.getTransportCosts().getTransportCost(prevAct.getLocation(), route.getEnd().getLocation(), prevAct.getEndTime(),
route.getDriver(), route.getVehicle());
c += problem.getActivityCosts().getActivityCost(route.getEnd(), route.getEnd().getArrTime(), route.getDriver(), route.getVehicle());
costs += c;
out.format(leftAlgin, routeNu, getVehicleString(route), route.getEnd().getName(), "-", Math.round(route.getEnd().getArrTime()), "undef",
Math.round(costs));
routeNu++;
}
return tablePrinter;
}
public static String exportRouteDetails(VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution,
PrinterColumnList<RoutePrinterContext> columns, CsvConfig csvConfig) {
ConfigurableTablePrinter<RoutePrinterContext> table = buildRouteDetailsTable(problem, solution, columns);
return table.exportToCsv(csvConfig);
}
// ----------------------------------------------------------
public static void printVehicleSummary(VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution,
PrinterColumnList<VehicleSummaryContext> columns) {
printVehicleSummary(SYSTEM_OUT_AS_PRINT_WRITER, problem, solution, columns);
SYSTEM_OUT_AS_PRINT_WRITER.flush();
}
public static void printVehicleSummary(PrintWriter out, VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution,
PrinterColumnList<VehicleSummaryContext> columns) {
ConfigurableTablePrinter<VehicleSummaryContext> vehicleTablePrinter = buildVehicleSummaryTable(problem, solution, columns);
out.println(vehicleTablePrinter.print());
}
public static String exportVehicleSummary(VehicleRoutingProblem problem, VehicleRoutingProblemSolution solution,
PrinterColumnList<VehicleSummaryContext> columns, CsvConfig csvConfig) {
ConfigurableTablePrinter<VehicleSummaryContext> table = buildVehicleSummaryTable(problem, solution, columns);
return table.exportToCsv(csvConfig);
}
protected static ConfigurableTablePrinter<VehicleSummaryContext> buildVehicleSummaryTable(VehicleRoutingProblem problem,
VehicleRoutingProblemSolution solution, PrinterColumnList<VehicleSummaryContext> columns) {
ConfigurableTablePrinter<VehicleSummaryContext> vehicleTablePrinter = new ConfigurableTablePrinter<>(columns);
List<VehicleRoute> list = solution.getRoutes();
for (VehicleRoute route : list) {
vehicleTablePrinter.addRow(new VehicleSummaryContext(route, problem));
out.format("+--------------------------------------------------------------------------------------------------------------------------------+%n");
if (!solution.getUnassignedJobs().isEmpty()) {
out.format("+----------------+%n");
out.format("| unassignedJobs |%n");
out.format("+----------------+%n");
String unassignedJobAlgin = "| %-14s |%n";
for (Job j : solution.getUnassignedJobs()) {
out.format(unassignedJobAlgin, j.getId());
}
out.format("+----------------+%n");
}
return vehicleTablePrinter;
}
private static String getVehicleString(VehicleRoute route) {
return route.getVehicle().getId();
}
private static Jobs getNuOfJobs(VehicleRoutingProblem problem) {
int nShipments = 0;
int nServices = 0;
int nBreaks = 0;
for (Job j : problem.getJobs().values()) {
if (j instanceof ShipmentJob) {
nShipments++;
}
if (j instanceof ServiceJob) {
nServices++;
}
if (j instanceof Break) {
nBreaks++;
}
}
return new Jobs(nServices, nShipments, nBreaks);
}
}

View file

@ -0,0 +1,249 @@
package com.graphhopper.jsprit.core.reporting;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.graphhopper.jsprit.core.algorithm.objectivefunction.ComponentValue;
import com.graphhopper.jsprit.core.algorithm.objectivefunction.RouteLevelComponentValue;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem.FleetSize;
import com.graphhopper.jsprit.core.problem.job.Job;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.RouteDetailsConfig.Column;
import com.graphhopper.jsprit.core.reporting.RouteDetailsConfig.DisplayMode;
import hu.vissy.texttable.BorderFormatter;
import hu.vissy.texttable.BorderFormatter.DefaultFormatters;
import hu.vissy.texttable.TableFormatter;
import hu.vissy.texttable.TableFormatter.Builder;
import hu.vissy.texttable.column.ColumnDefinition;
import hu.vissy.texttable.contentformatter.CellContentFormatter;
import hu.vissy.texttable.dataconverter.NumberDataConverter;
import hu.vissy.texttable.dataextractor.StatefulDataExtractor;
public class SolutionPrinter2 {
private static class Entry {
String key;
String value;
public Entry(String key, Object value) {
super();
this.key = key;
this.value = "" + value;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
}
private static class Aggregator {
double sum;
}
// Wrapping System.out into a PrintWriter
private static final PrintWriter SYSTEM_OUT_AS_PRINT_WRITER = new PrintWriter(System.out);
public static void print(VehicleRoutingProblem problem,
VehicleRoutingProblemSolution solution) {
print(SYSTEM_OUT_AS_PRINT_WRITER, problem, solution);
}
public static void print(PrintWriter out, VehicleRoutingProblem problem,
VehicleRoutingProblemSolution solution) {
printProblemTable(out, problem);
printSolutionSummary(out, solution);
printCostDetails(out, solution);
printRouteDetails(out, problem, solution);
printVehicleSummary(out, problem, solution);
out.flush();
}
private static void printProblemTable(PrintWriter out, VehicleRoutingProblem problem) {
TableFormatter<Entry> problemTableDef = createKeyValueTable("Problem");
List<Entry> data = new ArrayList<>();
data.add(new Entry("fleetsize", problem.getFleetSize()));
data.add(new Entry("maxNoVehicles", problem.getFleetSize() == FleetSize.FINITE
? problem.getVehicles().size() : "unlimited"));
data.add(null);
data.add(new Entry("noJobs", problem.getJobs().values().size()));
for (Map.Entry<Class<? extends Job>, Long> jc : getNuOfJobs(problem).entrySet()) {
data.add(new Entry(" " + jc.getKey().getSimpleName(), jc.getValue()));
}
out.println(problemTableDef.apply(data));
}
private static void printSolutionSummary(PrintWriter out,
VehicleRoutingProblemSolution solution) {
TableFormatter<Entry> problemTableDef = createKeyValueTable("Solution");
List<Entry> data = new ArrayList<>();
data.add(new Entry("costs", String.format("%6.2f", solution.getCost()).trim()));
data.add(new Entry("noVehicles", solution.getRoutes().size()));
data.add(new Entry("unassignedJobs", solution.getUnassignedJobs().size()));
out.println(problemTableDef.apply(data));
}
private static void printCostDetails(PrintWriter out, VehicleRoutingProblemSolution solution) {
printCostComponents(out, solution);
printPerRouteCosts(out, solution);
}
private static void printCostComponents(PrintWriter out,
VehicleRoutingProblemSolution solution) {
TableFormatter<ComponentValue> tableDef = new TableFormatter.Builder<ComponentValue>()
.withBorderFormatter(new BorderFormatter.Builder(
DefaultFormatters.ASCII_LINEDRAW).build())
.withHeading("Cost components")
.withColumn(ColumnDefinition.<ComponentValue, String>createSimpleStateless(
"component id", c -> c.getKey()))
.withColumn(ColumnDefinition.<ComponentValue, Double>createSimpleStateless(
"value", c -> c.getValue()))
.withColumn(ColumnDefinition.<ComponentValue, Double>createSimpleStateless(
"weight", c -> c.getWeight()))
.withColumn(ColumnDefinition.<ComponentValue, Double>createSimpleStateless(
"weighted value", c -> c.getWeightedValue()))
.build();
out.println(tableDef.apply(solution.getDetailedCost()));
}
private static void printPerRouteCosts(PrintWriter out,
VehicleRoutingProblemSolution solution) {
Builder<ComponentValue> builder = new TableFormatter.Builder<ComponentValue>()
.withBorderFormatter(new BorderFormatter.Builder(
DefaultFormatters.ASCII_LINEDRAW).build())
.withHeading("Route level costs (weighted)")
.withShowAggregation(true)
.withColumn(new ColumnDefinition.StatelessBuilder<ComponentValue, String>()
.withTitle("component id").withDataExtractor(c -> c.getKey())
.withAggregateRowConstant("Total").build());
for (VehicleRoute r : solution.getRoutes()) {
builder.withColumn(
new ColumnDefinition.StatefulBuilder<ComponentValue, Aggregator, Double>()
.withTitle("Route " + r.getId())
.withCellContentFormatter(CellContentFormatter.rightAlignedCell())
.withDataConverter(NumberDataConverter.defaultDoubleFormatter())
.withDataExtractor(new StatefulDataExtractor<>((cv, agg) -> {
Double val = ((RouteLevelComponentValue) cv)
.getRouteValue(r.getId()).orElse(null);
if (val != null) {
agg.sum += val;
}
return val;
}, Aggregator::new, (k, agg) -> agg.sum))
.build());
}
TableFormatter<ComponentValue> tableDef = builder.build();
out.println(tableDef.apply(solution.getDetailedCost().stream()
.filter(cv -> cv instanceof RouteLevelComponentValue)
.collect(Collectors.toList())));
}
private static TableFormatter<Entry> createKeyValueTable(String heading) {
TableFormatter<Entry> problemTableDef = new TableFormatter.Builder<Entry>()
.withBorderFormatter(new BorderFormatter.Builder(
DefaultFormatters.ASCII_LINEDRAW).build())
.withHeading(heading)
.withColumn(ColumnDefinition.<Entry, String>createSimpleStateless("key",
en -> en.getKey()))
.withColumn(ColumnDefinition.<Entry, String>createSimpleStateless("value",
en -> en.getValue()))
.build();
return problemTableDef;
}
private static Map<Class<? extends Job>, Long> getNuOfJobs(VehicleRoutingProblem problem) {
return problem.getJobs().values().stream().map(j -> (Class<? extends Job>) j.getClass())
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}
private static void printRouteDetails(PrintWriter out, VehicleRoutingProblem problem,
VehicleRoutingProblemSolution solution) {
printUnassignedJobs(out, solution);
printRouteData(out, problem, solution);
}
private static void printUnassignedJobs(PrintWriter out,
VehicleRoutingProblemSolution solution) {
if (solution.getUnassignedJobs().isEmpty())
return;
}
private static void printRouteData(PrintWriter out, VehicleRoutingProblem problem,
VehicleRoutingProblemSolution solution) {
Builder<RouteDeatailsRecord> builder = new TableFormatter.Builder<RouteDeatailsRecord>()
.withBorderFormatter(
new BorderFormatter.Builder(DefaultFormatters.ASCII_LINEDRAW).build())
.withHeading("Route details");
new RouteDetailsConfig.Builder().withTimeDisplayMode(DisplayMode.BOTH)
.withColumns(Column.values()).build().getColumns()
.forEach(c -> builder.withColumn(c));
TableFormatter<RouteDeatailsRecord> tableDef = builder.build();
List<RouteDeatailsRecord> data = new ArrayList<>();
for (VehicleRoute route : new ArrayList<>(solution.getRoutes())) {
if (!data.isEmpty()) {
data.add(null);
}
data.add(new RouteDeatailsRecord(route, route.getStart(), problem));
for (TourActivity act : route.getActivities()) {
data.add(new RouteDeatailsRecord(route, act, problem));
}
data.add(new RouteDeatailsRecord(route, route.getEnd(), problem));
}
out.println(tableDef.apply(data));
}
private static void printVehicleSummary(PrintWriter out, VehicleRoutingProblem problem,
VehicleRoutingProblemSolution solution) {
Builder<VehicleSummaryRecord> builder = new TableFormatter.Builder<VehicleSummaryRecord>()
.withBorderFormatter(
new BorderFormatter.Builder(DefaultFormatters.ASCII_LINEDRAW).build())
.withHeading("Vehicle summary");
new VehicleSummaryConfig.Builder()
.withColumns(VehicleSummaryConfig.Column.values())
.build()
.getColumns()
.forEach(c -> builder.withColumn(c));
TableFormatter<VehicleSummaryRecord> tableDef = builder.build();
List<VehicleSummaryRecord> data = solution.getRoutes().stream()
.map(r -> new VehicleSummaryRecord(r, problem))
.collect(Collectors.toList());
out.println(tableDef.apply(data));
}
}

View file

@ -0,0 +1,420 @@
package com.graphhopper.jsprit.core.reporting;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
import hu.vissy.texttable.column.ColumnDefinition;
import hu.vissy.texttable.contentformatter.CellContentFormatter;
import hu.vissy.texttable.dataconverter.NumberDataConverter;
import hu.vissy.texttable.dataconverter.StringDataConverter;
public class VehicleSummaryConfig extends ColumnConfigBase {
public static final EnumSet<DisplayMode> MODE_SET_HUMAN = EnumSet.<DisplayMode>of(
DisplayMode.NUMERIC, DisplayMode.HUMAN_READABLE);
public static final EnumSet<DisplayMode> MODE_SET_ALL = EnumSet.<DisplayMode>of(
DisplayMode.NUMERIC, DisplayMode.HUMAN_READABLE, DisplayMode.PERCENT_ROUTE,
DisplayMode.PERCENT_SHIFT);
public enum DisplayMode {
GENERIC(""), NUMERIC(""), HUMAN_READABLE(" (H)"), PERCENT_ROUTE(" (R%)"), PERCENT_SHIFT(
" (S%)");
private String postfix;
private DisplayMode(String postfix) {
this.postfix = postfix;
}
public String getPostfix() {
return postfix;
}
}
public enum Column {
ROUTE_NUMBER(null) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, Integer>()
.withTitle("route #")
.withDataExtractor(r -> r.getRouteNr())
.withCellContentFormatter(CellContentFormatter.rightAlignedCell())
.withDataConverter(NumberDataConverter.defaultIntegerFormatter())
.build());
}
},
VEHICLE_NAME(null) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, String>()
.withTitle("vehicle")
.withDataExtractor(r -> r.getVehicle().getId())
.withDataConverter(new StringDataConverter())
.build());
}
},
VEHICLE_TYPE(null) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, String>()
.withTitle("vehicle type")
.withDataExtractor(r -> r.getVehicle().getType().getTypeId())
.withDataConverter(new StringDataConverter())
.build());
}
},
DRIVER(null) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, String>()
.withTitle("driver")
.withDataExtractor(r -> r.getDriver().getId())
.withDataConverter(new StringDataConverter())
.build());
}
},
ACTIVITY_COUNT(null) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, Integer>()
.withTitle("act count")
.withDataExtractor(r -> r.getActivityCount())
.withCellContentFormatter(CellContentFormatter.rightAlignedCell())
.withDataConverter(NumberDataConverter.defaultIntegerFormatter())
.build());
}
},
ACTIVITY_COUNT_BY_TYPE(null) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, Map<String, Integer>>()
.withTitle("act stat")
.withDataExtractor(r -> r.getActivityCountByType())
.withDataConverter(d -> d.entrySet().stream()
.map(en -> "[" + en.getKey() + "=" + en.getValue() + "]")
.collect(Collectors.joining()))
.build());
}
},
TRAVEL_DISTANCE(null) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return Collections.singletonList(
new ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, Long>()
.withTitle("travel dist")
.withDataExtractor(r -> r.getTravelDistance())
.withCellContentFormatter(CellContentFormatter.rightAlignedCell())
.withDataConverter(NumberDataConverter.defaultLongFormatter())
.build());
}
},
SHIFT_TIME_WINDOW(MODE_SET_HUMAN) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return createTimeWindowColumn(this, "shift tw", vehicleSummaryConfig,
r -> new TimeWindow(r.getVehicle().getEarliestDeparture(),
r.getVehicle().getLatestArrival()));
}
},
SHIFT_DURATION(MODE_SET_HUMAN) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return createDurationColumn(this, "shift dur", vehicleSummaryConfig,
r -> r.getShiftDuration());
}
},
ROUTE_TIME_WINDOW(MODE_SET_HUMAN) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return createTimeWindowColumn(this, "route tw", vehicleSummaryConfig,
r -> new TimeWindow(r.getStart(), r.getEnd()));
}
},
ROUTE_DURATION(MODE_SET_ALL) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return createDurationColumn(this, "route", vehicleSummaryConfig,
r -> r.getRouteDuration());
}
},
TRAVEL_DURATION(MODE_SET_ALL) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return createDurationColumn(this, "travel", vehicleSummaryConfig,
r -> r.getTravelDuration());
}
},
OPERATION_DURATION(MODE_SET_ALL) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return createDurationColumn(this, "operation", vehicleSummaryConfig,
r -> r.getOperationDuration());
}
},
ACTIVE_DURATION(MODE_SET_ALL) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return createDurationColumn(this, "active", vehicleSummaryConfig,
r -> r.getActiveDuration());
}
},
IDLE_DURATION(MODE_SET_ALL) {
@Override
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig) {
return createDurationColumn(this, "idle", vehicleSummaryConfig,
r -> r.getIdleDuration());
}
},
;
private EnumSet<DisplayMode> enabledFormats;
private Column(EnumSet<DisplayMode> enabledFormats) {
this.enabledFormats = enabledFormats;
}
public abstract List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createColumns(
VehicleSummaryConfig vehicleSummaryConfig);
public EnumSet<DisplayMode> getEnabledFormats() {
return enabledFormats;
}
private static List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createDurationColumn(
Column column, String title, VehicleSummaryConfig vehicleSummaryConfig,
Function<VehicleSummaryRecord, Long> extractor) {
EnumSet<DisplayMode> modes = composeDisplayModes(column, vehicleSummaryConfig);
List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> cols = new ArrayList<>();
for (DisplayMode m : MODE_SET_ALL) {
ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, ?> b = null;
if (modes.contains(m)) {
switch (m) {
case NUMERIC:
b = new ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, Long>()
.withDataExtractor(extractor)
.withDataConverter(NumberDataConverter.defaultLongFormatter());
break;
case HUMAN_READABLE:
b = new ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, Long>()
.withDataExtractor(extractor)
.withDataConverter(
d -> vehicleSummaryConfig.formatDurationHuman(d));
break;
case PERCENT_SHIFT:
b = new ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, Double>()
.withDataExtractor(
r -> (double) extractor.apply(r) / r.getShiftDuration())
.withDataConverter(defaultPercentFormatter());
// TODO
break;
case PERCENT_ROUTE:
b = new ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, Double>()
.withDataExtractor(
r -> (double) extractor.apply(r) / r.getRouteDuration())
.withDataConverter(defaultPercentFormatter());
// TODO
break;
default:
break;
}
if (b != null) {
b.withCellContentFormatter(CellContentFormatter.rightAlignedCell());
cols.add(b.withTitle(title + m.postfix).build());
}
}
}
return cols;
}
private static NumberDataConverter<Double> defaultPercentFormatter() {
NumberFormat formatter = NumberFormat.getPercentInstance();
formatter.setMaximumFractionDigits(2);
formatter.setMinimumFractionDigits(2);
formatter.setGroupingUsed(false);
formatter.setRoundingMode(RoundingMode.HALF_UP);
return new NumberDataConverter<>(formatter);
}
private static List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> createTimeWindowColumn(
Column column, String title, VehicleSummaryConfig vehicleSummaryConfig,
Function<VehicleSummaryRecord, TimeWindow> extractor) {
EnumSet<DisplayMode> modes = composeDisplayModes(column, vehicleSummaryConfig);
List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> cols = new ArrayList<>();
for(DisplayMode m : MODE_SET_ALL) {
if (modes.contains(m)) {
ColumnDefinition.StatelessBuilder<VehicleSummaryRecord, TimeWindow> b = new ColumnDefinition.StatelessBuilder<>();
switch (m) {
case NUMERIC:
b.withDataConverter(
tw -> vehicleSummaryConfig.formatTimeWindowNumeric(tw));
break;
case HUMAN_READABLE:
b.withDataConverter(
tw -> vehicleSummaryConfig.formatTimeWindowHuman(tw));
break;
default:
break;
}
if (b != null) {
b.withCellContentFormatter(CellContentFormatter.rightAlignedCell());
b.withDataExtractor(extractor);
cols.add(b.withTitle(title + m.postfix).build());
}
}
}
return cols;
}
private static EnumSet<DisplayMode> composeDisplayModes(Column column,
VehicleSummaryConfig vehicleSummaryConfig) {
EnumSet<DisplayMode> modes = EnumSet.copyOf(column.getEnabledFormats());
modes.retainAll(vehicleSummaryConfig.getDisplayModes());
return modes;
}
}
public static class Builder {
private LocalDateTime humanReadableOrigin = LocalDateTime.of(LocalDate.now(),
LocalTime.MIDNIGHT);
private EnumSet<DisplayMode> displayModes = MODE_SET_ALL;
private List<Column> columns;
private ChronoUnit lowUnit = ChronoUnit.SECONDS;
private ChronoUnit highUnit = ChronoUnit.HOURS;
public Builder() {
this.columns = new ArrayList<>();
}
public Builder withHumanReadableOrigin(LocalDateTime humanReadableOrigin) {
this.humanReadableOrigin = humanReadableOrigin;
return this;
}
public Builder withTimeDisplayModes(EnumSet<DisplayMode> displayModes) {
this.displayModes = displayModes;
return this;
}
public Builder withLowUnit(ChronoUnit lowUnit) {
this.lowUnit = lowUnit;
return this;
}
public Builder withHighUnit(ChronoUnit highUnit) {
this.highUnit = highUnit;
return this;
}
public Builder withColumn(Column columns) {
this.columns.add(columns);
return this;
}
public Builder withColumns(Column... columns) {
for (Column c : columns) {
withColumn(c);
}
return this;
}
public VehicleSummaryConfig build() {
return new VehicleSummaryConfig(this);
}
}
private EnumSet<DisplayMode> displayModes;
private List<Column> columns;
private VehicleSummaryConfig(Builder builder) {
this.displayModes = builder.displayModes;
this.columns = builder.columns;
setTimeFormatter(
new HumanReadableTimeFormatter(builder.humanReadableOrigin, builder.lowUnit));
setDurationFormatter(new HumanReadableDurationFormatter(builder.lowUnit, builder.highUnit));
}
public EnumSet<DisplayMode> getDisplayModes() {
return displayModes;
}
public List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> getColumns() {
List<ColumnDefinition<VehicleSummaryRecord, ?, ?>> columns = new ArrayList<>();
this.columns.forEach(c -> columns.addAll(c.createColumns(this)));
return columns;
}
}

View file

@ -1,4 +1,4 @@
package com.graphhopper.jsprit.core.reporting.vehicle;
package com.graphhopper.jsprit.core.reporting;
import java.util.HashMap;
import java.util.Map;
@ -10,9 +10,8 @@ import com.graphhopper.jsprit.core.problem.solution.route.activity.BreakActivity
import com.graphhopper.jsprit.core.problem.solution.route.activity.JobActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.problem.vehicle.Vehicle;
import com.graphhopper.jsprit.core.reporting.PrinterContext;
public class VehicleSummaryContext implements PrinterContext {
public class VehicleSummaryRecord {
private Vehicle vehicle;
private Driver driver;
private int routeNr;
@ -25,7 +24,7 @@ public class VehicleSummaryContext implements PrinterContext {
private long breakDuration;
private long travelDistance;
public VehicleSummaryContext(VehicleRoute route, VehicleRoutingProblem problem) {
public VehicleSummaryRecord(VehicleRoute route, VehicleRoutingProblem problem) {
routeNr = route.getId();
vehicle = route.getVehicle();
driver = route.getDriver();
@ -47,8 +46,8 @@ public class VehicleSummaryContext implements PrinterContext {
activityCountByType.put(type, activityCountByType.get(type) + 1);
operationDuration += jobAct.getOperationTime();
travelDuration += problem.getTransportCosts().getTransportTime(prevAct.getLocation(),
act.getLocation(),act.getArrTime(), route.getDriver(),
route.getVehicle());
act.getLocation(),act.getArrTime(), route.getDriver(),
route.getVehicle());
}
prevAct = act;
}
@ -92,7 +91,7 @@ public class VehicleSummaryContext implements PrinterContext {
public long getShiftDuration() {
return vehicle.getLatestArrival() == Double.MAX_VALUE ? getRouteDuration()
: (long) (vehicle.getLatestArrival() - vehicle.getEarliestDeparture());
: (long) (vehicle.getLatestArrival() - vehicle.getEarliestDeparture());
}
public long getRouteDuration() {
@ -118,10 +117,10 @@ public class VehicleSummaryContext implements PrinterContext {
@Override
public String toString() {
return "VehicleStatisticsContext [vehicleId=" + vehicle.getId() + ", driver=" + driver.getId() + ", routeNr=" + routeNr
+ ", start=" + start + ", end=" + end + ", activityCount=" + activityCount + ", activityCountByType="
+ activityCountByType + ", travelDuration=" + travelDuration + ", operationDuration=" + operationDuration
+ ", totalDuration=" + getRouteDuration() + ", travelDistance=" + travelDistance + ", breakDuration="
+ breakDuration + "]";
+ ", start=" + start + ", end=" + end + ", activityCount=" + activityCount + ", activityCountByType="
+ activityCountByType + ", travelDuration=" + travelDuration + ", operationDuration=" + operationDuration
+ ", totalDuration=" + getRouteDuration() + ", travelDistance=" + travelDistance + ", breakDuration="
+ breakDuration + "]";
}
public Long getActiveDuration() {

View file

@ -1,63 +0,0 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
/**
* Common abstract ancestor for column types.
*
* @author balage
*
* @param <T>
* The type it accepts.
*/
public abstract class AbstractColumnType<T> implements ColumnType<T> {
// The string to used as null value
private String nullValue = "";
public AbstractColumnType() {
super();
}
/**
* @param nullValue
* alternative null value
*/
public AbstractColumnType(String nullValue) {
super();
this.nullValue = nullValue;
}
/**
* {@inheritDoc}
*
* This basic implementation takes the burden to handle null values and
* calls the {@linkplain #convertNotNull(Object)} for all other values.
*
* @see com.graphhopper.jsprit.core.reporting.columndefinition.ColumnType#convert(java.lang.Object)
*
* @throws ClassCastException
* if the data is not accepted by the column type.
*/
@SuppressWarnings("unchecked")
@Override
public String convert(Object data) {
if (data == null) {
return nullValue;
} else {
if (accepts(data)) {
return convertNotNull((T) data);
} else {
throw new ClassCastException();
}
}
}
/**
* Converts the data into String. This function never gets null as
* parameter.
*
* @param data
* the non-null data to convert.
* @return The converted data.
*/
protected abstract String convertNotNull(T data);
}

View file

@ -1,80 +0,0 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
/**
* A column type with boolean values.
* <p>
* The display value for true and false values could be configured.
* </p>
*
* @author balage
*
*/
public class BooleanColumnType extends AbstractColumnType<Boolean> {
// The display value for true
private String trueValue = "true";
// The display value for false
private String falseValue = "false";
/**
* Konstructor. The column will use the default values for null, true or
* false.
*/
public BooleanColumnType() {
super();
}
/**
* Konstructor. The column will use the default values for true or false.
*
* @param nullValue
* The text representation for null values.
*/
public BooleanColumnType(String nullValue) {
super(nullValue);
}
/**
* Konstructor. The column will use the default values for null.
*
* @param trueValue
* The text representation for true values.
* @param falseValue
* The text representation for false values.
*/
public BooleanColumnType(String trueValue, String falseValue) {
super();
this.trueValue = trueValue;
this.falseValue = falseValue;
}
/**
* Konstructor.
*
* @param trueValue
* The text representation for true values.
* @param falseValue
* The text representation for false values.
* @param nullValue
* The text representation for null values.
*/
public BooleanColumnType(String trueValue, String falseValue, String nullValue) {
super(nullValue);
this.trueValue = trueValue;
this.falseValue = falseValue;
}
@Override
protected String convertNotNull(Boolean data) {
return data ? trueValue : falseValue;
}
/**
* {@inheritDoc}
*
* Only accepts {@linkplain Boolean} input.
*/
@Override
public boolean accepts(Object data) {
return data instanceof Boolean;
}
}

View file

@ -1,67 +0,0 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
/**
* Alignment of the column.
* <p>
* Longer values will be truncated, shorter values will be padded by spaces.
* </p>
*
* @author balage
*
*/
public enum ColumnAlignment {
/**
* The values are aligned left, padded on the right side.
*/
LEFT {
@Override
public String align(String data, int width) {
if (data.length() > width) {
return data.substring(0, width);
}
return String.format("%1$-" + width + "s", data);
}
},
/**
* The values are aligned right, padded on the left side.
*/
RIGHT {
@Override
public String align(String data, int width) {
if (data.length() > width) {
return data.substring(0, width);
}
return String.format("%1$" + width + "s", data);
}
},
/**
* The values are centered, padded on the both sides evenly (in case of odd
* character padding, the left padding will be one more than the right one).
*/
CENTER {
@Override
public String align(String data, int width) {
if (data.length() > width) {
return data.substring(0, width);
}
int leftPad = (width - data.length())/2;
return LEFT.align(RIGHT.align(data, width-leftPad), width);
}
};
/**
* Applies the alignment on the data according the width. Truncates or pads
* the value.
*
* @param data
* The data to align.
* @param width
* The width to pad to.
* @return The aligned (padded) values with the exact length of width.
*/
public abstract String align(String data, int width);
}

View file

@ -1,192 +0,0 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
/**
* Column definition. Contains all information for converting and formatting the
* column.
* <p>
* The definition itself immutable and cannot be directly instantiate. Use the
* {@linkplain Builder} class for constructing the definition.
* </p>
*
* @author balage
*
*/
public class ColumnDefinition {
/**
* The builder for {@linkplain ColumnDefinition}.
* <p>
* When it is not specified, the default title is null (the default title of
* the column will be used), the minWidth is 0, the maxWidth is
* {@linkplain Integer#MAX_VALUE} and the alignment is
* {@linkplain ColumnAlignment#LEFT}.
* </p>
*
* @author balage
*
*/
public static class Builder {
// Type of the column.
private ColumnType<?> type;
// The title of the column.
private String title;
// The minimal width of the column.
private int minWidth = 0;
// The maximal width of the column.
private int maxWidth = Integer.MAX_VALUE;
// The alignment of the column.
private ColumnAlignment alignment = ColumnAlignment.LEFT;
/**
* Constructor with title specified.
*
* @param type
* Type of the column.
* @param title
* The title of the column.
* @see {@linkplain #withTitle(String)}
*/
public Builder(ColumnType<?> type, String title) {
super();
this.type = type;
this.title = title;
}
/**
* Constructor.
*
* @param type
* Type of the column.
*/
public Builder(ColumnType<?> type) {
super();
this.type = type;
}
/**
* @param title
* The title of the column
* @return The builder object.
*/
public ColumnDefinition.Builder withTitle(String title) {
this.title = title;
return this;
}
/**
* @param minWidth
* The minimal width of the column.
* @return The builder object.
* @throws IllegalArgumentException
* If the minWidth is negative or higher than the maxWidth.
*/
public ColumnDefinition.Builder withMinWidth(int minWidth) {
if (minWidth < 0) {
throw new IllegalArgumentException("Minimal width should be non-negative.");
}
if (minWidth > maxWidth) {
throw new IllegalArgumentException("Minimal width should be less or equal than the maximal width.");
}
this.minWidth = minWidth;
return this;
}
/**
* @param maxWidth
* The maximal width of the column.
* @return The builder object.
* @throws IllegalArgumentException
* If the maxWidth is negative or less than the minWidth.
*/
public ColumnDefinition.Builder withMaxWidth(int maxWidth) {
if (maxWidth < 0) {
throw new IllegalArgumentException("Maximal width should be non-negative.");
}
if (maxWidth > maxWidth) {
throw new IllegalArgumentException("Maximal width should be greater or equal than the minimal width.");
}
this.maxWidth = maxWidth;
return this;
}
/**
* @param alignment
* The alignment of the column.
* @return The builder object.
*/
public ColumnDefinition.Builder withAlignment(ColumnAlignment alignment) {
this.alignment = alignment;
return this;
}
/**
* @return The constructed imutable definition object.
*/
public ColumnDefinition build() {
return new ColumnDefinition(this);
}
}
// Type of the column.
private ColumnType<?> type;
// The title of the column.
private String title;
// The minimal width of the column.
private int minWidth = 0;
// The maximal width of the column.
private int maxWidth = Integer.MAX_VALUE;
// The alignment of the column.
private ColumnAlignment alignment = ColumnAlignment.LEFT;
/**
* Private constructor for the builder.
*
* @param builder
* The builder.
*/
private ColumnDefinition(ColumnDefinition.Builder builder) {
type = builder.type;
title = builder.title;
minWidth = builder.minWidth;
maxWidth = builder.maxWidth;
alignment = builder.alignment;
}
/**
* @return The type of the column.
*/
public ColumnType<?> getType() {
return type;
}
/**
* @return The title of the column. If null, the default title will be used.
*/
public String getTitle() {
return title;
}
/**
* @return The minimal width of the column.
*/
public int getMinWidth() {
return minWidth;
}
/**
* @return The maximal width of the column.
*/
public int getMaxWidth() {
return maxWidth;
}
/**
* @return The alignment of the column.
*/
public ColumnAlignment getAlignment() {
return alignment;
}
}

View file

@ -1,30 +0,0 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
/**
* Column type.
*
* @author balage
*
* @param <T>
* The object type it accepts.
*/
public interface ColumnType<T> {
/**
* Converts the data into String.
*
* @param data
* the data to convert.
* @return The converted data.
*/
public String convert(Object data);
/**
* Checks if the given data is acceptable for the type. (Mostly by class
* type.)
*
* @param data
* the data to check
* @return True if the data can be converted by this implementation.
*/
public boolean accepts(Object data);
}

View file

@ -1,82 +0,0 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
/**
* A column type with double values.
* <p>
* The number of decimal places could be configured.
* </p>
*
* @author balage
*
*/
public class DoubleColumnType extends AbstractColumnType<Double> {
// The number of displayed decimal places
private int decimals = 2;
/**
* Konstructor. The column will use the default values for null and the
* significant decimal places.
*/
public DoubleColumnType() {
super();
}
/**
* Konstructor. The column will use the default values for the significant
* decimal places.
*
* @param nullValue
* The text representation for null values.
*/
public DoubleColumnType(String nullValue) {
super(nullValue);
}
/**
* Konstructor. The column will use the default values for null.
*
* @param decimals The number of decimal places to display.
* @throws IllegalArgumentException If the parameter is negative.
*/
public DoubleColumnType(int decimals) {
super();
if (decimals < 0) {
throw new IllegalArgumentException("Decimal places should be 0 or more.");
}
this.decimals = decimals;
}
/**
* Konstructor.
*
* @param decimals
* The number of decimal places to display.
* @param nullValue
* The text representation for null values.
* @throws IllegalArgumentException
* If the parameter is negative.
*/
public DoubleColumnType(int decimals, String nullValue) {
super(nullValue);
if (decimals < 0) {
throw new IllegalArgumentException("Decimal places should be 0 or more.");
}
this.decimals = decimals;
}
@Override
protected String convertNotNull(Double data) {
return String.format("%50." + decimals + "f", data).trim();
}
/**
* {@inheritDoc} Only accepts Double values.
*/
@Override
public boolean accepts(Object data) {
return data instanceof Double;
}
}

View file

@ -1,66 +0,0 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
/**
* Duration formatter for human readable format.
* <p>
* The formatter uses the {@linkplain DateTimeFormatter} for time value to
* string formatting. The default format is the standard ISO time format (
* <code>"HH:mm:ss"</code>). If the input long value is X, the time value is
* calculated by adding X of the units to a predefined origin. The default unit
* is {@linkplain ChronoUnit#SECONDS}.
* </p>
*
* @author balage
*
*/
public class HumanReadableDurationFormatter extends HumanReadableTimeFormatter {
// Default origin
public static final LocalDateTime DEFAULT_ORIGIN = LocalDateTime.of(LocalDate.now(), LocalTime.MIDNIGHT);
/**
* Constructor with default settings. See
* {@linkplain HumanReadableDurationFormatter} for default values.
*/
public HumanReadableDurationFormatter() {
}
/**
* Constructor with time mapping values, but with default formatting.
*
* @param unit
* The unit used to map the numerical value to the time value.
*/
public HumanReadableDurationFormatter(ChronoUnit unit) {
super(DEFAULT_ORIGIN, unit);
}
/**
* Constructor with user-defined formatting.
*
* @param dateFormatter
* The date formatter.
*/
public HumanReadableDurationFormatter(DateTimeFormatter dateFormatter) {
super(dateFormatter);
}
/**
* Constructor with both time mapping values and user-defined formatting.
*
* @param dateFormatter
* The date formatter.
* @param unit
* The unit used to map the numerical value to the time value.
*/
public HumanReadableDurationFormatter(DateTimeFormatter dateFormatter, ChronoUnit unit) {
super(dateFormatter, DEFAULT_ORIGIN, unit);
}
}

View file

@ -1,27 +0,0 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
/**
* Interface for columns with human readable formats.
*
* @author balage
*
* @param <T>
* The type of the class itself. (Self-reference)
*/
public interface HumanReadableEnabled<T extends HumanReadableEnabled<T>> {
/**
* Sets the formatter.
*
* @param formatter
* The formatter.
* @return The object itself.
*/
public T withFormatter(HumanReadableTimeFormatter formatter);
/**
* Marks the column human readable.
*
* @return The object itself.
*/
public T asHumanReadable();
}

View file

@ -1,41 +0,0 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
/**
* A column type with integer (4 byte) values.
*
* @author balage
*
*/
public class IntColumnType extends AbstractColumnType<Integer> {
/**
* Konstructor. The column will use the default values for null.
*/
public IntColumnType() {
super();
}
/**
* Konstructor.
*
* @param nullValue
* The text representation for null values.
*/
public IntColumnType(String nullValue) {
super(nullValue);
}
@Override
protected String convertNotNull(Integer data) {
return data.toString();
}
/**
* {@inheritDoc} Only accepts Integer values.
*/
@Override
public boolean accepts(Object data) {
return data instanceof Integer;
}
}

View file

@ -1,42 +0,0 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
/**
* A column type with long (8-byte) values.
*
* @author balage
*
*/
public class LongColumnType extends AbstractColumnType<Long> {
/**
* Konstructor. The column will use the default values for null.
*/
public LongColumnType() {
super();
}
/**
* Konstructor.
*
* @param nullValue
* The text representation for null values.
*/
public LongColumnType(String nullValue) {
super(nullValue);
}
@Override
protected String convertNotNull(Long data) {
return data.toString();
}
/**
* {@inheritDoc} Only accepts Long values.
*/
@Override
public boolean accepts(Object data) {
return data instanceof Long;
}
}

View file

@ -1,196 +0,0 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.PrinterColumnList;
import com.graphhopper.jsprit.core.reporting.route.ActivityCostPrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.ActivityDurationPrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.ActivityLoadChangePrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.ActivityTypePrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.ArrivalTimePrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.EndTimePrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.JobNamePrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.JobPriorityPrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.JobTypePrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.LoacationPrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.OperationDurationPrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.RouteCostPrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.RouteLoadPrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.RouteNumberPrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.RoutePrinterContext;
import com.graphhopper.jsprit.core.reporting.route.SelectedTimeWindowPrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.StartTimePrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.TimeWindowsPrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.TransportCostPrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.TravelDurationPrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.VehicleNamePrinterColumn;
import com.graphhopper.jsprit.core.reporting.route.WaitingDurationPrinterColumn;
/**
* Utility class to provide predefined column lists for Solution printing.
*
* @author balage
*
*/
public class SolutionPrintColumnLists {
/**
* The predefined column sets.
*
* @author balage
*
*/
public enum PredefinedList {
/**
* A minimal column set.
*/
MINIMAL,
/**
* A general, most often used column set.
*/
DEFAULT,
/**
* A verbose column set containing all columns.
*/
VERBOSE
}
private static final EnumMap<PredefinedList, List<Class<? extends AbstractPrinterColumn<RoutePrinterContext, ?, ?>>>> COLUMNS;
static {
COLUMNS = new EnumMap<>(PredefinedList.class);
List<Class<? extends AbstractPrinterColumn<RoutePrinterContext, ?, ?>>> minimalSet = new ArrayList<>();
minimalSet.add(RouteNumberPrinterColumn.class);
minimalSet.add(VehicleNamePrinterColumn.class);
minimalSet.add(ActivityTypePrinterColumn.class);
minimalSet.add(JobNamePrinterColumn.class);
minimalSet.add(ArrivalTimePrinterColumn.class);
minimalSet.add(EndTimePrinterColumn.class);
minimalSet.add(RouteCostPrinterColumn.class);
COLUMNS.put(PredefinedList.MINIMAL, minimalSet);
List<Class<? extends AbstractPrinterColumn<RoutePrinterContext, ?, ?>>> defaultSet = new ArrayList<>();
defaultSet.add(RouteNumberPrinterColumn.class);
defaultSet.add(VehicleNamePrinterColumn.class);
defaultSet.add(ActivityTypePrinterColumn.class);
defaultSet.add(JobNamePrinterColumn.class);
defaultSet.add(LoacationPrinterColumn.class);
defaultSet.add(ActivityLoadChangePrinterColumn.class);
defaultSet.add(OperationDurationPrinterColumn.class);
defaultSet.add(ArrivalTimePrinterColumn.class);
defaultSet.add(StartTimePrinterColumn.class);
defaultSet.add(EndTimePrinterColumn.class);
defaultSet.add(ActivityCostPrinterColumn.class);
defaultSet.add(RouteCostPrinterColumn.class);
COLUMNS.put(PredefinedList.DEFAULT, defaultSet);
List<Class<? extends AbstractPrinterColumn<RoutePrinterContext, ?, ?>>> verboseSet = new ArrayList<>();
verboseSet.add(RouteNumberPrinterColumn.class);
verboseSet.add(VehicleNamePrinterColumn.class);
verboseSet.add(ActivityTypePrinterColumn.class);
verboseSet.add(JobNamePrinterColumn.class);
verboseSet.add(JobTypePrinterColumn.class);
verboseSet.add(JobPriorityPrinterColumn.class);
verboseSet.add(LoacationPrinterColumn.class);
verboseSet.add(ActivityLoadChangePrinterColumn.class);
verboseSet.add(RouteLoadPrinterColumn.class);
verboseSet.add(TimeWindowsPrinterColumn.class);
verboseSet.add(OperationDurationPrinterColumn.class);
verboseSet.add(TravelDurationPrinterColumn.class);
verboseSet.add(WaitingDurationPrinterColumn.class);
verboseSet.add(ActivityDurationPrinterColumn.class);
verboseSet.add(ArrivalTimePrinterColumn.class);
verboseSet.add(StartTimePrinterColumn.class);
verboseSet.add(EndTimePrinterColumn.class);
verboseSet.add(SelectedTimeWindowPrinterColumn.class);
verboseSet.add(TransportCostPrinterColumn.class);
verboseSet.add(ActivityCostPrinterColumn.class);
verboseSet.add(RouteCostPrinterColumn.class);
COLUMNS.put(PredefinedList.VERBOSE, verboseSet);
}
/**
* Returns the predefined column set with all time, time window and duration
* columns printed as numbers.
*
* @param listType
* The predefined list id.
* @return The column list containing the predefined columns.
*/
public static PrinterColumnList<RoutePrinterContext> getNumeric(PredefinedList listType) {
return getList(listType, false, null);
}
/**
* Returns the predefined column set with all time, time window and duration
* columns printed with human readable format, using default formatting.
*
* @param listType
* The predefined list id.
* @return The column list containing the predefined columns.
*/
public static PrinterColumnList<RoutePrinterContext> getHumanReadable(PredefinedList listType) {
return getList(listType, true, null);
}
/**
* Returns the predefined column set with all time, time window and duration
* columns printed with human readable format, using the provided formatter.
*
* @param listType
* The predefined list id.
* @param timeFormatter
* the time formatter to use
* @return The column list containing the predefined columns.
*/
public static PrinterColumnList<RoutePrinterContext> getHumanReadable(PredefinedList listType,
HumanReadableTimeFormatter timeFormatter) {
return getList(listType, true, timeFormatter);
}
/**
* Generates the list.
*
* @param listType
* The id of the list.
* @param humanReadable
* Whether human readable format should be used
* @param timeFormatter
* The formatter to use (if null, the default will be used)
* @return The generated column list.
*/
private static PrinterColumnList<RoutePrinterContext> getList(PredefinedList listType, boolean humanReadable,
HumanReadableTimeFormatter timeFormatter) {
PrinterColumnList<RoutePrinterContext> res = new PrinterColumnList<>();
for (Class<? extends AbstractPrinterColumn<RoutePrinterContext, ?, ?>> c : COLUMNS.get(listType)) {
try {
AbstractPrinterColumn<RoutePrinterContext, ?, ?> col = c.newInstance();
if (humanReadable && col instanceof HumanReadableEnabled) {
HumanReadableEnabled<?> hrCol = (HumanReadableEnabled<?>) col;
hrCol.asHumanReadable();
if (timeFormatter != null) {
hrCol.withFormatter(timeFormatter);
}
}
res.addColumn(col);
} catch (InstantiationException | IllegalAccessException e) {
// Technically you can't get here as long as all column
// implementation has default constructor
throw new IllegalStateException(e);
}
}
return res;
}
}

View file

@ -1,42 +0,0 @@
package com.graphhopper.jsprit.core.reporting.columndefinition;
/**
* A column type for any values.
*
* @author balage
*
*/
public class StringColumnType extends AbstractColumnType<Object> {
/**
* Konstructor. The column will use the default values for null.
*/
public StringColumnType() {
super();
}
/**
* Konstructor.
*
* @param nullValue
* The text representation for null values.
*/
public StringColumnType(String nullValue) {
super(nullValue);
}
@Override
protected String convertNotNull(Object data) {
return data.toString();
}
/**
* {@inheritDoc} Accepts any type of values (uses
* {@linkplain Object#toString()}).
*/
@Override
public boolean accepts(Object data) {
return true;
}
}

View file

@ -1,53 +0,0 @@
package com.graphhopper.jsprit.core.reporting.job;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import com.graphhopper.jsprit.core.problem.job.Job;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* Name (id) of the job.
*
* <p>
* This column provides the {@linkplain Job#getId()} of the associated job of
* the activity for job activities and null for other route activities.
* </p>
*
* @author balage
*/
public class JobNamePrinterColumn<T extends JobPrinterContext> extends AbstractPrinterColumn<T, String, JobNamePrinterColumn<T>> {
/**
* Constructor.
*/
public JobNamePrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public JobNamePrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType("-"));
}
@Override
protected String getDefaultTitle() {
return "job name";
}
@Override
public String getData(T context) {
AbstractJob job = context.getJob();
return job == null ? null : job.getId();
}
}

View file

@ -1,21 +0,0 @@
package com.graphhopper.jsprit.core.reporting.job;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import com.graphhopper.jsprit.core.reporting.PrinterContext;
/**
* The context of the detailed route printer columns.
*
* <p>
* This is a semi-mutable class: only the activity could be altered. Therefore
* for each route a new instance should be created.
* </p>
*
* @author balage
*
*/
public interface JobPrinterContext extends PrinterContext {
public AbstractJob getJob();
}

View file

@ -1,58 +0,0 @@
package com.graphhopper.jsprit.core.reporting.job;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import com.graphhopper.jsprit.core.problem.job.Job;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnAlignment;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* Priority of the job.
*
* <p>
* This column provides the named (LOW, MEDIUM, HIGH) representation of
* {@linkplain Job#getPriority()} of the associated job of the activity for job
* activities and null for other route activities.
* </p>
*
* @author balage
*/
public class JobPriorityPrinterColumn<T extends JobPrinterContext>
extends AbstractPrinterColumn<T, String, JobPriorityPrinterColumn<T>> {
private static final String[] PRIORITY_NAMES = new String[] { "", "HIGH", "MEDIUM", "LOW" };
/**
* Constructor.
*/
public JobPriorityPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public JobPriorityPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType("-")).withAlignment(ColumnAlignment.CENTER);
}
@Override
protected String getDefaultTitle() {
return "priority";
}
@Override
public String getData(T context) {
AbstractJob job = context.getJob();
return job == null ? null : PRIORITY_NAMES[job.getPriority()];
}
}

View file

@ -1,52 +0,0 @@
package com.graphhopper.jsprit.core.reporting.job;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* Priority of the job.
*
* <p>
* This column provides the simple class name of the associated job of the
* activity for job activities and null for other route activities.
* </p>
*
* @author balage
*/
public class JobTypePrinterColumn<T extends JobPrinterContext> extends AbstractPrinterColumn<T, String, JobTypePrinterColumn<T>> {
/**
* Constructor.
*/
public JobTypePrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public JobTypePrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType("-"));
}
@Override
protected String getDefaultTitle() {
return "job type";
}
@Override
public String getData(JobPrinterContext context) {
AbstractJob job = context.getJob();
return job == null ? null : job.getClass().getSimpleName();
}
}

View file

@ -1,38 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnAlignment;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.IntColumnType;
/**
* Abstract base class for cost calculators.
*
* <p>
* this implementation only defines the ColumnDefinition as a right aligned
* integer column.
* </p>
*
* @author balage
*
*/
public abstract class AbstractCostPrinterColumn
extends AbstractPrinterColumn<RoutePrinterContext, Integer, AbstractCostPrinterColumn>
implements CostAndTimeExtractor {
public AbstractCostPrinterColumn() {
super();
}
public AbstractCostPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new IntColumnType()).withAlignment(ColumnAlignment.RIGHT);
}
}

View file

@ -1,36 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.HumanReadableDurationFormatter;
/**
* Abstract base class for duration columns.
*
* @author balage
*
* @param <T>
* Self reference.
* @See {@linkplain AbstractTimePrinterColumn}
*/
public abstract class AbstractDurationPrinterColumn<T extends AbstractDurationPrinterColumn<T>>
extends AbstractTimePrinterColumn<T> {
/**
* Constructor to define a numeric format column.
*/
public AbstractDurationPrinterColumn() {
this(null);
}
/**
* Constructor to define a numeric format column, with a post creation
* decorator provided.
*/
public AbstractDurationPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
withFormatter(new HumanReadableDurationFormatter());
}
}

View file

@ -1,85 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.HumanReadableTimeFormatter;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* Abstract base class for size columns.
*
* <p>
* The representation of a size is the dimension values listed comma separated
* and wrapped by brackets. (For example: [2, 0, -1])
* </p>
*
* @author balage
*
* @See {@linkplain HumanReadableTimeFormatter}
*/
public abstract class AbstractSizeDimensionPrinterColumn
extends AbstractPrinterColumn<RoutePrinterContext, String, AbstractSizeDimensionPrinterColumn> {
/**
* Constructor.
*/
public AbstractSizeDimensionPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public AbstractSizeDimensionPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType());
}
/**
* {@inheritDoc}
*
* <p>
* The result is a string representation of the size (the dimension values
* listed comma separated and wrapped by brackets) or null.
* </p>
*/
@Override
public String getData(RoutePrinterContext context) {
SizeDimension sd = getSizeDimension(context);
if (sd != null) {
return IntStream.range(0, sd.getNuOfDimensions()).mapToObj(i -> "" + sd.get(i))
.collect(Collectors.joining(", ", "[", "]"));
} else {
return null;
}
}
/**
* Extracts the size dimension.
*
* @param context
* The context.
* @return The size dimension or null.
*/
protected abstract SizeDimension getSizeDimension(RoutePrinterContext context);
protected SizeDimension calculateInitialLoad(RoutePrinterContext context) {
SizeDimension sd = SizeDimension.EMPTY;
for (TourActivity a : context.getRoute().getActivities()) {
sd = sd.add(a.getLoadChange());
}
sd = sd.getNegativeDimensions().abs();
return sd;
}
}

View file

@ -1,125 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.HumanReadableEnabled;
import com.graphhopper.jsprit.core.reporting.columndefinition.HumanReadableTimeFormatter;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* Abstract base class for time and (technically) duration columns.
*
* <p>
* Each columns derived from this abstract base has two variants: a numerical
* (an integer value) and a human readable. The numerical value displays the
* integer value representing the time values internally. The human readable
* value converts this value into a calendar (date and time) value.
* </p>
*
* @author balage
*
* @param <T>
* Self reference.
* @See {@linkplain HumanReadableTimeFormatter}
*/
public abstract class AbstractTimePrinterColumn<T extends AbstractTimePrinterColumn<T>>
extends AbstractPrinterColumn<RoutePrinterContext, String, AbstractTimePrinterColumn<T>>
implements HumanReadableEnabled<T> {
// The time formatter to use (only used when humanReadable flag is true)
private HumanReadableTimeFormatter formatter;
// Whether to use human readable form
private boolean humanReadable = false;
/**
* Constructor to define a numeric format column.
*/
public AbstractTimePrinterColumn() {
this(null);
}
/**
* Constructor to define a numeric format column, with a post creation
* decorator provided.
*/
public AbstractTimePrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
formatter = new HumanReadableTimeFormatter();
}
/**
* {@inheritDoc}
*
*/
@Override
@SuppressWarnings("unchecked")
public T withFormatter(HumanReadableTimeFormatter formatter) {
this.formatter = formatter;
return (T) this;
}
/**
* {@inheritDoc}
*
*/
@Override
@SuppressWarnings("unchecked")
public T asHumanReadable() {
this.humanReadable = true;
return (T) this;
}
/**
* {@inheritDoc}
*
* <p>
* The column builder returned will be a string column with the null value
* represented by a hyphen ("-").
* </p>
*
*/
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType("-"));
}
/**
* {@inheritDoc}
*
* <p>
* The implementation delegates the value extracting to the abstract method
* {@linkplain #getValue(RoutePrinterContext)}.
* <p>
* <p>
* If the value is null, returns null, otherwise it returns the string
* representation of the numeric value or the human readable format based on
* the humanReadable flag.
* </p>
*
*/
@Override
public String getData(RoutePrinterContext context) {
Long timeValue = getValue(context);
if (timeValue == null) {
return null;
}
if (humanReadable) {
return formatter.format(timeValue);
} else {
return ""+timeValue;
}
}
/**
* Extracts the numerical value for this time or duration column.
*
* @param context
* The context.
* @return The numerical value or null.
*/
protected abstract Long getValue(RoutePrinterContext context);
}

View file

@ -1,145 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.HumanReadableEnabled;
import com.graphhopper.jsprit.core.reporting.columndefinition.HumanReadableTimeFormatter;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* Abstract base class for time window columns.
*
* <p>
* Each columns derived from this abstract base has two variants: a numerical
* (an integer value) and a human readable. The numerical value displays the
* integer value pair representing the time windows, the same the algorithm used
* internally. The human readable value converts this value into a calendar
* (date and time) value pair.
* </p>
*
* @author balage
*
* @param <T>
* Self reference.
* @See {@linkplain HumanReadableTimeFormatter}
*/
public abstract class AbstractTimeWindowPrinterColumn<T extends AbstractTimeWindowPrinterColumn<T>>
extends AbstractPrinterColumn<RoutePrinterContext, String, AbstractTimeWindowPrinterColumn<T>>
implements HumanReadableEnabled<T> {
// The time formatter to use (only used when humanReadable flag is true)
private HumanReadableTimeFormatter formatter;
// Whether to use human readable form
private boolean humanReadable = false;
/**
* Constructor to define a numeric format column.
*/
public AbstractTimeWindowPrinterColumn() {
this(null);
}
/**
* Constructor to define a numeric format column, with a post creation
* decorator provided.
*/
public AbstractTimeWindowPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
formatter = new HumanReadableTimeFormatter();
}
@Override
@SuppressWarnings("unchecked")
public T withFormatter(HumanReadableTimeFormatter formatter) {
this.formatter = formatter;
return (T) this;
}
@Override
@SuppressWarnings("unchecked")
public T asHumanReadable() {
this.humanReadable = true;
return (T) this;
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType("-"));
}
/**
* {@inheritDoc}
*
* <p>
* The implementation delegates the value extracting to the abstract method
* {@linkplain #getValue(RoutePrinterContext)}.
* <p>
* <p>
* If the value is null or empty, returns null, otherwise it returns the
* string representation of the numeric value or the human readable format
* based on the humanReadable flag.
* </p>
*
*/
@Override
public String getData(RoutePrinterContext context) {
Collection<TimeWindow> timeWindows = getValue(context);
if (timeWindows == null || timeWindows.isEmpty()) {
return null;
}
return timeWindows.stream().map(tw -> formatTimeWindow(tw)).collect(Collectors.joining());
}
/**
* Formats the time window.
*
* <p>
* The implementation returns the two (start, end) values sepratated by
* hyphen (-) and wrapped within brackets. When the end value is
* {@linkplain Double#MAX_VALUE} it omits the value indicating open
* interval.
* </p>
*
* @param tw
* The time window to format.
* @return The string representation of the time window.
*/
protected String formatTimeWindow(TimeWindow tw) {
String res = "";
if (humanReadable) {
res = "[" + formatter.format((long) tw.getStart()) + "-";
if (tw.getEnd() == Double.MAX_VALUE) {
res += "";
} else {
res += formatter.format((long) tw.getEnd());
}
res += "]";
} else {
res = "[" + (long) tw.getStart() + "-";
if (tw.getEnd() == Double.MAX_VALUE) {
res += "";
} else {
res += (long) tw.getEnd();
}
res += "]";
}
return res;
}
/**
* Extracts the collection of time windows from the context.
*
* @param context
* The context.
* @return The collection of time windows.
*/
protected abstract Collection<TimeWindow> getValue(RoutePrinterContext context);
}

View file

@ -1,40 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* Cost of the activity.
*
* @author balage
*
*/
public class ActivityCostPrinterColumn extends AbstractCostPrinterColumn {
/**
* Constructor.
*/
public ActivityCostPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public ActivityCostPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "actCost";
}
@Override
public Integer getData(RoutePrinterContext context) {
return (int) getActivityCost(context);
}
}

View file

@ -1,66 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.solution.route.activity.Start;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* Activity duration column.
* <p>
* The activity duration is the sum of the activity operation (service) time and
* the transport time to the location.
* </p>
* <p>
* This column is stateful and stores the previous activity.
* </p>
*
* @author balage
*
* @see {@linkplain ArrivalTimePrinterColumn}
* @see {@linkplain StartTimePrinterColumn}
* @see {@linkplain EndTimePrinterColumn}
* @see {@linkplain TravelDurationPrinterColumn}
* @see {@linkplain WaitingDurationPrinterColumn}
* @see {@linkplain OperationDurationPrinterColumn}
*/
public class ActivityDurationPrinterColumn extends AbstractDurationPrinterColumn<ActivityDurationPrinterColumn>
implements CostAndTimeExtractor {
// The previous activity
private TourActivity prevAct;
/**
* Constructor.
*/
public ActivityDurationPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public ActivityDurationPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "duration";
}
@Override
public Long getValue(RoutePrinterContext context) {
TourActivity act = context.getActivity();
if (act instanceof Start) {
prevAct = null;
}
long val = (long) (getTransportTime(context, prevAct) + act.getOperationTime());
prevAct = act;
return val;
}
}

View file

@ -1,65 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.solution.route.activity.Start;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* The load change value (signed size) of the activity.
*
* <p>
* If the activity is a route start, the returned value is the initial load,
* otherwise the loadChange value of the activity.
* </p>
*
* @author balage
*
*/
public class ActivityLoadChangePrinterColumn extends AbstractSizeDimensionPrinterColumn {
/**
* Constructor.
*/
public ActivityLoadChangePrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public ActivityLoadChangePrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return super.getColumnBuilder().withMinWidth(10);
}
@Override
protected String getDefaultTitle() {
return "load change";
}
/**
* {@inheritDoc}
* <p>
* If the activity is a route start, the returned value is the initial load,
* otherwise the loadChange value of the activity.
* </p>
*/
@Override
protected SizeDimension getSizeDimension(RoutePrinterContext context) {
TourActivity act = context.getActivity();
if (act instanceof Start) {
return calculateInitialLoad(context);
} else {
return act.getLoadChange();
}
}
}

View file

@ -1,47 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.solution.route.activity.AbstractActivity;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* The type of the activity.
*
* @author balage
*
*/
public class ActivityTypePrinterColumn extends AbstractPrinterColumn<RoutePrinterContext, String, ActivityTypePrinterColumn> {
/**
* Constructor.
*/
public ActivityTypePrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public ActivityTypePrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType());
}
@Override
public String getData(RoutePrinterContext context) {
return ((AbstractActivity) context.getActivity()).getType();
}
@Override
protected String getDefaultTitle() {
return "activity";
}
}

View file

@ -1,59 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.solution.route.activity.Start;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* Arrival time of the activity.
* <p>
* For route start the value is undefined (null), for other activities, it is
* the earliest time the location of the activity is reached. (Note, that it is
* not the time the activity is started, there may be an idle time before.)
* </p>
*
* @author balage
*
* @see {@linkplain StartTimePrinterColumn}
* @see {@linkplain EndTimePrinterColumn}
* @see {@linkplain TravelDurationPrinterColumn}
* @see {@linkplain WaitingDurationPrinterColumn}
* @see {@linkplain OperationDurationPrinterColumn}
* @see {@linkplain ActivityDurationPrinterColumn}
*/
public class ArrivalTimePrinterColumn extends AbstractTimePrinterColumn<ArrivalTimePrinterColumn> {
/**
* Constructor.
*/
public ArrivalTimePrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public ArrivalTimePrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "arrTime";
}
@Override
public Long getValue(RoutePrinterContext context) {
TourActivity act = context.getActivity();
if (act instanceof Start) {
return null;
} else {
return (long) act.getArrTime();
}
}
}

View file

@ -1,55 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
/**
* Utility interface for extracting cost and time values from problem.
*
* @author balage
*
*/
public interface CostAndTimeExtractor {
/**
* Returns the activity cost extracted from the context.
*
* @param context
* The context.
* @return The activity cost.
*/
default double getActivityCost(RoutePrinterContext context) {
return context.getProblem().getActivityCosts().getActivityCost(context.getActivity(),
context.getActivity().getArrTime(), context.getRoute().getDriver(), context.getRoute().getVehicle());
}
/**
* Returns the transport cost extracted from the context.
*
* @param context
* The context.
* @return The transport cost.
*/
default double getTransportCost(RoutePrinterContext context, TourActivity prevAct) {
return prevAct == null ? 0d
: context.getProblem().getTransportCosts().getTransportCost(prevAct.getLocation(),
context.getActivity().getLocation(),
context.getActivity().getArrTime(), context.getRoute().getDriver(),
context.getRoute().getVehicle());
}
/**
* Returns the transport time extracted from the context.
*
* @param context
* The context.
* @return The transpoert time.
*/
default double getTransportTime(RoutePrinterContext context, TourActivity prevAct) {
return prevAct == null ? 0d
: context.getProblem().getTransportCosts().getTransportTime(prevAct.getLocation(),
context.getActivity().getLocation(),
context.getActivity().getArrTime(), context.getRoute().getDriver(),
context.getRoute().getVehicle());
}
}

View file

@ -1,58 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.solution.route.activity.End;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* End time of the activity.
* <p>
* For route end the value is undefined (null), for other activities, it is the
* time when the activity is finished and the vehicle could progress toward the
* next activity.
* </p>
*
* @author balage
*
* @see {@linkplain ArrivalTimePrinterColumn}
* @see {@linkplain StartTimePrinterColumn}
* @see {@linkplain TravelDurationPrinterColumn}
* @see {@linkplain WaitingDurationPrinterColumn}
* @see {@linkplain OperationDurationPrinterColumn}
* @see {@linkplain ActivityDurationPrinterColumn}
*/
public class EndTimePrinterColumn extends AbstractTimePrinterColumn<EndTimePrinterColumn> {
/**
* Constructor.
*/
public EndTimePrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public EndTimePrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "endTime";
}
@Override
public Long getValue(RoutePrinterContext context) {
TourActivity act = context.getActivity();
if (act instanceof End) {
return null;
} else {
return (long) act.getEndTime();
}
}
}

View file

@ -1,53 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import com.graphhopper.jsprit.core.problem.job.Job;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* Name (id) of the job.
*
* <p>
* This column provides the {@linkplain Job#getId()} of the associated job of
* the activity for job activities and null for other route activities.
* </p>
*
* @author balage
*/
public class JobNamePrinterColumn extends AbstractPrinterColumn<RoutePrinterContext, String, JobNamePrinterColumn> {
/**
* Constructor.
*/
public JobNamePrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public JobNamePrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType("-"));
}
@Override
protected String getDefaultTitle() {
return "job name";
}
@Override
public String getData(RoutePrinterContext context) {
AbstractJob job = context.getJob();
return job == null ? null : job.getId();
}
}

View file

@ -1,57 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import com.graphhopper.jsprit.core.problem.job.Job;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnAlignment;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* Priority of the job.
*
* <p>
* This column provides the named (LOW, MEDIUM, HIGH) representation of
* {@linkplain Job#getPriority()} of the associated job of the activity for job
* activities and null for other route activities.
* </p>
*
* @author balage
*/
public class JobPriorityPrinterColumn extends AbstractPrinterColumn<RoutePrinterContext, String, JobPriorityPrinterColumn> {
private static final String[] PRIORITY_NAMES = new String[] { "", "HIGH", "MEDIUM", "LOW" };
/**
* Constructor.
*/
public JobPriorityPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public JobPriorityPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType("-")).withAlignment(ColumnAlignment.CENTER);
}
@Override
protected String getDefaultTitle() {
return "priority";
}
@Override
public String getData(RoutePrinterContext context) {
AbstractJob job = context.getJob();
return job == null ? null : PRIORITY_NAMES[job.getPriority()];
}
}

View file

@ -1,52 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* Priority of the job.
*
* <p>
* This column provides the simple class name of the associated job of the
* activity for job activities and null for other route activities.
* </p>
*
* @author balage
*/
public class JobTypePrinterColumn extends AbstractPrinterColumn<RoutePrinterContext, String, JobTypePrinterColumn> {
/**
* Constructor.
*/
public JobTypePrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public JobTypePrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType("-"));
}
@Override
protected String getDefaultTitle() {
return "job type";
}
@Override
public String getData(RoutePrinterContext context) {
AbstractJob job = context.getJob();
return job == null ? null : job.getClass().getSimpleName();
}
}

View file

@ -1,54 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* Priority of the job.
*
* <p>
* This column provides the simple class name of the associated job of the
* activity for job activities and null for other route activities.
* </p>
*
* @author balage
*/
public class LoacationPrinterColumn extends AbstractPrinterColumn<RoutePrinterContext, String, LoacationPrinterColumn> {
/**
* Constructor.
*/
public LoacationPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public LoacationPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType("-"));
}
@Override
protected String getDefaultTitle() {
return "location";
}
@Override
public String getData(RoutePrinterContext context) {
TourActivity act = context.getActivity();
Location loc = act.getLocation();
return loc == null ? null : loc.getId();
}
}

View file

@ -1,53 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.solution.route.activity.AbstractActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* Duration of the activity.
* <p>
* The time it takes to complete the on-site task of the activity. This is the
* value from {@linkplain AbstractActivity#getOperationTime()}.
* </p>
*
* @author balage
*
* @see {@linkplain ArrivalTimePrinterColumn}
* @see {@linkplain StartTimePrinterColumn}
* @see {@linkplain EndTimePrinterColumn}
* @see {@linkplain TravelDurationPrinterColumn}
* @see {@linkplain WaitingDurationPrinterColumn}
* @see {@linkplain ActivityDurationPrinterColumn}
*/
public class OperationDurationPrinterColumn extends AbstractDurationPrinterColumn<OperationDurationPrinterColumn> {
/**
* Constructor.
*/
public OperationDurationPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public OperationDurationPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "opTime";
}
@Override
public Long getValue(RoutePrinterContext context) {
TourActivity act = context.getActivity();
return (long) act.getOperationTime();
}
}

View file

@ -1,62 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.solution.route.activity.Start;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* The aggregated cost of the route from start till the current activity.
*
* <p>
* This column sumarizes the cost of all activities from start till the current
* activity.
* </p>
* <p>
* This column is stateful and stores the sum from the prior activities on the
* route.
* </p>
*
* @author balage
*/
public class RouteCostPrinterColumn extends TransportCostPrinterColumn {
// The aggregated cost of the route so far.
private int aggregatedCost = 0;
/**
* Constructor.
*/
public RouteCostPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public RouteCostPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "routeCost";
}
@Override
public Integer getData(RoutePrinterContext context) {
if (context.getActivity() instanceof Start) {
aggregatedCost = 0;
}
Integer res = super.getData(context);
if (res != null) {
aggregatedCost += res;
}
aggregatedCost += getActivityCost(context);
return aggregatedCost;
}
}

View file

@ -1,61 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.SizeDimension;
import com.graphhopper.jsprit.core.problem.solution.route.activity.Start;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* The load of the vehicle after the current activity is finished.
*
* <p>
* This column represents the current load of the vehicle on the route after the
* cargo load/unload performed on the activity. For the start activity (at the
* start of the route) the value is the initialLoad.
* </p>
* <p>
* This column is stateful and stores the vehicle load from the prior activity
* on the route.
* </p>
*
* @author balage
*/
public class RouteLoadPrinterColumn extends AbstractSizeDimensionPrinterColumn {
// The current vehicle load
private SizeDimension aggregated;
/**
* Constructor.
*/
public RouteLoadPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public RouteLoadPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "load";
}
@Override
protected SizeDimension getSizeDimension(RoutePrinterContext context) {
TourActivity act = context.getActivity();
if (act instanceof Start) {
aggregated = calculateInitialLoad(context);
} else {
aggregated = aggregated.add(act.getLoadChange());
}
return aggregated;
}
}

View file

@ -1,49 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.IntColumnType;
/**
* The order number of the route.
*
* <p>
* This is the ordinal of the route.
* </p>
*
* @author balage
*/
public class RouteNumberPrinterColumn extends AbstractPrinterColumn<RoutePrinterContext, Integer, RouteNumberPrinterColumn> {
/**
* Constructor.
*/
public RouteNumberPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public RouteNumberPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new IntColumnType());
}
@Override
protected String getDefaultTitle() {
return "route";
}
@Override
public Integer getData(RoutePrinterContext context) {
return context.getRoute().getId();
}
}

View file

@ -1,83 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.job.AbstractJob;
import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute;
import com.graphhopper.jsprit.core.problem.solution.route.activity.JobActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.job.JobPrinterContext;
/**
* The context of the detailed route printer columns.
*
* <p>
* This is a semi-mutable class: only the activity could be altered. Therefore
* for each route a new instance should be created.
* </p>
*
* @author balage
*
*/
public class RoutePrinterContext implements JobPrinterContext {
// The route itself
private VehicleRoute route;
// The current activity
private TourActivity activity;
// The problem
private VehicleRoutingProblem problem;
/**
* Constructor.
*
* @param routeNr
* route id
* @param route
* the route
* @param activity
* current activity
* @param problem
* problem
*/
public RoutePrinterContext(VehicleRoute route, TourActivity activity, VehicleRoutingProblem problem) {
super();
this.route = route;
this.activity = activity;
this.problem = problem;
}
/**
* @return The route itself.
*/
public VehicleRoute getRoute() {
return route;
}
/**
* @return The current activity.
*/
public TourActivity getActivity() {
return activity;
}
/**
* @param activity
* The current activity.
*/
public void setActivity(TourActivity activity) {
this.activity = activity;
}
/**
* @return The problem.
*/
public VehicleRoutingProblem getProblem() {
return problem;
}
@Override
public AbstractJob getJob() {
return (getActivity() instanceof JobActivity) ? ((JobActivity) getActivity()).getJob() : null;
}
}

View file

@ -1,74 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.solution.route.activity.JobActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* The time window used in activity.
*
* <p>
* This is the time window which was choosen by the algorithm. The start time of
* the activity is within this time window and the end time is within or matches
* the end value of this time window.
* </p>
*
* @author balage
*
* @see {@linkplain TimeWindowsPrinterColumn}
* @see {@linkplain StartTimePrinterColumn}
*/
public class SelectedTimeWindowPrinterColumn extends AbstractTimeWindowPrinterColumn<SelectedTimeWindowPrinterColumn> {
/**
* Constructor.
*/
public SelectedTimeWindowPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public SelectedTimeWindowPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "selTimeWindow";
}
/**
* {@inheritDoc}
*
* <p>
* This implementation returns at most one time window: the one the activity
* start time is within.
* </p>
*/
@Override
protected Collection<TimeWindow> getValue(RoutePrinterContext context) {
TourActivity act = context.getActivity();
if (act instanceof JobActivity) {
Optional<TimeWindow> optTw = ((JobActivity) act).getTimeWindows().stream()
.filter(tw -> tw.contains(act.getEndTime() - act.getOperationTime()))
.findAny();
if (optTw.isPresent()) {
return Collections.singleton(optTw.get());
} else {
return null;
}
} else {
return null;
}
}
}

View file

@ -1,57 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.solution.route.activity.End;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* Start time of the activity.
* <p>
* For route end the value is undefined (null), for other activities, it is the
* time when the task on location is effectively started.
* </p>
*
* @author balage
*
* @see {@linkplain ArrivalTimePrinterColumn}
* @see {@linkplain EndTimePrinterColumn}
* @see {@linkplain TravelDurationPrinterColumn}
* @see {@linkplain WaitingDurationPrinterColumn}
* @see {@linkplain OperationDurationPrinterColumn}
* @see {@linkplain ActivityDurationPrinterColumn}
*/
public class StartTimePrinterColumn extends AbstractTimePrinterColumn<StartTimePrinterColumn> {
/**
* Constructor.
*/
public StartTimePrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public StartTimePrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "startTime";
}
@Override
public Long getValue(RoutePrinterContext context) {
TourActivity act = context.getActivity();
if (act instanceof End) {
return null;
} else {
return (long) (act.getEndTime() - act.getOperationTime());
}
}
}

View file

@ -1,54 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.Collection;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.solution.route.activity.JobActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* The time windows of the activity.
*
* <p>
* Returns all time windows assigned to the activity.
* </p>
*
* @author balage
*
* @see {@linkplain SelectedTimeWindowPrinterColumn}
*/
public class TimeWindowsPrinterColumn extends AbstractTimeWindowPrinterColumn<TimeWindowsPrinterColumn> {
/**
* Constructor.
*/
public TimeWindowsPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public TimeWindowsPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "timeWindows";
}
@Override
protected Collection<TimeWindow> getValue(RoutePrinterContext context) {
TourActivity act = context.getActivity();
if (act instanceof JobActivity) {
return ((JobActivity) act).getTimeWindows();
} else {
return null;
}
}
}

View file

@ -1,58 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.solution.route.activity.Start;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* The cost of travelling to the activity.
*
* <p>
* This is the cost of the transport from the previous to this activity. For the
* start of the route this value is undefined (null).
* </p>
* <p>
* This column is stateful and stores the previous activity.
* </p>
*
* @author balage
*/
public class TransportCostPrinterColumn extends AbstractCostPrinterColumn {
// The previous activity
private TourActivity prevAct;
/**
* Constructor.
*/
public TransportCostPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public TransportCostPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "transCost";
}
@Override
public Integer getData(RoutePrinterContext context) {
TourActivity act = context.getActivity();
if (act instanceof Start) {
prevAct = null;
}
double res = getTransportCost(context, prevAct);
prevAct = act;
return (int) res;
}
}

View file

@ -1,66 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.solution.route.activity.Start;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* Travel duration toward the location of the activity.
* <p>
* The time it takes to travel to the location of the activity. The value is
* undefined for route start activity (null).
* </p>
* <p>
* This column is stateful and stores the previous activity.
* </p>
*
* @author balage
*
* @see {@linkplain ArrivalTimePrinterColumn}
* @see {@linkplain StartTimePrinterColumn}
* @see {@linkplain EndTimePrinterColumn}
* @see {@linkplain WaitingDurationPrinterColumn}
* @see {@linkplain OperationDurationPrinterColumn}
* @see {@linkplain ActivityDurationPrinterColumn}
*/
public class TravelDurationPrinterColumn extends AbstractDurationPrinterColumn<TravelDurationPrinterColumn>
implements CostAndTimeExtractor {
// The previous activity
private TourActivity prevAct;
/**
* Constructor.
*/
public TravelDurationPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public TravelDurationPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "travel";
}
@Override
public Long getValue(RoutePrinterContext context) {
TourActivity act = context.getActivity();
if (act instanceof Start) {
prevAct = null;
}
long val = (long) (getTransportTime(context, prevAct));
prevAct = act;
return val;
}
}

View file

@ -1,49 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
/**
* The name of the vehicle associated by this route.
*
* <p>
* This colum returns the id of the vehicle of the route.
* </p>
*
* @author balage
*/
public class VehicleNamePrinterColumn extends AbstractPrinterColumn<RoutePrinterContext, String, VehicleNamePrinterColumn> {
/**
* Constructor.
*/
public VehicleNamePrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public VehicleNamePrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType());
}
@Override
protected String getDefaultTitle() {
return "vehicle";
}
@Override
public String getData(RoutePrinterContext context) {
return context.getRoute().getVehicle().getId();
}
}

View file

@ -1,61 +0,0 @@
package com.graphhopper.jsprit.core.reporting.route;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.problem.solution.route.activity.End;
import com.graphhopper.jsprit.core.problem.solution.route.activity.Start;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* Idle duration before starting the activity.
* <p>
* This is the time duration between the vehicle arrives to the location (
* {@linkplain ArrivalTimePrinterColumn}) and the activity could be started (
* {@linkplain StartTimePrinterColumn}). For route start and end this value is
* not defined (null).
* </p>
*
* @author balage
*
* @see {@linkplain ArrivalTimePrinterColumn}
* @see {@linkplain StartTimePrinterColumn}
* @see {@linkplain EndTimePrinterColumn}
* @see {@linkplain TravelDurationPrinterColumn}
* @see {@linkplain OperationDurationPrinterColumn}
* @see {@linkplain ActivityDurationPrinterColumn}
*/
public class WaitingDurationPrinterColumn extends AbstractDurationPrinterColumn<WaitingDurationPrinterColumn> {
/**
* Constructor.
*/
public WaitingDurationPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public WaitingDurationPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitle() {
return "waiting";
}
@Override
public Long getValue(RoutePrinterContext context) {
TourActivity act = context.getActivity();
if (act instanceof Start || act instanceof End) {
return null;
} else {
return (long) (act.getEndTime() - act.getOperationTime() - act.getArrTime());
}
}
}

View file

@ -1,188 +0,0 @@
package com.graphhopper.jsprit.core.reporting.vehicle;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnAlignment;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.HumanReadableDurationFormatter;
import com.graphhopper.jsprit.core.reporting.columndefinition.HumanReadableTimeFormatter;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
import com.graphhopper.jsprit.core.reporting.route.RoutePrinterContext;
/**
* Abstract base class for time and (technically) duration columns.
*
* <p>
* Each columns derived from this abstract base has two variants: a numerical
* (an integer value) and a human readable. The numerical value displays the
* integer value representing the time values internally. The human readable
* value converts this value into a calendar (date and time) value.
* </p>
*
* @author balage
*
* @param <T>
* Self reference.
* @See {@linkplain HumanReadableTimeFormatter}
*/
public abstract class AbstractVehicleDurationPrinterColumn<T extends AbstractVehicleDurationPrinterColumn<T>>
extends AbstractPrinterColumn<VehicleSummaryContext, String, AbstractVehicleDurationPrinterColumn<T>> {
public enum Mode {
NUMERIC(""), HUMAN_READABLE(" (H)"), PERCENT_ROUTE(" (R%)"), PERCENT_SHIFT(" (S%)");
private String postfix;
private Mode(String postfix) {
this.postfix = postfix;
}
public String getPostfix() {
return postfix;
}
}
// The time formatter to use (only used when humanReadable flag is true)
private HumanReadableDurationFormatter formatter;
// Whether to use human readable form
private Mode mode = Mode.NUMERIC;
// Percent decimals
private int percentDecimals = 2;
/**
* Constructor to define a numeric format column.
*/
public AbstractVehicleDurationPrinterColumn() {
this(null);
}
/**
* Constructor to define a numeric format column, with a post creation
* decorator provided.
*/
public AbstractVehicleDurationPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
formatter = new HumanReadableDurationFormatter();
withDisplayMode(Mode.NUMERIC);
}
/**
* @param formatter
* The formatter used for {@linkplain Mode#HUMAN_READABLE}
* format.
*
*/
@SuppressWarnings("unchecked")
public T withFormatter(HumanReadableDurationFormatter formatter) {
this.formatter = formatter;
return (T) this;
}
@Override
protected String getDefaultTitle() {
return getDefaultTitleBase() + mode.postfix;
}
/**
* @return The base of the default title. It will be extended by the
* mode-specific postfix.
*/
protected abstract String getDefaultTitleBase();
/**
* @param mode
* The display mode.
*/
@SuppressWarnings("unchecked")
public T withDisplayMode(Mode mode) {
this.mode = mode;
return (T) this;
}
/**
* @param digits
* Number of decimal digits when mode is
* {@linkplain Mode#PERCENT_SHIFT} or
* {@linkplain Mode#PERCENT_ROUTE}.
* @throws IllegalArgumentException
* When the digits parameter is negative.
*/
@SuppressWarnings("unchecked")
public T withPercentDecimalDigits(int digits) {
if (digits < 0) {
throw new IllegalArgumentException("Decimal digit count should be non-negative.");
}
this.percentDecimals = digits;
return (T) this;
}
/**
* {@inheritDoc}
*
* <p>
* The column builder returned will be a string column with the null value
* represented by a hyphen ("-").
* </p>
*
*/
@Override
public ColumnDefinition.Builder getColumnBuilder() {
ColumnDefinition.Builder builder = new ColumnDefinition.Builder(new StringColumnType("-"));
if (mode != Mode.HUMAN_READABLE) {
builder.withAlignment(ColumnAlignment.RIGHT);
}
return builder;
}
/**
* {@inheritDoc}
*
* <p>
* The implementation delegates the value extracting to the abstract method
* {@linkplain #getValue(RoutePrinterContext)}.
* <p>
* <p>
* If the value is null, returns null, otherwise it returns the string
* representation of the numeric value or the human readable format based on
* the humanReadable flag.
* </p>
*
*/
@Override
public String getData(VehicleSummaryContext context) {
Long timeValue = getValue(context);
if (timeValue == null) {
return null;
}
switch (mode) {
case NUMERIC:
return "" + timeValue;
case HUMAN_READABLE:
return formatter.format(timeValue);
case PERCENT_ROUTE:
return formatPercent(timeValue, context.getRouteDuration() - context.getBreakDuration());
case PERCENT_SHIFT:
return formatPercent(timeValue, context.getShiftDuration() - context.getBreakDuration());
default:
throw new AssertionError("Can't get here.");
}
}
private String formatPercent(Long timeValue, long total) {
double pct = (100d * timeValue) / total;
return String.format("%20." + percentDecimals + "f %%", pct).trim();
}
/**
* Extracts the numerical value for this time or duration column.
*
* @param context
* The context.
* @return The numerical value or null.
*/
protected abstract Long getValue(VehicleSummaryContext context);
}

View file

@ -1,153 +0,0 @@
package com.graphhopper.jsprit.core.reporting.vehicle;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
import com.graphhopper.jsprit.core.reporting.AbstractPrinterColumn;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
import com.graphhopper.jsprit.core.reporting.columndefinition.HumanReadableEnabled;
import com.graphhopper.jsprit.core.reporting.columndefinition.HumanReadableTimeFormatter;
import com.graphhopper.jsprit.core.reporting.columndefinition.StringColumnType;
import com.graphhopper.jsprit.core.reporting.route.RoutePrinterContext;
/**
* Abstract base class for time window columns.
*
* <p>
* Each columns derived from this abstract base has two variants: a numerical
* (an integer value) and a human readable. The numerical value displays the
* integer value pair representing the time windows, the same the algorithm used
* internally. The human readable value converts this value into a calendar
* (date and time) value pair.
* </p>
*
* @author balage
*
* @param <T>
* Self reference.
* @See {@linkplain HumanReadableTimeFormatter}
*/
public abstract class AbstractVehicleTimeWindowPrinterColumn<T extends AbstractVehicleTimeWindowPrinterColumn<T>>
extends AbstractPrinterColumn<VehicleSummaryContext, String, AbstractVehicleTimeWindowPrinterColumn<T>>
implements HumanReadableEnabled<T> {
// The time formatter to use (only used when humanReadable flag is true)
private HumanReadableTimeFormatter formatter;
// Whether to use human readable form
private boolean humanReadable = false;
/**
* Constructor to define a numeric format column.
*/
public AbstractVehicleTimeWindowPrinterColumn() {
this(null);
}
/**
* Constructor to define a numeric format column, with a post creation
* decorator provided.
*/
public AbstractVehicleTimeWindowPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
formatter = new HumanReadableTimeFormatter();
}
@Override
@SuppressWarnings("unchecked")
public T withFormatter(HumanReadableTimeFormatter formatter) {
this.formatter = formatter;
return (T) this;
}
@Override
@SuppressWarnings("unchecked")
public T asHumanReadable() {
this.humanReadable = true;
return (T) this;
}
@Override
protected String getDefaultTitle() {
return getDefaultTitleBase() + (humanReadable ? " (H)" : "");
}
protected abstract String getDefaultTitleBase();
@Override
public ColumnDefinition.Builder getColumnBuilder() {
return new ColumnDefinition.Builder(new StringColumnType("-"));
}
/**
* {@inheritDoc}
*
* <p>
* The implementation delegates the value extracting to the abstract method
* {@linkplain #getValue(RoutePrinterContext)}.
* <p>
* <p>
* If the value is null or empty, returns null, otherwise it returns the
* string representation of the numeric value or the human readable format
* based on the humanReadable flag.
* </p>
*
*/
@Override
public String getData(VehicleSummaryContext context) {
Collection<TimeWindow> timeWindows = getValue(context);
if (timeWindows == null || timeWindows.isEmpty()) {
return null;
}
return timeWindows.stream().map(tw -> formatTimeWindow(tw)).collect(Collectors.joining());
}
/**
* Formats the time window.
*
* <p>
* The implementation returns the two (start, end) values sepratated by
* hyphen (-) and wrapped within brackets. When the end value is
* {@linkplain Double#MAX_VALUE} it omits the value indicating open
* interval.
* </p>
*
* @param tw
* The time window to format.
* @return The string representation of the time window.
*/
protected String formatTimeWindow(TimeWindow tw) {
String res = "";
if (humanReadable) {
res = "[" + formatter.format((long) tw.getStart()) + "-";
if (tw.getEnd() == Double.MAX_VALUE) {
res += "";
} else {
res += formatter.format((long) tw.getEnd());
}
res += "]";
} else {
res = "[" + (long) tw.getStart() + "-";
if (tw.getEnd() == Double.MAX_VALUE) {
res += "";
} else {
res += (long) tw.getEnd();
}
res += "]";
}
return res;
}
/**
* Extracts the collection of time windows from the context.
*
* @param context
* The context.
* @return The collection of time windows.
*/
protected abstract Collection<TimeWindow> getValue(VehicleSummaryContext context);
}

View file

@ -1,46 +0,0 @@
package com.graphhopper.jsprit.core.reporting.vehicle;
import java.util.function.Consumer;
import com.graphhopper.jsprit.core.reporting.columndefinition.ColumnDefinition;
/**
* Travel duration toward the location of the activity.
* <p>
* The time it takes to travel to the location of the activity. The value is
* undefined for route start activity (null).
* </p>
*
* @author balage
*
*/
public class VehicleActiveDurationPrinterColumn extends AbstractVehicleDurationPrinterColumn<VehicleActiveDurationPrinterColumn> {
/**
* Constructor.
*/
public VehicleActiveDurationPrinterColumn() {
super();
}
/**
* Constructor with a post creation decorator provided.
*/
public VehicleActiveDurationPrinterColumn(Consumer<ColumnDefinition.Builder> decorator) {
super(decorator);
}
@Override
protected String getDefaultTitleBase() {
return "active";
}
@Override
public Long getValue(VehicleSummaryContext context) {
return context.getActiveDuration();
}
}

Some files were not shown because too many files have changed in this diff Show more