package com.opendoorlogistics.core.scripts.formulae.rules; import java.util.ArrayList; import java.util.List; import com.opendoorlogistics.api.ExecutionReport; import com.opendoorlogistics.api.tables.ODLDatastore; import com.opendoorlogistics.api.tables.ODLTable; import com.opendoorlogistics.api.tables.ODLTableReadOnly; import com.opendoorlogistics.core.formulae.Function; import com.opendoorlogistics.core.formulae.FunctionFactory; import com.opendoorlogistics.core.formulae.FunctionImpl; import com.opendoorlogistics.core.formulae.FunctionParameters; import com.opendoorlogistics.core.formulae.FunctionUtils; import com.opendoorlogistics.core.formulae.Functions; import com.opendoorlogistics.core.formulae.definitions.FunctionDefinition; import com.opendoorlogistics.core.formulae.definitions.FunctionDefinitionLibrary; import com.opendoorlogistics.core.formulae.definitions.FunctionDefinition.ArgumentType; import com.opendoorlogistics.core.scripts.TableReference; import com.opendoorlogistics.core.scripts.execution.adapters.IndexedDatastores; import com.opendoorlogistics.core.tables.ColumnValueProcessor; import com.opendoorlogistics.core.tables.utils.TableUtils; import com.opendoorlogistics.core.utils.strings.StandardisedCache; import com.opendoorlogistics.core.utils.strings.Strings; /** * A rule lookup is built from an input table which is comprised of one or more * selector fields and one result field. Each row in the table is a rule and * lower-index rules have higher priority. An input list of values (one per * selector field) is given to the rule lookup. If a selector field is null it * matches to anything. If a selector field is not null then the corresponding * input value must match it. The return value from the lowest index rule is * then selected for the input list. This function assumes the input table is * not modified after building. * * A rule lookup object is therefore built to return results from only a single * column in a rule lookup table. * @author Phil * */ public class FmRuleLookup extends FunctionImpl { private final Object[] results; private final int nc; private final String[] selectorFieldNames; private final RuleNode tree; private FmRuleLookup(Object[] results, int nc, String[] selectorFieldNames,RuleNode tree) { this.results = results; this.nc = nc; this.tree = tree; this.selectorFieldNames = selectorFieldNames; } public FmRuleLookup(Function[] inputSelectors, String[] selectorFieldNames, ODLTableReadOnly table, String returnField) { super(inputSelectors); this.selectorFieldNames = selectorFieldNames; // Match the columns nc = selectorFieldNames.length; int[] cols = new int[nc]; for (int i = 0; i < nc; i++) { cols[i] = identifyFieldname(table, selectorFieldNames[i]); } // Also for results int resultCol = identifyFieldname(table, returnField); // get matrix of selector values int n = table.getRowCount(); List<List<Object>> selectorsMatrix = new ArrayList<List<Object>>(); for (int rule = 0; rule < n; rule++) { List<Object> selectors4Rule = new ArrayList<Object>(); selectorsMatrix.add(selectors4Rule); for (int col = 0; col < nc; col++) { Object s = table.getValueAt(rule, cols[col]); selectors4Rule.add(s); } } // Build a lookup tree results = new Object[n]; RuleNode baseNode =RuleNode.buildTree(selectorsMatrix); tree = baseNode; // save results for (int rule = 0; rule < n; rule++) { results[rule] = table.getValueAt(rule, resultCol); } } private int identifyFieldname(ODLTableReadOnly table, String fieldname) { int col = TableUtils.findColumnIndx(table, fieldname); if (col == -1) { throw new RuntimeException("Cannot identify column " + fieldname + " in table " + table.getName() + " needed by cascading lookup."); } return col; } @Override public Object execute(FunctionParameters parameters) { Object[] childexe = executeChildFormulae(parameters, false); if (childexe == null) { return Functions.EXECUTION_ERROR; } int ruleNb = tree.findRuleNumber(childexe); if (ruleNb != -1) { return results[ruleNb]; } return null; } @Override public Function deepCopy() { // Return a new object giving it the immutable internal data return new FmRuleLookup(results, nc,selectorFieldNames, tree); } public static void buildRuleLookup(FunctionDefinitionLibrary library, final IndexedDatastores<? extends ODLTable> datastores,final int defaultDatastoreIndex, final ExecutionReport result) { for (int lookupsize = 1; lookupsize <= 7; lookupsize++) { FunctionDefinition dfn = new FunctionDefinition("rulelookup"); dfn.setGroup("rulelookup"); dfn.setDescription("A rule lookup is built from an input table which is comprised of one or more selector fields and one result field." + " Each row in the table is a rule and lower-index rules have higher priority." + " An input list of values (one per selector field) is given to the rule lookup." + "If a selector field is null it matches to anything. " + " If a selector field is not null then the corresponding input value must match it." + " The return value from the lowest index rule is then selected for the input list."); for (int i = 0; i < lookupsize; i++) { dfn.addArg("SearchValue" + (i + 1)); } final int tr = dfn.addArg("TableReference", ArgumentType.TABLE_REFERENCE_CONSTANT); final int[] lookupFieldNames = new int[lookupsize]; for (int i = 0; i < lookupsize; i++) { lookupFieldNames[i] = dfn.addArg("LookupFieldName" + (i + 1), ArgumentType.STRING_CONSTANT); } final int retField = dfn.addArg("ReturnField", ArgumentType.STRING_CONSTANT); dfn.setFactory(new FunctionFactory() { @Override public Function createFunction(Function... children) { // parse the table reference.. (format is already validated) String sTableRef = FunctionUtils.getConstantString(children[tr]); TableReference tableRef = TableReference.create(sTableRef, result); if (tableRef == null) { result.setFailed("Error reading table reference in formula rulelookup."); return null; } // find the datastore index ... int dsIndx = defaultDatastoreIndex; if (Strings.isEmpty(tableRef.getDatastoreName()) == false) { dsIndx = datastores.getIndex(tableRef.getDatastoreName()); if (dsIndx == -1) { result.setFailed("Error getting datastore " + tableRef.getDatastoreName() + " used in formula rulelookup."); return null; } } // Then the datastore ODLDatastore<? extends ODLTable> ds = datastores.getDatastore(dsIndx); if (ds == null || result.isFailed()) { result.setFailed("Error getting datastore " + tableRef + " used in formula rulelookup."); return null; } // Then the table ODLTableReadOnly table = TableUtils.findTable(ds, tableRef.getTableName()); if (table == null) { result.setFailed("Error getting table " + tableRef + " used in formula rulelookup."); return null; } // get column names and input functions String[] colNames = new String[lookupFieldNames.length]; Function[] funcs = new Function[lookupFieldNames.length]; for (int i = 0; i < colNames.length; i++) { colNames[i] = FunctionUtils.getConstantString(children[lookupFieldNames[i]]); funcs[i] = children[i]; } // get return field name String retName = FunctionUtils.getConstantString(children[retField]); // get functions in an array return new FmRuleLookup(funcs, colNames, table, retName); } }); library.add(dfn); } } }