package com.opendoorlogistics.core.scripts.execution.adapters.vls;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.opendoorlogistics.api.ExecutionReport;
import com.opendoorlogistics.api.Factory;
import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.StringConventions;
import com.opendoorlogistics.api.Tables;
import com.opendoorlogistics.api.components.PredefinedTags;
import com.opendoorlogistics.api.standardcomponents.map.MapTileProvider;
import com.opendoorlogistics.api.tables.ODLColumnType;
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.core.formulae.FormulaParser;
import com.opendoorlogistics.core.formulae.Function;
import com.opendoorlogistics.core.formulae.FunctionImpl;
import com.opendoorlogistics.core.formulae.FunctionParameters;
import com.opendoorlogistics.core.formulae.Functions;
import com.opendoorlogistics.core.formulae.Functions.FmConst;
import com.opendoorlogistics.core.formulae.definitions.FunctionDefinitionLibrary;
import com.opendoorlogistics.core.gis.map.data.BackgroundImage;
import com.opendoorlogistics.core.gis.map.data.DrawableObjectImpl;
import com.opendoorlogistics.core.scripts.TargetIODsInterpreter;
import com.opendoorlogistics.core.scripts.elements.AdaptedTableConfig;
import com.opendoorlogistics.core.scripts.elements.AdapterConfig;
import com.opendoorlogistics.core.scripts.execution.ScriptExecutionBlackboardImpl;
import com.opendoorlogistics.core.scripts.execution.adapters.AdapterBuilder;
import com.opendoorlogistics.core.scripts.execution.adapters.AdapterBuilderUtils;
import com.opendoorlogistics.core.scripts.execution.adapters.BuiltAdapters;
import com.opendoorlogistics.core.scripts.execution.adapters.vls.Style.OutputFormula;
import com.opendoorlogistics.core.scripts.formulae.FmLocalElement;
import com.opendoorlogistics.core.scripts.formulae.TableParameters;
import com.opendoorlogistics.core.scripts.formulae.rules.RuleNode;
import com.opendoorlogistics.core.scripts.formulae.rules.RuleNode.RuleFilter;
import com.opendoorlogistics.core.scripts.wizard.ColumnNameMatch;
import com.opendoorlogistics.core.tables.ColumnValueProcessor;
import com.opendoorlogistics.core.tables.beans.BeanMapping;
import com.opendoorlogistics.core.tables.beans.BeanMapping.BeanDatastoreMapping;
import com.opendoorlogistics.core.tables.beans.BeanMapping.BeanTableMappingImpl;
import com.opendoorlogistics.core.tables.decorators.datastores.AdaptedDecorator;
import com.opendoorlogistics.core.tables.decorators.datastores.AdaptedDecorator.AdapterMapping;
import com.opendoorlogistics.core.tables.decorators.datastores.UnionDecorator;
import com.opendoorlogistics.core.tables.memory.ODLDatastoreImpl;
import com.opendoorlogistics.core.tables.utils.DatastoreCopier;
import com.opendoorlogistics.core.tables.utils.TableUtils;
import com.opendoorlogistics.core.utils.Numbers;
import com.opendoorlogistics.core.utils.strings.StandardisedStringTreeMap;
import com.opendoorlogistics.core.utils.strings.Strings;
import gnu.trove.set.hash.TIntHashSet;
public class VLSBuilder {
private static final BeanDatastoreMapping INPUT_VLS_ONLY;
private static final int INPUT_VIEW_INDX;
private static final int INPUT_LAYER_INDX;
private static final int INPUT_STYLE_INDX;
private static final int INPUT_EXTRA_FIELDS_INDX;
private static final BeanTableMappingImpl SOURCE_TABLE;
private static final Function[] APPEARANCE_KEY_ACCESSORS;
private final ODLApi api;
public VLSBuilder(ODLApi api) {
this.api = api;
}
static {
INPUT_VLS_ONLY = BeanMapping.buildDatastore(View.class, Layer.class, Style.class, ExtraFields.class);
INPUT_VIEW_INDX = 0;
INPUT_LAYER_INDX = 1;
INPUT_STYLE_INDX = 2;
INPUT_EXTRA_FIELDS_INDX = 3;
SOURCE_TABLE = BeanMapping.buildTable(VLSSourceDrawables.class);
APPEARANCE_KEY_ACCESSORS = new Function[4];
for (int i = 0; i < Style.NB_RULE_KEYS; i++) {
APPEARANCE_KEY_ACCESSORS[i] = new FmLocalElement(VLSSourceDrawables.COL_VLSKEY1 + i, "VLSKey" + (i + 1));
}
}
// public static class VLSConfig {
// private List<String> datasourceTableReferences = new ArrayList<String>();
//
// public List<String> getDatasourceTableReferences() {
// return datasourceTableReferences;
// }
//
// public void setDatasourceTableReferences(List<String>
// datasourceTableReferences) {
// this.datasourceTableReferences = datasourceTableReferences;
// }
//
// }
/*
* Get view-layer-style tables
*/
public static ODLDatastore<? extends ODLTableDefinition> getVLSTableDefinitions() {
return INPUT_VLS_ONLY.getDefinition();
}
public static ODLTableDefinition getSourceTableDefinition() {
return SOURCE_TABLE.getTableDefinition();
}
public static interface VLSDependencyInjector {
int getTableCount();
String getTableName(int i);
ODLTable buildTable(int i);
ODLTable buildTableFormula(String s);
Function buildFormula(String formula, ODLTableDefinition table);
}
private static class MatchedLayer {
Layer layer;
ArrayList<MatchedStyle> styles = new ArrayList<MatchedStyle>();
SourceTable source;
MapTileProvider mapTileProvider;
LayerType layerType;
RuleNode styleLookupTree;
}
private static class MatchedStyle {
MatchedStyle(Style style) {
super();
this.style = style;
}
final Style style;
Function functions[] = new Function[Style.OutputFormula.values().length];
Function filter;
}
private enum LayerType {
BACKGROUND_IMAGE(false, PredefinedTags.BACKGROUND_IMAGE, PredefinedTags.BACKGROUND_IMAGE), BACKGROUND(true, PredefinedTags.DRAWABLES_INACTIVE_BACKGROUND,
PredefinedTags.DRAWABLES_INACTIVE_BACKGROUND, "background"), ACTIVE(true, PredefinedTags.DRAWABLES, "active"), FOREGROUND(true, PredefinedTags.DRAWABLES_INACTIVE_FOREGROUND,
PredefinedTags.DRAWABLES_INACTIVE_FOREGROUND, "foreground");
final String tablename;
final String[] keywords;
final boolean drawable;
private LayerType(boolean drawable, String tablename, String... keywords) {
this.drawable = drawable;
this.tablename = tablename;
this.keywords = keywords;
}
private static final StandardisedStringTreeMap<LayerType> KEYWORD_MAP = new StandardisedStringTreeMap<VLSBuilder.LayerType>(false);
static {
for (LayerType type : LayerType.values()) {
for (String keyword : type.keywords) {
KEYWORD_MAP.put(keyword, type);
}
}
}
public static LayerType identify(String s) {
return KEYWORD_MAP.get(s);
}
public static String orderedCommaSeparatedKeywords() {
StringBuilder builder = new StringBuilder();
for (LayerType type : values()) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(type.keywords[0]);
}
return builder.toString();
}
}
/**
* Finds the input table and validates it to the table definition, returning
* a table mapped to have the correct fields in the correct order
*
* @author Phil
*
*/
private static class TableFinder {
final ODLApi api;
final VLSDependencyInjector injector;
final ExecutionReport report;
final Map<String, Integer> tables;
private TableFinder(ODLApi api, VLSDependencyInjector injector, ExecutionReport report) {
this.api = api;
this.injector = injector;
this.report = report;
// make a lookup of table names
tables = api.stringConventions().createStandardisedMap();
for (int i = 0; i < injector.getTableCount(); i++) {
tables.put(injector.getTableName(i), i);
}
}
ODLTable fetchRawSourceTable(String name, boolean isOptional) {
Integer indx = tables.get(name);
ODLTable ret = null;
if (indx != null) {
ret = injector.buildTable(indx);
}
if (ret == null && !isOptional) {
report.setFailed("Cannot find table required by view-layer-style adapter: " + name);
}
return ret;
}
SourceTable fetchValidatedSourceTable(String name, boolean isOptional, ODLTableDefinition definition) {
ODLTable raw = fetchRawSourceTable(name, isOptional);
if (report.isFailed() || raw == null) {
return null;
}
SourceTable ret = new SourceTable();
ret.raw = raw;
// Do simple name-based mapping to ensure all fields are present and
// in correct order, returning the mapped table
ODLDatastoreImpl<ODLTableDefinition> tmpDfn = new ODLDatastoreImpl<ODLTableDefinition>(null);
tmpDfn.addTable(definition);
ODLDatastoreImpl<ODLTable> tmpData = new ODLDatastoreImpl<ODLTable>(null);
tmpData.addTable(ret.raw);
ODLDatastore<? extends ODLTable> mapped = new TargetIODsInterpreter(api).buildScriptExecutionAdapter(tmpData, tmpDfn, report);
if (mapped != null && !report.isFailed()) {
ret.validated = mapped.getTableAt(0);
return ret;
}
if (report.isFailed()) {
report.setFailed("Error reading view-layer-style table: " + name);
}
return null;
}
}
private class ViewHierarchy {
List<View> background = new ArrayList<>();
List<View> active = new ArrayList<>();
List<View> foreground = new ArrayList<>();
View current;
}
private ViewHierarchy readViewHierarchy(SourceTable viewTable, ExecutionReport report) {
ViewHierarchy ret = new ViewHierarchy();
// Read views table and turn into map
List<View> views = readViewTable(viewTable, report);
StringConventions strings = api.stringConventions();
Map<String, View> viewById = strings.createStandardisedMap();
for (View view : views) {
if (strings.isEmptyString(view.getId())) {
report.setFailed("Empty or null ID found for view row.");
return null;
}
if (viewById.containsKey(view.getId())) {
report.setFailed("Found duplicate view id: " + view.getId());
return null;
}
viewById.put(view.getId(), view);
}
// Find the main view
ret.current = findView(views, report);
if (report.isFailed()) {
return null;
}
Set<String> includedViews = strings.createStandardisedSet();
class Helper {
List<View> getNewViews(String ids, int maxAllowed) {
if (ids == null) {
return new ArrayList<>(0);
}
String[] split = ids.split(",");
if (split.length > maxAllowed) {
report.setFailed("Found activeViewId field in views table containing more than one view id.");
return new ArrayList<>(0);
}
// ArrayList<String> tmp = new ArrayList<>(split.length);
ArrayList<View> ret = new ArrayList<>();
for (String s : split) {
s = strings.standardise(s);
if (!strings.isEmptyString(s) && !includedViews.contains(s)) {
includedViews.add(s);
View view = viewById.get(s);
if (view == null) {
report.setFailed("Unknown view id referenced in include field in the views table:" + s);
return new ArrayList<>(0);
}
// add recursion
ret.addAll(recurse(view));
// add myself
ret.add(view);
}
}
return ret;
}
List<View> recurse(View view) {
ArrayList<View> ret = new ArrayList<>();
ret.addAll(getNewViews(view.getBackgroundViewIds(), Integer.MAX_VALUE));
ret.addAll(getNewViews(view.getActiveViewId(), 1));
ret.addAll(getNewViews(view.getForegroundViewIds(), Integer.MAX_VALUE));
return ret;
}
}
// Build the view hierarchy, including each view only once
includedViews.add(ret.current.getId());
Helper helper = new Helper();
ret.background = helper.getNewViews(ret.current.getBackgroundViewIds(), Integer.MAX_VALUE);
ret.active = helper.getNewViews(ret.current.getActiveViewId(), 1);
ret.foreground = helper.getNewViews(ret.current.getForegroundViewIds(), Integer.MAX_VALUE);
return ret;
}
public ODLDatastore<? extends ODLTable> build(VLSDependencyInjector injector, ExecutionReport report) {
// Try getting built in tables
TableFinder finder = new TableFinder(api, injector, report);
SourceTable viewTable = finder.fetchValidatedSourceTable(View.TABLE_NAME, false, INPUT_VLS_ONLY.getTableMapping(INPUT_VIEW_INDX).getTableDefinition());
SourceTable layerTable = finder.fetchValidatedSourceTable(Layer.TABLE_NAME, false, INPUT_VLS_ONLY.getTableMapping(INPUT_LAYER_INDX).getTableDefinition());
SourceTable styleTable = finder.fetchValidatedSourceTable(Style.TABLE_NAME, false, INPUT_VLS_ONLY.getTableMapping(INPUT_STYLE_INDX).getTableDefinition());
SourceTable extraFieldDfnsTable = finder.fetchValidatedSourceTable(ExtraFields.TABLE_NAME, true, INPUT_VLS_ONLY.getTableMapping(INPUT_EXTRA_FIELDS_INDX).getTableDefinition());
if (report.isFailed()) {
return null;
}
ViewHierarchy viewHierarchy = readViewHierarchy(viewTable, report);
if (report.isFailed()) {
return null;
}
// Read the additional field definitions
ODLTableDefinition extraFldsDfns = readExtraFields(extraFieldDfnsTable, report);
if(report.isFailed()){
return null;
}
// Get layers for this view including their data source tables
ArrayList<MatchedLayer> matchedLayers = new ArrayList<VLSBuilder.MatchedLayer>();
StringConventions strings = api.stringConventions();
Map<String, MatchedLayer> matchedLayersMap = strings.createStandardisedMap();
readLayers(finder, layerTable, viewHierarchy,extraFldsDfns, matchedLayers, matchedLayersMap, report);
if (report.isFailed()) {
return null;
}
// Add styles objects to layer objects
addStylesToLayers(styleTable, matchedLayersMap, report);
if (report.isFailed()) {
return null;
}
compileStyles(injector, matchedLayers, report);
if (report.isFailed()) {
return null;
}
AdaptedDecorator<ODLTable> ret = createAdapter(matchedLayers, extraFldsDfns,report);
return ret;
}
private ODLTableDefinition readExtraFields(SourceTable extraFieldDfnsTable, ExecutionReport report) {
ODLDatastoreAlterable<? extends ODLTableDefinitionAlterable> ds = api.tables().createDefinitionDs();
ODLTableDefinitionAlterable table = ds.createTable(ExtraFields.TABLE_NAME, -1);
if (extraFieldDfnsTable == null) {
return table;
}
// get list of all field names
Set<String> allFields = api.stringConventions().createStandardisedSet();
for(int i =0 ; i < DrawableObjectImpl.DRAWABLES_ONLY_DS.getTableCount();i++){
ODLTableDefinition drawables = DrawableObjectImpl.DRAWABLES_ONLY_DS.getTableAt(i);
int nc = drawables.getColumnCount();
for(int j =0 ; j < nc ; j++){
allFields.add(drawables.getColumnName(j));
}
}
List<ExtraFields> rows = INPUT_VLS_ONLY.getTableMapping(INPUT_EXTRA_FIELDS_INDX).readObjectsFromTable(extraFieldDfnsTable.validated, report);
if (report.isFailed()) {
return null;
}
for (ExtraFields fld : rows) {
ODLColumnType type = api.tables().getColumnType(fld.getType());
if (type == null) {
report.setFailed("Unidentified column type in extra fields table in VLS adapter: " + fld.getType());
return null;
}
String name = fld.getName();
if (api.stringConventions().isEmptyStandardised(name)) {
report.setFailed("Empty name found for field in extra fields table in VLS adapter.");
return null;
}
// check not already used
if(allFields.contains(name)){
report.setFailed("Found field in extra fields table in VLS adapter which has been included twice: " + name);
return null;
}
table.addColumn(-1, name, type, 0);
allFields.add(name);
}
return table;
}
private AdaptedDecorator<ODLTable> createAdapter(List<MatchedLayer> matchedLayers,ODLTableDefinition extraFields, ExecutionReport report) {
// Now process all layers into the datastore adapter
ArrayList<MatchedLayer> layersInType = new ArrayList<VLSBuilder.MatchedLayer>(matchedLayers.size());
AdapterMapping mapping = AdapterMapping.createUnassignedMapping(DrawableObjectImpl.ACTIVE_BACKGROUND_FOREGROUND_IMAGE_DS,true);
ArrayList<ODLDatastore<? extends ODLTable>> dsList = new ArrayList<ODLDatastore<? extends ODLTable>>(matchedLayers.size());
for (LayerType layerType : LayerType.values()) {
// Get all the layers for this layertype
layersInType.clear();
for (MatchedLayer ml : matchedLayers) {
if (ml.layerType == layerType) {
layersInType.add(ml);
}
}
ODLTableDefinition destinationTable = TableUtils.findTable(DrawableObjectImpl.ACTIVE_BACKGROUND_FOREGROUND_IMAGE_DS, layerType.tablename);
int nl = layersInType.size();
if (nl == 0) {
if (layerType != LayerType.BACKGROUND_IMAGE) {
// add the extra fields (even though they're empty)
int nbExtra = extraFields!=null? extraFields.getColumnCount() : 0;
for(int i =0 ; i < nbExtra;i++){
mapping.addMappedField(destinationTable.getImmutableId(), extraFields.getColumnName(i), extraFields.getColumnType(i), -1);
}
}
// nothing to do...
continue;
}
// Get the destination table
Tables tables = api.tables();
if (layerType == LayerType.BACKGROUND_IMAGE) {
// Create a table containing all the map tile providers in-order
ODLDatastoreAlterable<? extends ODLTableAlterable> imgDs = tables.createAlterableDs();
ODLTable table = (ODLTable) tables.copyTableDefinition(BackgroundImage.BEAN_MAPPING.getTableDefinition(), imgDs);
mapping.setTableSourceId(destinationTable.getImmutableId(), dsList.size(), table.getImmutableId());
dsList.add(imgDs);
for (int i = 0; i < table.getColumnCount(); i++) {
mapping.setFieldSourceIndx(destinationTable.getImmutableId(), i, i);
}
for (MatchedLayer ml : layersInType) {
if (ml.mapTileProvider != null) {
BackgroundImage bi = new BackgroundImage();
bi.setTileProvider(ml.mapTileProvider);
BackgroundImage.BEAN_MAPPING.writeObjectToTable(bi, table);
}
}
} else {
// // Add the extra fields to the destination table if needed
// if(extraFields!=null && extraFields.getColumnCount()>0){
// destinationTable =tables.copyTableDefinition(destinationTable, tables.createAlterableDs());
// tables.copyTableDefinition(extraFields, (ODLTableDefinitionAlterable)destinationTable);
// }
if (nl == 1) {
// non-union - add directly
MatchedLayer ml = layersInType.get(0);
mapping.setTableSourceId(destinationTable.getImmutableId(), dsList.size(), ml.source.validated.getImmutableId());
setFieldMapping(ml, destinationTable.getImmutableId(),extraFields, mapping);
dsList.add(wrapTableInDs(ml.source.validated));
} else {
// union - we build individual adapters, then place in a
// union decorator which we add to the final adapter
// build adapters for each layer
ArrayList<ODLDatastore<? extends ODLTable>> dsListToUnion = new ArrayList<ODLDatastore<? extends ODLTable>>(nl);
for (MatchedLayer ml : layersInType) {
AdapterMapping singleSourceMapping = AdapterMapping.createUnassignedMapping(destinationTable,true);
singleSourceMapping.setTableSourceId(destinationTable.getImmutableId(), 0, ml.source.validated.getImmutableId());
setFieldMapping(ml, destinationTable.getImmutableId(),extraFields, singleSourceMapping);
AdaptedDecorator<ODLTable> singleLayerDecorator = new AdaptedDecorator<ODLTable>(singleSourceMapping, wrapTableInDs(ml.source.validated));
dsListToUnion.add(singleLayerDecorator);
}
// union them together
UnionDecorator<ODLTable> union = new UnionDecorator<ODLTable>(dsListToUnion);
// set the mapping to point towards the union
mapping.setTableSourceId(destinationTable.getImmutableId(), dsList.size(), union.getTableAt(0).getImmutableId());
for (int i = 0; i <= DrawableObjectImpl.COL_MAX; i++) {
mapping.setFieldSourceIndx(destinationTable.getImmutableId(), i, i);
}
// also add the extra fields to the additional mapping
int nbExtra = extraFields!=null? extraFields.getColumnCount() : 0;
for(int i =0 ; i < nbExtra;i++){
mapping.addMappedField(destinationTable.getImmutableId(), extraFields.getColumnName(i), extraFields.getColumnType(i), DrawableObjectImpl.COL_MAX+ 1 + i);
}
dsList.add(union);
}
}
}
// Finally return the decorator
AdaptedDecorator<ODLTable> ret = new AdaptedDecorator<ODLTable>(mapping, dsList);
return ret;
}
private void compileStyles(VLSDependencyInjector injector, ArrayList<MatchedLayer> matchedLayers, ExecutionReport report) {
StringConventions strings = api.stringConventions();
for (MatchedLayer layer : matchedLayers) {
// Get the values which need to match for each style in this layer
ArrayList<List<Object>> selectors = new ArrayList<List<Object>>();
for (MatchedStyle style : layer.styles) {
ArrayList<Object> values = new ArrayList<Object>();
for (int i = 0; i < Style.NB_RULE_KEYS; i++) {
String key = style.style.getRuleKey(i);
if (AdapterBuilderUtils.getFormulaFromText(key) != null) {
report.setFailed("Functions are not allowed for style appearance key fields: " + key);
return;
}
values.add(key);
}
selectors.add(values);
}
// Compile the style keys into a lookup tree
layer.styleLookupTree = RuleNode.buildTree(selectors, true);
// Compile the formulae for the styles
for (MatchedStyle style : layer.styles) {
// build output formula
for (OutputFormula ft : Style.OutputFormula.values()) {
String styleValue = style.style.getFormula(ft);
Function function = null;
if (strings.isEmptyString(styleValue)) {
function = null;
} else {
// Test if this value is actually a formula (starts with
// :=)
String styleFormula = AdapterBuilderUtils.getFormulaFromText(styleValue);
if (styleFormula != null) {
// Build the formula against the raw table (with the
// additional fields),
// not the validated one where additional fields are
// stripped
function = injector.buildFormula(styleFormula, layer.source.raw);
if (report.isFailed()) {
report.setFailed("Failed to build style formula " + ft.name() + " in styles table: " + styleFormula);
return;
}
} else {
// Create constant formula with correct type
Object value = ColumnValueProcessor.convertToMe(ft.outputType, styleValue);
if (value == null) {
report.setFailed("Could not convert value \"" + styleValue + "\" for style formula " + ft.name() + " to required type " + ft.outputType.name() + ".");
} else {
function = new FmConst(value);
}
}
}
// Save the compiled function
style.functions[ft.ordinal()] = function;
}
// build filter formula
String filterFormula = AdapterBuilderUtils.getFormulaFromText(style.style.getFilter());
if (!strings.isEmptyString(style.style.getFilter()) && filterFormula == null) {
report.setFailed("Found a non-empty filter formulae in styles table that did not begin with := (the symbol which indicates a formula).");
return;
}
if (filterFormula != null) {
style.filter = injector.buildFormula(filterFormula, layer.source.raw);
if (report.isFailed()) {
report.setFailed("Failed to build filter formula in styles table:" + filterFormula);
return;
}
}
}
}
}
/**
* Add the style objects to the layer objects, matching on layerid field
*
* @param styleTable
* @param matchedLayersMap
* @param report
*/
private void addStylesToLayers(SourceTable styleTable, Map<String, MatchedLayer> matchedLayersMap, ExecutionReport report) {
List<Style> styles = INPUT_VLS_ONLY.getTableMapping(INPUT_STYLE_INDX).readObjectsFromTable(styleTable.validated, report);
if (styles.size() < styleTable.validated.getRowCount()) {
report.setFailed("Failed to read one or more style records correctly.");
}
if (report.isFailed()) {
return;
}
for (Style style : styles) {
// check layer id
if (api.stringConventions().isEmptyString(style.getLayerId())) {
report.setFailed("Empty or null layer ID found for style.");
return;
}
MatchedLayer ml = matchedLayersMap.get(style.getLayerId());
if (ml != null) {
ml.styles.add(new MatchedStyle(style));
}
}
}
private void readLayers(TableFinder finder, SourceTable layerTable, ViewHierarchy viewHierarchy, ODLTableDefinition additionalFields,List<MatchedLayer> matchedLayers, Map<String, MatchedLayer> matchedLayersMap,
ExecutionReport report) {
StringConventions strings = api.stringConventions();
Map<String, ODLTable> sourceTables = strings.createStandardisedMap();
// Read layer objects
List<Layer> layers = INPUT_VLS_ONLY.getTableMapping(INPUT_LAYER_INDX).readObjectsFromTable(layerTable.validated, report);
if (layers.size() < layerTable.validated.getRowCount() || report.isFailed()) {
report.setFailed("Failed to read one or more layer records correctly.");
}
// Collate by view
Map<String, LinkedList<Layer>> layersByView = strings.createStandardisedMap(new Factory<LinkedList<Layer>>() {
@Override
public LinkedList<Layer> create() {
return new LinkedList<>();
}
});
for (Layer layer : layers) {
layersByView.get(layer.getViewId()).add(layer);
}
int[] countByLayerType = new int[LayerType.values().length];
class Helper {
void addView(View view, LayerType lt) {
// Loop over each layer object within the view
LinkedList<Layer> layers4View = layersByView.get(view.getId());
if (layers4View != null) {
for (Layer layer : layers4View) {
// check layer id
if (strings.isEmptyString(layer.getId())) {
report.setFailed("Empty or null ID found for layer row.");
return;
}
if (matchedLayersMap.containsKey(layer.getId())) {
report.setFailed("Duplicate layerid found: " + layer.getId());
return;
}
// create matched layer object
MatchedLayer ml = new MatchedLayer();
ml.layer = layer;
matchedLayers.add(ml);
matchedLayersMap.put(layer.getId(), ml);
// identify layer type, using position of the active layer to
// identify background and foreground if string is empty
if (lt == null && Strings.isEmpty(layer.getLayerType())) {
report.setFailed("Layer with id " + layer.getId() + " has empty layer type.");
return;
}
ml.layerType = lt;
if (ml.layerType == null) {
ml.layerType = LayerType.identify(layer.getLayerType());
}
if (ml.layerType == null) {
report.setFailed("Unidentified layer type: " + layer.getLayerType());
return;
}
// validate layer type order
if (ml.layerType == LayerType.ACTIVE && countByLayerType[ml.layerType.ordinal()] > 0) {
report.setFailed("Found more than one active layer in a view: " + ml.layer.getId());
return;
}
for (int i = 0; i < countByLayerType.length; i++) {
int count = countByLayerType[i];
if (count > 0 && ml.layerType.ordinal() < i) {
report.setFailed("Found layer type " + ml.layerType.keywords[0] + " defined after layer type " + LayerType.values()[i].keywords[0]
+ ". Layers must be defined in order " + LayerType.orderedCommaSeparatedKeywords() + ".");
return;
}
}
countByLayerType[ml.layerType.ordinal()]++;
}
}
}
void addViews(Iterable<View> views, LayerType lt) {
for (View view : views) {
addView(view, lt);
}
}
}
Helper helper = new Helper();
helper.addViews(viewHierarchy.background, LayerType.BACKGROUND);
helper.addViews(viewHierarchy.active, LayerType.ACTIVE);
helper.addView(viewHierarchy.current, null);
helper.addViews(viewHierarchy.foreground, LayerType.FOREGROUND);
// Process matched layer sources
for (MatchedLayer ml : matchedLayers) {
Layer layer = ml.layer;
// process source - note this could be a formula pointing to a
// shapefile or similar in the future
if (strings.isEmptyString(layer.getSource())) {
report.setFailed("Empty or null source value found for the data source for row in layers table with layer id " + layer.getId() + ".");
return;
}
// see if source already processed, if not then process it
if (ml.layerType == LayerType.BACKGROUND_IMAGE) {
ml.mapTileProvider = fetchMapTileProvider(layer, report);
} else {
ml.source = fetchLayerSourceTable(layer, sourceTables, additionalFields, finder, report);
}
if (report.isFailed()) {
return;
}
}
}
private MapTileProvider fetchMapTileProvider(Layer layer, ExecutionReport report) {
String source = layer.getSource();
String sFormula = AdapterBuilderUtils.getFormulaFromText(source);
if (sFormula == null) {
report.setFailed("Background image layer with id " + layer.getId() + " found without a source formula; formula values begin with :=");
return null;
}
Function function = null;
if (sFormula != null) {
FormulaParser loader = new FormulaParser(null, FunctionDefinitionLibrary.DEFAULT_LIB, null);
try {
function = loader.parse(sFormula);
} catch (Exception e) {
report.setFailed(e);
}
}
if (function == null || report.isFailed()) {
report.setFailed("Background image layer with id " + layer.getId() + " - failed to compile source formula.");
return null;
}
MapTileProvider provider = null;
boolean wrongType = false;
try {
Object exec = function.execute(null);
if (exec != Functions.EXECUTION_ERROR) {
if (exec != null && exec instanceof MapTileProvider) {
provider = (MapTileProvider) exec;
} else {
wrongType = true;
}
}
} catch (Exception e) {
}
if (provider == null) {
// report.setFailed("Layer with type " +
// LayerType.BACKGROUND_IMAGE.keywords[0] + " had a problem
// executing the formula: " + source);
report.setFailed("Background image layer with id " + layer.getId() + " - failed to execute source formula."
+ (wrongType ? " Result must be of type " + ODLColumnType.MAP_TILE_PROVIDER.name() + "." : ""));
}
return provider;
}
private List<View> readViewTable(SourceTable viewTable, ExecutionReport report) {
return INPUT_VLS_ONLY.getTableMapping(INPUT_VIEW_INDX).readObjectsFromTable(viewTable.validated, report);
}
private View findView(List<View> views, ExecutionReport report) {
if (views.size() == 0) {
report.setFailed("No view row provided in view table.");
return null;
}
// Get the first view we find witrh isdefault=1
for (View view : views) {
if (view.getIsDefault() == 1) {
return view;
}
}
// If not found just take the first
return views.get(0);
}
private static class SourceTable {
ODLTable raw;
ODLTable validated;
int []extraFieldIndicesInRaw;
}
private SourceTable fetchLayerSourceTable(Layer layer, Map<String, ODLTable> rawSourceTables, ODLTableDefinition additionalFields, TableFinder finder, ExecutionReport report) {
// Get the output table definition
ODLTableDefinition outTableDfn = VLSSourceDrawables.BEAN_MAPPING.getTableDefinition();
// Get the raw table, trying (1) cache, (2) table formula and (3) input table
SourceTable ret = new SourceTable();
ret.raw = rawSourceTables.get(layer.getSource());
if (ret.raw == null) {
String layerFormula = AdapterBuilderUtils.getFormulaFromText(layer.getSource());
if (layerFormula != null) {
ret.raw = finder.injector.buildTableFormula(layer.getSource());
} else {
ret.raw = finder.fetchRawSourceTable(VLSSourceDrawables.SOURCE_PREFIX + layer.getSource(), false);
}
}
if (report.isFailed()) {
return null;
}
// Cache the raw source table
rawSourceTables.put(layer.getSource(), ret.raw);
// Apply filtering to the raw table if we have it...
if (!api.stringConventions().isEmptyString(layer.getFilter())) {
String filterFormula = AdapterBuilderUtils.getFormulaFromText(layer.getFilter());
if (filterFormula != null) {
String dummyRawID = "RawDSID";
// Create a dummy adapter for the filtering
AdapterConfig dummyAdapter = new AdapterConfig("DummyID");
AdaptedTableConfig dummyTable = new AdaptedTableConfig();
dummyTable.setName(ret.raw.getName());
dummyAdapter.getTables().add(dummyTable);
int nc = ret.raw.getColumnCount();
for (int i = 0; i < nc; i++) {
dummyTable.addMappedColumn(ret.raw.getColumnName(i), ret.raw.getColumnName(i), ret.raw.getColumnType(i), ret.raw.getColumnFlags(i));
}
dummyTable.setFilterFormula(filterFormula);
dummyTable.setFrom(dummyRawID, ret.raw.getName());
ScriptExecutionBlackboardImpl bb = new ScriptExecutionBlackboardImpl(false);
BuiltAdapters builtAdapters = new BuiltAdapters();
builtAdapters.addAdapter(dummyRawID, wrapTableInDs(ret.raw));
AdapterBuilder builder = new AdapterBuilder(dummyAdapter, null, bb, null, builtAdapters);
ODLDatastore<? extends ODLTable> built = builder.build();
if (bb.isFailed()) {
report.add(bb);
report.setFailed("Failed to process filter formula in layer: " + filterFormula);
return null;
}
ret.raw = built.getTableAt(0);
} else {
report.setFailed("Failed to parse filter \"" + layer.getFilter() + "\" in layer table.");
}
}
// Match columns based on name and also match by geom type if not already found
ColumnNameMatch columnNameMatch = new ColumnNameMatch(ret.raw, outTableDfn);
if (columnNameMatch.getMatchForTableB(VLSSourceDrawables.COL_GEOMETRY) == -1) {
columnNameMatch.setMatchForTableB(VLSSourceDrawables.COL_GEOMETRY, TableUtils.findColumnIndx(ret.raw, ODLColumnType.GEOM));
}
// Check either latitude and longitude or geometry have mapped
boolean geomMatch = columnNameMatch.getMatchForTableB(VLSSourceDrawables.COL_GEOMETRY) != -1;
boolean latMatch = columnNameMatch.getMatchForTableB(VLSSourceDrawables.COL_LATITUDE) != -1;
boolean lngMatch = columnNameMatch.getMatchForTableB(VLSSourceDrawables.COL_LONGITUDE) != -1;
if (!geomMatch && (!latMatch || !lngMatch)) {
report.setFailed("Failed to map either geometry column or latitude and longitudes columns in input VLS table: " + layer.getSource());
return null;
}
// Apply the field mapping to object defining the field mapping
AdapterMapping mapping = AdapterMapping.createUnassignedMapping(outTableDfn,true);
mapping.setTableSourceId(outTableDfn.getImmutableId(), 0, ret.raw.getImmutableId());
int ndc = outTableDfn.getColumnCount();
for (int i = 0; i < ndc; i++) {
mapping.setFieldSourceIndx(outTableDfn.getImmutableId(), i, columnNameMatch.getMatchForTableB(i));
}
// Ensure the raw has the extra fields and map them to the validated
if(additionalFields!=null && additionalFields.getColumnCount()>0){
int nbExtra = additionalFields.getColumnCount();
ret.extraFieldIndicesInRaw = new int[nbExtra];
Map<String, Integer> map = api.tables().getColumnNamesMap(ret.raw);
for(int col =0 ; col < nbExtra;col++){
String name = additionalFields.getColumnName(col);
Integer indx = map.get(name);
if(indx==null){
report.setFailed("Additional field " + additionalFields.getColumnName(col) + " is missing from source table " + layer.getSource() + ".");
return null;
}
ret.extraFieldIndicesInRaw[col] = indx;
mapping.addMappedField(outTableDfn.getImmutableId(), name, additionalFields.getColumnType(col), indx);
}
}else{
ret.extraFieldIndicesInRaw = new int[0];
}
// Create an adapter
AdaptedDecorator<ODLTable> adapter = new AdaptedDecorator<ODLTable>(mapping, ret.raw);
ret.validated = adapter.getTableAt(0);
return ret;
}
/**
* Set mapping which defines a drawables table. Latitude, longitude and
* geometry and non-overlapping layer come directly from the input source
* table, everything else comes from a style function
*
* @param ml
* @param destinationTableId
* @param mapping
*/
private void setFieldMapping(MatchedLayer ml, int destinationTableId,ODLTableDefinition extraFields, AdapterMapping mapping) {
// get set of columns read directly from validated source and never
// controlled by styles
TIntHashSet fromSource = new TIntHashSet();
fromSource.add(DrawableObjectImpl.COL_LATITUDE);
fromSource.add(DrawableObjectImpl.COL_LONGITUDE);
fromSource.add(DrawableObjectImpl.COL_GEOMETRY);
fromSource.add(DrawableObjectImpl.COL_NOPL_GROUP_KEY);
// now set the source for each column as appropriate
for (int drawablesCol = 0; drawablesCol <= DrawableObjectImpl.COL_MAX; drawablesCol++) {
if (fromSource.contains(drawablesCol)) {
// Read directly from validated source table
mapping.setFieldSourceIndx(destinationTableId, drawablesCol, drawablesCol);
} else {
// Style formula based on unvalidated raw source table,
// defaulting to validated when formula unavailable empty
mapping.setFieldFormula(destinationTableId, drawablesCol, new StyleFunction(ml, drawablesCol));
}
}
// additional fields come from the raw...
int []extraSrcs = ml.source.extraFieldIndicesInRaw;
if(extraSrcs!=null && extraSrcs.length>0){
if(extraFields.getColumnCount()!= extraSrcs.length){
throw new RuntimeException();
}
for(int i =0 ; i < extraSrcs.length; i++){
final int finalI=i;
mapping.addMappedFormula(destinationTableId, extraFields.getColumnName(i), extraFields.getColumnType(i), new FunctionImpl() {
@Override
public Object execute(FunctionParameters parameters) {
TableParameters tp = (TableParameters)parameters;
return ml.source.raw.getValueById(tp.getRowId(), extraSrcs[finalI]);
}
@Override
public Function deepCopy() {
throw new IllegalArgumentException();
}
});
}
}
}
private static ODLDatastoreImpl<ODLTable> wrapTableInDs(ODLTable source) {
ODLDatastoreImpl<ODLTable> tmpDatastore = new ODLDatastoreImpl<ODLTable>(null);
tmpDatastore.addTable(source);
return tmpDatastore;
}
private static class StyleFunction extends FunctionImpl {
private final MatchedLayer layer;
private final Style.OutputFormula styleFormulaType;
private final List<ODLDatastore<? extends ODLTable>> rawTableWrappedInDsList;
private final int targetDrawableColumn;
private final RuleFilter ruleFilter = new RuleFilter() {
@Override
public boolean filter(Object filterData, Object[] values, int ruleNb) {
TableParameters rawTP = (TableParameters) filterData;
MatchedStyle style = layer.styles.get(ruleNb);
if (style.filter != null) {
Long l = Numbers.toLong(style.filter.execute(rawTP));
if (l != null && l == 1) {
return true;
} else {
return false;
}
}
return true;
}
};
StyleFunction(MatchedLayer layer, int targetDrawableColumn) {
this.layer = layer;
this.rawTableWrappedInDsList = Arrays.asList(wrapTableInDs(layer.source.raw));
this.targetDrawableColumn = targetDrawableColumn;
// try to find a corresponding style formula type
OutputFormula found = null;
for (OutputFormula ft : OutputFormula.values()) {
if (ft.drawablesColumn == targetDrawableColumn) {
found = ft;
break;
}
}
this.styleFormulaType = found;
}
@Override
public Object execute(FunctionParameters parameters) {
TableParameters tp = (TableParameters) parameters;
// Get table parameters for the raw input table
TableParameters rawTP = new TableParameters(rawTableWrappedInDsList, 0, rawTableWrappedInDsList.get(0).getTableAt(0).getImmutableId(), tp.getRowId(), tp.hasRowNb()?tp.getRowNb():-1, null);
// Try to find a matching style
MatchedStyle style = null;
if (styleFormulaType != null) {
if (layer.styles.size() > 0) {
Object[] keys = new Object[Style.NB_RULE_KEYS];
for (int i = 0; i < Style.NB_RULE_KEYS; i++) {
keys[i] = APPEARANCE_KEY_ACCESSORS[i].execute(parameters);
if (keys[i] == Functions.EXECUTION_ERROR) {
return Functions.EXECUTION_ERROR;
}
}
int rule = layer.styleLookupTree.findRuleNumber(keys, ruleFilter, rawTP);
if (rule != -1) {
style = layer.styles.get(rule);
}
}
}
// use the style
if (style != null) {
// if we don't have a function then use the source table
if (style.functions[styleFormulaType.ordinal()] == null) {
if (targetDrawableColumn != -1) {
return layer.source.validated.getValueAt(tp.getRowNb(), targetDrawableColumn);
}
return null;
}
// Styles are executed against the raw table
Object val = style.functions[styleFormulaType.ordinal()].execute(rawTP);
if (val == Functions.EXECUTION_ERROR) {
return Functions.EXECUTION_ERROR;
}
// Just return the executed value if we're not doing flag
// mapping logic
if (styleFormulaType.booleanToFlag == -1) {
return val;
}
// Otherwise process flag mapping. First get the base value from
// the validated table
Object baseValue = getValidatedTableValue(tp);
if (baseValue == Functions.EXECUTION_ERROR) {
return Functions.EXECUTION_ERROR;
}
// Then transform the base value to long
Long lBase = Numbers.toLong(baseValue);
long ret = lBase != null ? lBase : 0;
// Then transform the executed style value to a bool and add /
// remove the flag
Long lBool = Numbers.toLong(val);
if (lBool != null) {
if (lBool == 1) {
// add flag
ret |= styleFormulaType.booleanToFlag;
} else {
// remove flag
ret &= ~styleFormulaType.booleanToFlag;
}
}
return ret;
}
// take min and max zoom from the layers if set
if (targetDrawableColumn == DrawableObjectImpl.COL_MIN_ZOOM && layer.layer.getMinZoom() != null) {
return layer.layer.getMinZoom();
}
if (targetDrawableColumn == DrawableObjectImpl.COL_MAX_ZOOM && layer.layer.getMaxZoom() != null) {
return layer.layer.getMaxZoom();
}
// If we have no default style we take the corresponding value from
// the validated table (which extends drawable)
return getValidatedTableValue(tp);
}
private Object getValidatedTableValue(TableParameters tp) {
if (tp.hasRowNb()) {
return layer.source.validated.getValueAt(tp.getRowNb(), targetDrawableColumn);
} else if (tp.getRowId() != -1) {
return layer.source.validated.getValueById(tp.getRowId(), targetDrawableColumn);
}
return null;
}
@Override
public Function deepCopy() {
throw new UnsupportedOperationException();
}
}
}