mirror of
https://github.com/graphhopper/jsprit.git
synced 2020-01-24 07:45:05 +01:00
improve vehicle fleet manager
This commit is contained in:
parent
75417e57b2
commit
df105da98d
2 changed files with 60 additions and 74 deletions
|
|
@ -20,7 +20,9 @@ import jsprit.core.problem.vehicle.VehicleImpl.NoVehicle;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
class VehicleFleetManagerImpl implements VehicleFleetManager {
|
class VehicleFleetManagerImpl implements VehicleFleetManager {
|
||||||
|
|
@ -51,7 +53,6 @@ class VehicleFleetManagerImpl implements VehicleFleetManager {
|
||||||
|
|
||||||
public Vehicle getVehicle() {
|
public Vehicle getVehicle() {
|
||||||
return vehicleList.get(0);
|
return vehicleList.get(0);
|
||||||
// return vehicleList.getFirst();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
|
|
@ -64,19 +65,21 @@ class VehicleFleetManagerImpl implements VehicleFleetManager {
|
||||||
|
|
||||||
private Collection<Vehicle> vehicles;
|
private Collection<Vehicle> vehicles;
|
||||||
|
|
||||||
private Set<Vehicle> lockedVehicles;
|
private TypeContainer[] vehicleTypes;
|
||||||
|
|
||||||
private Map<VehicleTypeKey, TypeContainer> typeMapOfAvailableVehicles;
|
private boolean[] locked;
|
||||||
|
|
||||||
private Map<VehicleTypeKey, Vehicle> penaltyVehicles = new HashMap<VehicleTypeKey, Vehicle>();
|
|
||||||
|
|
||||||
|
private Vehicle[] vehicleArr;
|
||||||
|
|
||||||
public VehicleFleetManagerImpl(Collection<Vehicle> vehicles) {
|
public VehicleFleetManagerImpl(Collection<Vehicle> vehicles) {
|
||||||
super();
|
super();
|
||||||
this.vehicles = vehicles;
|
this.vehicles = vehicles;
|
||||||
this.lockedVehicles = new HashSet<Vehicle>();
|
int arrSize = vehicles.size() + 2;
|
||||||
makeMap();
|
locked = new boolean[arrSize];
|
||||||
logger.debug("initialise " + this);
|
vehicleArr = new Vehicle[arrSize];
|
||||||
|
vehicleTypes = new TypeContainer[arrSize];
|
||||||
|
initializeVehicleTypes();
|
||||||
|
logger.debug("initialise {}",this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -84,10 +87,12 @@ class VehicleFleetManagerImpl implements VehicleFleetManager {
|
||||||
return "[name=finiteVehicles]";
|
return "[name=finiteVehicles]";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeMap() {
|
private void initializeVehicleTypes() {
|
||||||
typeMapOfAvailableVehicles = new HashMap<VehicleTypeKey, TypeContainer>();
|
for(int i=0;i< vehicleTypes.length;i++){
|
||||||
penaltyVehicles = new HashMap<VehicleTypeKey, Vehicle>();
|
vehicleTypes[i] = new TypeContainer();
|
||||||
|
}
|
||||||
for (Vehicle v : vehicles) {
|
for (Vehicle v : vehicles) {
|
||||||
|
vehicleArr[v.getIndex()]=v;
|
||||||
addVehicle(v);
|
addVehicle(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -96,19 +101,11 @@ class VehicleFleetManagerImpl implements VehicleFleetManager {
|
||||||
if (v.getType() == null) {
|
if (v.getType() == null) {
|
||||||
throw new IllegalStateException("vehicle needs type");
|
throw new IllegalStateException("vehicle needs type");
|
||||||
}
|
}
|
||||||
VehicleTypeKey typeKey = new VehicleTypeKey(v.getType().getTypeId(), v.getStartLocation().getId(), v.getEndLocation().getId(), v.getEarliestDeparture(), v.getLatestArrival(), v.getSkills(), v.isReturnToDepot());
|
vehicleTypes[v.getVehicleTypeIdentifier().getIndex()].add(v);
|
||||||
if (!typeMapOfAvailableVehicles.containsKey(typeKey)) {
|
|
||||||
typeMapOfAvailableVehicles.put(typeKey, new TypeContainer());
|
|
||||||
}
|
|
||||||
typeMapOfAvailableVehicles.get(typeKey).add(v);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeVehicle(Vehicle v) {
|
private void removeVehicle(Vehicle v) {
|
||||||
VehicleTypeKey key = new VehicleTypeKey(v.getType().getTypeId(), v.getStartLocation().getId(), v.getEndLocation().getId(), v.getEarliestDeparture(), v.getLatestArrival(), v.getSkills(), v.isReturnToDepot());
|
vehicleTypes[v.getVehicleTypeIdentifier().getIndex()].remove(v);
|
||||||
if (typeMapOfAvailableVehicles.containsKey(key)) {
|
|
||||||
typeMapOfAvailableVehicles.get(key).remove(v);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -121,13 +118,9 @@ class VehicleFleetManagerImpl implements VehicleFleetManager {
|
||||||
@Override
|
@Override
|
||||||
public Collection<Vehicle> getAvailableVehicles() {
|
public Collection<Vehicle> getAvailableVehicles() {
|
||||||
List<Vehicle> vehicles = new ArrayList<Vehicle>();
|
List<Vehicle> vehicles = new ArrayList<Vehicle>();
|
||||||
for (VehicleTypeKey key : typeMapOfAvailableVehicles.keySet()) {
|
for(int i=0;i< vehicleTypes.length;i++){
|
||||||
if (!typeMapOfAvailableVehicles.get(key).isEmpty()) {
|
if(!vehicleTypes[i].isEmpty()){
|
||||||
vehicles.add(typeMapOfAvailableVehicles.get(key).getVehicle());
|
vehicles.add(vehicleTypes[i].getVehicle());
|
||||||
} else {
|
|
||||||
if (penaltyVehicles.containsKey(key)) {
|
|
||||||
vehicles.add(penaltyVehicles.get(key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vehicles;
|
return vehicles;
|
||||||
|
|
@ -136,15 +129,9 @@ class VehicleFleetManagerImpl implements VehicleFleetManager {
|
||||||
@Override
|
@Override
|
||||||
public Collection<Vehicle> getAvailableVehicles(Vehicle withoutThisType) {
|
public Collection<Vehicle> getAvailableVehicles(Vehicle withoutThisType) {
|
||||||
List<Vehicle> vehicles = new ArrayList<Vehicle>();
|
List<Vehicle> vehicles = new ArrayList<Vehicle>();
|
||||||
VehicleTypeKey thisKey = new VehicleTypeKey(withoutThisType.getType().getTypeId(), withoutThisType.getStartLocation().getId(), withoutThisType.getEndLocation().getId(), withoutThisType.getEarliestDeparture(), withoutThisType.getLatestArrival(), withoutThisType.getSkills(), withoutThisType.isReturnToDepot());
|
for(int i=0;i< vehicleTypes.length;i++){
|
||||||
for (VehicleTypeKey key : typeMapOfAvailableVehicles.keySet()) {
|
if(!vehicleTypes[i].isEmpty() && i != withoutThisType.getVehicleTypeIdentifier().getIndex()){
|
||||||
if (key.equals(thisKey)) continue;
|
vehicles.add(vehicleTypes[i].getVehicle());
|
||||||
if (!typeMapOfAvailableVehicles.get(key).isEmpty()) {
|
|
||||||
vehicles.add(typeMapOfAvailableVehicles.get(key).getVehicle());
|
|
||||||
} else {
|
|
||||||
if (penaltyVehicles.containsKey(key)) {
|
|
||||||
vehicles.add(penaltyVehicles.get(key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vehicles;
|
return vehicles;
|
||||||
|
|
@ -158,11 +145,13 @@ class VehicleFleetManagerImpl implements VehicleFleetManager {
|
||||||
if (vehicles.isEmpty() || vehicle instanceof NoVehicle) {
|
if (vehicles.isEmpty() || vehicle instanceof NoVehicle) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boolean locked = lockedVehicles.add(vehicle);
|
if(locked[vehicle.getIndex()]){
|
||||||
removeVehicle(vehicle);
|
|
||||||
if (!locked) {
|
|
||||||
throw new IllegalStateException("cannot lock vehicle twice " + vehicle.getId());
|
throw new IllegalStateException("cannot lock vehicle twice " + vehicle.getId());
|
||||||
}
|
}
|
||||||
|
else{
|
||||||
|
locked[vehicle.getIndex()] = true;
|
||||||
|
removeVehicle(vehicle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
|
|
@ -170,11 +159,10 @@ class VehicleFleetManagerImpl implements VehicleFleetManager {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void unlock(Vehicle vehicle) {
|
public void unlock(Vehicle vehicle) {
|
||||||
if (vehicles.isEmpty() || vehicle instanceof NoVehicle) {
|
if (vehicle == null || vehicles.isEmpty() || vehicle instanceof NoVehicle) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (vehicle == null) return;
|
locked[vehicle.getIndex()] = false;
|
||||||
lockedVehicles.remove(vehicle);
|
|
||||||
addVehicle(vehicle);
|
addVehicle(vehicle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,7 +171,7 @@ class VehicleFleetManagerImpl implements VehicleFleetManager {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isLocked(Vehicle vehicle) {
|
public boolean isLocked(Vehicle vehicle) {
|
||||||
return lockedVehicles.contains(vehicle);
|
return locked[vehicle.getIndex()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
|
|
@ -191,18 +179,11 @@ class VehicleFleetManagerImpl implements VehicleFleetManager {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void unlockAll() {
|
public void unlockAll() {
|
||||||
Collection<Vehicle> locked = new ArrayList<Vehicle>(lockedVehicles);
|
for(int i=0;i<vehicleArr.length;i++){
|
||||||
for (Vehicle v : locked) {
|
if(locked[i]){
|
||||||
unlock(v);
|
unlock(vehicleArr[i]);
|
||||||
}
|
}
|
||||||
if (!lockedVehicles.isEmpty()) {
|
|
||||||
throw new IllegalStateException("no vehicle must be locked");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public int sizeOfLockedVehicles() {
|
|
||||||
return lockedVehicles.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package jsprit.core.problem.vehicle;
|
package jsprit.core.problem.vehicle;
|
||||||
|
|
||||||
import jsprit.core.problem.Location;
|
import jsprit.core.problem.Location;
|
||||||
|
import jsprit.core.problem.VehicleRoutingProblem;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
@ -31,9 +32,9 @@ public class TestVehicleFleetManagerImpl {
|
||||||
|
|
||||||
VehicleFleetManager fleetManager;
|
VehicleFleetManager fleetManager;
|
||||||
|
|
||||||
Vehicle v1;
|
VehicleImpl v1;
|
||||||
|
|
||||||
Vehicle v2;
|
VehicleImpl v2;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
|
@ -42,6 +43,7 @@ public class TestVehicleFleetManagerImpl {
|
||||||
v1 = VehicleImpl.Builder.newInstance("standard").setStartLocation(Location.newInstance("loc")).setType(VehicleTypeImpl.Builder.newInstance("standard").build()).build();
|
v1 = VehicleImpl.Builder.newInstance("standard").setStartLocation(Location.newInstance("loc")).setType(VehicleTypeImpl.Builder.newInstance("standard").build()).build();
|
||||||
v2 = VehicleImpl.Builder.newInstance("foo").setStartLocation(Location.newInstance("fooLoc")).setType(VehicleTypeImpl.Builder.newInstance("foo").build()).build();
|
v2 = VehicleImpl.Builder.newInstance("foo").setStartLocation(Location.newInstance("fooLoc")).setType(VehicleTypeImpl.Builder.newInstance("foo").build()).build();
|
||||||
|
|
||||||
|
VehicleRoutingProblem.Builder.newInstance().addVehicle(v1).addVehicle(v2).build();
|
||||||
// v1.
|
// v1.
|
||||||
vehicles.add(v1);
|
vehicles.add(v1);
|
||||||
vehicles.add(v2);
|
vehicles.add(v2);
|
||||||
|
|
@ -61,6 +63,20 @@ public class TestVehicleFleetManagerImpl {
|
||||||
assertEquals(1, vehicles.size());
|
assertEquals(1, vehicles.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLock2() {
|
||||||
|
fleetManager.lock(v1);
|
||||||
|
fleetManager.lock(v2);
|
||||||
|
fleetManager.unlock(v2);
|
||||||
|
assertTrue(fleetManager.isLocked(v1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsLocked() {
|
||||||
|
fleetManager.lock(v1);
|
||||||
|
assertTrue(fleetManager.isLocked(v1));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLockTwice() {
|
public void testLockTwice() {
|
||||||
fleetManager.lock(v1);
|
fleetManager.lock(v1);
|
||||||
|
|
@ -94,22 +110,6 @@ public class TestVehicleFleetManagerImpl {
|
||||||
assertEquals(2, vehicles_.size());
|
assertEquals(2, vehicles_.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWithPenalty_whenHavingOneRegularVehicleAvailable_noPenaltyVehicleIsReturn() {
|
|
||||||
Vehicle penalty4standard = VehicleImpl.Builder.newInstance("standard_penalty").setStartLocation(Location.newInstance("loc")).
|
|
||||||
setType(VehicleTypeImpl.Builder.newInstance("standard").build()).build();
|
|
||||||
|
|
||||||
List<Vehicle> vehicles = new ArrayList<Vehicle>();
|
|
||||||
vehicles.add(v1);
|
|
||||||
vehicles.add(v2);
|
|
||||||
vehicles.add(penalty4standard);
|
|
||||||
VehicleFleetManager fleetManager = new FiniteFleetManagerFactory(vehicles).createFleetManager();
|
|
||||||
|
|
||||||
Collection<Vehicle> availableVehicles = fleetManager.getAvailableVehicles();
|
|
||||||
assertEquals(2, availableVehicles.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void whenAddingTwoVehiclesWithSameTypeIdAndLocation_getAvailableVehicleShouldReturnOnlyOneOfThem() {
|
public void whenAddingTwoVehiclesWithSameTypeIdAndLocation_getAvailableVehicleShouldReturnOnlyOneOfThem() {
|
||||||
VehicleTypeImpl type = VehicleTypeImpl.Builder.newInstance("standard").build();
|
VehicleTypeImpl type = VehicleTypeImpl.Builder.newInstance("standard").build();
|
||||||
|
|
@ -140,6 +140,7 @@ public class TestVehicleFleetManagerImpl {
|
||||||
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
||||||
Vehicle v2 = VehicleImpl.Builder.newInstance("v2").setStartLocation(Location.newInstance("loc")).setEndLocation(Location.newInstance("endLoc"))
|
Vehicle v2 = VehicleImpl.Builder.newInstance("v2").setStartLocation(Location.newInstance("loc")).setEndLocation(Location.newInstance("endLoc"))
|
||||||
.setType(type2).setEarliestStart(0.).setLatestArrival(10.).build();
|
.setType(type2).setEarliestStart(0.).setLatestArrival(10.).build();
|
||||||
|
VehicleRoutingProblem.Builder.newInstance().addVehicle(v1).addVehicle(v2).build();
|
||||||
VehicleFleetManager fleetManager = new FiniteFleetManagerFactory(Arrays.asList(v1, v2)).createFleetManager();
|
VehicleFleetManager fleetManager = new FiniteFleetManagerFactory(Arrays.asList(v1, v2)).createFleetManager();
|
||||||
Collection<Vehicle> vehicles = fleetManager.getAvailableVehicles();
|
Collection<Vehicle> vehicles = fleetManager.getAvailableVehicles();
|
||||||
assertEquals(2, vehicles.size());
|
assertEquals(2, vehicles.size());
|
||||||
|
|
@ -154,6 +155,7 @@ public class TestVehicleFleetManagerImpl {
|
||||||
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
||||||
Vehicle v2 = VehicleImpl.Builder.newInstance("v2").setStartLocation(Location.newInstance("loc")).setEndLocation(Location.newInstance("endLoc"))
|
Vehicle v2 = VehicleImpl.Builder.newInstance("v2").setStartLocation(Location.newInstance("loc")).setEndLocation(Location.newInstance("endLoc"))
|
||||||
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
||||||
|
VehicleRoutingProblem.Builder.newInstance().addVehicle(v1).addVehicle(v2).build();
|
||||||
VehicleFleetManager fleetManager = new FiniteFleetManagerFactory(Arrays.asList(v1, v2)).createFleetManager();
|
VehicleFleetManager fleetManager = new FiniteFleetManagerFactory(Arrays.asList(v1, v2)).createFleetManager();
|
||||||
Collection<Vehicle> vehicles = fleetManager.getAvailableVehicles();
|
Collection<Vehicle> vehicles = fleetManager.getAvailableVehicles();
|
||||||
assertEquals(2, vehicles.size());
|
assertEquals(2, vehicles.size());
|
||||||
|
|
@ -168,6 +170,7 @@ public class TestVehicleFleetManagerImpl {
|
||||||
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
||||||
Vehicle v2 = VehicleImpl.Builder.newInstance("v2").setStartLocation(Location.newInstance("loc")).setEndLocation(Location.newInstance("endLoc"))
|
Vehicle v2 = VehicleImpl.Builder.newInstance("v2").setStartLocation(Location.newInstance("loc")).setEndLocation(Location.newInstance("endLoc"))
|
||||||
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
||||||
|
VehicleRoutingProblem.Builder.newInstance().addVehicle(v1).addVehicle(v2).build();
|
||||||
VehicleFleetManager fleetManager = new FiniteFleetManagerFactory(Arrays.asList(v1, v2)).createFleetManager();
|
VehicleFleetManager fleetManager = new FiniteFleetManagerFactory(Arrays.asList(v1, v2)).createFleetManager();
|
||||||
Collection<Vehicle> vehicles = fleetManager.getAvailableVehicles();
|
Collection<Vehicle> vehicles = fleetManager.getAvailableVehicles();
|
||||||
assertEquals(2, vehicles.size());
|
assertEquals(2, vehicles.size());
|
||||||
|
|
@ -182,6 +185,7 @@ public class TestVehicleFleetManagerImpl {
|
||||||
.setType(type).setEarliestStart(5.).setLatestArrival(10.).build();
|
.setType(type).setEarliestStart(5.).setLatestArrival(10.).build();
|
||||||
Vehicle v2 = VehicleImpl.Builder.newInstance("v2").setStartLocation(Location.newInstance("loc")).setEndLocation(Location.newInstance("endLoc"))
|
Vehicle v2 = VehicleImpl.Builder.newInstance("v2").setStartLocation(Location.newInstance("loc")).setEndLocation(Location.newInstance("endLoc"))
|
||||||
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
||||||
|
VehicleRoutingProblem.Builder.newInstance().addVehicle(v1).addVehicle(v2).build();
|
||||||
VehicleFleetManager fleetManager = new FiniteFleetManagerFactory(Arrays.asList(v1, v2)).createFleetManager();
|
VehicleFleetManager fleetManager = new FiniteFleetManagerFactory(Arrays.asList(v1, v2)).createFleetManager();
|
||||||
Collection<Vehicle> vehicles = fleetManager.getAvailableVehicles();
|
Collection<Vehicle> vehicles = fleetManager.getAvailableVehicles();
|
||||||
assertEquals(2, vehicles.size());
|
assertEquals(2, vehicles.size());
|
||||||
|
|
@ -196,6 +200,7 @@ public class TestVehicleFleetManagerImpl {
|
||||||
.setType(type).setEarliestStart(0.).setLatestArrival(20.).build();
|
.setType(type).setEarliestStart(0.).setLatestArrival(20.).build();
|
||||||
Vehicle v2 = VehicleImpl.Builder.newInstance("v2").setStartLocation(Location.newInstance("loc")).setEndLocation(Location.newInstance("endLoc"))
|
Vehicle v2 = VehicleImpl.Builder.newInstance("v2").setStartLocation(Location.newInstance("loc")).setEndLocation(Location.newInstance("endLoc"))
|
||||||
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
.setType(type).setEarliestStart(0.).setLatestArrival(10.).build();
|
||||||
|
VehicleRoutingProblem.Builder.newInstance().addVehicle(v1).addVehicle(v2).build();
|
||||||
VehicleFleetManager fleetManager = new FiniteFleetManagerFactory(Arrays.asList(v1, v2)).createFleetManager();
|
VehicleFleetManager fleetManager = new FiniteFleetManagerFactory(Arrays.asList(v1, v2)).createFleetManager();
|
||||||
Collection<Vehicle> vehicles = fleetManager.getAvailableVehicles();
|
Collection<Vehicle> vehicles = fleetManager.getAvailableVehicles();
|
||||||
assertEquals(2, vehicles.size());
|
assertEquals(2, vehicles.size());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue