/*
* Copyright 2011 JBoss Inc
*
* 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.guided.dtable.client.wizard.pages;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.drools.workbench.models.guided.dtable.shared.model.ActionCol52;
import org.drools.workbench.models.guided.dtable.shared.model.ActionInsertFactCol52;
import org.drools.workbench.models.guided.dtable.shared.model.ActionSetFieldCol52;
import org.drools.workbench.models.guided.dtable.shared.model.BaseColumn;
import org.drools.workbench.models.guided.dtable.shared.model.ConditionCol52;
import org.drools.workbench.models.guided.dtable.shared.model.DTCellValue52;
import org.drools.workbench.models.guided.dtable.shared.model.GuidedDecisionTable52;
import org.drools.workbench.models.guided.dtable.shared.model.Pattern52;
import org.drools.workbench.screens.guided.dtable.client.widget.table.LimitedEntryDropDownManager;
import org.drools.workbench.screens.guided.dtable.model.GuidedDecisionTableUtils;
import org.drools.workbench.models.commons.shared.oracle.model.DropDownData;
import org.drools.workbench.models.commons.shared.oracle.PackageDataModelOracle;
import org.kie.workbench.common.widgets.client.util.ConstraintValueEditorHelper;
/**
* A utility class to expand Condition column definitions into rows. Action
* columns are not expanded, as the use-case is that a user-determined action
* should be specified for each combination of Conditons. Where a column is
* defined as having multiple values (Guvnor enum, Java enum or Decision Table
* Value List) the number of rows is the Cartesian Product of all combinations.
*/
public class RowExpander {
private Map<BaseColumn, ColumnValues> expandedColumns = new IdentityHashMap<BaseColumn, ColumnValues>();
private List<ColumnValues> columns;
private final GuidedDecisionTable52 model;
private final GuidedDecisionTableUtils modelUtils;
private final PackageDataModelOracle oracle;
private static final List<DTCellValue52> EMPTY_VALUE = new ArrayList<DTCellValue52>();
{
EMPTY_VALUE.add( new DTCellValue52() );
}
/**
* Constructor
* @param model
* @param oracle
*/
public RowExpander( final GuidedDecisionTable52 model,
final PackageDataModelOracle oracle ) {
this.columns = new ArrayList<ColumnValues>();
this.model = model;
this.oracle = oracle;
this.modelUtils = new GuidedDecisionTableUtils( oracle,
model );
//Add all columns to Expander to generate row data. The AnalysisCol is not added
//as its data is transient, not held in the underlying Decision Table's data
addRowNumberColumn();
addRowDescriptionColumn();
addConditionColumns();
addActionColumns();
}
public List<ColumnValues> getColumns() {
return this.columns;
}
private void addRowNumberColumn() {
ColumnValues cv = new RowNumberColumnValues( columns,
EMPTY_VALUE,
new DTCellValue52() );
cv.setExpandColumn( false );
this.expandedColumns.put( model.getRowNumberCol(),
cv );
this.columns.add( cv );
}
private void addRowDescriptionColumn() {
ColumnValues cv = new RowDescriptionColumnValues( columns,
EMPTY_VALUE,
new DTCellValue52() );
cv.setExpandColumn( false );
this.expandedColumns.put( model.getDescriptionCol(),
cv );
this.columns.add( cv );
}
private void addConditionColumns() {
for ( Pattern52 p : model.getPatterns() ) {
addColumn( p );
}
}
private void addActionColumns() {
for ( ActionCol52 a : model.getActionCols() ) {
if ( a instanceof ActionSetFieldCol52 ) {
ActionSetFieldCol52 afc = (ActionSetFieldCol52) a;
addColumn( afc );
} else if ( a instanceof ActionInsertFactCol52 ) {
ActionInsertFactCol52 aif = (ActionInsertFactCol52) a;
addColumn( aif );
}
}
}
private void addColumn( final Pattern52 p ) {
for ( ConditionCol52 c : p.getChildColumns() ) {
addColumn( p,
c );
}
}
private void addColumn( final Pattern52 p,
final ConditionCol52 c ) {
switch ( model.getTableFormat() ) {
case EXTENDED_ENTRY:
addExtendedEntryColumn( p,
c );
break;
case LIMITED_ENTRY:
addLimitedEntryColumn( c );
break;
}
}
private void addExtendedEntryColumn( final Pattern52 p,
final ConditionCol52 c ) {
ColumnValues cv = null;
String[] values = new String[]{ };
if ( modelUtils.hasValueList( c ) ) {
values = modelUtils.getValueList( c );
values = getSplitValues( values );
cv = new ColumnValues( columns,
convertValueList( values ),
c.getDefaultValue() );
} else if ( oracle.hasEnums( p.getFactType(),
c.getFactField() ) ) {
final LimitedEntryDropDownManager.Context context = new LimitedEntryDropDownManager.Context( p,
c );
cv = new ColumnDynamicValues( columns,
oracle,
context,
c.getDefaultValue() );
} else {
cv = new ColumnValues( columns,
convertValueList( values ),
c.getDefaultValue() );
}
if ( cv != null ) {
this.expandedColumns.put( c,
cv );
this.columns.add( cv );
}
}
private void addLimitedEntryColumn( final ConditionCol52 c ) {
final List<DTCellValue52> values = new ArrayList<DTCellValue52>();
values.add( new DTCellValue52( Boolean.TRUE ) );
values.add( new DTCellValue52( Boolean.FALSE ) );
final ColumnValues cv = new ColumnValues( columns,
values,
c.getDefaultValue() );
this.expandedColumns.put( c,
cv );
this.columns.add( cv );
}
private String[] getSplitValues( final String[] values ) {
final String[] splitValues = new String[ values.length ];
for ( int i = 0; i < values.length; i++ ) {
String v = values[ i ];
String[] splut = ConstraintValueEditorHelper.splitValue( v );
splitValues[ i ] = splut[ 0 ];
}
return splitValues;
}
private static List<DTCellValue52> convertValueList( final String[] values ) {
final List<DTCellValue52> convertedValues = new ArrayList<DTCellValue52>();
for ( String value : values ) {
convertedValues.add( new DTCellValue52( value ) );
}
return convertedValues;
}
private void addColumn( final ActionSetFieldCol52 a ) {
final ColumnValues cv = new ColumnValues( columns,
EMPTY_VALUE,
model.getTableFormat() == GuidedDecisionTable52.TableFormat.EXTENDED_ENTRY ? a.getDefaultValue() : new DTCellValue52( Boolean.FALSE ) );
cv.setExpandColumn( false );
this.expandedColumns.put( a,
cv );
this.columns.add( cv );
}
private void addColumn( final ActionInsertFactCol52 a ) {
final ColumnValues cv = new ColumnValues( columns,
EMPTY_VALUE,
model.getTableFormat() == GuidedDecisionTable52.TableFormat.EXTENDED_ENTRY ? a.getDefaultValue() : new DTCellValue52( Boolean.FALSE ) );
cv.setExpandColumn( false );
this.expandedColumns.put( a,
cv );
this.columns.add( cv );
}
/**
* Rather than return a List of rows as the expanded form we expose an
* Iterator with which the expanded form can be retrieved. This decision was
* to avoid potentially hugh transient Lists being created; as the results
* from this class will be transformed into other representations.
* @return
*/
public RowIterator iterator() {
return new RowIterator();
}
/**
* Indicate whether the provided column should be expanded or not. If the
* column was not part of the Decision Table used in the Constructor no
* action is taken.
* @param column
* @param expandColumn
*/
public void setExpandColumn( final BaseColumn column,
final boolean expandColumn ) {
final ColumnValues cv = this.expandedColumns.get( column );
if ( cv == null ) {
return;
}
cv.setExpandColumn( expandColumn );
}
/**
* An iterator that retrieves the expanded rows one at a time
*/
public class RowIterator
implements
Iterator<List<DTCellValue52>> {
//Check if all columns have had their value lists consumed
@Override
public boolean hasNext() {
for ( ColumnValues cv : columns ) {
if ( !cv.isAllValuesUsed() ) {
return true;
}
}
return false;
}
//Build a row from the columns current values and advance the first column. Columns
//check whether all their values have been used and advance the subsequent column
//so a ripple effect can be observed, with one column advancing the next, which
//advances the next and so on...
@Override
public List<DTCellValue52> next() {
//We have a row that is potentially partially populated as the dependent enum data has not been set
//So ask columns to update their value lists based on the current row definition. This will force
//the dependent enumeration value lists to be populated.
boolean refreshRow = false;
List<DTCellValue52> row;
do {
refreshRow = false;
row = new ArrayList<DTCellValue52>();
for ( ColumnValues cv : columns ) {
row.add( cv.getCurrentValue() );
}
for ( ColumnValues cv : columns ) {
if ( cv instanceof ColumnDynamicValues ) {
final ColumnDynamicValues cdv = (ColumnDynamicValues) cv;
refreshRow = refreshRow || cdv.assertValueList( row );
}
}
} while ( refreshRow );
//Advance the first column to the next value
columns.get( columns.size() - 1 ).advanceColumnValue();
return row;
}
@Override
public void remove() {
throw new UnsupportedOperationException( "remove is not supported on RowIterator" );
}
}
/**
* Container for a columns values
*/
static class ColumnValues {
List<DTCellValue52> values;
List<DTCellValue52> originalValues;
List<ColumnValues> columns;
DTCellValue52 value;
DTCellValue52 defaultValue;
Iterator<DTCellValue52> iterator;
boolean expandColumn = true;
boolean isAllValuesUsed = false;
ColumnValues( final List<ColumnValues> columns,
final List<DTCellValue52> values,
final DTCellValue52 defaultValue ) {
this.columns = columns;
this.defaultValue = defaultValue;
this.values = values;
this.originalValues = this.values;
//If no values were provided add the default and record that all values have been used
if ( this.values.size() == 0 ) {
this.values = new ArrayList<DTCellValue52>();
this.values.add( defaultValue );
this.originalValues = this.values;
this.isAllValuesUsed = true;
}
//Initialise value to the first in the list
this.iterator = this.values.iterator();
this.value = iterator.next();
}
void setExpandColumn( final boolean expandColumn ) {
this.expandColumn = expandColumn;
if ( expandColumn ) {
this.values = this.originalValues;
this.isAllValuesUsed = false;
} else {
this.values = new ArrayList<DTCellValue52>();
this.values.add( defaultValue );
this.isAllValuesUsed = true;
}
//Initialise value to the first in the list
this.iterator = this.values.iterator();
this.value = iterator.next();
}
/**
* Get the current value of the column
* @return
*/
DTCellValue52 getCurrentValue() {
return this.value;
}
/**
* Advance to the next value for the column, resetting to the beginning
* of the list if all values have been used. The reset operation also
* advances the next columns value.
* @return
*/
void advanceColumnValue() {
if ( iterator.hasNext() ) {
value = iterator.next();
} else {
isAllValuesUsed = true;
this.iterator = this.values.iterator();
this.value = iterator.next();
int myIndex = columns.indexOf( this );
if ( myIndex > 0 ) {
columns.get( myIndex - 1 ).advanceColumnValue();
}
}
}
/**
* Have all values in the columns list been used
* @return
*/
boolean isAllValuesUsed() {
return this.isAllValuesUsed;
}
}
/**
* Container for a columns values that are dynamically generated
*/
static class ColumnDynamicValues extends ColumnValues {
private final LimitedEntryDropDownManager.Context context;
private final PackageDataModelOracle oracle;
private boolean initialiseValueList = true;
ColumnDynamicValues( final List<ColumnValues> columns,
final PackageDataModelOracle oracle,
final LimitedEntryDropDownManager.Context context,
final DTCellValue52 defaultValue ) {
super( columns,
EMPTY_VALUE,
defaultValue );
this.oracle = oracle;
this.context = context;
//Check if there is an enumeration
final DropDownData dd = oracle.getEnums( context.getBasePattern().getFactType(),
( (ConditionCol52) context.getBaseColumn() ).getFactField(),
new HashMap<String, String>() );
if ( dd != null ) {
this.values = convertValueList( getSplitValues( dd.getFixedList() ) );
this.originalValues = this.values;
this.initialiseValueList = false;
this.isAllValuesUsed = false;
}
//Initialise value to the first in the list
this.iterator = this.values.iterator();
this.value = iterator.next();
}
private String[] getSplitValues( final String[] values ) {
final String[] splitValues = new String[ values.length ];
for ( int i = 0; i < values.length; i++ ) {
final String v = values[ i ];
final String[] splut = ConstraintValueEditorHelper.splitValue( v );
splitValues[ i ] = splut[ 0 ];
}
return splitValues;
}
/**
* Assert that the Value List is correct for data contained in the row
* @param row
* @returns true if the Value List has changed and the row should be
* refreshed
*/
boolean assertValueList( final List<DTCellValue52> row ) {
if ( !this.expandColumn ) {
return false;
}
final boolean refreshRow = this.initialiseValueList;
if ( refreshRow ) {
final Map<String, String> currentValueMap = new HashMap<String, String>();
for ( int iCol = 0; iCol < this.columns.size(); iCol++ ) {
final ColumnValues cv = this.columns.get( iCol );
if ( cv instanceof ColumnDynamicValues ) {
final ColumnDynamicValues cdv = (ColumnDynamicValues) cv;
if ( cdv.context.getBasePattern().equals( this.context.getBasePattern() ) ) {
final ConditionCol52 cc = (ConditionCol52) cdv.context.getBaseColumn();
final DTCellValue52 value = row.get( iCol );
if ( value != null ) {
currentValueMap.put( cc.getFactField(),
value.getStringValue() );
}
}
}
}
this.initialiseValueList = false;
final DropDownData dd = oracle.getEnums( context.getBasePattern().getFactType(),
( (ConditionCol52) context.getBaseColumn() ).getFactField(),
currentValueMap );
if ( dd != null ) {
this.values = convertValueList( getSplitValues( dd.getFixedList() ) );
this.originalValues = this.values;
this.isAllValuesUsed = false;
} else {
this.values = new ArrayList<DTCellValue52>();
this.values.add( defaultValue );
this.originalValues = this.values;
this.isAllValuesUsed = true;
}
//Initialise value to the first in the list
this.iterator = this.values.iterator();
this.value = iterator.next();
}
return refreshRow;
}
/**
* Advance to the next value for the column, resetting to the beginning
* of the list if all values have been used. The reset operation also
* advances the next columns value.
* @return
*/
void advanceColumnValue() {
if ( iterator.hasNext() ) {
value = iterator.next();
} else {
isAllValuesUsed = true;
this.initialiseValueList = true;
this.iterator = values.iterator();
value = iterator.next();
int myIndex = columns.indexOf( this );
if ( myIndex > 0 ) {
columns.get( myIndex - 1 ).advanceColumnValue();
}
}
}
}
/**
* Container for Row Number column values
*/
static class RowNumberColumnValues extends ColumnValues {
RowNumberColumnValues( final List<ColumnValues> columns,
final List<DTCellValue52> values,
final DTCellValue52 defaultValue ) {
super( columns,
values,
defaultValue );
}
@Override
DTCellValue52 getCurrentValue() {
//GUVNOR-1960: Always return a new instance
return new DTCellValue52();
}
}
/**
* Container for Row Description column values
*/
static class RowDescriptionColumnValues extends ColumnValues {
RowDescriptionColumnValues( final List<ColumnValues> columns,
final List<DTCellValue52> values,
final DTCellValue52 defaultValue ) {
super( columns,
values,
defaultValue );
}
@Override
DTCellValue52 getCurrentValue() {
//GUVNOR-1960: Always return a new instance
return new DTCellValue52();
}
}
}