/*
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.drools.workbench.screens.dtablexls.backend.server.conversion;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.drools.decisiontable.parser.ActionType;
import org.drools.decisiontable.parser.ActionType.Code;
import org.drools.decisiontable.parser.DefaultRuleSheetListener;
import org.drools.decisiontable.parser.RuleSheetListener;
import org.drools.decisiontable.parser.RuleSheetParserUtil;
import org.drools.decisiontable.parser.SourceBuilder;
import org.drools.decisiontable.parser.xls.PropertiesSheetListener;
import org.drools.decisiontable.parser.xls.PropertiesSheetListener.CaseInsensitiveMap;
import org.drools.template.model.Global;
import org.drools.template.model.Import;
import org.drools.template.model.Package;
import org.drools.workbench.models.datamodel.oracle.PackageDataModelOracle;
import org.drools.workbench.models.guided.dtable.shared.conversion.ConversionMessageType;
import org.drools.workbench.models.guided.dtable.shared.conversion.ConversionResult;
import org.drools.workbench.models.guided.dtable.shared.model.GuidedDecisionTable52;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.DefaultDescriptionBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableActivationGroupBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableAgendaGroupBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableAutoFocusBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableCalendarsBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableDateEffectiveBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableDateExpiresBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableDescriptionBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableDurationBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableLHSBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableLockonActiveBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableMetadataBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableNameBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableNoLoopBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableRHSBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableRuleflowGroupBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableSalienceBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableSourceBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableSourceBuilderDirect;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableSourceBuilderIndirect;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.GuidedDecisionTableTimerBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.HasColumnHeadings;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.ParameterUtilities;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.ParameterizedValueBuilder;
import org.drools.workbench.screens.dtablexls.backend.server.conversion.builders.RowNumberBuilder;
public class GuidedDecisionTableGeneratorListener
implements
RuleSheetListener {
//keywords
private static final int ACTION_ROW = 1;
private static final int OBJECT_TYPE_ROW = 2;
private static final int CODE_ROW = 3;
private static final int LABEL_ROW = 4;
//Row Number column must always be at position 0
private static final int ROW_NUMBER_COLUMN_INDEX = 0;
//Description column must always be at position 1
private static final int DESCRIPTION_COLUMN_INDEX = 1;
private static GuidedDecisionTableSourceBuilder ROW_NUMBER_BUILDER = new RowNumberBuilder();
private static GuidedDecisionTableSourceBuilder DEFAULT_DESCRIPTION_BUILDER = new DefaultDescriptionBuilder();
//State machine variables for this parser
private boolean _isInRuleTable = false;
private int _ruleRow;
private int _ruleStartColumn;
private int _ruleEndColumn;
private int _ruleStartRow;
private boolean _currentSequentialFlag = false; // indicates that we are in sequential mode
private boolean _currentEscapeQuotesFlag = true; // indicates that we are escaping quotes
private GuidedDecisionTable52 _dtable;
private boolean _isNewDataRow = false;
//Accumulated output
private Map<Integer, ActionType> _actions;
private final List<GuidedDecisionTable52> _dtables = new ArrayList<GuidedDecisionTable52>();
private List<GuidedDecisionTableSourceBuilder> _sourceBuilders;
//RuleSet wide configuration
private final PropertiesSheetListener _propertiesListener = new PropertiesSheetListener();
//Results of conversion
private ConversionResult _conversionResult;
//DataModelOracle used for type-identification
private PackageDataModelOracle _dmo;
private ParameterUtilities _parameterUtilities;
public GuidedDecisionTableGeneratorListener(final ConversionResult conversionResult,
final PackageDataModelOracle _dmo) {
this._conversionResult = conversionResult;
this._dmo = _dmo;
}
public CaseInsensitiveMap getProperties() {
return this._propertiesListener.getProperties();
}
public Package getRuleSet() {
throw new UnsupportedOperationException("Use getGuidedDecisionTables() instead.");
}
public List<Import> getImports() {
return RuleSheetParserUtil.getImportList(getProperties().getProperty(DefaultRuleSheetListener.IMPORT_TAG));
}
public List<Global> getGlobals() {
return RuleSheetParserUtil.getVariableList(getProperties().getProperty(DefaultRuleSheetListener.VARIABLES_TAG));
}
public List<String> getFunctions() {
return getProperties().getProperty(DefaultRuleSheetListener.FUNCTIONS_TAG);
}
public List<String> getQueries() {
return getProperties().getProperty(DefaultRuleSheetListener.QUERIES_TAG);
}
public List<String> getTypeDeclarations() {
return getProperties().getProperty(DefaultRuleSheetListener.DECLARES_TAG);
}
public List<GuidedDecisionTable52> getGuidedDecisionTables() {
return _dtables;
}
public void startSheet(final String name) {
}
public void finishSheet() {
this._propertiesListener.finishSheet();
finishRuleTable();
}
public void newRow(final int rowNumber,
final int columns) {
if (rowNumber > this._ruleStartRow + LABEL_ROW) {
this._isNewDataRow = true;
}
assertValueBuildersRowData();
}
private void assertValueBuildersRowData() {
// ExcelParser skips "null" cells retrieved from Apache POI therefore ensure when a
// new row is created the GuidedDecisionTableSourceBuilders contain sufficient row data
if (_sourceBuilders != null) {
final int rowCount = getRowCount();
final int columnCount = _sourceBuilders.size();
for (GuidedDecisionTableSourceBuilder sb : _sourceBuilders) {
if (sb instanceof GuidedDecisionTableSourceBuilderDirect) {
final GuidedDecisionTableSourceBuilderDirect sbd = (GuidedDecisionTableSourceBuilderDirect) sb;
if (sbd.getRowCount() < rowCount) {
sbd.addCellValue(rowCount,
columnCount,
"");
}
} else if (sb instanceof GuidedDecisionTableSourceBuilderIndirect) {
final GuidedDecisionTableSourceBuilderIndirect sbi = (GuidedDecisionTableSourceBuilderIndirect) sb;
for (ParameterizedValueBuilder pvb : sbi.getValueBuilders().values()) {
if (pvb.getColumnData().size() < rowCount) {
pvb.addCellValue(rowCount,
columnCount,
"");
}
}
}
}
}
}
private int getRowCount() {
//RowNumberBuilder always contains the correct number of actual rules
for (GuidedDecisionTableSourceBuilder sb : _sourceBuilders) {
if (sb instanceof RowNumberBuilder) {
return sb.getRowCount();
}
}
return 0;
}
public void newCell(final int row,
final int column,
final String value,
int mergedColStart) {
//Ignore empty cells past the last ACTION_ROW cell
if (isCellValueEmpty(value) && column > _ruleEndColumn) {
return;
}
if (_isInRuleTable && row == this._ruleStartRow) {
return;
}
if (this._isInRuleTable) {
processRuleCell(row,
column,
value,
mergedColStart);
} else {
processNonRuleCell(row,
column,
value);
}
}
/**
* This gets called each time a "new" rule table is found.
*/
private void initRuleTable(final int row,
final int column,
final String value) {
preInitRuleTable(row,
column,
value);
this._isInRuleTable = true;
this._actions = new HashMap<Integer, ActionType>();
this._ruleStartColumn = column;
this._ruleEndColumn = column;
this._ruleStartRow = row;
this._ruleRow = row + LABEL_ROW + 1;
this._isNewDataRow = false;
// setup stuff for the rules to come.. (the order of these steps are important !)
this._currentSequentialFlag = getSequentialFlag();
this._currentEscapeQuotesFlag = getEscapeQuotesFlag();
this._parameterUtilities = new ParameterUtilities();
//Setup new Decision Table
this._dtable = new GuidedDecisionTable52();
this._dtable.setTableFormat(GuidedDecisionTable52.TableFormat.EXTENDED_ENTRY);
this._dtable.setTableName(RuleSheetParserUtil.getRuleName(value));
this._dtable.setPackageName(_dmo.getPackageName());
this._sourceBuilders = new ArrayList<GuidedDecisionTableSourceBuilder>();
ROW_NUMBER_BUILDER.clearValues();
DEFAULT_DESCRIPTION_BUILDER.clearValues();
this._sourceBuilders.add(ROW_NUMBER_COLUMN_INDEX,
ROW_NUMBER_BUILDER);
this._sourceBuilders.add(DESCRIPTION_COLUMN_INDEX,
DEFAULT_DESCRIPTION_BUILDER);
postInitRuleTable(row,
column,
value);
}
private void finishRuleTable() {
if (this._isInRuleTable) {
assertValueBuildersRowData();
populateDecisionTable();
this._dtables.add(this._dtable);
this._currentSequentialFlag = false;
this._isInRuleTable = false;
this._isNewDataRow = false;
}
}
private void populateDecisionTable() {
final GuidedDecisionTablePopulater populator = new GuidedDecisionTablePopulater(_dtable,
_sourceBuilders,
_conversionResult,
_dmo,
_ruleStartRow + LABEL_ROW + 1,
_ruleStartColumn);
populator.populate();
}
/**
* Called before rule table initialisation. Subclasses may override this
* method to do additional processing.
*/
protected void preInitRuleTable(int row,
int column,
String value) {
}
/**
* Called after rule table initialisation. Subclasses may override this
* method to do additional processing.
*/
protected void postInitRuleTable(int row,
int column,
String value) {
}
private boolean getSequentialFlag() {
final String seqFlag = getProperties().getSingleProperty(DefaultRuleSheetListener.SEQUENTIAL_FLAG,
"false");
return RuleSheetParserUtil.isStringMeaningTrue(seqFlag);
}
private boolean getEscapeQuotesFlag() {
final String escFlag = getProperties().getSingleProperty(DefaultRuleSheetListener.ESCAPE_QUOTES_FLAG,
"true");
return RuleSheetParserUtil.isStringMeaningTrue(escFlag);
}
private void processNonRuleCell(final int row,
final int column,
final String value) {
String testVal = value.trim().toLowerCase();
if (testVal.startsWith(DefaultRuleSheetListener.RULE_TABLE_TAG)) {
initRuleTable(row,
column,
value.trim());
} else {
this._propertiesListener.newCell(row,
column,
value,
RuleSheetListener.NON_MERGED);
}
}
private void processRuleCell(final int row,
final int column,
final String value,
final int mergedColStart) {
String trimVal = value.trim();
String testVal = trimVal.toLowerCase();
if (testVal.startsWith(DefaultRuleSheetListener.RULE_TABLE_TAG)) {
finishRuleTable();
initRuleTable(row,
column,
trimVal);
return;
}
// Row description set according to _ruleStartColumn preceding column
if (column < this._ruleStartColumn) {
if (row - this._ruleStartRow > LABEL_ROW &&
(column + 1) == this._ruleStartColumn &&
row - this._ruleRow < 2) {
DEFAULT_DESCRIPTION_BUILDER.addCellValue(row,
1,
trimVal);
}
return;
}
// Ignore any further cells from the rule def row
if (row == this._ruleStartRow) {
return;
}
switch (row - this._ruleStartRow) {
case ACTION_ROW:
//CONDITION, ACTION, ATTRIBUTE etc...
doActionTypeCell(row,
column,
trimVal);
break;
case OBJECT_TYPE_ROW:
//Pattern definition ("Driver", "Smurf" etc...)
doObjectTypeCell(row,
column,
trimVal,
mergedColStart);
break;
case CODE_ROW:
//Column definition ("age > $1" etc...)
doCodeCell(row,
column,
trimVal);
break;
case LABEL_ROW:
//Decision Table Column header
doLabelCell(row,
column,
trimVal);
break;
default:
if (this._isNewDataRow) {
this._isNewDataRow = false;
ROW_NUMBER_BUILDER.addCellValue(row,
0,
"");
}
doDataCell(row,
column,
trimVal);
break;
}
}
private void doActionTypeCell(final int row,
final int column,
final String trimVal) {
_ruleEndColumn = column;
ActionType.addNewActionType(this._actions,
trimVal,
column,
row);
final ActionType actionType = getActionForColumn(row,
column);
GuidedDecisionTableSourceBuilder sb = null;
switch (actionType.getCode()) {
case CONDITION:
//SourceBuilders for CONDITIONs are set when processing the Object row
case ACTION:
//SourceBuilders for ACTIONs are set when processing the Object row
break;
case NAME:
sb = new GuidedDecisionTableNameBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case DESCRIPTION:
//Remove default Description Column builder and add that provided
this._sourceBuilders.remove(DEFAULT_DESCRIPTION_BUILDER);
sb = new GuidedDecisionTableDescriptionBuilder(row - 1,
column,
this._conversionResult);
//Description column must always be at position 1
this._sourceBuilders.add(DESCRIPTION_COLUMN_INDEX,
sb);
actionType.setSourceBuilder(sb);
break;
case SALIENCE:
sb = new GuidedDecisionTableSalienceBuilder(row - 1,
column,
this._currentSequentialFlag,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case DURATION:
sb = new GuidedDecisionTableDurationBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case TIMER:
sb = new GuidedDecisionTableTimerBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case CALENDARS:
sb = new GuidedDecisionTableCalendarsBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case NOLOOP:
sb = new GuidedDecisionTableNoLoopBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case LOCKONACTIVE:
sb = new GuidedDecisionTableLockonActiveBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case AUTOFOCUS:
sb = new GuidedDecisionTableAutoFocusBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case ACTIVATIONGROUP:
sb = new GuidedDecisionTableActivationGroupBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case AGENDAGROUP:
sb = new GuidedDecisionTableAgendaGroupBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case RULEFLOWGROUP:
sb = new GuidedDecisionTableRuleflowGroupBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case DATEEFFECTIVE:
sb = new GuidedDecisionTableDateEffectiveBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case DATEEXPIRES:
sb = new GuidedDecisionTableDateExpiresBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
case METADATA:
sb = new GuidedDecisionTableMetadataBuilder(row - 1,
column,
this._conversionResult);
actionType.setSourceBuilder(sb);
this._sourceBuilders.add(sb);
break;
}
}
private void doObjectTypeCell(final int row,
final int column,
final String value,
final int mergedColStart) {
if (value.indexOf("$param") > -1 || value.indexOf("$1") > -1) {
final String message = "It looks like you have snippets in the row that is " +
"meant for object declarations." + " Please insert an additional row before the snippets, " +
"at cell " + RuleSheetParserUtil.rc2name(row,
column);
this._conversionResult.addMessage(message,
ConversionMessageType.ERROR);
}
ActionType actionType = getActionForColumn(row,
column);
if (mergedColStart == RuleSheetListener.NON_MERGED) {
if (actionType.getCode() == Code.CONDITION) {
GuidedDecisionTableSourceBuilder sb = new GuidedDecisionTableLHSBuilder(row - 1,
column,
value,
this._parameterUtilities,
this._conversionResult);
this._sourceBuilders.add(sb);
actionType.setSourceBuilder(sb);
} else if (actionType.getCode() == Code.ACTION) {
GuidedDecisionTableSourceBuilder sb = new GuidedDecisionTableRHSBuilder(row - 1,
column,
value,
this._sourceBuilders,
this._parameterUtilities,
this._conversionResult);
this._sourceBuilders.add(sb);
actionType.setSourceBuilder(sb);
}
} else {
if (column == mergedColStart) {
if (actionType.getCode() == Code.CONDITION) {
GuidedDecisionTableSourceBuilder sb = new GuidedDecisionTableLHSBuilder(row - 1,
column,
value,
this._parameterUtilities,
this._conversionResult);
this._sourceBuilders.add(sb);
actionType.setSourceBuilder(sb);
} else if (actionType.getCode() == Code.ACTION) {
GuidedDecisionTableSourceBuilder sb = new GuidedDecisionTableRHSBuilder(row - 1,
column,
value,
this._sourceBuilders,
this._parameterUtilities,
this._conversionResult);
this._sourceBuilders.add(sb);
actionType.setSourceBuilder(sb);
}
} else {
ActionType startOfMergeAction = getActionForColumn(row,
mergedColStart);
actionType.setSourceBuilder(startOfMergeAction.getSourceBuilder());
}
}
}
private void doCodeCell(final int row,
final int column,
final String value) {
final ActionType actionType = getActionForColumn(row,
column);
if (actionType.getSourceBuilder() == null) {
if (actionType.getCode() == Code.CONDITION) {
GuidedDecisionTableSourceBuilder sb = new GuidedDecisionTableLHSBuilder(row - 2,
column,
"",
this._parameterUtilities,
this._conversionResult);
this._sourceBuilders.add(sb);
actionType.setSourceBuilder(sb);
} else if (actionType.getCode() == Code.ACTION) {
GuidedDecisionTableSourceBuilder sb = new GuidedDecisionTableRHSBuilder(row - 2,
column,
"",
this._sourceBuilders,
this._parameterUtilities,
this._conversionResult);
this._sourceBuilders.add(sb);
actionType.setSourceBuilder(sb);
}
}
if (value.trim().equals("") &&
(actionType.getCode() == Code.ACTION ||
actionType.getCode() == Code.CONDITION ||
actionType.getCode() == Code.METADATA)) {
final String message = "Code description in cell " +
RuleSheetParserUtil.rc2name(row,
column) +
" does not contain any code specification. It should!";
this._conversionResult.addMessage(message,
ConversionMessageType.ERROR);
}
actionType.addTemplate(row,
column,
value);
}
private void doLabelCell(final int row,
final int column,
final String value) {
final ActionType actionType = getActionForColumn(row,
column);
SourceBuilder sb = actionType.getSourceBuilder();
if (sb instanceof HasColumnHeadings) {
((HasColumnHeadings) sb).setColumnHeader(column,
value);
}
}
private void doDataCell(final int row,
final int column,
final String value) {
final ActionType actionType = getActionForColumn(row,
column);
if (row - this._ruleRow > 1) {
// Encountered a row gap from the last rule. This is not part of the ruleset.
finishRuleTable();
processNonRuleCell(row,
column,
value);
return;
}
if (row > this._ruleRow) {
// In a new row/rule
this._ruleRow++;
}
//Add data to column definition
actionType.addCellValue(row,
column,
value,
_currentEscapeQuotesFlag);
}
private boolean isCellValueEmpty(final String value) {
return value == null || "".equals(value.trim());
}
private ActionType getActionForColumn(final int row,
final int column) {
final ActionType actionType = this._actions.get(column);
if (actionType == null) {
final String message = "Code description in cell " +
RuleSheetParserUtil.rc2name(row,
column) +
" does not have an 'ACTION' or 'CONDITION' column header.";
this._conversionResult.addMessage(message,
ConversionMessageType.ERROR);
}
return actionType;
}
}