/*******************************************************************************
* Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com)
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v3
* which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt
******************************************************************************/
package com.opendoorlogistics.components.scheduleeditor.data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.components.PredefinedTags;
import com.opendoorlogistics.api.standardcomponents.ScheduleEditor.EditorTable;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLTableReadOnly;
import com.opendoorlogistics.api.tables.beans.BeanMappedRow;
import com.opendoorlogistics.components.scheduleeditor.DisplayFields;
import com.opendoorlogistics.components.scheduleeditor.ScheduleEditorComponent;
import com.opendoorlogistics.components.scheduleeditor.ScheduleEditorConstants;
import com.opendoorlogistics.components.scheduleeditor.data.beans.ResourceDescription;
import com.opendoorlogistics.components.scheduleeditor.data.beans.ResourceType;
import com.opendoorlogistics.components.scheduleeditor.data.beans.Task;
import com.opendoorlogistics.components.scheduleeditor.data.beans.TaskOrder;
import com.opendoorlogistics.core.tables.beans.BeanMapping.BeanDatastoreMapping;
import com.opendoorlogistics.core.tables.beans.BeanMapping.BeanTableMappingImpl;
import com.opendoorlogistics.core.tables.utils.TableUtils;
import com.opendoorlogistics.core.utils.strings.StandardisedStringTreeMap;
import com.opendoorlogistics.core.utils.strings.Strings;
public class EditorData {
private final Task[] tasks;
private final Resource[] resources;
private final TaskOrder[] order;
private final StandardisedStringTreeMap<Task> tasksByTaskId = new StandardisedStringTreeMap<>(true);
private final StandardisedStringTreeMap<TaskOrder> taskOrderByTaskOrderId = new StandardisedStringTreeMap<>(true);
private final StandardisedStringTreeMap<String> resourceByTaskId = new StandardisedStringTreeMap<>(true);
private final StandardisedStringTreeMap<Resource> resourceByResourceId = new StandardisedStringTreeMap<>(true);
private final HashMap<EditorTable, DisplayFields> displayFieldsByType;
/**
* Construct also validated the data
* @param stops
* @param vehicles
* @param order
*/
private EditorData(Task[] stops, Resource[] vehicles, TaskOrder[] order, HashMap<EditorTable, DisplayFields> displayFieldsByType) {
this.order = order;
this.tasks = stops;
this.displayFieldsByType = displayFieldsByType;
// save stops by id
for (Task stop : stops) {
if (tasksByTaskId.get(stop.getId()) != null) {
throw new RuntimeException("Duplicate stop-id " + stop.getId());
}
tasksByTaskId.put(stop.getId(), stop);
if(stop.getId().contains(System.lineSeparator())){
throw new RuntimeException("Stop-id found with new line character.");
}
}
// ensure vehicles are unique and ids are OK
for (Resource vehicle : vehicles) {
// cannot use unloaded id
if (Strings.equalsStd(vehicle.getId(), ScheduleEditorConstants.UNLOADED_VEHICLE)) {
throw new RuntimeException("Invalid vehicle-id " + vehicle.getId() + " found in vehicles table.");
}
if (resourceByResourceId.get(vehicle.getId()) != null) {
throw new RuntimeException("Duplicate vehicle-id " + vehicle.getId());
}
resourceByResourceId.put(vehicle.getId(), vehicle);
}
// validate stop order table
for (TaskOrder stopOrder : order) {
// cannot use unloaded vehicle id
if (Strings.equalsStd(stopOrder.getResourceId(), ScheduleEditorConstants.UNLOADED_VEHICLE)) {
throw new RuntimeException("Invalid vehicle-id " + stopOrder.getResourceId() + " found in stop-order table.");
}
// ensure vehicle known
if (resourceByResourceId.get(stopOrder.getResourceId()) == null) {
throw new RuntimeException("Unknown vehicle-id " + stopOrder.getResourceId() + " found in stop-order table.");
}
// ensure stop known
Task stop = tasksByTaskId.get(stopOrder.getTaskId());
if (stop == null) {
throw new RuntimeException("Unknown stop-id " + stopOrder.getResourceId() + " found in stop-order table.");
}
// ensure same stop not added twice
if(resourceByTaskId.get(stopOrder.getTaskId())!=null){
throw new RuntimeException("Stop-id is repeated twice in stop-order table: "+ stopOrder.getTaskId());
}
resourceByTaskId.put(stopOrder.getTaskId(), stopOrder.getResourceId());
taskOrderByTaskOrderId.put(stopOrder.getTaskId(), stopOrder);
}
// if we have not-loads, create a dummy vehicle for anything not loaded
this.resources = new Resource[vehicles.length+1];
this.resources[0]= new Resource();
this.resources[0].setId(ScheduleEditorConstants.UNLOADED_VEHICLE);
resourceByResourceId.put(ScheduleEditorConstants.UNLOADED_VEHICLE, this.resources[0]);
for(int i =1 ; i< this.resources.length;i++){
this.resources[i] = vehicles[i-1];
}
}
public static EditorData read(ODLApi api,ODLDatastore<? extends ODLTableReadOnly> ioDs) {
// process additional display-only fields (can be on stop order and stops)
// read stop order
ScheduleEditorComponent comp = new ScheduleEditorComponent();
List<BeanMappedRow> list = readTable(ioDs, comp.getTableName(EditorTable.TASK_ORDER),false);
TaskOrder[] order = new TaskOrder[list.size()];
for (int i = 0; i < order.length; i++) {
order[i] = (TaskOrder) list.get(i);
}
// read types and translate into actual vehicles
ArrayList<Resource> allResources = new ArrayList<>();
list = readTable(ioDs, comp.getTableName(EditorTable.RESOURCE_TYPES),false);
Set<String> vehiceIds = api.stringConventions().createStandardisedSet();
for (int row = 0; row < list.size(); row++) {
ResourceType type =(ResourceType) list.get(row);
long nb=type.getNumber();
if(nb>Integer.MAX_VALUE || nb<0){
throw new RuntimeException("Illegal number of reources in column " + PredefinedTags.NUMBER_OF_VEHICLES + ".");
}
for(int vehicleNumber=0;vehicleNumber<(int)nb;vehicleNumber++){
Resource vehicle = new Resource();
vehicle.setId(api.stringConventions().getVehicleId(type.getId(),(int) nb, vehicleNumber));
if(vehiceIds.contains(vehicle.getId())){
throw new RuntimeException("Duplicate resource id: " + vehicle.getId());
}
vehiceIds.add(vehicle.getId());
vehicle.setName(api.stringConventions().getVehicleName(type.getName(),(int) nb, vehicleNumber));
allResources.add(vehicle);
}
}
// add dummy vehicle records for any vehicles referenced in stop which are unknown...
for(TaskOrder so:order){
String vid = so.getResourceId();
if(vehiceIds.contains(vid)==false){
Resource vehicle = new Resource();
vehicle.setId(vid);
vehicle.setName(vid);
allResources.add(vehicle);
vehiceIds.add(vid);
}
}
// read descriptions table
Map<String,String> descriptionsByResourceId = api.stringConventions().createStandardisedMap();
List<BeanMappedRow> descriptionList = readTable(ioDs, comp.getTableName(EditorTable.RESOURCE_DESCRIPTIONS),true);
for(BeanMappedRow bmr : descriptionList){
ResourceDescription d = (ResourceDescription)bmr;
if(d.getResourceId()!=null){
descriptionsByResourceId.put(d.getResourceId(), d.getDescription());
}
}
for(Resource res : allResources){
res.setDescription(descriptionsByResourceId.get(res.getId()));
}
// turn vehicles list into an array
Resource[] vehicles = allResources.toArray(new Resource[allResources.size()]);
// read stops
list = readTable(ioDs, comp.getTableName(EditorTable.TASKS),false);
Task[] stops = new Task[list.size()];
for (int i = 0; i < stops.length; i++) {
stops[i] = (Task) list.get(i);
}
// read display fields
HashMap<EditorTable, DisplayFields> displayFieldsByType = new HashMap<>();
for(int i=0 ; i<ioDs.getTableCount();i++){
ODLTableReadOnly table = ioDs.getTableAt(i);
DisplayFields displayFields = new DisplayFields(api, table);
if(displayFields.getTableType()!=null){
displayFieldsByType.put(displayFields.getTableType(), displayFields);
}
}
// create editor data object, which also does validation
return new EditorData(stops, vehicles, order, displayFieldsByType);
}
/**
* @param ioDs
* @param index
* @return
*/
private static List<BeanMappedRow> readTable(ODLDatastore<? extends ODLTableReadOnly> ioDs, String name, boolean isOptional) {
ScheduleEditorComponent component = new ScheduleEditorComponent();
BeanDatastoreMapping beanMapping = component.getBeanMapping();
BeanTableMappingImpl mapping = beanMapping.getTableMapping(name);
ODLTableReadOnly table = TableUtils.findTable(ioDs, name);
if (table == null) {
if(isOptional){
return new ArrayList<BeanMappedRow>();
}
throw new RuntimeException("No " + mapping.getTableDefinition().getName() + " table available.");
}
List<BeanMappedRow> list = mapping.readObjectsFromTable(table);
for(BeanMappedRow bmr:list){
if(BeanMappedRowExt.class.isInstance(bmr)){
((BeanMappedRowExt)bmr).setTable(table);
}
}
return list;
}
public Task[] getTasks() {
return tasks;
}
public Resource[] getResources() {
return resources;
}
public TaskOrder[] getOrder() {
return order;
}
public Resource getResource(String id) {
return resourceByResourceId.get(id);
}
public Task[] getTasksByResource(String vehicleId) {
ArrayList<Task> matchingStops = new ArrayList<>();
if (Strings.equalsStd(vehicleId, ScheduleEditorConstants.UNLOADED_VEHICLE)) {
// get not-loads
for(Task stop:tasks){
if(resourceByTaskId.get(stop.getId())==null){
matchingStops.add(stop);
}
}
} else {
for (TaskOrder edo : order) {
if (Strings.equalsStd(edo.getResourceId(), vehicleId)) {
Task stop = tasksByTaskId.get(edo.getTaskId());
if (stop == null) {
throw new RuntimeException("Unknown stop-id " + edo.getTaskId() + " found in stop-order table.");
}
matchingStops.add(stop);
// stopIds.add(edo.getStopId());
}
}
}
return matchingStops.toArray(new Task[matchingStops.size()]);
}
public Task getTask(String id){
return tasksByTaskId.get(id);
}
public TaskOrder getTaskOrderById(String stopId){
return taskOrderByTaskOrderId.get(stopId);
}
public DisplayFields getDisplayFields(EditorTable tableType){
return displayFieldsByType.get(tableType);
}
}