/*******************************************************************************
* 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.api.impl.scripts;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.components.ODLComponent;
import com.opendoorlogistics.api.scripts.ScriptAdapter;
import com.opendoorlogistics.api.scripts.ScriptComponentConfig;
import com.opendoorlogistics.api.scripts.ScriptElement;
import com.opendoorlogistics.api.scripts.ScriptInputTables;
import com.opendoorlogistics.api.scripts.ScriptInstruction;
import com.opendoorlogistics.api.scripts.ScriptOption;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLTableDefinition;
import com.opendoorlogistics.core.components.ODLGlobalComponents;
import com.opendoorlogistics.core.scripts.ScriptConstants;
import com.opendoorlogistics.core.scripts.TargetIODsInterpreter;
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.ComponentConfig;
import com.opendoorlogistics.core.scripts.elements.InstructionConfig;
import com.opendoorlogistics.core.scripts.elements.Option;
import com.opendoorlogistics.core.scripts.elements.OutputConfig;
import com.opendoorlogistics.core.scripts.elements.Script;
import com.opendoorlogistics.core.scripts.elements.ScriptBaseElement;
import com.opendoorlogistics.core.scripts.utils.ScriptUtils;
import com.opendoorlogistics.core.scripts.utils.ScriptUtils.FindScriptElement;
import com.opendoorlogistics.core.utils.Pair;
import com.opendoorlogistics.core.utils.strings.Strings;
import com.opendoorlogistics.core.utils.strings.Strings.DoesStringExist;
public class ScriptOptionImpl extends ScriptElementImpl implements ScriptOption {
protected final ScriptOptionImpl parentOption;
protected final Option option;
protected final ScriptInputTables inputTables;
/**
* Use this constructor when wrapping a pre-existing option
* @param api
* @param inputTables
* @param option
* @param parentOption
*/
public ScriptOptionImpl(ODLApi api,ScriptInputTables inputTables, Option option,ScriptOptionImpl parentOption){
super(api,null,option);
this.option = option;
this.inputTables = inputTables;
this.parentOption = parentOption;
}
/**
* Use this constructor when adding a new option to an existing script option object
* @param api
* @param inputTables
* @param parentOption
*/
private ScriptOptionImpl(ODLApi api,ScriptInputTables inputTables, ScriptOptionImpl parentOption) {
super(api,parentOption, null);
this.option = (Option)getElement();
this.inputTables = inputTables;
this.parentOption = parentOption;
}
/**
* For the input script and option id, create a wrapped hierarchy and return the ScriptOption
* corresponding to the option id
* @param api
* @param script
* @param optionId
* @param inputTables
* @return
*/
public static ScriptOption createWrapperHierarchy(ODLApi api,Script script, String optionId, ScriptInputTables inputTables){
List<Option> path = ScriptUtils.getOptionPath(script,optionId);
if(path==null || path.size()==0){
throw new RuntimeException("Corrupt script, option id unknown: " + optionId);
}
ScriptOption ret=null;
for(Option option:path){
if(ret==null){
// top-level
ret = new ScriptOptionImpl(api, inputTables,option, null);
}else{
// below top level
ret = ret.getChildOption(option.getOptionId());
if(ret==null){
throw new RuntimeException("Corrupt script");
}
}
if(Strings.equalsStd(ret.getOptionId(), option.getOptionId())==false){
throw new RuntimeException("Corrupt script");
}
}
return ret;
}
protected enum FindMode{
CANNOT_EXIST_ANYWHERE,
MUST_EXIST_IN_CURRENT_OPTION,
MUST_EXIST_IN_AVAILABLE_OPTIONS,
NO_RESTRICTION
}
protected Option findOption(final String optionId) {
return ScriptUtils.getScriptElement(new FindScriptElement<Option>() {
@Override
public Option find(Option option) {
if (Strings.equalsStd(optionId, option.getOptionId())) {
return option;
}
return null;
}
}, root());
}
@Override
public String createUniqueOptionId(String baseId) {
return Strings.makeUnique(baseId, new DoesStringExist() {
@Override
public boolean isExisting(String s) {
return findOption(s)!=null;
}
});
}
@Override
public String createUniqueDatastoreId(String baseId) {
return ScriptUtils.createUniqueDatastoreId(root(), baseId);
}
@Override
public String createUniqueComponentConfigId(String baseId) {
return Strings.makeUnique(baseId, new DoesStringExist() {
@Override
public boolean isExisting(String s) {
return findComponentConfig(s)!=null;
}
});
}
/**
* Get the adapter. The whole script is searched, not just the current option.
*
* @param adapterId
* @param shouldExist
* @return
*/
protected AdapterConfig findAdapter(final String adapterId, FindMode findOption) {
// find root option node, getting all available options as we go...
final HashSet<Option> availableOptions = new HashSet<>();
ScriptOptionImpl root = findRoot(availableOptions);
class RecurseFind {
Pair<AdapterConfig,Option> recurse(Option node) {
for (AdapterConfig config : node.getAdapters()) {
if (Strings.equalsStd(adapterId, config.getId())) {
return new Pair<AdapterConfig,Option>(config,node);
}
}
for (Option child : node.getOptions()) {
Pair<AdapterConfig,Option> config = recurse(child);
if (config != null) {
return config;
}
}
return null;
}
}
Pair<AdapterConfig,Option> ret = new RecurseFind().recurse(root.option);
if(findOption!=null){
switch(findOption){
case CANNOT_EXIST_ANYWHERE:
if (ret != null) {
throw new RuntimeException("Adapter with input id already exists: " + adapterId);
}
break;
case MUST_EXIST_IN_CURRENT_OPTION:
case MUST_EXIST_IN_AVAILABLE_OPTIONS:
if(ret==null){
throw new RuntimeException("Data adapter with id not found: " + adapterId);
}
if(findOption == FindMode.MUST_EXIST_IN_CURRENT_OPTION && ret.getSecond() != option){
throw new RuntimeException("Data adapter exists but not within current script option: " + adapterId);
}
if(findOption == FindMode.MUST_EXIST_IN_AVAILABLE_OPTIONS && availableOptions.contains(ret.getSecond())==false){
throw new RuntimeException("Data adapter exists but not within current script option or its parent options: " + adapterId);
}
break;
default:
break;
}
}
if(ret!=null){
return ret.getFirst();
}
return null;
}
@Override
protected ScriptBaseElement createRootElement() {
return new Option();
}
protected AdapterColumnConfig getAdaptedColumn(String adapterId, int tableIndex, String columnName, FindMode findMode) {
AdaptedTableConfig table = getAdaptedTable(adapterId, tableIndex, findMode);
if (table != null) {
for (AdapterColumnConfig col : table.getColumns()) {
if (Strings.equalsStd(col.getName(), columnName)) {
return col;
}
}
if (findMode!= FindMode.CANNOT_EXIST_ANYWHERE) {
throw new RuntimeException("Column with name not found: " + columnName);
}
}
return null;
}
protected AdaptedTableConfig getAdaptedTable(String adapterId, int tableIndex, FindMode findMode) {
AdapterConfig config = findAdapter(adapterId, findMode);
AdaptedTableConfig table = config.getTable(tableIndex);
return table;
}
@Override
public ScriptAdapter addDataAdapter(String adapterId) {
if(Strings.isEmpty(adapterId) || api.values().equalsStandardised(adapterId, api.stringConventions().getSpreadsheetAdapterId())){
throw new RuntimeException("Illegal adapter id: " + adapterId);
}
adapterId = createUniqueDatastoreId(adapterId);
findAdapter(adapterId, FindMode.CANNOT_EXIST_ANYWHERE);
AdapterConfig ac = new AdapterConfig(adapterId);
option.getAdapters().add(ac);
return new ScriptAdapterImpl(api,this, ac);
}
@Override
public ScriptInstruction addInstruction(String inputDataAdapter, String componentId, int mode, Serializable config) {
InstructionConfig instruction = new InstructionConfig();
ScriptOptionImpl root = findRoot(null);
instruction.setUuid(ScriptUtils.createUniqueInstructionId(root.option));
instruction.setComponentConfig(config);
instruction.setComponent(componentId);
instruction.setDatastore(inputDataAdapter);
instruction.setExecutionMode(mode);
option.getInstructions().add(instruction);
ScriptInstruction ret= new ScriptInstructionImpl(api,this, instruction);
if(ret.getInstructionOutput()!=null){
ODLComponent component = ODLGlobalComponents.getProvider().getComponent(componentId);
ret.setOutputDatastoreId(createUniqueDatastoreId(component!=null ? "Output of " + component.getName(): "Output"));
}
return ret;
}
@Override
public ODLApi getApi() {
return api;
}
@Override
public ScriptOption addOption(String id, String name) {
ScriptOptionImpl ret = new ScriptOptionImpl(api,inputTables, this);
if (Strings.isEmpty(id)) {
throw new RuntimeException("Option id cannot be empty.");
}
id = createUniqueOptionId(id);
if(findOption(id)!=null){
throw new RuntimeException("Option id already exists in script: " + id);
}
if (Strings.isEmpty(name)) {
throw new RuntimeException("Option name cannot be empty.");
}
ret.option.setOptionId(id);
ret.option.setName(name);
option.getOptions().add(ret.option);
return ret;
}
@Override
public ScriptInstruction addInstruction(String inputDataAdapter, String componentId, int mode) {
ODLComponent component = ODLGlobalComponents.getProvider().getComponent(componentId);
if (component == null) {
throw new RuntimeException("Unknown component: " + componentId);
}
Serializable config = null;
if (component.getConfigClass() != null) {
try {
config = component.getConfigClass().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return addInstruction(inputDataAdapter, componentId, mode, config);
}
@Override
public ScriptInstruction addInstruction(String inputDataAdapter, String componentId, int mode, String configId) {
ScriptInstruction ret= addInstruction(inputDataAdapter, componentId, mode,(Serializable) null);
option.getInstructions().get(ret.getIndex()).setConfigId(configId);
return ret;
}
//
// @Override
// public ScriptAdapter addDataAdapter(String adapterId, String sourceAdapterId, ODLDatastore<? extends ODLTableDefinition> destination) {
// ScriptAdapter ret = addDataAdapter(adapterId);
// adapterId = ret.getAdapterId();
// for (int i = 0; i < destination.getTableCount(); i++) {
// ODLTableDefinition destTable = destination.getTableAt(i);
//
// // try getting source table
// ODLTableDefinition sourceTable = null;
//
// // try getting from the input tables first
// if(inputTables!=null){
// for(int j =0 ; j<inputTables.size();j++){
// if(Strings.equalsStd(adapterId, inputTables.getSourceDatastoreId(j)) &&
// Strings.equalsStd(destTable.getName(), inputTables.getSourceTable(j).getName())){
// sourceTable = inputTables.getSourceTable(j);
// }
// }
// }
//
// // then try getting internally to the script
// if(sourceTable==null){
// AdapterConfig adapterConfig = findAdapter(sourceAdapterId, FindMode.MUST_EXIST_IN_AVAILABLE_OPTIONS);
// for(AdaptedTableConfig tableConfig:adapterConfig){
// if(Strings.equalsStd(tableConfig.getName(), destTable.getName())){
// sourceTable = tableConfig;
// break;
// }
// }
// }
//
// int indx=-1;
// if (sourceTable != null) {
// indx = ret.addSourcedTableToAdapter(sourceTable, destTable);
// } else {
// indx = ret.addSourcelessTable(destTable);
// }
//
// // set the source adapter on the table
// getAdaptedTable(adapterId, indx, FindMode.MUST_EXIST_IN_CURRENT_OPTION).setFromDatastore(sourceAdapterId);
// }
// return ret;
// }
// @Override
// public ODLDatastore<? extends ODLTableDefinition> getSelectedInputTables() {
// return selectedInputTables;
// }
// @Override
// public ODLDatastore<? extends ODLTableDefinition> getSpreadsheetDefinition() {
// return spreadsheetDatastore;
// }
// @Override
// public void setOptionEditorLabel( String note) {
// option.setEditorLabel(note);
// }
//
// @Override
// public void setAdapterEditorLabel(String adapterId,String label) {
// findAdapter(adapterId, FindMode.MUST_EXIST_IN_CURRENT_OPTION).setEditorLabel(label);
// }
//
// @Override
// public void setComponentConfigurationEditorLabel(String id,String html) {
// for(ComponentConfig c:option.getComponentConfigs()){
// if(Strings.equalsStd(c.getConfigId(), id)){
// c.setEditorLabel(html);
// }
// }
// }
//
//
// @Override
// public void setInstructionEditorLabel(int instructionIndex, String label) {
// option.getInstructions().get(instructionIndex).setEditorLabel(label);
// }
@Override
public ScriptElement addCopyTable(String sourceAdapterId, String sourceTableName, OutputType type, String destinationTableName) {
OutputConfig outputConfig = new OutputConfig();
outputConfig.setDatastore(sourceAdapterId);
outputConfig.setInputTable(sourceTableName);
outputConfig.setType(type);
outputConfig.setDestinationTable(destinationTableName);
option.getOutputs().add(outputConfig);
//return option.getOutputs().size()-1;
return new ScriptElementImpl(api,this, outputConfig);
}
//
// @Override
// public void setOutputEditorLabel(int outputIndex, String label) {
// option.getOutputs().get(outputIndex).setEditorLabel(label);
// }
@Override
public ScriptComponentConfig addComponentConfig(String configId, String componentid, Serializable config) {
configId = createUniqueComponentConfigId(configId);
ComponentConfig conf = new ComponentConfig();
conf.setComponent(componentid);
conf.setComponentConfig(config);
conf.setConfigId(configId);
option.getComponentConfigs().add(conf);
return new ScriptComponentConfigImpl(api,this, conf);
}
/**
* @param configId
* @return
*/
protected ComponentConfig findComponentConfig(String configId) {
return ScriptUtils.getComponentConfig(root(), configId);
}
private Option root() {
return findRoot(null).option;
}
@Override
public void setSynced(boolean scriptIsSynced) {
option.setSynchronised(scriptIsSynced);
}
@Override
protected ScriptOptionImpl findRoot(final HashSet<Option> availableOptions) {
if(availableOptions!=null){
availableOptions.add(option);
}
ScriptOptionImpl root = this;
while (root.parentOption != null) {
root = root.parentOption;
if(availableOptions!=null){
availableOptions.add(root.option);
}
}
return root;
}
@Override
public String getOptionId() {
return option.getOptionId();
}
@Override
public ScriptInputTables getInputTables() {
return inputTables;
}
@Override
public ScriptOption getParent() {
return parentOption;
}
@Override
public int getChildOptionCount() {
return option.getOptions().size();
}
@Override
public ScriptOption getChildOption(int i) {
return new ScriptOptionImpl(api, inputTables, option.getOptions().get(i), this);
}
@Override
public ScriptOption getChildOption(String optionId) {
for(int i =0 ; i<getChildOptionCount();i++){
ScriptOption child = getChildOption(i);
if(Strings.equalsStd(child.getOptionId(), optionId)){
return child;
}
}
return null;
}
public Option getOption(){
return option;
}
@Override
public ScriptAdapter addDataAdapterLinkedToInputTables(String baseId, ODLDatastore<? extends ODLTableDefinition> destination) {
ScriptAdapter ret = addDataAdapter(baseId);
AdapterConfig adapterConfig = new TargetIODsInterpreter(api).buildAdapterConfig(inputTables, destination);
for(AdaptedTableConfig table:adapterConfig){
((ScriptAdapterImpl)ret).addAdaptedTable(table);
}
return ret;
}
}