/*******************************************************************************
* 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.core.scripts.execution.adapters;
import java.util.ArrayList;
import java.util.Collection;
import com.opendoorlogistics.api.ExecutionReport;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLDatastoreAlterable;
import com.opendoorlogistics.api.tables.ODLTable;
import com.opendoorlogistics.api.tables.ODLTableAlterable;
import com.opendoorlogistics.api.tables.ODLTableDefinition;
import com.opendoorlogistics.api.tables.ODLTableDefinitionAlterable;
import com.opendoorlogistics.api.tables.ODLTableReadOnly;
import com.opendoorlogistics.api.tables.TableFlags;
import com.opendoorlogistics.core.scripts.ScriptConstants;
import com.opendoorlogistics.core.scripts.elements.AdaptedTableConfig;
import com.opendoorlogistics.core.scripts.elements.AdapterColumnConfig;
import com.opendoorlogistics.core.scripts.elements.AdapterConfig;
import com.opendoorlogistics.core.scripts.elements.AdapterColumnConfig.SortField;
import com.opendoorlogistics.core.tables.decorators.datastores.AdaptedDecorator;
import com.opendoorlogistics.core.tables.decorators.datastores.AdaptedDecorator.AdapterMapping;
import com.opendoorlogistics.core.tables.memory.ODLDatastoreImpl;
import com.opendoorlogistics.core.tables.utils.TableUtils;
import com.opendoorlogistics.core.utils.strings.StandardisedStringSet;
import com.opendoorlogistics.core.utils.strings.Strings;
final public class AdapterBuilderUtils {
public static final long MAPPING_FLAGS_MISSING_SET_OPTIONAL_OK = 0x01;
private AdapterBuilderUtils() {
}
public static void mapFields(ODLTableDefinition srcTable, int destinationTableId, AdaptedTableConfig destinationTable, AdapterMapping mapping,
long mappingFlags,
ExecutionReport result) {
// process each of the destination fields
for (int destFieldIndx = 0; destFieldIndx < destinationTable.getColumnCount(); destFieldIndx++) {
AdapterColumnConfig field = destinationTable.getColumn(destFieldIndx);
if (!mapSingleField(srcTable, destinationTableId, field, destFieldIndx, mapping,mappingFlags, result)) {
return;
}
}
}
static boolean mapSingleField(ODLTableDefinition srcTable,
int destinationTableId,
AdapterColumnConfig destinationField,
int destinationFieldIndx, AdapterMapping mapping,
long mappingFlags,
ExecutionReport result) {
int srcFieldIndx = -1;
// match based on name
if (Strings.isEmpty(destinationField.getFrom()) == false) {
srcFieldIndx = TableUtils.findColumnIndx(srcTable, destinationField.getFrom(), true);
}
boolean isOptional = (destinationField.getFlags() & TableFlags.FLAG_IS_OPTIONAL) == TableFlags.FLAG_IS_OPTIONAL;
boolean isSourceFieldSet = Strings.isEmpty(destinationField.getFrom()) == false;
boolean missingSetOk = (mappingFlags & MAPPING_FLAGS_MISSING_SET_OPTIONAL_OK)==MAPPING_FLAGS_MISSING_SET_OPTIONAL_OK;
if (srcFieldIndx == -1 && (!isOptional || (isSourceFieldSet && !missingSetOk))) {
if(isSourceFieldSet){
result.setFailed("Could not find source field \"" + destinationField.getFrom() + "\" required by field \"" + destinationField.getName() + "\" within data adapter.");
}else{
result.setFailed("The destination field \"" + destinationField.getName() + "\" within a data adapter does not have its source set.");
}
return false;
}
mapping.setFieldSourceIndx(destinationTableId, destinationFieldIndx, srcFieldIndx);
return true;
}
/**
* Create a simple adapter which maps the source to the destination based on
* matching field and table names but doesn't do anything else.
*
* It follows this convention: - Empty destination table - take all source
* fields - Non-empty destination table - matched fields based on name
*
* @param source
* @param destinationConfig
* @param result
* @return
*/
public static <T extends ODLTableReadOnly> ODLDatastore<T> createSimpleAdapter(ODLDatastore<? extends T> source, AdapterConfig destinationConfig,
ExecutionReport result) {
// find source tables
int [] srcTables = new int[destinationConfig.getTableCount()];
for(int i =0; i < srcTables.length ; i++){
AdaptedTableConfig tableConfig = destinationConfig.getTables().get(i);
// test if table is optional
boolean optional = (tableConfig.getFlags() & TableFlags.FLAG_IS_OPTIONAL) != 0;
// find source table in the datasource
srcTables[i] = TableUtils.findTableIndex(source, tableConfig.getFromTable(), true);
// if no tables matches BUT we only have one possible match, take this
if (srcTables[i] == -1 && source.getTableCount() == 1 && destinationConfig.getTableCount() == 1) {
srcTables[i] = 0;
}
if (srcTables[i] == -1 && (optional == false || Strings.isEmpty(tableConfig.getFromTable()) == false)) {
result.setFailed("Could not find table \"" + tableConfig.getFromTable() + "\".");
return null;
}
}
// Replace the adapter config with a new config where any empty destination tables get the whole source table,
// following the convention that no destination fields = take all source fields
AdapterConfig replacementConfig = new AdapterConfig();
for(int i =0; i < srcTables.length ; i++){
if(srcTables[i]!=-1 && destinationConfig.getTable(i).getColumnCount()==0){
ODLTableDefinition srcTable = source.getTableAt(srcTables[i]);
AdapterConfig.addSameNameTable(srcTable, replacementConfig);
}else{
replacementConfig.getTables().add(destinationConfig.getTable(i));
}
}
destinationConfig = replacementConfig;
// get the datastore structure the view should generate
ODLDatastoreAlterable<? extends ODLTableDefinitionAlterable> destination = destinationConfig.createOutputDefinition();
// create a mapping with records for the destination tables and fields but no sources yet
AdapterMapping mapping = AdapterMapping.createUnassignedMapping(destination);
// now match fields
ArrayList<ODLDatastore<? extends T>> datasources = new ArrayList<>();
datasources.add(source);
for (int destTableIndx = 0; destTableIndx < destinationConfig.getTables().size(); destTableIndx++) {
AdaptedTableConfig tableConfig = destinationConfig.getTables().get(destTableIndx);
// map table if we have the source
if (srcTables[destTableIndx] != -1) {
// get definitions of source and destination tables
ODLTableDefinition srcTable = source.getTableAt(srcTables[destTableIndx]);
ODLTableDefinitionAlterable destTable = destination.getTableAt(destTableIndx);
// tell the mapping where to find the table
mapping.setTableSourceId(destTable.getImmutableId(), 0, srcTable.getImmutableId());
mapFields(srcTable, destTable.getImmutableId(), tableConfig, mapping,MAPPING_FLAGS_MISSING_SET_OPTIONAL_OK, result);
if (result.isFailed()) {
result.log("Could not build data adapter.");
return null;
}
}
}
// now create the adapter from the mapping
AdaptedDecorator<T> ret = new AdaptedDecorator<T>(mapping, datasources);
return ret;
}
// private static boolean mapTableInternal(ODLTableDefinition srcTable, AdaptedTableConfig tableConfig, ODLTableDefinition destTable,
// AdapterMapping mapping, ExecutionReport result) {
//
// // tell the mapping where to find the table
// mapping.setTableSourceId(destTable.getImmutableId(), 0, srcTable.getImmutableId());
//
// mapFields(srcTable, destTable.getImmutableId(), tableConfig, mapping, result);
// if (result.isFailed()) {
// result.log("Could not build data adapter.");
// return false;
// }
// return true;
// }
/**
* Split group by and non group by columns
* @param tableConfig
* @param groupByFields
* @param nonGroupByFields
*/
public static void splitGroupByCols(AdaptedTableConfig tableConfig, Collection<Integer> groupByFields,Collection<Integer> nonGroupByFields){
for (int destFieldIndx = 0; destFieldIndx < tableConfig.getColumnCount(); destFieldIndx++) {
AdapterColumnConfig field = tableConfig.getColumn(destFieldIndx);
if ((field.getFlags() & TableFlags.FLAG_IS_GROUP_BY_FIELD) == TableFlags.FLAG_IS_GROUP_BY_FIELD) {
if(groupByFields!=null){
groupByFields.add(destFieldIndx);
}
} else {
if(nonGroupByFields!=null){
nonGroupByFields.add(destFieldIndx);
}
}
}
}
public static boolean hasGroupByColumn(AdaptedTableConfig config){
for(AdapterColumnConfig col:config.getColumns()){
if(col.getIsGroupBy()){
return true;
}
}
return false;
}
/**
* Converts a standard adapter table into one suitable for an update query.
* In an update query we copy the (calculated) value from column i to column i+1
* for column indices i = 0, 2, 4, etc... Columns i=1, 3, 5 etc point back to
* the field in the original table. We therefore copy calculated values
* back onto original table values.
* @param table
* @return
*/
public static AdaptedTableConfig convertToUpdateQueryTable(AdaptedTableConfig table, ExecutionReport report){
AdaptedTableConfig ret = table.createNoColumnsCopy();
ret.setName("UpdateQueryTable");
for(AdapterColumnConfig col : table.getColumns()){
// cannot have group by flag
if((col.getFlags() & TableFlags.FLAG_IS_GROUP_BY_FIELD)==TableFlags.FLAG_IS_GROUP_BY_FIELD){
report.setFailed("Found column in update query with group by flag: " + col.getName());
return null;
}
// source value, including the current size in the new name so names are guaranteed unique (assuming they start unique)
AdapterColumnConfig src = new AdapterColumnConfig(col, ret.getColumnCount());
src.setName("calculated_"+ col.getName() + "_" + ret.getColumnCount());
ret.addMappedColumn(src);
// write value back column. Setting the value writes back to the original field with the column name
AdapterColumnConfig output = new AdapterColumnConfig(col, ret.getColumnCount());
output.setName("output"+ col.getName() + "_" + ret.getColumnCount());
output.setUseFormula(false);
output.setFrom(col.getName());
ret.addMappedColumn(output);
}
return ret;
}
public static int indexOf(String nameTable, AdapterConfig config){
int n = config.getTableCount();
for(int i =0 ;i<n;i++){
if(Strings.equalsStd(nameTable, config.getTable(i).getName())){
return i;
}
}
return -1;
}
public static AdaptedTableConfig createUniqueSortColsCopy(AdaptedTableConfig atc){
atc = atc.deepCopy();
int n = atc.getColumnCount();
for(int i =0 ;i<n;i++){
AdapterColumnConfig col = atc.getColumn(i);
if(col.getSortField() != SortField.NO){
boolean duplicate=false;
for(int j=0;j<n && duplicate==false;j++){
if(i!=j && Strings.equalsStd(col.getName(), atc.getColumnName(j))){
duplicate = true;
}
}
if(duplicate){
String name = TableUtils.getUniqueNumberedColumnName("SortField", atc);
col.setName(name);
}
}
}
return atc;
}
/**
* Build the empty table which would result from joining the outer and inner tables
* @param outerTable
* @param innerTable
* @return
*/
public static ODLDatastore<? extends ODLTable> buildEmptyJoinTable(ODLTableDefinition outerTable, ODLTableDefinition innerTable){
ODLDatastoreAlterable<ODLTableAlterable> ret = ODLDatastoreImpl.alterableFactory.create();
StandardisedStringSet fieldNames = new StandardisedStringSet(false);
int no = outerTable.getColumnCount();
ODLTableAlterable table = ret.createTable(innerTable.getName(), -1);
TableUtils.removeTableFlags(table, TableFlags.UI_EDIT_PERMISSION_FLAGS);
for(int i = 0 ; i < no ; i++){
table.addColumn(-1, outerTable.getName() + "." + outerTable.getColumnName(i), outerTable.getColumnType(i), 0);
fieldNames.add(table.getColumnName(i));
}
int ni = innerTable.getColumnCount();
for(int i =0 ; i < ni ; i++){
String name = innerTable.getColumnName(i);
if(fieldNames.contains(name)){
throw new RuntimeException("Joining of tables results in a fieldname appearing twice: " + name);
}
table.addColumn(-1, name, innerTable.getColumnType(i), 0);
}
return ret;
}
/**
* Get formula text or return null if the string isn't a formula
*
* @param text
* @return
*/
public static String getFormulaFromText(String text) {
if (text != null) {
text = text.trim();
if (text.startsWith(ScriptConstants.FORMULA_PREFIX)) {
return text.substring(2, text.length());
}
}
return null;
}
}