/* * 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.builders; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.drools.decisiontable.parser.ActionType; import org.drools.decisiontable.parser.RuleSheetParserUtil; import org.drools.template.model.SnippetBuilder; import org.drools.template.model.SnippetBuilder.SnippetType; import org.drools.template.parser.DecisionTableParseException; import org.drools.workbench.models.datamodel.oracle.DataType; 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.BRLConditionVariableColumn; import org.drools.workbench.models.guided.dtable.shared.model.BRLVariableColumn; /** * Builder for Condition columns */ public class GuidedDecisionTableLHSBuilder implements HasColumnHeadings, GuidedDecisionTableSourceBuilderIndirect { private final int headerRow; private final int headerCol; //DRL generation parameters private String colDefPrefix; private String colDefSuffix; private boolean hasPattern; private String andop; private List<String> drlFragments = new ArrayList<String>(); //Operators used to detect whether a template contains an operator or implies "==" private static Set<String> operators; static { operators = new HashSet<String>(); operators.add( "==" ); operators.add( "=" ); operators.add( "!=" ); operators.add( "<" ); operators.add( ">" ); operators.add( "<=" ); operators.add( ">=" ); operators.add( "contains" ); operators.add( "matches" ); operators.add( "memberOf" ); operators.add( "str[startsWith]" ); operators.add( "str[endsWith]" ); operators.add( "str[length]" ); } private static final Pattern patParFrm = Pattern.compile( "\\(\\s*\\)\\s*from\\b" ); private static final Pattern patFrm = Pattern.compile( "\\s+from\\s+" ); private static final Pattern patPar = Pattern.compile( "\\(\\s*\\)" ); private static final Pattern patEval = Pattern.compile( "\\beval\\s*(?:\\(\\s*\\)\\s*)?$" ); //Map of column headers, keyed on XLS column index private final Map<Integer, String> columnHeaders = new HashMap<Integer, String>(); //Map of column value parsers, keyed on XLS column index private final Map<Integer, ParameterizedValueBuilder> valueBuilders = new HashMap<Integer, ParameterizedValueBuilder>(); //Utility class to convert XLS parameters to BRLFragment Template keys private final ParameterUtilities parameterUtilities; private ConversionResult conversionResult; public GuidedDecisionTableLHSBuilder( final int row, final int column, final String colDefinition, final ParameterUtilities parameterUtilities, final ConversionResult conversionResult ) { this.headerRow = row; this.headerCol = column; this.parameterUtilities = parameterUtilities; this.conversionResult = conversionResult; preProcessColumnDefinition( colDefinition ); } private void preProcessColumnDefinition( final String colDefinition ) { //Determine DRL generation parameters String colDef = colDefinition == null ? "" : colDefinition; if ( "".equals( colDef ) ) { colDefPrefix = colDefSuffix = ""; hasPattern = false; andop = ""; return; } hasPattern = true; // ...eval final Matcher matEval = patEval.matcher( colDef ); if ( matEval.find() ) { colDefPrefix = colDef.substring( 0, matEval.start() ) + "eval("; colDefSuffix = ")"; andop = " && "; return; } andop = ", "; // ...(<b> ) from... final Matcher matParFrm = patParFrm.matcher( colDef ); if ( matParFrm.find() ) { colDefPrefix = colDef.substring( 0, matParFrm.start() ) + '('; colDefSuffix = ") from" + colDef.substring( matParFrm.end() ); return; } // ...from... final Matcher matFrm = patFrm.matcher( colDef ); if ( matFrm.find() ) { colDefPrefix = colDef.substring( 0, matFrm.start() ) + "("; colDefSuffix = ") from " + colDef.substring( matFrm.end() ); return; } // ...(<b> )... Matcher matPar = patPar.matcher( colDef ); if ( matPar.find() ) { colDefPrefix = colDef.substring( 0, matPar.start() ) + '('; colDefSuffix = ")" + colDef.substring( matPar.end() ); return; } // <a> colDefPrefix = colDef + '('; colDefSuffix = ")"; } @Override public List<BRLVariableColumn> getVariableColumns() { if ( !hasPattern ) { //Add separate columns for each ValueBuilder return addExplicitColumns(); } else { //Add a single column for all ValueBuilders return addPatternColumn(); } } @Override public Map<Integer, ParameterizedValueBuilder> getValueBuilders() { return this.valueBuilders; } //An explicit column does not add constraints to a Pattern. It does not have a value in the OBJECT row private List<BRLVariableColumn> addExplicitColumns() { //Sort column builders by column index to ensure Actions are added in the correct sequence final Set<Integer> sortedIndexes = new TreeSet<Integer>( this.valueBuilders.keySet() ); final List<BRLVariableColumn> variableColumns = new ArrayList<BRLVariableColumn>(); for ( Integer index : sortedIndexes ) { final ParameterizedValueBuilder vb = this.valueBuilders.get( index ); final List<BRLVariableColumn> vbVariableColumns = new ArrayList<BRLVariableColumn>(); if ( vb instanceof LiteralValueBuilder ) { vbVariableColumns.addAll( addLiteralColumn( (LiteralValueBuilder) vb ) ); for ( BRLVariableColumn vbVariableColumn : vbVariableColumns ) { ( (BRLConditionVariableColumn) vbVariableColumn ).setHeader( this.columnHeaders.get( index ) ); } } else { vbVariableColumns.addAll( addBRLFragmentColumn( vb ) ); for ( BRLVariableColumn vbVariableColumn : vbVariableColumns ) { ( (BRLConditionVariableColumn) vbVariableColumn ).setHeader( this.columnHeaders.get( index ) ); } } variableColumns.addAll( vbVariableColumns ); } return variableColumns; } private List<BRLVariableColumn> addLiteralColumn( final LiteralValueBuilder vb ) { final List<BRLVariableColumn> variableColumns = new ArrayList<BRLVariableColumn>(); final BRLConditionVariableColumn parameterColumn = new BRLConditionVariableColumn( "", DataType.TYPE_BOOLEAN ); variableColumns.add( parameterColumn ); //Store DRL fragment for use by GuidedDecisionTableRHSBuilder drlFragments.add( vb.getTemplate() ); return variableColumns; } private List<BRLVariableColumn> addBRLFragmentColumn( final ParameterizedValueBuilder vb ) { final List<BRLVariableColumn> variableColumns = new ArrayList<BRLVariableColumn>(); for ( String parameter : vb.getParameters() ) { final BRLConditionVariableColumn parameterColumn = new BRLConditionVariableColumn( parameter, DataType.TYPE_OBJECT ); variableColumns.add( parameterColumn ); } //Store DRL fragment for use by GuidedDecisionTableRHSBuilder drlFragments.add( vb.getTemplate() ); return variableColumns; } //A Pattern column adds constraints to a Pattern. It has a value in the OBJECT row private List<BRLVariableColumn> addPatternColumn() { //Sort column builders by column index to ensure columns are added in the correct sequence final TreeSet<Integer> sortedIndexes = new TreeSet<Integer>( this.valueBuilders.keySet() ); final List<BRLVariableColumn> variableColumns = new ArrayList<BRLVariableColumn>(); //DRL prefix final StringBuffer drl = new StringBuffer(); drl.append( this.colDefPrefix ); String sep = ""; //DRL fragment for ( Integer index : sortedIndexes ) { final ParameterizedValueBuilder vb = this.valueBuilders.get( index ); for ( String parameter : vb.getParameters() ) { final BRLConditionVariableColumn parameterColumn = new BRLConditionVariableColumn( parameter, DataType.TYPE_OBJECT ); parameterColumn.setHeader( this.columnHeaders.get( index ) ); variableColumns.add( parameterColumn ); } drl.append( sep ).append( vb.getTemplate() ); sep = this.andop; } //DRL suffix drl.append( this.colDefSuffix ); //Store DRL fragment for use by GuidedDecisionTableRHSBuilder drlFragments.add( drl.toString() ); return variableColumns; } @Override public void addTemplate( final int row, final int column, final String content ) { //Validate column template if ( valueBuilders.containsKey( column ) ) { final String message = "Internal error: Can't have a code snippet added twice to one spreadsheet column."; this.conversionResult.addMessage( message, ConversionMessageType.ERROR ); return; } //Add new template final String template = content.trim(); try { this.valueBuilders.put( column, getValueBuilder( template ) ); } catch ( DecisionTableParseException pe ) { this.conversionResult.addMessage( pe.getMessage(), ConversionMessageType.WARNING ); } } @Override public void setColumnHeader( final int column, final String value ) { this.columnHeaders.put( column, value.trim() ); } private ParameterizedValueBuilder getValueBuilder( final String content ) { // Work out the type of "template":- // age ---> SnippetType.SINGLE // age == ---> SnippetType.SINGLE // age == $param ---> SnippetType.PARAM // age == $1 || age == $2 ---> SnippetType.INDEXED // forall{age < $}{,} ---> SnippetType.FORALL String template = content.trim(); SnippetType type = SnippetBuilder.getType( template ); if ( type == SnippetType.SINGLE ) { type = SnippetType.PARAM; boolean hasExplicitOperator = false; for ( String op : operators ) { if ( template.endsWith( op ) ) { hasExplicitOperator = true; break; } } if ( !hasExplicitOperator ) { template = template + " =="; } template = template + " \""; template = template + SnippetBuilder.PARAM_STRING + "\""; } //Make a ValueBuilder for the template switch ( type ) { case INDEXED: return new IndexedParametersValueBuilder( template, parameterUtilities, ParameterizedValueBuilder.Part.LHS ); case PARAM: return new SingleParameterValueBuilder( template, parameterUtilities, ParameterizedValueBuilder.Part.LHS ); case SINGLE: return new LiteralValueBuilder( template ); } throw new DecisionTableParseException( "SnippetBuilder.SnippetType '" + type.toString() + "' is not supported. The column will not be added." ); } @Override public void addCellValue( final int row, final int column, final String value ) { //Add new row to column data final ParameterizedValueBuilder vb = this.valueBuilders.get( column ); if ( vb == null ) { final String message = "No code snippet for CONDITION, above cell " + RuleSheetParserUtil.rc2name( this.headerRow + 2, this.headerCol ); this.conversionResult.addMessage( message, ConversionMessageType.ERROR ); return; } vb.addCellValue( row, column, value ); } @Override public ActionType.Code getActionTypeCode() { return ActionType.Code.CONDITION; } @Override public String getResult() { final StringBuilder sb = new StringBuilder(); for ( String drlFragment : drlFragments ) { sb.append( drlFragment ).append( "\n" ); } return sb.toString(); } @Override public void clearValues() { throw new UnsupportedOperationException(); } @Override public boolean hasValues() { throw new UnsupportedOperationException(); } @Override public int getRowCount() { int maxRowCount = 0; for ( ParameterizedValueBuilder pvb : valueBuilders.values() ) { maxRowCount = Math.max( maxRowCount, pvb.getColumnData().size() ); } return maxRowCount; } }