
org.openl.rules.data.DataTableBindHelper Maven / Gradle / Ivy
package org.openl.rules.data;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.openl.OpenL;
import org.openl.binding.IBindingContext;
import org.openl.binding.impl.BindHelper;
import org.openl.exception.OpenLCompilationException;
import org.openl.meta.StringValue;
import org.openl.rules.lang.xls.syntax.TableSyntaxNode;
import org.openl.rules.table.IGridTable;
import org.openl.rules.table.ILogicalTable;
import org.openl.rules.table.openl.GridCellSourceCodeModule;
import org.openl.syntax.exception.SyntaxNodeException;
import org.openl.syntax.exception.SyntaxNodeExceptionUtils;
import org.openl.syntax.impl.IdentifierNode;
import org.openl.syntax.impl.Tokenizer;
import org.openl.types.IOpenClass;
import org.openl.types.IOpenField;
import org.openl.util.ArrayTool;
public class DataTableBindHelper {
private static final char INDEX_ROW_REFERENCE_START_SYMBOL = '>';
private static final String FPK = "_PK_";
/** Indicates that field is a constructor.
*/
// Protected to make javadoc reference.
protected static final String CONSTRUCTOR_FIELD = "this";
private static final String CODE_DELIMETERS = ". \n\r";
private static final String INDEX_ROW_REFERENCE_DELIMITER = " >\n\r";
/**
* Foreign keys row is optional for data table. It consists reference for
* field value to other table. Foreign keys always starts from
* {@value #INDEX_ROW_REFERENCE_START_SYMBOL} symbol.
*
* @param dataTable
* @return TRUE
if second row in data table body (next to the
* field row) consists even one value, in any column, starts with
* {@value #INDEX_ROW_REFERENCE_START_SYMBOL} symbol.
*/
public static boolean hasForeignKeysRow(ILogicalTable dataTable) {
ILogicalTable potentialForeignKeysRow = dataTable.getRows(1, 1);
int columnsCount = potentialForeignKeysRow.getWidth();
for (int i = 0; i < columnsCount; i++) {
ILogicalTable cell = potentialForeignKeysRow.getColumn(i);
String value = cell.getSource().getCell(0, 0).getStringValue();
if (value == null || value.trim().length() == 0) {
continue;
}
return value.charAt(0) == INDEX_ROW_REFERENCE_START_SYMBOL;
}
return false;
}
/**
* Gets the table body, by skipping the table header and properties
* sections.
*
* @param tsn
* @return Table body without table header and properties section.
*/
public static ILogicalTable getTableBody(TableSyntaxNode tsn) {
int startRow = 0;
if (!tsn.hasPropertiesDefinedInTable()) {
startRow = 1;
} else {
startRow = 2;
}
return tsn.getTable().getRows(startRow);
}
/**
* Checks if table representation is horizontal. Horizontal is data table
* where parameters are listed from left to right. Example:
*
*
*
* param1
* param2
* param3
*
*
* param1 value
* param2 value
* param3 value
*
*
*
* @param dataTableBody
* @param tableType
* @return TRUE
if table is horizontal.
*/
public static boolean isHorizontalTable(ILogicalTable dataTableBody, IOpenClass tableType) {
// If data table body contains only one row, we consider it is vertical.
//
if (dataTableBody.getHeight() != 1) {
int fieldsCount1 = countFields(dataTableBody, tableType);
int fieldsCount2 = countFields(dataTableBody.transpose(), tableType);
return fieldsCount1 >= fieldsCount2;
}
return false;
}
/**
* Goes through the data table columns from left to right, and count number
* of {@link IOpenField}
.
*
* @param dataTable
* @param tableType
* @return Number of {@link IOpenField}
found in the data
* table.
*/
private static int countFields(ILogicalTable dataTable, IOpenClass tableType) {
int count = 0;
int width = dataTable.getWidth();
for (int i = 0; i < width; ++i) {
String fieldName = dataTable.getColumn(i).getSource().getCell(0, 0).getStringValue();
if (fieldName == null) {
continue;
}
// Remove extra spaces.
//
fieldName = StringUtils.trim(fieldName);
IOpenField field = findField(fieldName, null, tableType);
if (field != null) {
count += 1;
}
}
return count;
}
public static IOpenField findField(String fieldName, ITable table, IOpenClass tableType) {
if (FPK.equals(fieldName)) {
return new PrimaryKeyField(FPK, table);
}
return tableType.getField(fieldName, true);
}
/**
* Gets the horizontal table representation from current table. If it was
* vertical it will be transposed.
*
* @param tableBody
* @param tableType
* @return Horizontal representation of table.
*/
public static ILogicalTable getHorizontalTable(ILogicalTable tableBody, IOpenClass tableType) {
ILogicalTable resultTable = null;
if (tableBody != null) {
if (isHorizontalTable(tableBody, tableType)) {
resultTable = tableBody;
} else {
resultTable = tableBody.transpose();
}
}
return resultTable;
}
/**
* Gets the Data_With_Titles rows from the data table body. Data_With_Titles
* start row consider to be the next row after descriptor section of the
* table and till the end of the table.
*
* @param horizDataTableBody Horizontal representation of data table body.
* @return Data_With_Titles rows for current data table body.
*/
public static ILogicalTable getHorizontalDataWithTitle(ILogicalTable horizDataTableBody) {
int startIndex = getStartIndexForDataWithTitlesSection(horizDataTableBody);
return horizDataTableBody.getRows(startIndex);
}
/**
* Gets the sub table for displaying on business view.
*
* @param tableBody data table body.
* @param tableType
* @return Data_With_Titles section for current data table body.
*/
public static ILogicalTable getSubTableForBusinessView(ILogicalTable tableBody, IOpenClass tableType) {
if (isHorizontalTable(tableBody, tableType)) {
return getHorizontalDataWithTitle(tableBody);
} else {
return getVerticalDataWithTitle(tableBody);
}
}
/**
* Gets the Data_With_Titles columns from the data table body. Data_With_Titles
* start column consider to be the next column after descriptor section of the
* table and till the end of the table.
*
* @param verticalTableBody Vertical representation of data table body.
* @return Data_With_Titles columns for current data table body.
*/
private static ILogicalTable getVerticalDataWithTitle(ILogicalTable verticalTableBody) {
ILogicalTable horizDataTableBody = verticalTableBody.transpose();
int startIndex = getStartIndexForDataWithTitlesSection(horizDataTableBody);
return verticalTableBody.getColumns(startIndex);
}
/**
* Gets the start index of the Data_With_Titles section of the data
* table body.
* It depends on whether table has or no the foreign key row.
* Works with horizontal representation of data table.
*
* @param horizDataTableBody Horizontal representation of data table body.
* @return Number of the start row for the Data_With_Titles section.
*/
private static int getStartIndexForDataWithTitlesSection(ILogicalTable horizDataTableBody) {
boolean hasForeignKeysRow = hasForeignKeysRow(horizDataTableBody);
if (hasForeignKeysRow) {
// Data_With_Titles will starts from this row.
//
return 2;
}
// Data_With_Titles will starts from this row.
//
return 1;
}
/**
* Gets the descriptor rows from the data table body. Descriptor rows are
* obligatory parameter row and optional foreign key row if it exists in the
* table.
*
* @param horizDataTableBody Horizontal representation of data table body.
* @return Descriptor rows for current data table body.
*/
public static ILogicalTable getDescriptorRows(ILogicalTable horizDataTableBody) {
int endRow = getEndRowForDescriptorSection(horizDataTableBody);
return horizDataTableBody.getRows(0, endRow);
}
/**
* Gets the number of end row for descriptor section of the data table body.
* It depends on whether table has or no the foreign key row.
*
* @param horizDataTableBody Horizontal representation of data table body.
* @return Number of end row for descriptor section.
*/
private static int getEndRowForDescriptorSection(ILogicalTable horizDataTableBody) {
boolean hasForeignKeysRow = hasForeignKeysRow(horizDataTableBody);
if (hasForeignKeysRow) {
// descriptorRows will consist fieldRow + iforeignKeyRow.
//
return 1;
}
// descriptorRows will consist only fieldRow.
//
return 0;
}
/**
* Gets title for column if required or returns blank value.
*
* @param dataWithTitleRows Logical part of the data table. Consider to
* include all rows from base table after header section
* (consists from header row + property section) and descriptor
* section (consists from JavaBean name obligatory + optional
* index row, see {@link #hasForeignKeysRow(ILogicalTable)}).
* This part of table may consists from optional first title row
* and followed data rows.
* @param bindingContext is used for optimization {@link GridCellSourceCodeModule} in execution mode. Can be null
.
* @param column Number of column in data table.
* @param hasColumnTitleRow Flag shows if data table has column tytle row.
* @return Column title (aka Display name).
*/
public static StringValue makeColumnTitle(IBindingContext bindingContext, ILogicalTable dataWithTitleRows,
int column, boolean hasColumnTitleRow) {
String value = StringUtils.EMPTY;
if (hasColumnTitleRow) {
ILogicalTable titleCell = dataWithTitleRows.getSubtable(column, 0, 1, 1);
value = titleCell.getSource().getCell(0, 0).getStringValue();
// remove extra spaces
value = StringUtils.trimToEmpty(value);
return new StringValue(value, value, value, new GridCellSourceCodeModule(titleCell.getSource(),
bindingContext));
}
return new StringValue(value, value, value, null);
}
/**
*
* @param bindingContext is used for optimization {@link GridCellSourceCodeModule} in execution mode. Can be null
.
* @param table
* @param type
* @param openl
* @param descriptorRows
* @param dataWithTitleRows
* @param hasForeignKeysRow
* @param hasColumnTytleRow
* @return
* @throws Exception
*/
public static ColumnDescriptor[] makeDescriptors(IBindingContext bindingContext, ITable table, IOpenClass type,
OpenL openl, ILogicalTable descriptorRows, ILogicalTable dataWithTitleRows, boolean hasForeignKeysRow,
boolean hasColumnTytleRow) throws Exception {
int width = descriptorRows.getWidth();
ColumnDescriptor[] columnDescriptors = new ColumnDescriptor[width];
List columnIdentifiers = getColumnIdentifiers(bindingContext, table, descriptorRows);
for (int columnNum = 0; columnNum < columnIdentifiers.size(); columnNum++) {
IdentifierNode[] fieldAccessorChainTokens = columnIdentifiers.get(columnNum);
if (fieldAccessorChainTokens != null) {
IOpenField descriptorField = null;
// indicates if field is a constructor.
boolean constructorField = false;
if (fieldAccessorChainTokens.length == 1) {
// process single field in chain, e.g. driver;
IdentifierNode fieldNameNode = fieldAccessorChainTokens[0];
if (CONSTRUCTOR_FIELD.equals(fieldNameNode.getIdentifier())) {
constructorField = true;
} else {
descriptorField = getWritableField(fieldNameNode, table, type);
}
} else {
// process the chain of fields, e.g. driver.homeAdress.street;
descriptorField = processFieldsChain(table, type, fieldAccessorChainTokens);
}
IdentifierNode foreignKeyTable = null;
IdentifierNode foreignKey = null;
if (hasForeignKeysRow) {
IdentifierNode[] foreignKeyTokens = getForeignKeyTokens(bindingContext, descriptorRows, columnNum);
foreignKeyTable = foreignKeyTokens.length > 0 ? foreignKeyTokens[0] : null;
foreignKey = foreignKeyTokens.length > 1 ? foreignKeyTokens[1] : null;
}
StringValue header = DataTableBindHelper.makeColumnTitle(bindingContext, dataWithTitleRows, columnNum,
hasColumnTytleRow);
ColumnDescriptor currentColumnDescriptor = getColumnDescriptor(openl,
descriptorField,
constructorField,
foreignKeyTable,
foreignKey,
header);
columnDescriptors[columnNum] = currentColumnDescriptor;
}
}
return columnDescriptors;
}
/**
*
* @param bindingContext is used for optimization {@link GridCellSourceCodeModule} in execution mode. Can be null
.
* @param table is needed only for error processing. Can be null
.
* @param descriptorRows
* @return
* @throws OpenLCompilationException
*/
public static List getColumnIdentifiers(IBindingContext bindingContext, ITable table, ILogicalTable descriptorRows) {
int width = descriptorRows.getWidth();
List identifiers = new ArrayList();
for (int columnNum = 0; columnNum < width; columnNum++) {
GridCellSourceCodeModule cellSourceModule = getCellSourceModule(descriptorRows, columnNum);
cellSourceModule.update(bindingContext);
String code = cellSourceModule.getCode();
if (code.length() != 0) {
IdentifierNode[] fieldAccessorChainTokens = null;
try {
// fields names nodes
fieldAccessorChainTokens = Tokenizer.tokenize(cellSourceModule, CODE_DELIMETERS);
} catch (OpenLCompilationException e) {
String message = String.format("Cannot parse field source \"%s\"", code);
SyntaxNodeException error = SyntaxNodeExceptionUtils.createError(message, cellSourceModule);
processError(table, error);
}
if (contains(identifiers, fieldAccessorChainTokens)) {
String message = String.format("Found duplicate of field \"%s\"", code);
SyntaxNodeException error = SyntaxNodeExceptionUtils.createError(message, cellSourceModule);
processError(table, error);
} else {
identifiers.add(fieldAccessorChainTokens);
}
} else {
identifiers.add(null);
}
}
return identifiers;
}
private static GridCellSourceCodeModule getCellSourceModule(ILogicalTable descriptorRows,
int columnNum) {
IGridTable gridTable = descriptorRows.getColumn(columnNum).getSource();
GridCellSourceCodeModule cellSourceModule = new GridCellSourceCodeModule(gridTable);
return cellSourceModule;
}
private static ColumnDescriptor getColumnDescriptor(OpenL openl,
IOpenField descriptorField,
boolean constructorField,
IdentifierNode foreignKeyTable,
IdentifierNode foreignKey,
StringValue header) {
ColumnDescriptor currentColumnDescriptor;
if (foreignKeyTable != null) {
currentColumnDescriptor = new ForeignKeyColumnDescriptor(descriptorField,
foreignKeyTable,
foreignKey,
header,
openl, constructorField);
} else {
currentColumnDescriptor = new ColumnDescriptor(descriptorField, header, openl, constructorField);
}
return currentColumnDescriptor;
}
/**
* Process the chain of fields, e.g. driver.homeAdress.street;
*
* @return {@link IOpenField} for fields chain.
*/
private static IOpenField processFieldsChain(ITable table, IOpenClass type,
IdentifierNode[] fieldAccessorChainTokens) {
IOpenField chainField = null;
IOpenClass loadedFieldType = type;
// the chain of fields to access the target field, e.g. for
// driver.name it will be array consisting of two fields:
// 1st for driver, 2nd for name
IOpenField[] fieldAccessorChain = new IOpenField[fieldAccessorChainTokens.length];
for (int fieldIndex = 0; fieldIndex < fieldAccessorChain.length; fieldIndex++) {
IdentifierNode fieldNameNode = fieldAccessorChainTokens[fieldIndex];
IOpenField fieldInChain = getWritableField(fieldNameNode, table, loadedFieldType);
if (fieldInChain == null) {
// in this case current field and all the followings in fieldAccessorChain will be nulls.
//
break;
}
loadedFieldType = fieldInChain.getType();
fieldAccessorChain[fieldIndex] = fieldInChain;
}
if (!ArrayTool.contains((fieldAccessorChain), null)) { // check successful loading of all
// fields in fieldAccessorChain.
chainField = new FieldChain(type, fieldAccessorChain);
}
return chainField;
}
private static void processError(ITable table, SyntaxNodeException error) {
if (table != null) {
if(table.getTableSyntaxNode() != null){
table.getTableSyntaxNode().addError(error);
}
BindHelper.processError(error);
}
}
/**
* Returns foreign_key_tokens from the current column.
*
* @param bindingContext is used for optimization {@link GridCellSourceCodeModule} in execution mode. Can be null
.
* @param descriptorRows
* @param columnNum
* @return
* @throws OpenLCompilationException
*
* @see {@link #hasForeignKeysRow(ILogicalTable)}.
*/
private static IdentifierNode[] getForeignKeyTokens(IBindingContext bindingContext, ILogicalTable descriptorRows, int columnNum) throws OpenLCompilationException {
ILogicalTable logicalRegion = descriptorRows.getSubtable(columnNum, 1, 1, 1);
GridCellSourceCodeModule indexRowSourceModule = new GridCellSourceCodeModule(
logicalRegion.getSource(), bindingContext);
// Should be in format
// "> reference_table_name [reference_table_key_column]"
return Tokenizer.tokenize(indexRowSourceModule, INDEX_ROW_REFERENCE_DELIMITER);
}
/**
* Gets the field, and if it is not null
and isWritable,
* returns it. In other case processes errors and return null
.
*
* @param currentFieldNameNode
* @param table
* @param loadedFieldType
* @return
*/
private static IOpenField getWritableField(IdentifierNode currentFieldNameNode,
ITable table,
IOpenClass loadedFieldType) {
String fieldName = currentFieldNameNode.getIdentifier();
IOpenField field = DataTableBindHelper.findField(fieldName, table, loadedFieldType);
if (field == null) {
String errorMessage = String.format("Field \"%s\" not found in %s", fieldName, loadedFieldType.getName());
SyntaxNodeException error = SyntaxNodeExceptionUtils.createError(errorMessage, currentFieldNameNode);
processError(table, error);
return null;
}
if (!field.isWritable()) {
String message = String.format("Field '%s' is not writable in %s", fieldName, loadedFieldType.getName());
SyntaxNodeException error = SyntaxNodeExceptionUtils.createError(message, currentFieldNameNode);
processError(table, error);
return null;
}
return field;
}
private static boolean contains(List identifiers, IdentifierNode[] identifier) {
for (IdentifierNode[] existIdentifier : identifiers) {
if (isEqualsIdentifier(existIdentifier, identifier)) {
return true;
}
}
return false;
}
private static boolean isEqualsIdentifier(IdentifierNode[] identifier1, IdentifierNode[] identifier2) {
if (identifier1 == null || identifier2 == null) {
return false;
}
if (identifier1.length != identifier2.length) {
return false;
}
for (int i = 0; i < identifier1.length; i++) {
if (!identifier1[i].getIdentifier().equals(identifier2[i].getIdentifier())) {
return false;
}
}
return true;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy