package com.opendoorlogistics.core.scripts.parameters;
import static com.opendoorlogistics.core.scripts.parameters.beans.WithKeyParametersTable.COL_KEY;
import static com.opendoorlogistics.core.scripts.parameters.beans.WithKeyParametersTable.COL_PROMPT_TYPE;
import static com.opendoorlogistics.core.scripts.parameters.beans.WithKeyParametersTable.COL_VALUE;
import static com.opendoorlogistics.core.scripts.parameters.beans.WithKeyParametersTable.COL_VALUE_TYPE;
import java.util.List;
import java.util.UUID;
import com.opendoorlogistics.api.ExecutionReport;
import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.scripts.ScriptAdapter;
import com.opendoorlogistics.api.scripts.ScriptAdapter.ScriptAdapterType;
import com.opendoorlogistics.api.scripts.ScriptAdapterTable;
import com.opendoorlogistics.api.scripts.parameters.Parameters;
import com.opendoorlogistics.api.scripts.parameters.ParametersControlFactory;
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.ODLTableReadOnly;
import com.opendoorlogistics.core.api.impl.ODLApiImpl;
import com.opendoorlogistics.core.api.impl.scripts.ScriptAdapterImpl;
import com.opendoorlogistics.core.cache.RecentlyUsedCache;
import com.opendoorlogistics.core.scripts.ScriptConstants;
import com.opendoorlogistics.core.scripts.elements.AdapterConfig;
import com.opendoorlogistics.core.scripts.parameters.beans.NoKeyParameterValues;
import com.opendoorlogistics.core.scripts.parameters.beans.NoKeyParametersTable;
import com.opendoorlogistics.core.scripts.parameters.beans.WithKeyParameterValues;
import com.opendoorlogistics.core.scripts.parameters.beans.WithKeyParametersTable;
import com.opendoorlogistics.core.scripts.parameters.controls.ControlFactory;
import com.opendoorlogistics.core.tables.beans.BeanMapping;
import com.opendoorlogistics.core.tables.beans.BeanMapping.BeanDatastoreMapping;
import com.opendoorlogistics.core.utils.strings.EnumStdLookup;
public class ParametersImpl implements Parameters {
public static final String TABLE_NAME = "Parameter";
public static final String PARAMETER_VALUES_TABLE_NAME = "ParameterValues";
public static final String VALUES_TABLE_NAME = "ParameterValues";
public static final String DS_ID = "internal";
private static final BeanDatastoreMapping BEAN_NO_KEY_DS_MAPPING = BeanMapping.buildDatastore(NoKeyParametersTable.class, NoKeyParameterValues.class);
private static final BeanDatastoreMapping BEAN_WITH_KEY_DS_MAPPING = BeanMapping.buildDatastore(WithKeyParametersTable.class, WithKeyParameterValues.class);
// private static final BeanTableMapping NO_KEY_BEAN_MAPPING =
// BEAN_NO_KEY_DS_MAPPING.getTableMapping(0);
private static final EnumStdLookup<ODLColumnType> COL_TYPE_LOOKUP = new EnumStdLookup<ODLColumnType>(ODLColumnType.class);
private static final EnumStdLookup<PromptType> PROMPT_TYPE_LOOKUP = new EnumStdLookup<PromptType>(PromptType.class);
private final ODLApi api;
private static volatile ParametersControlFactory PARAMETERS_CONTROL;
/**
* Last used values cache for parameters is separate to main ODL Studio data cache as it doesn't store
* large data and we don't want it cleared when the user clears the rest of the cache.
* e.g. if the road network graph has been rebuilt the user might clear the main data cache to reload
* results based on this file, but they still want the UI to remember the location of the last road network file.
*/
private RecentlyUsedCache parameterLastUsedValuesCache = new RecentlyUsedCache("ParametersLastUsedValues", 1024*1024);
private static class CachedParameterKey{
final UUID scriptUUID;
final String key;
CachedParameterKey(UUID scriptUUID, String key) {
this.scriptUUID = scriptUUID;
this.key = key;
}
/**
* Estimate of size in bytes
*/
public int bytesSize(){
return key.length() *2 + 5*8;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
result = prime * result + ((scriptUUID == null) ? 0 : scriptUUID.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CachedParameterKey other = (CachedParameterKey) obj;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
if (scriptUUID == null) {
if (other.scriptUUID != null)
return false;
} else if (!scriptUUID.equals(other.scriptUUID))
return false;
return true;
}
}
static {
List<ParametersControlFactory> ctrls = new ODLApiImpl().loadPlugins(ParametersControlFactory.class);
if (ctrls.size() > 0) {
System.out.println("Loaded parameter control factory plugin");
PARAMETERS_CONTROL = ctrls.get(0);
} else {
PARAMETERS_CONTROL = new ControlFactory();
}
}
public ParametersImpl(ODLApi api) {
super();
this.api = api;
}
@Override
public ODLTableDefinition tableDefinition(boolean includeKey) {
return getTableDefinition(includeKey, 0);
}
private ODLTableDefinition getTableDefinition(boolean includeKey, int indx) {
return (includeKey ? BEAN_WITH_KEY_DS_MAPPING : BEAN_NO_KEY_DS_MAPPING).getDefinition().getTableAt(indx);
}
@Override
public ODLTableDefinition valuesTableDefinition(boolean includeKeyColumn) {
return getTableDefinition(includeKeyColumn, 1);
}
@Override
public Object getValue(ODLTableReadOnly parametersTable, String name) {
String val = getRawColValue(name, parametersTable, COL_VALUE);
ODLColumnType type = getColumnType(parametersTable, name);
if (type != null) {
return api.values().convertValue(val, type);
}
return null;
}
@Override
public ODLColumnType getColumnType(ODLTableReadOnly parametersTable, String name) {
return COL_TYPE_LOOKUP.get(getRawColValue(name, parametersTable, COL_VALUE_TYPE));
}
@Override
public PromptType getPromptType(ODLTableReadOnly parametersTable, String name) {
return PROMPT_TYPE_LOOKUP.get(getRawColValue(name, parametersTable, COL_PROMPT_TYPE));
}
private long getRowId(String name, ODLTableReadOnly parametersTable) {
if (name != null) {
long[] find = parametersTable.find(COL_KEY, name);
if (find != null && find.length > 0) {
return find[0];
}
}
return -1;
}
private String getRawColValue(String name, ODLTableReadOnly parametersTable, int col) {
long rowId = getRowId(name, parametersTable);
if (rowId != -1) {
return (String) parametersTable.getValueById(rowId, col);
}
return null;
}
@Override
public String getDSId() {
return DS_ID;
}
@Override
public ODLDatastore<? extends ODLTableDefinition> dsDefinition(boolean includeKey) {
return (includeKey ? BEAN_WITH_KEY_DS_MAPPING : BEAN_NO_KEY_DS_MAPPING).getDefinition();
}
@Override
public String getByRow(ODLTableReadOnly parametersTable, int row, ParamDefinitionField type) {
return (String) parametersTable.getValueAt(row, getWithKeyColIndx(type));
}
@Override
public boolean exists(ODLTableReadOnly parametersTable, String key) {
return getRowId(key, parametersTable) != -1;
}
@Override
public ParametersControlFactory getControlFactory() {
return PARAMETERS_CONTROL;
}
// @Override
// public String getParameterControlComponentId() {
// return "com.opendoorlogistics.core.parameters.control";
// }
private int getWithKeyColIndx(ParamDefinitionField type) {
switch (type) {
case KEY:
return COL_KEY;
case VALUE_TYPE:
return COL_VALUE_TYPE;
// case DEFAULT_VALUE:
// return COL_DEFAULT_VALUE;
// case EDITOR_TYPE:
// return COL_EDITOR_TYPE;
case PROMPT_TYPE:
return COL_PROMPT_TYPE;
case VALUE:
return COL_VALUE;
}
return -1;
}
@Override
public String getParametersTableName() {
return TABLE_NAME;
}
@Override
public String getByKey(ODLTableReadOnly parametersTable, String key, ParamDefinitionField type) {
long rowId = getRowId(key, parametersTable);
if (rowId != -1) {
return (String) parametersTable.getValueById(rowId, getWithKeyColIndx(type));
}
return null;
}
@Override
public void setByKey(ODLTable table, String key, ParamDefinitionField type, String newValue) {
long rowId = getRowId(key, table);
if (rowId != -1) {
table.setValueById(newValue, rowId, getWithKeyColIndx(type));
}
}
@Override
public ODLDatastore<? extends ODLTableReadOnly> exampleDs() {
ODLDatastoreAlterable<? extends ODLTableAlterable> ret = api.tables().createAlterableDs();
api.tables().copyTableDefinition(tableDefinition(true), ret);
for (int i = 0; i < 3; i++) {
WithKeyParametersTable o = new WithKeyParametersTable();
o.setKey("View" + (i + 1));
o.setValue("View" + (i + 1));
o.setPromptType(PromptType.ATTACH.name());
BEAN_WITH_KEY_DS_MAPPING.getTableMapping(0).writeObjectToTable(o, ret.getTableAt(0));
}
return ret;
}
@Override
public String getParamDefinitionFieldName(ParamDefinitionField type) {
switch (type) {
case KEY:
return Parameters.FIELDNAME_KEY;
case VALUE_TYPE:
return Parameters.FIELDNAME_VALUE_TYPE;
// case DEFAULT_VALUE:
// return Parameters.FIELDNAME_DEFAULT_VALUE;
// case EDITOR_TYPE:
// return Parameters.FIELDNAME_EDITOR_TYPE;
case PROMPT_TYPE:
return Parameters.FIELDNAME_PROMPT_TYPE;
case VALUE:
return Parameters.FIELDNAME_VALUE;
}
return null;
}
/**
* Called from the script editor UI...
*
* @return
*/
public AdapterConfig createParameterAdapter(String id) {
AdapterConfig config = new AdapterConfig(id);
config.setAdapterType(ScriptAdapterType.PARAMETER);
// wrap the config in the api object so we can user our high-level api
// code to configure it...
ScriptAdapter adapter = new ScriptAdapterImpl(api, null, config);
adapter.setName(adapter.getAdapterId());
adapter.setAdapterType(ScriptAdapterType.PARAMETER);
// add parameters table
ScriptAdapterTable newParameter = adapter.addSourcelessTable(tableDefinition(false));
ODLTable dataTable = (ODLTable) api.tables().copyTableDefinition(tableDefinition(false), api.tables().createAlterableDs());
dataTable.createEmptyRow(-1);
dataTable.setValueAt(PromptType.ATTACH_POPUP.name(), 0, api.tables().findColumnIndex(dataTable, Parameters.FIELDNAME_PROMPT_TYPE));
dataTable.setValueAt(ODLColumnType.STRING.name(), 0, api.tables().findColumnIndex(dataTable, Parameters.FIELDNAME_VALUE_TYPE));
newParameter.setDataTable(dataTable);
// newParameter.setFormula(Parameters.FIELDNAME_VALUE_TYPE, "\"" +
// ODLColumnType.STRING.name()+ "\"");
// newParameter.setFormula(Parameters.FIELDNAME_EDITOR_TYPE, "\"\"");
// newParameter.setFormula(Parameters.FIELDNAME_PROMPT_TYPE, "\"" +
// PromptType.ATTACH.name() + "\"");
// String inputTableName = tableDefinition(false).getName();
// newParameter.setSourceTable(":=emptytable(\"" +inputTableName+
// "\",1)", inputTableName);
newParameter.setSourceTable(ScriptConstants.SCRIPT_EMBEDDED_TABLE_DATA_DS, "");
// add available values table...
ScriptAdapterTable availableValues = adapter.addSourcelessTable(valuesTableDefinition(false));
// String valuesTableName = valuesTableDefinition(false).getName();
// availableValues.setSourceTable(":=emptytable(\"" +valuesTableName+
// "\",0)", valuesTableName);
availableValues.setSourceTable(ScriptConstants.SCRIPT_EMBEDDED_TABLE_DATA_DS, "");
// availableValues.setFormula(0, "\"\"");
return config;
}
@Override
public void registerControlFactory(ParametersControlFactory factory) {
PARAMETERS_CONTROL = factory;
}
@Override
public ODLTable findTable(ODLDatastore<? extends ODLTable> ds, TableType type) {
return api.tables().findTable(ds, type == TableType.PARAMETERS ? TABLE_NAME : PARAMETER_VALUES_TABLE_NAME);
}
/**
* Apply the string defining the visible parameters override to the input
* parameters table, return the filtered and ordered result as a new table.
*
* @param overrideCommand
* @param parametersTable
* @param report
* @return
*/
public ODLTable applyVisibleOverrides(String overrideCommand, ODLTableReadOnly parametersTable, ExecutionReport report) {
String formatMsg = "Visible parameters override should be a comma-separated line in the format [PROMPT_TYPE] parametername, e.g. ATTACH Potential, Sales, POPUP Workload";
ODLTable ret = (ODLTable) api.tables().copyTableDefinition(parametersTable, api.tables().createAlterableDs());
if (overrideCommand != null) {
String[] params = overrideCommand.split(",");
for (int i = 0; i < params.length; i++) {
String[] words = params[i].split("\\s+");
if (words.length == 0 || words.length > 2) {
report.setFailed("Incorrect format in visible parameters override. " + formatMsg);
return null;
}
// Get and validate prompt type if we have one
PromptType promptType = PromptType.ATTACH_POPUP;
int paramWordIndx = 0;
if (words.length == 2) {
words[0] = api.stringConventions().standardise(words[0]);
if(words[0].length()>0){
promptType = PROMPT_TYPE_LOOKUP.get(words[0]);
if (promptType == null) {
report.setFailed("Unidentified prompt type in visible parameters override: " + words[0] + ".\n" + formatMsg);
return null;
}
}
paramWordIndx++;
}
String key = words[paramWordIndx];
key = api.stringConventions().standardise(key);
if (key.length() > 0) {
long rowId = getRowId(key, parametersTable);
if (rowId == -1) {
report.setFailed("Cannot find parameter \"" + key + "\" referenced in visible parameters override. " + formatMsg);
return null;
}
// copy the parameter over
api.tables().copyRowById(parametersTable, rowId, ret);
// but set its new prompt type
setByKey(ret, key, ParamDefinitionField.PROMPT_TYPE, promptType.name());
}
}
}
return ret;
}
@Override
public String getLastValue(String parameterName) {
return (String)parameterLastUsedValuesCache.get(new CachedParameterKey(null, parameterName));
}
@Override
public void saveLastValue( String parameterName, String value) {
CachedParameterKey key = new CachedParameterKey(null, parameterName);
int bytes = key.bytesSize();
if(value!=null){
bytes += value.length()*2;
}
parameterLastUsedValuesCache.put(key, value, bytes);
}
}