Package org.drools.workbench.models.commons.backend.rule

Source Code of org.drools.workbench.models.commons.backend.rule.RuleModelDRLPersistenceImpl$RHSActionVisitor

/*
* Copyright 2012 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.models.commons.backend.rule;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.drools.compiler.compiler.DrlParser;
import org.drools.compiler.compiler.DroolsParserException;
import org.drools.compiler.lang.descr.AccumulateDescr;
import org.drools.compiler.lang.descr.AndDescr;
import org.drools.compiler.lang.descr.AnnotationDescr;
import org.drools.compiler.lang.descr.AttributeDescr;
import org.drools.compiler.lang.descr.BaseDescr;
import org.drools.compiler.lang.descr.BehaviorDescr;
import org.drools.compiler.lang.descr.CollectDescr;
import org.drools.compiler.lang.descr.ConditionalElementDescr;
import org.drools.compiler.lang.descr.EntryPointDescr;
import org.drools.compiler.lang.descr.EvalDescr;
import org.drools.compiler.lang.descr.ExprConstraintDescr;
import org.drools.compiler.lang.descr.FromDescr;
import org.drools.compiler.lang.descr.GlobalDescr;
import org.drools.compiler.lang.descr.NotDescr;
import org.drools.compiler.lang.descr.OrDescr;
import org.drools.compiler.lang.descr.PackageDescr;
import org.drools.compiler.lang.descr.PatternDescr;
import org.drools.compiler.lang.descr.PatternSourceDescr;
import org.drools.compiler.lang.descr.RuleDescr;
import org.drools.core.base.evaluators.EvaluatorRegistry;
import org.drools.core.base.evaluators.Operator;
import org.drools.core.util.ReflectiveVisitor;
import org.drools.workbench.models.commons.backend.imports.ImportsParser;
import org.drools.workbench.models.commons.backend.imports.ImportsWriter;
import org.drools.workbench.models.commons.backend.packages.PackageNameParser;
import org.drools.workbench.models.commons.backend.packages.PackageNameWriter;
import org.drools.workbench.models.commons.backend.rule.context.LHSGeneratorContext;
import org.drools.workbench.models.commons.backend.rule.context.LHSGeneratorContextFactory;
import org.drools.workbench.models.commons.backend.rule.context.RHSGeneratorContext;
import org.drools.workbench.models.commons.backend.rule.context.RHSGeneratorContextFactory;
import org.drools.workbench.models.datamodel.imports.Import;
import org.drools.workbench.models.datamodel.imports.Imports;
import org.drools.workbench.models.datamodel.oracle.DataType;
import org.drools.workbench.models.datamodel.oracle.MethodInfo;
import org.drools.workbench.models.datamodel.oracle.ModelField;
import org.drools.workbench.models.datamodel.oracle.OperatorsOracle;
import org.drools.workbench.models.datamodel.oracle.PackageDataModelOracle;
import org.drools.workbench.models.datamodel.rule.ActionCallMethod;
import org.drools.workbench.models.datamodel.rule.ActionExecuteWorkItem;
import org.drools.workbench.models.datamodel.rule.ActionFieldFunction;
import org.drools.workbench.models.datamodel.rule.ActionFieldList;
import org.drools.workbench.models.datamodel.rule.ActionFieldValue;
import org.drools.workbench.models.datamodel.rule.ActionGlobalCollectionAdd;
import org.drools.workbench.models.datamodel.rule.ActionInsertFact;
import org.drools.workbench.models.datamodel.rule.ActionInsertLogicalFact;
import org.drools.workbench.models.datamodel.rule.ActionRetractFact;
import org.drools.workbench.models.datamodel.rule.ActionSetField;
import org.drools.workbench.models.datamodel.rule.ActionUpdateField;
import org.drools.workbench.models.datamodel.rule.ActionWorkItemFieldValue;
import org.drools.workbench.models.datamodel.rule.BaseSingleFieldConstraint;
import org.drools.workbench.models.datamodel.rule.CEPWindow;
import org.drools.workbench.models.datamodel.rule.CompositeFactPattern;
import org.drools.workbench.models.datamodel.rule.CompositeFieldConstraint;
import org.drools.workbench.models.datamodel.rule.ConnectiveConstraint;
import org.drools.workbench.models.datamodel.rule.DSLSentence;
import org.drools.workbench.models.datamodel.rule.ExpressionCollection;
import org.drools.workbench.models.datamodel.rule.ExpressionField;
import org.drools.workbench.models.datamodel.rule.ExpressionFormLine;
import org.drools.workbench.models.datamodel.rule.ExpressionMethod;
import org.drools.workbench.models.datamodel.rule.ExpressionMethodParameter;
import org.drools.workbench.models.datamodel.rule.ExpressionPart;
import org.drools.workbench.models.datamodel.rule.ExpressionText;
import org.drools.workbench.models.datamodel.rule.ExpressionUnboundFact;
import org.drools.workbench.models.datamodel.rule.ExpressionVariable;
import org.drools.workbench.models.datamodel.rule.FactPattern;
import org.drools.workbench.models.datamodel.rule.FieldConstraint;
import org.drools.workbench.models.datamodel.rule.FieldNature;
import org.drools.workbench.models.datamodel.rule.FieldNatureType;
import org.drools.workbench.models.datamodel.rule.FreeFormLine;
import org.drools.workbench.models.datamodel.rule.FromAccumulateCompositeFactPattern;
import org.drools.workbench.models.datamodel.rule.FromCollectCompositeFactPattern;
import org.drools.workbench.models.datamodel.rule.FromCompositeFactPattern;
import org.drools.workbench.models.datamodel.rule.FromEntryPointFactPattern;
import org.drools.workbench.models.datamodel.rule.IAction;
import org.drools.workbench.models.datamodel.rule.IFactPattern;
import org.drools.workbench.models.datamodel.rule.IPattern;
import org.drools.workbench.models.datamodel.rule.RuleAttribute;
import org.drools.workbench.models.datamodel.rule.RuleMetadata;
import org.drools.workbench.models.datamodel.rule.RuleModel;
import org.drools.workbench.models.datamodel.rule.SingleFieldConstraint;
import org.drools.workbench.models.datamodel.rule.SingleFieldConstraintEBLeftSide;
import org.drools.workbench.models.datamodel.rule.builder.DRLConstraintValueBuilder;
import org.drools.workbench.models.datamodel.rule.visitors.ToStringExpressionVisitor;
import org.drools.workbench.models.datamodel.workitems.HasBinding;
import org.drools.workbench.models.datamodel.workitems.PortableBooleanParameterDefinition;
import org.drools.workbench.models.datamodel.workitems.PortableFloatParameterDefinition;
import org.drools.workbench.models.datamodel.workitems.PortableIntegerParameterDefinition;
import org.drools.workbench.models.datamodel.workitems.PortableObjectParameterDefinition;
import org.drools.workbench.models.datamodel.workitems.PortableParameterDefinition;
import org.drools.workbench.models.datamodel.workitems.PortableStringParameterDefinition;
import org.drools.workbench.models.datamodel.workitems.PortableWorkDefinition;

import static org.drools.core.util.StringUtils.*;
import static org.drools.workbench.models.commons.backend.rule.RuleModelPersistenceHelper.*;

/**
* This class persists the rule model to DRL and back
*/
public class RuleModelDRLPersistenceImpl
        implements
        RuleModelPersistence {

    private static final String WORKITEM_PREFIX = "wi";

    private static final RuleModelPersistence INSTANCE = new RuleModelDRLPersistenceImpl();

    //This is the default dialect for rules not specifying one explicitly
    protected DRLConstraintValueBuilder constraintValueBuilder = DRLConstraintValueBuilder.getBuilder( DRLConstraintValueBuilder.DEFAULT_DIALECT );

    //Keep a record of all variable bindings for Actions that depend on them
    protected Map<String, IFactPattern> bindingsPatterns;
    protected Map<String, FieldConstraint> bindingsFields;

    protected RuleModelDRLPersistenceImpl() {
        // register custom evaluators
        new EvaluatorRegistry( getClass().getClassLoader() );
    }

    public static RuleModelPersistence getInstance() {
        return INSTANCE;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.drools.ide.common.server.util.RuleModelPersistence#marshal(org.drools.guvnor
     * .client.modeldriven.brl.RuleModel)
     */
    public String marshal( final RuleModel model ) {
        return marshalRule( model );
    }

    protected String marshalRule( final RuleModel model ) {
        boolean isDSLEnhanced = model.hasDSLSentences();
        bindingsPatterns = new HashMap<String, IFactPattern>();
        bindingsFields = new HashMap<String, FieldConstraint>();

        fixActionInsertFactBindings( model.rhs );

        StringBuilder buf = new StringBuilder();

        //Build rule
        this.marshalPackageHeader( model,
                                   buf );
        this.marshalRuleHeader( model,
                                buf );
        this.marshalMetadata( buf,
                              model );
        this.marshalAttributes( buf,
                                model );

        buf.append( "\twhen\n" );
        this.marshalLHS( buf,
                         model,
                         isDSLEnhanced,
                         new LHSGeneratorContextFactory() );
        buf.append( "\tthen\n" );
        this.marshalRHS( buf,
                         model,
                         isDSLEnhanced,
                         new RHSGeneratorContextFactory() );
        this.marshalFooter( buf );
        return buf.toString();
    }

    protected void fixActionInsertFactBindings( final IAction[] rhs ) {
        final Set<String> existingBindings = extractExistingActionBindings( rhs );
        for ( IAction action : rhs ) {
            if ( action instanceof ActionInsertFact ) {
                final ActionInsertFact aif = (ActionInsertFact) action;
                if ( aif.getFieldValues().length > 0 && aif.getBoundName() == null ) {
                    int idx = 0;
                    String binding = "fact" + idx;
                    while ( existingBindings.contains( binding ) ) {
                        idx++;
                        binding = "fact" + idx;
                    }
                    existingBindings.add( binding );
                    aif.setBoundName( binding );
                }
            }
        }
    }

    private Set<String> extractExistingActionBindings( final IAction[] rhs ) {
        final Set<String> bindings = new HashSet<String>();
        for ( IAction action : rhs ) {
            if ( action instanceof ActionInsertFact ) {
                final ActionInsertFact aif = (ActionInsertFact) action;
                if ( aif.getBoundName() != null ) {
                    bindings.add( aif.getBoundName() );
                }
            }
        }
        return bindings;
    }

    protected void marshalFooter( final StringBuilder buf ) {
        buf.append( "end\n" );
    }

    //Append package name and imports to DRL
    protected void marshalPackageHeader( final RuleModel model,
                                         final StringBuilder buf ) {
        PackageNameWriter.write( buf,
                                 model );
        ImportsWriter.write( buf,
                             model );
    }

    //Append rule header
    protected void marshalRuleHeader( final RuleModel model,
                                      final StringBuilder buf ) {
        buf.append( "rule \"" + marshalRuleName( model ) + "\"" );
        if ( null != model.parentName && model.parentName.length() > 0 ) {
            buf.append( " extends \"" + model.parentName + "\"\n" );
        } else {
            buf.append( '\n' );
        }
    }

    protected String marshalRuleName( final RuleModel model ) {
        return model.name;
    }

    /**
     * Marshal model attributes
     * @param buf
     * @param model
     */
    protected void marshalAttributes( final StringBuilder buf,
                                      final RuleModel model ) {
        boolean hasDialect = false;
        for ( int i = 0; i < model.attributes.length; i++ ) {
            RuleAttribute attr = model.attributes[ i ];

            buf.append( "\t" );
            buf.append( attr );

            buf.append( "\n" );
            if ( attr.getAttributeName().equals( "dialect" ) ) {
                constraintValueBuilder = DRLConstraintValueBuilder.getBuilder( attr.getValue() );
                hasDialect = true;
            }
        }
        // Un comment below for mvel
        if ( !hasDialect ) {
            RuleAttribute attr = new RuleAttribute( "dialect",
                                                    DRLConstraintValueBuilder.DEFAULT_DIALECT );
            buf.append( "\t" );
            buf.append( attr );
            buf.append( "\n" );
        }
    }

    /**
     * Marshal model metadata
     * @param buf
     * @param model
     */
    protected void marshalMetadata( final StringBuilder buf,
                                    final RuleModel model ) {
        if ( model.metadataList != null ) {
            for ( int i = 0; i < model.metadataList.length; i++ ) {
                buf.append( "\t" ).append( model.metadataList[ i ] ).append( "\n" );
            }
        }
    }

    /**
     * Marshal LHS patterns
     * @param buf
     * @param model
     */
    protected void marshalLHS( final StringBuilder buf,
                               final RuleModel model,
                               final boolean isDSLEnhanced,
                               final LHSGeneratorContextFactory generatorContextFactory ) {
        String indentation = "\t\t";
        String nestedIndentation = indentation;
        boolean isNegated = model.isNegated();

        if ( model.lhs != null ) {
            if ( isNegated ) {
                nestedIndentation += "\t";
                buf.append( indentation );
                buf.append( "not (\n" );
            }
            LHSPatternVisitor visitor = getLHSPatternVisitor( isDSLEnhanced,
                                                              buf,
                                                              nestedIndentation,
                                                              isNegated,
                                                              generatorContextFactory );
            for ( IPattern cond : model.lhs ) {
                visitor.visit( cond );
            }
            if ( model.isNegated() ) {
                //Delete the spurious " and ", added by LHSPatternVisitor.visitFactPattern, when the rule is negated
                buf.delete( buf.length() - 5,
                            buf.length() );
                buf.append( "\n" );
                buf.append( indentation );
                buf.append( ")\n" );
            }
        }
    }

    protected LHSPatternVisitor getLHSPatternVisitor( final boolean isDSLEnhanced,
                                                      final StringBuilder buf,
                                                      final String nestedIndentation,
                                                      final boolean isNegated,
                                                      final LHSGeneratorContextFactory generatorContextFactory ) {
        return new LHSPatternVisitor( isDSLEnhanced,
                                      bindingsPatterns,
                                      bindingsFields,
                                      constraintValueBuilder,
                                      generatorContextFactory,
                                      buf,
                                      nestedIndentation,
                                      isNegated );
    }

    protected void marshalRHS( final StringBuilder buf,
                               final RuleModel model,
                               final boolean isDSLEnhanced,
                               final RHSGeneratorContextFactory generatorContextFactory ) {
        String indentation = "\t\t";
        if ( model.rhs != null ) {

            //Add boiler-plate for actions operating on Dates
            Map<String, List<ActionFieldValue>> classes = getRHSClassDependencies( model );
            if ( classes.containsKey( DataType.TYPE_DATE ) ) {
                buf.append( indentation );
                buf.append( "java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat(\"" + System.getProperty( "drools.dateformat" ) + "\");\n" );
            }

            //Add boiler-plate for actions operating on WorkItems
            if ( !getRHSWorkItemDependencies( model ).isEmpty() ) {
                buf.append( indentation );
                buf.append( "org.drools.core.process.instance.WorkItemManager wim = (org.drools.core.process.instance.WorkItemManager) drools.getWorkingMemory().getWorkItemManager();\n" );
            }

            //Marshall the model itself
            RHSActionVisitor actionVisitor = getRHSActionVisitor( isDSLEnhanced,
                                                                  buf,
                                                                  indentation,
                                                                  generatorContextFactory );

            //Reconcile ActionSetField and ActionUpdateField calls
            final List<IAction> actions = new ArrayList<IAction>();
            for ( IAction action : model.rhs ) {
                if ( action instanceof ActionSetField ) {
                    final ActionSetField asf = (ActionSetField) action;
                    final ActionSetFieldWrapper afw = findExistingAction( asf,
                                                                          actions );
                    if ( afw == null ) {
                        actions.add( new ActionSetFieldWrapper( asf,
                                                                ( asf instanceof ActionUpdateField ) ) );
                    } else {
                        final List<ActionFieldValue> existingActionFieldValue = new ArrayList<ActionFieldValue>( Arrays.asList( afw.getAction().getFieldValues() ) );
                        for ( ActionFieldValue afv : asf.getFieldValues() ) {
                            existingActionFieldValue.add( afv );
                        }
                        final ActionFieldValue[] temp = new ActionFieldValue[ existingActionFieldValue.size() ];
                        afw.getAction().setFieldValues( existingActionFieldValue.toArray( temp ) );
                    }

                } else {
                    actions.add( action );
                }
            }
            model.rhs = new IAction[ actions.size() ];
            for ( int i = 0; i < actions.size(); i++ ) {
                final IAction action = actions.get( i );
                if ( action instanceof ActionSetFieldWrapper ) {
                    model.rhs[ i ] = ( (ActionSetFieldWrapper) action ).getAction();
                } else {
                    model.rhs[ i ] = action;
                }
            }

            //Now generate DRL
            for ( IAction action : model.rhs ) {
                actionVisitor.visit( action );
            }
        }
    }

    private ActionSetFieldWrapper findExistingAction( final ActionSetField asf,
                                                      final List<IAction> actions ) {
        for ( IAction action : actions ) {
            if ( action instanceof ActionSetFieldWrapper ) {
                final ActionSetFieldWrapper afw = (ActionSetFieldWrapper) action;
                if ( asf.getVariable().equals( afw.getAction().getVariable() ) && ( asf instanceof ActionUpdateField ) == afw.isUpdate() ) {
                    return afw;
                }
            }
        }
        return null;
    }

    private static class ActionSetFieldWrapper implements IAction {

        private final ActionSetField action;
        private final boolean isUpdate;

        private ActionSetFieldWrapper( final ActionSetField action,
                                       final boolean isUpdate ) {
            this.action = clone( action );
            this.isUpdate = isUpdate;
        }

        private ActionSetField getAction() {
            return action;
        }

        private boolean isUpdate() {
            return isUpdate;
        }

        private ActionSetField clone( final ActionSetField action ) {
            if ( action instanceof ActionUpdateField ) {
                final ActionUpdateField auf = (ActionUpdateField) action;
                final ActionUpdateField clone = new ActionUpdateField( auf.getVariable() );
                clone.setFieldValues( auf.getFieldValues() );
                return clone;

            } else if ( action instanceof ActionCallMethod ) {
                final ActionCallMethod acm = (ActionCallMethod) action;
                final ActionCallMethod clone = new ActionCallMethod( acm.getVariable() );
                clone.setState( acm.getState() );
                clone.setMethodName( acm.getMethodName() );
                clone.setFieldValues( acm.getFieldValues() );
                return clone;

            } else if ( action instanceof ActionSetField ) {
                final ActionSetField clone = new ActionSetField( action.getVariable() );
                clone.setFieldValues( action.getFieldValues() );
                return clone;

            } else {
                return action;
            }
        }

    }

    protected RHSActionVisitor getRHSActionVisitor( final boolean isDSLEnhanced,
                                                    final StringBuilder buf,
                                                    final String indentation,
                                                    final RHSGeneratorContextFactory generatorContextFactory ) {
        return new RHSActionVisitor( isDSLEnhanced,
                                     bindingsPatterns,
                                     bindingsFields,
                                     constraintValueBuilder,
                                     generatorContextFactory,
                                     buf,
                                     indentation );
    }

    private Map<String, List<ActionFieldValue>> getRHSClassDependencies( final RuleModel model ) {
        if ( model != null ) {
            RHSClassDependencyVisitor dependencyVisitor = new RHSClassDependencyVisitor();
            for ( IAction action : model.rhs ) {
                dependencyVisitor.visit( action );
            }
            return dependencyVisitor.getRHSClasses();
        }

        Map<String, List<ActionFieldValue>> empty = Collections.emptyMap();
        return empty;
    }

    private List<PortableWorkDefinition> getRHSWorkItemDependencies( final RuleModel model ) {
        if ( model != null ) {
            List<PortableWorkDefinition> workItems = new ArrayList<PortableWorkDefinition>();
            for ( IAction action : model.rhs ) {
                if ( action instanceof ActionExecuteWorkItem ) {
                    workItems.add( ( (ActionExecuteWorkItem) action ).getWorkDefinition() );
                }
            }
            return workItems;
        }

        List<PortableWorkDefinition> empty = Collections.emptyList();
        return empty;
    }

    public static class LHSPatternVisitor extends ReflectiveVisitor {

        protected StringBuilder buf;
        private boolean isDSLEnhanced;
        private boolean isPatternNegated;
        private String indentation;
        private Map<String, IFactPattern> bindingsPatterns;
        private Map<String, FieldConstraint> bindingsFields;
        protected DRLConstraintValueBuilder constraintValueBuilder;
        protected LHSGeneratorContextFactory generatorContextFactory;

        protected final LHSGeneratorContext rootContext;

        public LHSPatternVisitor( final boolean isDSLEnhanced,
                                  final Map<String, IFactPattern> bindingsPatterns,
                                  final Map<String, FieldConstraint> bindingsFields,
                                  final DRLConstraintValueBuilder constraintValueBuilder,
                                  final LHSGeneratorContextFactory generatorContextFactory,
                                  final StringBuilder b,
                                  final String indentation,
                                  final boolean isPatternNegated ) {
            this.isDSLEnhanced = isDSLEnhanced;
            this.bindingsPatterns = bindingsPatterns;
            this.bindingsFields = bindingsFields;
            this.constraintValueBuilder = constraintValueBuilder;
            this.generatorContextFactory = generatorContextFactory;
            this.rootContext = generatorContextFactory.newGeneratorContext();
            this.indentation = indentation;
            this.isPatternNegated = isPatternNegated;
            this.buf = b;
        }

        protected void preGeneratePattern( final LHSGeneratorContext gctx ) {
            // empty, overridden by rule templates
        }

        protected void postGeneratePattern( final LHSGeneratorContext gctx ) {
            // empty, overridden by rule templates
        }

        protected void preGenerateNestedConnector( final LHSGeneratorContext gctx ) {
            // empty, overridden by rule templates
        }

        protected void postGenerateNestedConnector( final LHSGeneratorContext gctx ) {
            // empty, overridden by rule templates
        }

        protected void preGenerateNestedConstraint( final LHSGeneratorContext gctx ) {
            // empty, overridden by rule templates
        }

        protected void postGenerateNestedConstraint( final LHSGeneratorContext gctx ) {
            // empty, overridden by rule templates
        }

        public void visitFactPattern( final FactPattern pattern ) {
            buf.append( indentation );
            if ( isDSLEnhanced ) {
                // adding passthrough markup
                buf.append( ">" );
            }

            final LHSGeneratorContext gctx = generatorContextFactory.newChildGeneratorContext( rootContext,
                                                                                               pattern );
            preGeneratePattern( gctx );

            generateFactPattern( pattern,
                                 gctx );
            if ( isPatternNegated ) {
                buf.append( " and " );
            }

            postGeneratePattern( gctx );
            buf.append( "\n" );
        }

        public void visitFreeFormLine( final FreeFormLine ffl ) {
            if ( ffl.getText() == null ) {
                return;
            }
            String[] lines = ffl.getText().split( "\\n|\\r\\n" );
            for ( String line : lines ) {
                this.buf.append( indentation );
                if ( isDSLEnhanced ) {
                    buf.append( ">" );
                }
                this.buf.append( line + "\n" );
            }
        }

        public void visitCompositeFactPattern( final CompositeFactPattern pattern ) {
            buf.append( indentation );
            if ( isDSLEnhanced ) {
                // adding passthrough markup
                buf.append( ">" );
            }
            if ( CompositeFactPattern.COMPOSITE_TYPE_EXISTS.equals( pattern.getType() ) ) {
                renderCompositeFOL( pattern );
            } else if ( CompositeFactPattern.COMPOSITE_TYPE_NOT.equals( pattern.getType() ) ) {
                renderCompositeFOL( pattern );
            } else if ( CompositeFactPattern.COMPOSITE_TYPE_OR.equals( pattern.getType() ) ) {
                buf.append( "( " );
                if ( pattern.getPatterns() != null ) {
                    for ( int i = 0; i < pattern.getPatterns().length; i++ ) {
                        if ( i > 0 ) {
                            buf.append( " " );
                            buf.append( pattern.getType() );
                            buf.append( " " );
                        }
                        renderSubPattern( pattern,
                                          i );
                    }
                }
                buf.append( " )\n" );
            }
        }

        public void visitFromCompositeFactPattern( final FromCompositeFactPattern pattern ) {
            visitFromCompositeFactPattern( pattern,
                                           false );
        }

        public void visitFromCompositeFactPattern( final FromCompositeFactPattern pattern,
                                                   final boolean isSubPattern ) {
            buf.append( indentation );
            if ( !isSubPattern && isDSLEnhanced ) {
                // adding passthrough markup
                buf.append( ">" );
            }

            if ( pattern.getFactPattern() != null ) {
                final LHSGeneratorContext gctx = generatorContextFactory.newChildGeneratorContext( rootContext,
                                                                                                   pattern.getFactPattern() );
                generateFactPattern( pattern.getFactPattern(),
                                     gctx );

                buf.append( " from " );
                renderExpression( pattern.getExpression() );
                buf.append( "\n" );
            }
        }

        public void visitFromCollectCompositeFactPattern( final FromCollectCompositeFactPattern pattern ) {
            visitFromCollectCompositeFactPattern( pattern,
                                                  false );
        }

        public void visitFromCollectCompositeFactPattern( final FromCollectCompositeFactPattern pattern,
                                                          final boolean isSubPattern ) {
            buf.append( indentation );
            if ( !isSubPattern && isDSLEnhanced ) {
                // adding passthrough markup
                buf.append( ">" );
            }

            if ( pattern.getFactPattern() != null ) {
                final LHSGeneratorContext gctx = generatorContextFactory.newChildGeneratorContext( rootContext,
                                                                                                   pattern.getFactPattern() );
                generateFactPattern( pattern.getFactPattern(),
                                     gctx );

                buf.append( " from collect ( " );
                if ( pattern.getRightPattern() != null ) {
                    if ( pattern.getRightPattern() instanceof FactPattern ) {
                        generateFactPattern( (FactPattern) pattern.getRightPattern(),
                                             generatorContextFactory.newGeneratorContext() );
                    } else if ( pattern.getRightPattern() instanceof FromAccumulateCompositeFactPattern ) {
                        visitFromAccumulateCompositeFactPattern( (FromAccumulateCompositeFactPattern) pattern.getRightPattern(),
                                                                 isSubPattern );
                    } else if ( pattern.getRightPattern() instanceof FromCollectCompositeFactPattern ) {
                        visitFromCollectCompositeFactPattern( (FromCollectCompositeFactPattern) pattern.getRightPattern(),
                                                              isSubPattern );
                    } else if ( pattern.getRightPattern() instanceof FromEntryPointFactPattern ) {
                        visitFromEntryPointFactPattern( (FromEntryPointFactPattern) pattern.getRightPattern(),
                                                        isSubPattern );
                    } else if ( pattern.getRightPattern() instanceof FromCompositeFactPattern ) {
                        visitFromCompositeFactPattern( (FromCompositeFactPattern) pattern.getRightPattern(),
                                                       isSubPattern );
                    } else if ( pattern.getRightPattern() instanceof FreeFormLine ) {
                        visitFreeFormLine( (FreeFormLine) pattern.getRightPattern() );
                    } else {
                        throw new IllegalArgumentException( "Unsupported pattern " + pattern.getRightPattern() + " for FROM COLLECT" );
                    }
                }
                buf.append( ") \n" );
            }
        }

        public void visitFromAccumulateCompositeFactPattern( final FromAccumulateCompositeFactPattern pattern ) {
            visitFromAccumulateCompositeFactPattern( pattern,
                                                     false );
        }

        public void visitFromAccumulateCompositeFactPattern( final FromAccumulateCompositeFactPattern pattern,
                                                             final boolean isSubPattern ) {
            buf.append( indentation );
            if ( !isSubPattern && isDSLEnhanced ) {
                // adding passthrough markup
                buf.append( ">" );
            }

            if ( pattern.getFactPattern() != null ) {
                final LHSGeneratorContext gctx = generatorContextFactory.newChildGeneratorContext( rootContext,
                                                                                                   pattern.getFactPattern() );
                generateFactPattern( pattern.getFactPattern(),
                                     gctx );

                buf.append( " from accumulate ( " );
                if ( pattern.getSourcePattern() != null ) {
                    if ( pattern.getSourcePattern() instanceof FactPattern ) {
                        final LHSGeneratorContext soucrceGctx = generatorContextFactory.newGeneratorContext();
                        generateFactPattern( (FactPattern) pattern.getSourcePattern(),
                                             soucrceGctx );

                    } else if ( pattern.getSourcePattern() instanceof FromAccumulateCompositeFactPattern ) {
                        visitFromAccumulateCompositeFactPattern( (FromAccumulateCompositeFactPattern) pattern.getSourcePattern(),
                                                                 isSubPattern );

                    } else if ( pattern.getSourcePattern() instanceof FromCollectCompositeFactPattern ) {
                        visitFromCollectCompositeFactPattern( (FromCollectCompositeFactPattern) pattern.getSourcePattern(),
                                                              isSubPattern );

                    } else if ( pattern.getSourcePattern() instanceof FromEntryPointFactPattern ) {
                        visitFromEntryPointFactPattern( (FromEntryPointFactPattern) pattern.getSourcePattern(),
                                                        isSubPattern );

                    } else if ( pattern.getSourcePattern() instanceof FromCompositeFactPattern ) {
                        visitFromCompositeFactPattern( (FromCompositeFactPattern) pattern.getSourcePattern(),
                                                       isSubPattern );

                    } else {
                        throw new IllegalArgumentException( "Unsupported pattern " + pattern.getSourcePattern() + " for FROM ACCUMULATE" );
                    }
                }
                buf.append( ",\n" );

                if ( pattern.useFunctionOrCode().equals( FromAccumulateCompositeFactPattern.USE_FUNCTION ) ) {
                    buf.append( indentation + "\t" );
                    buf.append( pattern.getFunction() );
                } else {
                    buf.append( indentation + "\tinit( " );
                    buf.append( pattern.getInitCode() );
                    buf.append( " ),\n" );
                    buf.append( indentation + "\taction( " );
                    buf.append( pattern.getActionCode() );
                    buf.append( " ),\n" );
                    if ( pattern.getReverseCode() != null && !pattern.getReverseCode().trim().equals( "" ) ) {
                        buf.append( indentation + "\treverse( " );
                        buf.append( pattern.getReverseCode() );
                        buf.append( " ),\n" );
                    }
                    buf.append( indentation + "\tresult( " );
                    buf.append( pattern.getResultCode() );
                    buf.append( " )\n" );
                }
                buf.append( ") \n" );
            }
        }

        public void visitFromEntryPointFactPattern( final FromEntryPointFactPattern pattern ) {
            visitFromEntryPointFactPattern( pattern,
                                            false );
        }

        public void visitFromEntryPointFactPattern( final FromEntryPointFactPattern pattern,
                                                    final boolean isSubPattern ) {
            buf.append( indentation );
            if ( !isSubPattern && isDSLEnhanced ) {
                // adding passthrough markup
                buf.append( ">" );
            }

            if ( pattern.getFactPattern() != null ) {
                final LHSGeneratorContext gctx = generatorContextFactory.newChildGeneratorContext( rootContext,
                                                                                                   pattern.getFactPattern() );
                generateFactPattern( pattern.getFactPattern(),
                                     gctx );
                buf.append( " from entry-point \"" + pattern.getEntryPointName() + "\"\n" );
            }
        }

        private void renderCompositeFOL( final CompositeFactPattern pattern ) {
            buf.append( pattern.getType() );
            if ( pattern.getPatterns() != null ) {
                buf.append( " (" );
                for ( int i = 0; i < pattern.getPatterns().length; i++ ) {
                    renderSubPattern( pattern,
                                      i );
                    if ( i != pattern.getPatterns().length - 1 ) {
                        buf.append( " and " );
                    }
                }
                buf.append( ") \n" );
            }
        }

        private void renderSubPattern( final CompositeFactPattern pattern,
                                       final int subIndex ) {
            if ( pattern.getPatterns() == null || pattern.getPatterns().length == 0 ) {
                return;
            }
            IFactPattern subPattern = pattern.getPatterns()[ subIndex ];
            if ( subPattern instanceof FactPattern ) {
                final LHSGeneratorContext gctx = generatorContextFactory.newChildGeneratorContext( rootContext,
                                                                                                   subPattern );
                this.generateFactPattern( (FactPattern) subPattern,
                                          gctx );

            } else if ( subPattern instanceof FromAccumulateCompositeFactPattern ) {
                this.visitFromAccumulateCompositeFactPattern( (FromAccumulateCompositeFactPattern) subPattern,
                                                              true );

            } else if ( subPattern instanceof FromCollectCompositeFactPattern ) {
                this.visitFromCollectCompositeFactPattern( (FromCollectCompositeFactPattern) subPattern,
                                                           true );

            } else if ( subPattern instanceof FromCompositeFactPattern ) {
                this.visitFromCompositeFactPattern( (FromCompositeFactPattern) subPattern,
                                                    true );

            } else {
                throw new IllegalStateException( "Unsupported Pattern: " + subPattern.getClass().getName() );
            }
        }

        private void renderExpression( final ExpressionFormLine expression ) {
            final ToStringExpressionVisitor visitor = new ToStringExpressionVisitor( expression.getBinding(),
                                                                                     constraintValueBuilder );
            buf.append( expression.getText( visitor ) );
        }

        public void visitDSLSentence( final DSLSentence sentence ) {
            buf.append( indentation );
            buf.append( sentence.interpolate() );
            buf.append( "\n" );
        }

        private void generateFactPattern( final FactPattern pattern,
                                          final LHSGeneratorContext gctx ) {
            if ( pattern.isNegated() ) {
                buf.append( "not " );
            } else if ( pattern.isBound() ) {
                bindingsPatterns.put( pattern.getBoundName(),
                                      pattern );
                buf.append( pattern.getBoundName() );
                buf.append( " : " );
            }
            if ( pattern.getFactType() != null ) {
                buf.append( pattern.getFactType() );
            }
            buf.append( "( " );

            // top level constraints
            if ( pattern.getConstraintList() != null ) {
                generateConstraints( pattern,
                                     gctx );
            }
            buf.append( ")" );

            //Add CEP window definition
            CEPWindow window = pattern.getWindow();
            if ( window.isDefined() ) {
                buf.append( " " );
                buf.append( window.getOperator() );
                buf.append( buildOperatorParameterDRL( window.getParameters() ) );
            }
        }

        private void generateConstraints( final FactPattern pattern,
                                          final LHSGeneratorContext parentContext ) {
            LHSGeneratorContext gctx = null;
            for ( int constraintIndex = 0; constraintIndex < pattern.getFieldConstraints().length; constraintIndex++ ) {
                FieldConstraint constr = pattern.getConstraintList().getConstraints()[ constraintIndex ];

                if ( constraintIndex == 0 ) {
                    gctx = generatorContextFactory.newChildGeneratorContext( parentContext,
                                                                             constr );
                } else {
                    gctx = generatorContextFactory.newPeerGeneratorContext( gctx,
                                                                            constr );
                }

                generateConstraint( constr,
                                    gctx );
            }
        }

        public void generateSeparator( final FieldConstraint constr,
                                       final LHSGeneratorContext gctx ) {
            if ( !doesPeerHaveOutput( gctx ) ) {
                return;
            }
            if ( gctx.getParent().getFieldConstraint() instanceof CompositeFieldConstraint ) {
                CompositeFieldConstraint cconstr = (CompositeFieldConstraint) gctx.getParent().getFieldConstraint();
                buf.append( cconstr.getCompositeJunctionType() + " " );
            } else {
                if ( buf.length() > 2 && !( buf.charAt( buf.length() - 2 ) == ',' ) ) {
                    buf.append( ", " );
                }
            }
        }

        protected boolean doesPeerHaveOutput( final LHSGeneratorContext gctx ) {
            final List<LHSGeneratorContext> peers = generatorContextFactory.getPeers( gctx );
            for ( LHSGeneratorContext c : peers ) {
                if ( c.isHasOutput() ) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Recursively process the nested constraints. It will only put brackets
         * in for the ones that aren't at top level. This makes for more
         * readable DRL in the most common cases.
         */
        protected void generateConstraint( final FieldConstraint con,
                                           final LHSGeneratorContext parentContext ) {
            generateSeparator( con,
                               parentContext );
            if ( con instanceof CompositeFieldConstraint ) {
                CompositeFieldConstraint cfc = (CompositeFieldConstraint) con;
                FieldConstraint[] nestedConstraints = cfc.getConstraints();
                if ( nestedConstraints != null ) {
                    LHSGeneratorContext nestedGctx = generatorContextFactory.newChildGeneratorContext( parentContext,
                                                                                                       con );
                    preGenerateNestedConstraint( nestedGctx );
                    if ( parentContext.getParent().getFieldConstraint() instanceof CompositeFieldConstraint ) {
                        buf.append( "( " );
                    }
                    LHSGeneratorContext gctx = null;
                    for ( int nestedConstraintIndex = 0; nestedConstraintIndex < nestedConstraints.length; nestedConstraintIndex++ ) {
                        FieldConstraint nestedConstr = nestedConstraints[ nestedConstraintIndex ];

                        if ( nestedConstraintIndex == 0 ) {
                            gctx = generatorContextFactory.newChildGeneratorContext( nestedGctx,
                                                                                     nestedConstr );
                        } else {
                            gctx = generatorContextFactory.newPeerGeneratorContext( gctx,
                                                                                    nestedConstr );
                        }

                        generateConstraint( nestedConstr,
                                            gctx );
                    }
                    if ( parentContext.getParent().getFieldConstraint() instanceof CompositeFieldConstraint ) {
                        buf.append( ")" );
                    }
                    postGenerateNestedConstraint( parentContext );
                }
            } else {
                generateSingleFieldConstraint( (SingleFieldConstraint) con,
                                               parentContext );
            }
        }

        private void generateSingleFieldConstraint( final SingleFieldConstraint constr,
                                                    final LHSGeneratorContext gctx ) {
            if ( constr.getConstraintValueType() == BaseSingleFieldConstraint.TYPE_PREDICATE ) {
                buf.append( "eval( " );
                buf.append( constr.getValue() );
                buf.append( " )" );
                gctx.setHasOutput( true );

            } else {
                if ( constr.isBound() ) {
                    bindingsFields.put( constr.getFieldBinding(),
                                        constr );
                    buf.append( constr.getFieldBinding() );
                    buf.append( " : " );
                }

                assertConstraintValue( constr );

                if ( isConstraintComplete( constr ) ) {
                    if ( constr instanceof SingleFieldConstraintEBLeftSide ) {
                        final SingleFieldConstraintEBLeftSide sfcexp = ( (SingleFieldConstraintEBLeftSide) constr );
                        final ToStringExpressionVisitor visitor = new ToStringExpressionVisitor( sfcexp.getExpressionLeftSide().getBinding(),
                                                                                                 constraintValueBuilder );
                        buf.append( sfcexp.getExpressionLeftSide().getText( visitor ) );
                    } else {
                        SingleFieldConstraint parent = (SingleFieldConstraint) constr.getParent();
                        StringBuilder parentBuf = new StringBuilder();
                        while ( parent != null ) {
                            String fieldName = parent.getFieldName();
                            parentBuf.insert( 0,
                                              fieldName + "." );
                            parent = (SingleFieldConstraint) parent.getParent();
                        }
                        buf.append( parentBuf );
                        String fieldName = constr.getFieldName();
                        buf.append( fieldName );
                    }

                    final Map<String, String> parameters = constr.getParameters();
                    if ( constr.getConnectives() == null ) {
                        generateNormalFieldRestriction( constr,
                                                        parameters );
                    } else {
                        generateConnectiveFieldRestriction( constr,
                                                            parameters,
                                                            gctx );
                    }
                    gctx.setHasOutput( true );
                }
            }
        }

        private void generateNormalFieldRestriction( final SingleFieldConstraint constr,
                                                     final Map<String, String> parameters ) {
            if ( constr instanceof SingleFieldConstraintEBLeftSide ) {
                SingleFieldConstraintEBLeftSide sfexp = (SingleFieldConstraintEBLeftSide) constr;
                addFieldRestriction( buf,
                                     sfexp.getConstraintValueType(),
                                     sfexp.getExpressionLeftSide().getGenericType(),
                                     sfexp.getOperator(),
                                     parameters,
                                     sfexp.getValue(),
                                     sfexp.getExpressionValue(),
                                     true );
            } else {
                addFieldRestriction( buf,
                                     constr.getConstraintValueType(),
                                     constr.getFieldType(),
                                     constr.getOperator(),
                                     parameters,
                                     constr.getValue(),
                                     constr.getExpressionValue(),
                                     true );
            }
        }

        private void generateConnectiveFieldRestriction( final SingleFieldConstraint constr,
                                                         final Map<String, String> parameters,
                                                         final LHSGeneratorContext gctx ) {
            LHSGeneratorContext cctx = generatorContextFactory.newChildGeneratorContext( gctx,
                                                                                         constr );
            if ( constr instanceof SingleFieldConstraintEBLeftSide ) {
                SingleFieldConstraintEBLeftSide sfexp = (SingleFieldConstraintEBLeftSide) constr;
                addConnectiveFieldRestriction( buf,
                                               sfexp.getConstraintValueType(),
                                               sfexp.getExpressionLeftSide().getGenericType(),
                                               sfexp.getOperator(),
                                               parameters,
                                               sfexp.getValue(),
                                               sfexp.getExpressionValue(),
                                               cctx,
                                               true );
            } else {
                addConnectiveFieldRestriction( buf,
                                               constr.getConstraintValueType(),
                                               constr.getFieldType(),
                                               constr.getOperator(),
                                               parameters,
                                               constr.getValue(),
                                               constr.getExpressionValue(),
                                               cctx,
                                               true );
            }

            for ( int j = 0; j < constr.getConnectives().length; j++ ) {
                final ConnectiveConstraint conn = constr.getConnectives()[ j ];
                final Map<String, String> connectiveParameters = conn.getParameters();

                addConnectiveFieldRestriction( buf,
                                               conn.getConstraintValueType(),
                                               conn.getFieldType(),
                                               conn.getOperator(),
                                               connectiveParameters,
                                               conn.getValue(),
                                               conn.getExpressionValue(),
                                               cctx,
                                               true );
            }
        }

        private void assertConstraintValue( final SingleFieldConstraint sfc ) {
            if ( DataType.TYPE_STRING.equals( sfc.getFieldType() ) ) {
                if ( sfc.getValue() == null ) {
                    sfc.setValue( "" );
                }
            }
        }

        private boolean isConstraintComplete( final SingleFieldConstraint constr ) {
            if ( constr.getConstraintValueType() == BaseSingleFieldConstraint.TYPE_EXPR_BUILDER_VALUE ) {
                return true;
            } else if ( constr instanceof SingleFieldConstraintEBLeftSide ) {
                return true;
            } else if ( constr.getFieldBinding() != null ) {
                return true;
            }
            final String operator = constr.getOperator();
            final String fieldType = constr.getFieldType();
            final String fieldValue = constr.getValue();
            if ( operator == null ) {
                return false;
            }
            if ( operator.equals( "== null" ) || operator.equals( "!= null" ) ) {
                return true;
            }
            if ( DataType.TYPE_STRING.equals( fieldType ) ) {
                return true;
            }

            return !( fieldValue == null || fieldValue.isEmpty() );
        }

        protected void addConnectiveFieldRestriction( final StringBuilder buf,
                                                      final int type,
                                                      final String fieldType,
                                                      final String operator,
                                                      final Map<String, String> parameters,
                                                      final String value,
                                                      final ExpressionFormLine expression,
                                                      LHSGeneratorContext gctx,
                                                      final boolean spaceBeforeOperator ) {
            addFieldRestriction( buf, type, fieldType, operator, parameters, value, expression, spaceBeforeOperator );
        }

        private void addFieldRestriction( final StringBuilder buf,
                                          final int type,
                                          final String fieldType,
                                          final String operator,
                                          final Map<String, String> parameters,
                                          final String value,
                                          final ExpressionFormLine expression,
                                          final boolean spaceBeforeOperator ) {
            if ( operator == null ) {
                return;
            }

            if ( spaceBeforeOperator ) {
                buf.append( " " );
            }
            buf.append( operator );

            if ( parameters != null && parameters.size() > 0 ) {
                buf.append( buildOperatorParameterDRL( parameters ) );
            }

            switch ( type ) {
                case BaseSingleFieldConstraint.TYPE_RET_VALUE:
                    buildReturnValueFieldValue( value,
                                                buf );
                    break;
                case BaseSingleFieldConstraint.TYPE_LITERAL:
                    buildLiteralFieldValue( operator,
                                            type,
                                            fieldType,
                                            value,
                                            buf );
                    break;
                case BaseSingleFieldConstraint.TYPE_EXPR_BUILDER_VALUE:
                    buildExpressionFieldValue( expression,
                                               buf );
                    break;
                case BaseSingleFieldConstraint.TYPE_TEMPLATE:
                    buildTemplateFieldValue( type,
                                             fieldType,
                                             value,
                                             buf );
                    break;
                case BaseSingleFieldConstraint.TYPE_ENUM:
                    buildEnumFieldValue( operator,
                                         type,
                                         fieldType,
                                         value,
                                         buf );
                    break;
                default:
                    buildDefaultFieldValue( operator,
                                            value,
                                            buf );
            }
        }

        protected void buildReturnValueFieldValue( final String value,
                                                   final StringBuilder buf ) {
            buf.append( " " );
            buf.append( "( " );
            buf.append( value );
            buf.append( " )" );
            buf.append( " " );
        }

        protected StringBuilder buildOperatorParameterDRL( final Map<String, String> parameters ) {
            String className = parameters.get( SharedConstants.OPERATOR_PARAMETER_GENERATOR );
            if ( className == null ) {
                throw new IllegalStateException( "Implementation of 'org.kie.guvnor.guided.server.util.OperatorParameterDRLBuilder' undefined. Unable to build Operator Parameter DRL." );
            }

            try {
                OperatorParameterDRLBuilder builder = (OperatorParameterDRLBuilder) Class.forName( className ).newInstance();
                return builder.buildDRL( parameters );
            } catch ( ClassNotFoundException cnfe ) {
                throw new IllegalStateException( "Unable to generate Operator DRL using class '" + className + "'.",
                                                 cnfe );
            } catch ( IllegalAccessException iae ) {
                throw new IllegalStateException( "Unable to generate Operator DRL using class '" + className + "'.",
                                                 iae );
            } catch ( InstantiationException ie ) {
                throw new IllegalStateException( "Unable to generate Operator DRL using class '" + className + "'.",
                                                 ie );
            }

        }

        protected void buildLiteralFieldValue( final String operator,
                                               final int type,
                                               final String fieldType,
                                               final String value,
                                               final StringBuilder buf ) {
            if ( OperatorsOracle.operatorRequiresList( operator ) ) {
                populateValueList( buf,
                                   type,
                                   fieldType,
                                   value );
            } else {
                if ( !operator.equals( "== null" ) && !operator.equals( "!= null" ) ) {
                    buf.append( " " );
                    constraintValueBuilder.buildLHSFieldValue( buf,
                                                               type,
                                                               fieldType,
                                                               value );
                }
            }
            buf.append( " " );
        }

        private void populateValueList( final StringBuilder buf,
                                        final int type,
                                        final String fieldType,
                                        final String value ) {
            String workingValue = value.trim();
            if ( workingValue.startsWith( "(" ) ) {
                workingValue = workingValue.substring( 1 );
            }
            if ( workingValue.endsWith( ")" ) ) {
                workingValue = workingValue.substring( 0,
                                                       workingValue.length() - 1 );
            }
            final String[] values = workingValue.split( "," );
            buf.append( " ( " );
            for ( String v : values ) {
                v = v.trim();
                if ( v.startsWith( "\"" ) ) {
                    v = v.substring( 1 );
                }
                if ( v.endsWith( "\"" ) ) {
                    v = v.substring( 0,
                                     v.length() - 1 );
                }
                constraintValueBuilder.buildLHSFieldValue( buf,
                                                           type,
                                                           fieldType,
                                                           v );
                buf.append( ", " );
            }
            buf.delete( buf.length() - 2,
                        buf.length() );
            buf.append( " )" );
        }

        protected void buildExpressionFieldValue( final ExpressionFormLine expression,
                                                  final StringBuilder buf ) {
            if ( expression != null ) {
                buf.append( " " );
                final ToStringExpressionVisitor visitor = new ToStringExpressionVisitor( expression.getBinding(),
                                                                                         constraintValueBuilder );
                buf.append( expression.getText( visitor ) );
                buf.append( " " );
            }
        }

        protected void buildTemplateFieldValue( final int type,
                                                final String fieldType,
                                                final String value,
                                                final StringBuilder buf ) {
            buf.append( " " );
            constraintValueBuilder.buildLHSFieldValue( buf,
                                                       type,
                                                       fieldType,
                                                       "@{removeDelimitingQuotes(" + value + ")}" );
            buf.append( " " );
        }

        private void buildEnumFieldValue( final String operator,
                                          final int type,
                                          final String fieldType,
                                          final String value,
                                          final StringBuilder buf ) {

            if ( OperatorsOracle.operatorRequiresList( operator ) ) {
                populateValueList( buf,
                                   type,
                                   fieldType,
                                   value );
            } else {
                if ( !operator.equals( "== null" ) && !operator.equals( "!= null" ) ) {
                    buf.append( " " );
                    constraintValueBuilder.buildLHSFieldValue( buf,
                                                               type,
                                                               fieldType,
                                                               value );
                }
            }
            buf.append( " " );
        }

        protected void buildDefaultFieldValue( final String operator,
                                               final String value,
                                               final StringBuilder buf ) {
            if ( !operator.equals( "== null" ) && !operator.equals( "!= null" ) ) {
                buf.append( " " );
                buf.append( value );
            }
            buf.append( " " );
        }

    }

    public static class RHSActionVisitor extends ReflectiveVisitor {

        protected StringBuilder buf;
        private boolean isDSLEnhanced;
        private String indentation;
        private Map<String, IFactPattern> bindingsPatterns;
        private Map<String, FieldConstraint> bindingsFields;
        protected DRLConstraintValueBuilder constraintValueBuilder;
        protected RHSGeneratorContextFactory generatorContextFactory;

        protected final RHSGeneratorContext rootContext;

        //Keep a record of Work Items that are instantiated for Actions that depend on them
        private Set<String> instantiatedWorkItems;

        public RHSActionVisitor( final boolean isDSLEnhanced,
                                 final Map<String, IFactPattern> bindingsPatterns,
                                 final Map<String, FieldConstraint> bindingsFields,
                                 final DRLConstraintValueBuilder constraintValueBuilder,
                                 final RHSGeneratorContextFactory generatorContextFactory,
                                 final StringBuilder b,
                                 final String indentation ) {
            this.isDSLEnhanced = isDSLEnhanced;
            this.bindingsPatterns = bindingsPatterns;
            this.bindingsFields = bindingsFields;
            this.constraintValueBuilder = constraintValueBuilder;
            this.generatorContextFactory = generatorContextFactory;
            this.rootContext = generatorContextFactory.newGeneratorContext();
            this.indentation = indentation;
            this.instantiatedWorkItems = new HashSet<String>();
            this.buf = b;
        }

        protected void preGenerateAction( final RHSGeneratorContext gctx ) {
            // empty, overridden by rule templates
        }

        protected void postGenerateAction( final RHSGeneratorContext gctx ) {
            // empty, overridden by rule templates
        }

        protected void preGenerateSetMethodCallParameterValue( final RHSGeneratorContext gctx,
                                                               final ActionFieldValue fieldValue ) {
            gctx.setHasOutput( true );
        }

        public void visitActionInsertFact( final ActionInsertFact action ) {
            this.generateInsertCall( action,
                                     false );
        }

        public void visitActionInsertLogicalFact( final ActionInsertLogicalFact action ) {
            this.generateInsertCall( action,
                                     true );
        }

        public void visitFreeFormLine( final FreeFormLine ffl ) {
            if ( ffl.getText() == null ) {
                return;
            }
            String[] lines = ffl.getText().split( "\\n|\\r\\n" );
            for ( String line : lines ) {
                this.buf.append( indentation );
                if ( isDSLEnhanced ) {
                    buf.append( ">" );
                }
                this.buf.append( line + "\n" );
            }
        }

        private void generateInsertCall( final ActionInsertFact action,
                                         final boolean isLogic ) {
            buf.append( indentation );
            if ( isDSLEnhanced ) {
                buf.append( ">" );
            }
            final String binding = action.getBoundName();
            if ( action.getFieldValues().length == 0 && binding == null ) {
                buf.append( ( isLogic ) ? "insertLogical( new " : "insert( new " );

                buf.append( action.getFactType() );
                buf.append( "() );\n" );
            } else {
                buf.append( action.getFactType() );
                buf.append( " " + binding );
                buf.append( " = new " );
                buf.append( action.getFactType() );
                buf.append( "();\n" );
                generateSetMethodCalls( binding,
                                        action.getFieldValues() );

                buf.append( indentation );
                if ( isDSLEnhanced ) {
                    buf.append( ">" );
                }
                if ( isLogic ) {
                    buf.append( "insertLogical( " );
                    buf.append( binding );
                    buf.append( " );\n" );
                } else {
                    buf.append( "insert( " );
                    buf.append( binding );

                    buf.append( " );\n" );
                }
            }
        }

        public void visitActionUpdateField( final ActionUpdateField action ) {
            final RHSGeneratorContext gctx = generatorContextFactory.newChildGeneratorContext( rootContext,
                                                                                               action );
            preGenerateAction( gctx );

            buf.append( indentation );
            if ( isDSLEnhanced ) {
                buf.append( ">" );
            }
            buf.append( "modify( " ).append( action.getVariable() ).append( " ) {\n" );
            this.generateModifyMethodCalls( action.getFieldValues(),
                                            gctx );
            buf.append( "\n" ).append( indentation );
            if ( isDSLEnhanced ) {
                buf.append( ">" );
            }
            buf.append( "}\n" );

            postGenerateAction( gctx );
        }

        public void visitActionGlobalCollectionAdd( final ActionGlobalCollectionAdd add ) {
            buf.append( indentation );
            if ( isDSLEnhanced ) {
                buf.append( ">" );
            }
            buf.append( add.getGlobalName() + ".add( " + add.getFactName() + " );\n" );
        }

        public void visitActionRetractFact( final ActionRetractFact action ) {
            buf.append( indentation );
            if ( isDSLEnhanced ) {
                buf.append( ">" );
            }
            buf.append( "retract( " );
            buf.append( action.getVariableName() );
            buf.append( " );\n" );
        }

        public void visitDSLSentence( final DSLSentence sentence ) {
            buf.append( indentation );
            buf.append( sentence.interpolate() );
            buf.append( "\n" );
        }

        public void visitActionExecuteWorkItem( final ActionExecuteWorkItem action ) {
            String wiName = action.getWorkDefinition().getName();
            String wiImplName = WORKITEM_PREFIX + wiName;

            instantiatedWorkItems.add( wiName );

            buf.append( indentation );
            buf.append( "org.drools.core.process.instance.impl.WorkItemImpl " );
            buf.append( wiImplName );
            buf.append( " = new org.drools.core.process.instance.impl.WorkItemImpl();\n" );
            buf.append( indentation );
            buf.append( wiImplName );
            buf.append( ".setName( \"" );
            buf.append( wiName );
            buf.append( "\" );\n" );
            for ( PortableParameterDefinition ppd : action.getWorkDefinition().getParameters() ) {
                makeWorkItemParameterDRL( ppd,
                                          wiImplName );
            }
            buf.append( indentation );
            buf.append( "wim.internalExecuteWorkItem( " );
            buf.append( wiImplName );
            buf.append( " );\n" );
        }

        private void makeWorkItemParameterDRL( final PortableParameterDefinition ppd,
                                               final String wiImplName ) {
            boolean makeParameter = true;

            //Only add bound parameters if their binding exists (i.e. the corresponding column has a value or - for Limited Entry - is true)
            if ( ppd instanceof HasBinding ) {
                HasBinding hb = (HasBinding) ppd;
                if ( hb.isBound() ) {
                    String binding = hb.getBinding();
                    makeParameter = isBindingValid( binding );
                }
            }
            if ( makeParameter ) {
                buf.append( indentation );
                buf.append( wiImplName );
                buf.append( ".getParameters().put( \"" );
                buf.append( ppd.getName() );
                buf.append( "\", " );
                buf.append( ppd.asString() );
                buf.append( " );\n" );
            }
        }

        private boolean isBindingValid( final String binding ) {
            if ( bindingsPatterns.containsKey( binding ) ) {
                return true;
            }
            if ( bindingsFields.containsKey( binding ) ) {
                return true;
            }
            return false;
        }

        public void visitActionSetField( final ActionSetField action ) {
            if ( action instanceof ActionCallMethod ) {
                this.generateSetMethodCallsMethod( (ActionCallMethod) action,
                                                   action.getFieldValues() );
            } else {
                this.generateSetMethodCalls( action.getVariable(),
                                             action.getFieldValues() );
            }
        }

        private void generateSetMethodCalls( final String variableName,
                                             final ActionFieldValue[] fieldValues ) {
            for ( int i = 0; i < fieldValues.length; i++ ) {
                generateSetMethodCall( variableName,
                                       fieldValues[ i ] );
            }
        }

        protected void generateSetMethodCall( final String variableName,
                                              final ActionFieldValue fieldValue ) {
            buf.append( indentation );
            if ( isDSLEnhanced ) {
                buf.append( ">" );
            }
            buf.append( variableName );

            if ( fieldValue instanceof ActionFieldFunction ) {
                buf.append( "." );
                buf.append( fieldValue.getField() );
            } else {
                buf.append( ".set" );
                buf.append( Character.toUpperCase( fieldValue.getField().charAt( 0 ) ) );
                buf.append( fieldValue.getField().substring( 1 ) );
            }
            buf.append( "( " );
            generateSetMethodCallParameterValue( buf,
                                                 fieldValue );
            buf.append( " );\n" );
        }

        private void generateSetMethodCallParameterValue( final StringBuilder buf,
                                                          final ActionFieldValue fieldValue ) {
            if ( fieldValue.isFormula() ) {
                buildFormulaFieldValue( fieldValue,
                                        buf );
            } else if ( fieldValue.getNature() == FieldNatureType.TYPE_VARIABLE ) {
                buildVariableFieldValue( fieldValue,
                                         buf );
            } else if ( fieldValue.getNature() == FieldNatureType.TYPE_TEMPLATE ) {
                buildTemplateFieldValue( fieldValue,
                                         buf );
            } else if ( fieldValue instanceof ActionWorkItemFieldValue ) {
                buildWorkItemFieldValue( (ActionWorkItemFieldValue) fieldValue,
                                         buf );
            } else {
                buildDefaultFieldValue( fieldValue,
                                        buf );
            }
        }

        private void generateModifyMethodCalls( final ActionFieldValue[] fieldValues,
                                                final RHSGeneratorContext parentContext ) {
            RHSGeneratorContext gctx = null;
            for ( int index = 0; index < fieldValues.length; index++ ) {
                final ActionFieldValue fieldValue = fieldValues[ index ];
                if ( index == 0 ) {
                    gctx = generatorContextFactory.newChildGeneratorContext( parentContext,
                                                                             fieldValue );
                } else {
                    gctx = generatorContextFactory.newPeerGeneratorContext( gctx,
                                                                            fieldValue );
                }

                preGenerateSetMethodCallParameterValue( gctx,
                                                        fieldValue );
                generateModifyMethodSeparator( gctx,
                                               fieldValue );
                generateModifyMethodCall( gctx,
                                          fieldValue );
            }
        }

        protected void generateModifyMethodCall( final RHSGeneratorContext gctx,
                                                 final ActionFieldValue fieldValue ) {
            buf.append( indentation ).append( indentation );
            if ( isDSLEnhanced ) {
                buf.append( ">" );
            }

            if ( fieldValue instanceof ActionFieldFunction ) {
                buf.append( fieldValue.getField() );
            } else {
                buf.append( "set" );
                buf.append( Character.toUpperCase( fieldValue.getField().charAt( 0 ) ) );
                buf.append( fieldValue.getField().substring( 1 ) );
            }
            buf.append( "( " );
            generateSetMethodCallParameterValue( buf,
                                                 fieldValue );
            buf.append( " )" );
        }

        protected void generateModifyMethodSeparator( final RHSGeneratorContext gctx,
                                                      final ActionFieldValue fieldValue ) {
            if ( doesPeerHaveOutput( gctx ) ) {
                buf.append( ", \n" );
            }
        }

        private boolean doesPeerHaveOutput( final RHSGeneratorContext gctx ) {
            final List<RHSGeneratorContext> peers = generatorContextFactory.getPeers( gctx );
            for ( RHSGeneratorContext c : peers ) {
                if ( c.isHasOutput() ) {
                    return true;
                }
            }
            return false;
        }

        protected void buildFormulaFieldValue( final ActionFieldValue fieldValue,
                                               final StringBuilder buf ) {
            buf.append( fieldValue.getValue() );
        }

        protected void buildVariableFieldValue( final ActionFieldValue fieldValue,
                                                final StringBuilder buf ) {
            buf.append( fieldValue.getValue().substring( 1 ) );
        }

        protected void buildTemplateFieldValue( final ActionFieldValue fieldValue,
                                                final StringBuilder buf ) {
            constraintValueBuilder.buildRHSFieldValue( buf,
                                                       fieldValue.getType(),
                                                       "@{removeDelimitingQuotes(" + fieldValue.getValue() + ")}" );
        }

        protected void buildWorkItemFieldValue( final ActionWorkItemFieldValue afv,
                                                final StringBuilder buf ) {
            if ( instantiatedWorkItems.contains( afv.getWorkItemName() ) ) {
                buf.append( "(" );
                buf.append( afv.getWorkItemParameterClassName() );
                buf.append( ") " );
                buf.append( WORKITEM_PREFIX );
                buf.append( afv.getWorkItemName() );
                buf.append( ".getResult( \"" );
                buf.append( afv.getWorkItemParameterName() );
                buf.append( "\" )" );
            } else {
                buf.append( "null" );
            }
        }

        protected void buildDefaultFieldValue( final ActionFieldValue fieldValue,
                                               final StringBuilder buf ) {
            constraintValueBuilder.buildRHSFieldValue( buf,
                                                       fieldValue.getType(),
                                                       fieldValue.getValue() );
        }

        private void generateSetMethodCallsMethod( final ActionCallMethod action,
                                                   final FieldNature[] fieldValues ) {
            buf.append( indentation );
            if ( isDSLEnhanced ) {
                buf.append( ">" );
            }
            buf.append( action.getVariable() );
            buf.append( "." );

            buf.append( action.getMethodName() );

            buf.append( "( " );
            boolean isFirst = true;
            for ( int i = 0; i < fieldValues.length; i++ ) {
                ActionFieldFunction valueFunction = (ActionFieldFunction) fieldValues[ i ];
                if ( isFirst == true ) {
                    isFirst = false;
                } else {
                    buf.append( ", " );
                }

                if ( valueFunction.isFormula() ) {
                    buf.append( valueFunction.getValue() );
                } else if ( valueFunction.getNature() == FieldNatureType.TYPE_VARIABLE ) {
                    buf.append( valueFunction.getValue() );
                } else {
                    buildDefaultFieldValue( valueFunction,
                                            buf );
                }
            }
            buf.append( " );\n" );
        }
    }

    public static class RHSClassDependencyVisitor extends ReflectiveVisitor {

        private Map<String, List<ActionFieldValue>> classes = new HashMap<String, List<ActionFieldValue>>();

        public void visitFreeFormLine( FreeFormLine ffl ) {
            //Do nothing other than preventing ReflectiveVisitor recording an error
        }

        public void visitActionGlobalCollectionAdd( final ActionGlobalCollectionAdd add ) {
            //Do nothing other than preventing ReflectiveVisitor recording an error
        }

        public void visitActionRetractFact( final ActionRetractFact action ) {
            //Do nothing other than preventing ReflectiveVisitor recording an error
        }

        public void visitDSLSentence( final DSLSentence sentence ) {
            //Do nothing other than preventing ReflectiveVisitor recording an error
        }

        public void visitActionInsertFact( final ActionInsertFact action ) {
            getClasses( action.getFieldValues() );
        }

        public void visitActionInsertLogicalFact( final ActionInsertLogicalFact action ) {
            getClasses( action.getFieldValues() );
        }

        public void visitActionUpdateField( final ActionUpdateField action ) {
            getClasses( action.getFieldValues() );
        }

        public void visitActionSetField( final ActionSetField action ) {
            getClasses( action.getFieldValues() );
        }

        public void visitActionExecuteWorkItem( final ActionExecuteWorkItem action ) {
            //Do nothing other than preventing ReflectiveVisitor recording an error
        }

        public Map<String, List<ActionFieldValue>> getRHSClasses() {
            return classes;
        }

        private void getClasses( ActionFieldValue[] fieldValues ) {
            for ( ActionFieldValue afv : fieldValues ) {
                String type = afv.getType();
                List<ActionFieldValue> afvs = classes.get( type );
                if ( afvs == null ) {
                    afvs = new ArrayList<ActionFieldValue>();
                    classes.put( type,
                                 afvs );
                }
                afvs.add( afv );
            }
        }

    }

    /**
     * @see RuleModelPersistence#unmarshal(String, List, PackageDataModelOracle)
     */
    public RuleModel unmarshal( final String str,
                                final List<String> globals,
                                final PackageDataModelOracle dmo ) {
        if ( str == null || str.isEmpty() ) {
            return new RuleModel();
        }
        return getRuleModel( preprocessDRL( str,
                                            false ).registerGlobals( dmo,
                                                                     globals ),
                             dmo );
    }

    public RuleModel unmarshalUsingDSL( final String str,
                                        final List<String> globals,
                                        final PackageDataModelOracle dmo,
                                        final String... dsls ) {
        if ( str == null || str.isEmpty() ) {
            return new RuleModel();
        }
        return getRuleModel( parseDSLs( preprocessDRL( str,
                                                       true ),
                                        dsls ).registerGlobals( dmo,
                                                                globals ),
                             dmo );
    }

    private ExpandedDRLInfo parseDSLs( final ExpandedDRLInfo expandedDRLInfo,
                                       final String[] dsls ) {
        for ( String dsl : dsls ) {
            for ( String line : dsl.split( "\n" ) ) {
                String dslPattern = line.trim();
                if ( dslPattern.length() > 0 ) {
                    if ( dslPattern.startsWith( "[when]" ) ) {
                        expandedDRLInfo.lhsDslPatterns.add( extractDslPattern( dslPattern.substring( "[when]".length() ) ) );
                    } else if ( dslPattern.startsWith( "[then]" ) ) {
                        expandedDRLInfo.rhsDslPatterns.add( extractDslPattern( dslPattern.substring( "[then]".length() ) ) );
                    } else if ( dslPattern.startsWith( "[" ) ) {
                        String pattern = extractDslPattern( removeDslTopics( dslPattern ) );
                        expandedDRLInfo.lhsDslPatterns.add( pattern );
                        expandedDRLInfo.rhsDslPatterns.add( pattern );
                    }
                }
            }
        }
        return expandedDRLInfo;
    }

    private String removeDslTopics( final String line ) {
        int lastClosedSquare = -1;
        boolean lookForOpen = true;
        for ( int i = 0; i < line.length(); i++ ) {
            char ch = line.charAt( i );
            if ( lookForOpen ) {
                if ( ch == '[' ) {
                    lookForOpen = false;
                } else if ( !Character.isWhitespace( ch ) ) {
                    break;
                }
            } else {
                if ( ch == ']' ) {
                    lastClosedSquare = i;
                    lookForOpen = true;
                }
            }
        }
        return line.substring( lastClosedSquare + 1 );
    }

    private String extractDslPattern( final String line ) {
        return line.substring( 0,
                               line.indexOf( '=' ) ).trim();
    }

    private RuleModel getRuleModel( final ExpandedDRLInfo expandedDRLInfo,
                                    final PackageDataModelOracle dmo ) {
        //De-serialize model
        RuleDescr ruleDescr = parseDrl( expandedDRLInfo );
        RuleModel model = new RuleModel();
        model.name = ruleDescr.getName();
        model.parentName = ruleDescr.getParentName();

        Map<String, AnnotationDescr> annotations = ruleDescr.getAnnotations();
        if ( annotations != null ) {
            for ( AnnotationDescr annotation : annotations.values() ) {
                model.addMetadata( new RuleMetadata( annotation.getName(),
                                                     annotation.getValuesAsString() ) );
            }
        }

        //De-serialize Package name
        final String packageName = PackageNameParser.parsePackageName( expandedDRLInfo.plainDrl );
        model.setPackageName( packageName );

        //De-serialize imports
        final Imports imports = ImportsParser.parseImports( expandedDRLInfo.plainDrl );
        for ( Import item : imports.getImports() ) {
            model.getImports().addImport( item );
        }

        boolean isJavaDialect = parseAttributes( model,
                                                 ruleDescr.getAttributes() );
        Map<String, String> boundParams = parseLhs( model,
                                                    ruleDescr.getLhs(),
                                                    isJavaDialect,
                                                    expandedDRLInfo,
                                                    dmo );
        parseRhs( model,
                  expandedDRLInfo.consequence != null ? expandedDRLInfo.consequence : (String) ruleDescr.getConsequence(),
                  isJavaDialect,
                  boundParams,
                  expandedDRLInfo,
                  dmo );
        return model;
    }

    private ExpandedDRLInfo preprocessDRL( final String str,
                                           final boolean hasDsl ) {
        StringBuilder drl = new StringBuilder();
        String thenLine = null;
        List<String> lhsStatements = new ArrayList<String>();
        List<String> rhsStatements = new ArrayList<String>();

        String[] lines = str.split( "\n" );
        RuleSection ruleSection = RuleSection.HEADER;
        int lhsParenthesisBalance = 0;

        for ( String line : lines ) {
            if ( ruleSection == RuleSection.HEADER ) {
                drl.append( line ).append( "\n" );
                if ( line.contains( "when" ) ) {
                    ruleSection = RuleSection.LHS;
                }
                continue;
            }
            if ( ruleSection == RuleSection.LHS && line.contains( "then" ) ) {
                thenLine = line;
                ruleSection = RuleSection.RHS;
                continue;
            }
            if ( ruleSection == RuleSection.LHS ) {
                if ( lhsParenthesisBalance == 0 ) {
                    lhsStatements.add( line );
                } else {
                    String oldLine = lhsStatements.remove( lhsStatements.size() - 1 );
                    lhsStatements.add( oldLine + " " + line );
                }
                lhsParenthesisBalance += parenthesisBalance( line );
            } else {
                rhsStatements.add( line );
            }
        }

        return createExpandedDRLInfo( hasDsl,
                                      drl,
                                      thenLine,
                                      lhsStatements,
                                      rhsStatements );
    }

    private int parenthesisBalance( String str ) {
        int balance = 0;
        for ( char ch : str.toCharArray() ) {
            if ( ch == '(' ) {
                balance++;
            } else if ( ch == ')' ) {
                balance--;
            }
        }
        return balance;
    }

    private ExpandedDRLInfo createExpandedDRLInfo( final boolean hasDsl,
                                                   final StringBuilder drl,
                                                   final String thenLine,
                                                   final List<String> lhsStatements,
                                                   final List<String> rhsStatements ) {
        if ( !hasDsl ) {
            return processFreeFormStatement( drl,
                                             thenLine,
                                             lhsStatements,
                                             rhsStatements );
        }

        ExpandedDRLInfo expandedDRLInfo = new ExpandedDRLInfo( hasDsl );
        int lineCounter = -1;
        for ( String statement : lhsStatements ) {
            lineCounter++;
            String trimmed = statement.trim();
            if ( hasDsl && trimmed.startsWith( ">" ) ) {
                if ( isValidLHSStatement( trimmed ) ) {
                    drl.append( trimmed.substring( 1 ) ).append( "\n" );
                } else {
                    expandedDRLInfo.freeFormStatementsInLhs.put( lineCounter,
                                                                 trimmed.substring( 1 ) );
                }
            } else {
                expandedDRLInfo.dslStatementsInLhs.put( lineCounter,
                                                        trimmed );
            }
        }

        drl.append( thenLine ).append( "\n" );

        lineCounter = -1;
        for ( String statement : rhsStatements ) {
            lineCounter++;
            String trimmed = statement.trim();
            if ( trimmed.endsWith( "end" ) ) {
                trimmed = trimmed.substring( 0,
                                             trimmed.length() - 3 ).trim();
            }
            if ( trimmed.length() > 0 ) {
                if ( hasDsl && trimmed.startsWith( ">" ) ) {
                    drl.append( trimmed.substring( 1 ) ).append( "\n" );
                } else {
                    expandedDRLInfo.dslStatementsInRhs.put( lineCounter,
                                                            trimmed );
                }
            }
        }

        expandedDRLInfo.plainDrl = drl.toString();

        return expandedDRLInfo;
    }

    private ExpandedDRLInfo processFreeFormStatement( final StringBuilder drl,
                                                      final String thenLine,
                                                      final List<String> lhsStatements,
                                                      final List<String> rhsStatements ) {
        ExpandedDRLInfo expandedDRLInfo = new ExpandedDRLInfo( false );

        int lineCounter = -1;
        for ( String statement : lhsStatements ) {
            lineCounter++;
            String trimmed = statement.trim();
            if ( isValidLHSStatement( trimmed ) ) {
                drl.append( trimmed ).append( "\n" );
            } else {
                expandedDRLInfo.freeFormStatementsInLhs.put( lineCounter,
                                                             trimmed );
            }
        }

        drl.append( thenLine ).append( "\n" );

        expandedDRLInfo.consequence = "";
        lineCounter = -1;
        for ( String statement : rhsStatements ) {
            String trimmed = statement.trim();
            if ( trimmed.endsWith( "end" ) ) {
                trimmed = trimmed.substring( 0, trimmed.length() - 3 );
            }
            if ( trimmed.length() > 0 ) {
                expandedDRLInfo.consequence += ( trimmed + "\n" );
            }
            drl.append( statement ).append( "\n" );
        }

        expandedDRLInfo.plainDrl = drl.toString();
        return expandedDRLInfo;
    }

    private boolean isValidLHSStatement( final String lhs ) {
        // TODO: How to identify a non valid (free form) lhs statement?
        return ( lhs.indexOf( '(' ) >= 0 || lhs.indexOf( ':' ) >= 0 ) && lhs.indexOf( "//" ) == -1;
    }

    private enum RuleSection {HEADER, LHS, RHS}

    private static class ExpandedDRLInfo {

        private final boolean hasDsl;
        private String plainDrl;
        private String consequence;

        private Map<Integer, String> dslStatementsInLhs;
        private Map<Integer, String> dslStatementsInRhs;

        private Map<Integer, String> freeFormStatementsInLhs;

        private List<String> lhsDslPatterns;
        private List<String> rhsDslPatterns;

        private Set<String> globals = new HashSet<String>();

        private ExpandedDRLInfo( final boolean hasDsl ) {
            this.hasDsl = hasDsl;
            dslStatementsInLhs = new HashMap<Integer, String>();
            dslStatementsInRhs = new HashMap<Integer, String>();
            freeFormStatementsInLhs = new HashMap<Integer, String>();
            lhsDslPatterns = new ArrayList<String>();
            rhsDslPatterns = new ArrayList<String>();
        }

        public boolean hasGlobal( final String name ) {
            return globals.contains( name );
        }

        public ExpandedDRLInfo registerGlobals( final PackageDataModelOracle dmo,
                                                final List<String> globalStatements ) {
            if ( globalStatements != null ) {
                for ( String globalStatement : globalStatements ) {
                    String identifier = getIdentifier( globalStatement );
                    if ( identifier != null ) {
                        globals.add( identifier );
                    }
                }
            }
            Map<String, String> globalsFromDmo = dmo != null ? dmo.getPackageGlobals() : null;
            if ( globalsFromDmo != null ) {
                globals.addAll( globalsFromDmo.keySet() );
            }
            return this;
        }

        private String getIdentifier( String globalStatement ) {
            globalStatement = globalStatement.trim();
            if ( !globalStatement.startsWith( "global" ) ) {
                return null;
            }
            int lastSpace = globalStatement.lastIndexOf( ' ' );
            if ( lastSpace < 0 ) {
                return null;
            }
            String identifier = globalStatement.substring( lastSpace + 1 );
            if ( identifier.endsWith( ";" ) ) {
                identifier = identifier.substring( 0,
                                                   identifier.length() - 1 );
            }
            return identifier;
        }

        public ExpandedDRLInfo registerGlobalDescrs( final List<GlobalDescr> globalDescrs ) {
            if ( globalDescrs != null ) {
                for ( GlobalDescr globalDescr : globalDescrs ) {
                    globals.add( globalDescr.getIdentifier() );
                }
            }
            return this;
        }
    }

    private RuleDescr parseDrl( final ExpandedDRLInfo expandedDRLInfo ) {
        DrlParser drlParser = new DrlParser();
        PackageDescr packageDescr;
        try {
            packageDescr = drlParser.parse( true, expandedDRLInfo.plainDrl );
        } catch ( DroolsParserException e ) {
            throw new RuntimeException( e );
        }
        expandedDRLInfo.registerGlobalDescrs( packageDescr.getGlobals() );
        return packageDescr.getRules().get( 0 );
    }

    private boolean parseAttributes( final RuleModel m,
                                     final Map<String, AttributeDescr> attributes ) {
        boolean isJavaDialect = false;
        for ( Map.Entry<String, AttributeDescr> entry : attributes.entrySet() ) {
            String name = entry.getKey();
            String value = normalizeAttributeValue( entry.getValue().getValue().trim() );
            RuleAttribute ruleAttribute = new RuleAttribute( name,
                                                             value );
            m.addAttribute( ruleAttribute );
            isJavaDialect |= name.equals( "dialect" ) && value.equals( "java" );
        }
        return isJavaDialect;
    }

    private String normalizeAttributeValue( String value ) {
        if ( value.startsWith( "[" ) ) {
            value = value.substring( 1, value.length() - 1 ).trim();
        }
        if ( value.startsWith( "\"" ) ) {
            StringBuilder sb = new StringBuilder();
            String[] split = value.split( "," );
            sb.append( stripQuotes( split[ 0 ].trim() ) );
            for ( int i = 1; i < split.length; i++ ) {
                sb.append( ", " ).append( stripQuotes( split[ i ].trim() ) );
            }
            value = sb.toString();
        }
        return value;
    }

    private String stripQuotes( String value ) {
        if ( value.startsWith( "\"" ) ) {
            value = value.substring( 1, value.length() - 1 ).trim();
        }
        return value;
    }

    private Map<String, String> parseLhs( final RuleModel m,
                                          final AndDescr lhs,
                                          final boolean isJavaDialect,
                                          final ExpandedDRLInfo expandedDRLInfo,
                                          final PackageDataModelOracle dmo ) {
        Map<String, String> boundParams = new HashMap<String, String>();
        int lineCounter = -1;
        for ( BaseDescr descr : lhs.getDescrs() ) {
            lineCounter = parseNonDrlInLhs( m,
                                            expandedDRLInfo,
                                            lineCounter );
            IPattern pattern = parseBaseDescr( m,
                                               descr,
                                               isJavaDialect,
                                               boundParams,
                                               dmo );
            if ( pattern != null ) {
                m.addLhsItem( pattern );
            }
        }
        parseNonDrlInLhs( m,
                          expandedDRLInfo,
                          lineCounter );
        return boundParams;
    }

    private int parseNonDrlInLhs( final RuleModel m,
                                  final ExpandedDRLInfo expandedDRLInfo,
                                  int lineCounter ) {
        lineCounter++;
        lineCounter = parseDslInLhs( m,
                                     expandedDRLInfo,
                                     lineCounter );
        lineCounter = parseFreeForm( m,
                                     expandedDRLInfo,
                                     lineCounter );
        return lineCounter;
    }

    private int parseDslInLhs( final RuleModel m,
                               final ExpandedDRLInfo expandedDRLInfo,
                               int lineCounter ) {
        if ( expandedDRLInfo.hasDsl ) {
            String dslLine = expandedDRLInfo.dslStatementsInLhs.get( lineCounter );
            while ( dslLine != null ) {
                m.addLhsItem( toDSLSentence( expandedDRLInfo.lhsDslPatterns,
                                             dslLine ) );
                dslLine = expandedDRLInfo.dslStatementsInLhs.get( ++lineCounter );
            }
        }
        return lineCounter;
    }

    private int parseFreeForm( final RuleModel m,
                               final ExpandedDRLInfo expandedDRLInfo,
                               int lineCounter ) {
        String freeForm = expandedDRLInfo.freeFormStatementsInLhs.get( lineCounter );
        while ( freeForm != null ) {
            FreeFormLine ffl = new FreeFormLine();
            ffl.setText( freeForm );
            m.addLhsItem( ffl );
            freeForm = expandedDRLInfo.freeFormStatementsInLhs.get( ++lineCounter );
        }
        return lineCounter;
    }

    private IPattern parseBaseDescr( final RuleModel m,
                                     final BaseDescr descr,
                                     final boolean isJavaDialect,
                                     final Map<String, String> boundParams,
                                     final PackageDataModelOracle dmo ) {
        if ( descr instanceof PatternDescr ) {
            return parsePatternDescr( m,
                                      (PatternDescr) descr,
                                      isJavaDialect,
                                      boundParams,
                                      dmo );
        } else if ( descr instanceof AndDescr ) {
            AndDescr andDescr = (AndDescr) descr;
            return parseBaseDescr( m,
                                   andDescr.getDescrs().get( 0 ),
                                   isJavaDialect,
                                   boundParams,
                                   dmo );
        } else if ( descr instanceof EvalDescr ) {
            FreeFormLine freeFormLine = new FreeFormLine();
            freeFormLine.setText( "eval( " + ( (EvalDescr) descr ).getContent() + " )" );
            return freeFormLine;
        } else if ( descr instanceof ConditionalElementDescr ) {
            return parseExistentialElementDescr( m,
                                                 (ConditionalElementDescr) descr,
                                                 isJavaDialect,
                                                 boundParams,
                                                 dmo );
        }
        return null;
    }

    private IFactPattern parsePatternDescr( final RuleModel m,
                                            final PatternDescr pattern,
                                            final boolean isJavaDialect,
                                            final Map<String, String> boundParams,
                                            final PackageDataModelOracle dmo ) {
        if ( pattern.getSource() != null ) {
            return parsePatternSource( m,
                                       pattern,
                                       pattern.getSource(),
                                       isJavaDialect,
                                       boundParams,
                                       dmo );
        }
        return getFactPattern( m,
                               pattern,
                               isJavaDialect,
                               boundParams,
                               dmo );
    }

    private FactPattern getFactPattern( final RuleModel m,
                                        final PatternDescr pattern,
                                        final boolean isJavaDialect,
                                        final Map<String, String> boundParams,
                                        final PackageDataModelOracle dmo ) {
        String type = pattern.getObjectType();
        FactPattern factPattern = new FactPattern( getSimpleFactType( type,
                                                                      dmo ) );
        if ( pattern.getIdentifier() != null ) {
            String identifier = pattern.getIdentifier();
            factPattern.setBoundName( identifier );
            boundParams.put( identifier,
                             type );
        }

        parseConstraint( m,
                         factPattern,
                         pattern.getConstraint(),
                         isJavaDialect,
                         boundParams,
                         dmo );

        for ( BehaviorDescr behavior : pattern.getBehaviors() ) {
            if ( behavior.getText().equals( "window" ) ) {
                CEPWindow window = new CEPWindow();
                window.setOperator( "over window:" + behavior.getSubType() );
                window.setParameter( "org.drools.workbench.models.commons.backend.rule.operatorParameterGenerator",
                                     "org.drools.workbench.models.commons.backend.rule.CEPWindowOperatorParameterDRLBuilder" );
                List<String> params = behavior.getParameters();
                if ( params != null ) {
                    int i = 1;
                    for ( String param : params ) {
                        window.setParameter( "" + i++, param );
                    }
                }
                factPattern.setWindow( window );
            }
        }
        return factPattern;
    }

    private IFactPattern parsePatternSource( final RuleModel m,
                                             final PatternDescr pattern,
                                             final PatternSourceDescr patternSource,
                                             final boolean isJavaDialect,
                                             final Map<String, String> boundParams,
                                             final PackageDataModelOracle dmo ) {
        if ( patternSource instanceof AccumulateDescr ) {
            AccumulateDescr accumulate = (AccumulateDescr) patternSource;
            FromAccumulateCompositeFactPattern fac = new FromAccumulateCompositeFactPattern();
            fac.setSourcePattern( parseBaseDescr( m,
                                                  accumulate.getInput(),
                                                  isJavaDialect,
                                                  boundParams,
                                                  dmo ) );

            FactPattern factPattern = new FactPattern( pattern.getObjectType() );
            factPattern.setBoundName( pattern.getIdentifier() );
            boundParams.put( factPattern.getBoundName(),
                             factPattern.getFactType() );
            parseConstraint( m,
                             factPattern,
                             pattern.getConstraint(),
                             isJavaDialect,
                             boundParams,
                             dmo );

            fac.setFactPattern( factPattern );
            for ( AccumulateDescr.AccumulateFunctionCallDescr func : accumulate.getFunctions() ) {
                String funcName = func.getFunction();
                boolean first = true;
                StringBuilder sb = new StringBuilder();
                for ( String param : func.getParams() ) {
                    if ( first ) {
                        first = false;
                    } else {
                        sb.append( ", " );
                    }
                    sb.append( param );
                }
                fac.setFunction( funcName + "(" + sb + ")" );
                break;
            }
            return fac;
        } else if ( patternSource instanceof CollectDescr ) {
            CollectDescr collect = (CollectDescr) patternSource;
            FromCollectCompositeFactPattern fac = new FromCollectCompositeFactPattern();
            fac.setRightPattern( parseBaseDescr( m,
                                                 collect.getInputPattern(),
                                                 isJavaDialect,
                                                 boundParams,
                                                 dmo ) );
            fac.setFactPattern( getFactPattern( m,
                                                pattern,
                                                isJavaDialect,
                                                boundParams,
                                                dmo ) );
            return fac;
        } else if ( patternSource instanceof EntryPointDescr ) {
            EntryPointDescr entryPoint = (EntryPointDescr) patternSource;
            FromEntryPointFactPattern fep = new FromEntryPointFactPattern();
            fep.setEntryPointName( entryPoint.getText() );
            fep.setFactPattern( getFactPattern( m,
                                                pattern,
                                                isJavaDialect,
                                                boundParams,
                                                dmo ) );
            return fep;
        } else if ( patternSource instanceof FromDescr ) {
            FromDescr from = (FromDescr) patternSource;
            FromCompositeFactPattern fcfp = new FromCompositeFactPattern();
            FactPattern factPattern = new FactPattern( pattern.getObjectType() );
            factPattern.setBoundName( pattern.getIdentifier() );
            parseConstraint( m,
                             factPattern,
                             pattern.getConstraint(),
                             isJavaDialect,
                             boundParams,
                             dmo );

            fcfp.setFactPattern( factPattern );
            ExpressionFormLine expression = new ExpressionFormLine();
            fcfp.setExpression( expression );

            String dataSource = from.getDataSource().toString();
            String[] splitSource = dataSource.split( "\\." );
            ModelField[] fields = null;
            for ( int i = 0; i < splitSource.length; i++ ) {
                String sourcePart = splitSource[ i ];
                if ( i == 0 ) {
                    String type = boundParams.get( sourcePart );
                    expression.appendPart( new ExpressionVariable( sourcePart,
                                                                   type,
                                                                   DataType.TYPE_NUMERIC ) );
                    fields = findFields( m,
                                         dmo,
                                         type );
                } else {
                    ModelField modelField = null;
                    for ( ModelField field : fields ) {
                        if ( field.getName().equals( sourcePart ) ) {
                            modelField = field;
                            break;
                        }
                    }
                    if ( modelField == null ) {
                        final String previousClassName = expression.getClassType();
                        final List<MethodInfo> mis = dmo.getProjectMethodInformation().get( previousClassName );
                        boolean isMethod = false;
                        if ( mis != null ) {
                            for ( MethodInfo mi : mis ) {
                                if ( mi.getName().equals( sourcePart ) ) {
                                    expression.appendPart( new ExpressionMethod( mi.getName(),
                                                                                 mi.getReturnClassType(),
                                                                                 mi.getGenericType(),
                                                                                 mi.getParametricReturnType() ) );
                                    isMethod = true;
                                    break;
                                }
                            }
                        }
                        if ( isMethod == false ) {
                            expression.appendPart( new ExpressionText( sourcePart ) );
                        }
                    } else {
                        expression.appendPart( new ExpressionField( sourcePart,
                                                                    modelField.getClassName(),
                                                                    modelField.getType() ) );
                        fields = findFields( m,
                                             dmo,
                                             modelField.getClassName() );
                    }
                }
            }

            return fcfp;
        }
        throw new RuntimeException( "Unknown pattern source " + patternSource );
    }

    private CompositeFactPattern parseExistentialElementDescr( final RuleModel m,
                                                               final ConditionalElementDescr conditionalDescr,
                                                               final boolean isJavaDialect,
                                                               final Map<String, String> boundParams,
                                                               final PackageDataModelOracle dmo ) {
        CompositeFactPattern comp = conditionalDescr instanceof NotDescr ?
                new CompositeFactPattern( CompositeFactPattern.COMPOSITE_TYPE_NOT ) :
                conditionalDescr instanceof OrDescr ?
                        new CompositeFactPattern( CompositeFactPattern.COMPOSITE_TYPE_OR ) :
                        new CompositeFactPattern( CompositeFactPattern.COMPOSITE_TYPE_EXISTS );
        addPatternToComposite( m,
                               conditionalDescr,
                               comp,
                               isJavaDialect,
                               boundParams,
                               dmo );
        IFactPattern[] patterns = comp.getPatterns();
        return patterns != null && patterns.length > 0 ? comp : null;
    }

    private void addPatternToComposite( final RuleModel m,
                                        final ConditionalElementDescr conditionalDescr,
                                        final CompositeFactPattern comp,
                                        final boolean isJavaDialect,
                                        final Map<String, String> boundParams,
                                        final PackageDataModelOracle dmo ) {
        for ( Object descr : conditionalDescr.getDescrs() ) {
            if ( descr instanceof PatternDescr ) {
                comp.addFactPattern( parsePatternDescr( m,
                                                        (PatternDescr) descr,
                                                        isJavaDialect,
                                                        boundParams,
                                                        dmo ) );
            } else if ( descr instanceof ConditionalElementDescr ) {
                addPatternToComposite( m,
                                       (ConditionalElementDescr) descr,
                                       comp,
                                       isJavaDialect,
                                       boundParams,
                                       dmo );
            }
        }
    }

    private void parseConstraint( final RuleModel m,
                                  final FactPattern factPattern,
                                  final ConditionalElementDescr constraint,
                                  final boolean isJavaDialect,
                                  final Map<String, String> boundParams,
                                  final PackageDataModelOracle dmo ) {
        for ( BaseDescr descr : constraint.getDescrs() ) {
            if ( descr instanceof ExprConstraintDescr ) {
                ExprConstraintDescr exprConstraint = (ExprConstraintDescr) descr;
                Expr expr = parseExpr( exprConstraint.getExpression(),
                                       isJavaDialect,
                                       boundParams,
                                       dmo );
                factPattern.addConstraint( expr.asFieldConstraint( m,
                                                                   factPattern ) );
            }
        }
    }

    private static String findOperator( final String expr ) {
        final Set<String> potentialOperators = new HashSet<String>();
        for ( Operator op : Operator.getAllOperators() ) {
            String opString = op.getOperatorString();
            if ( op.isNegated() ) {
                if ( expr.contains( " not " + opString ) ) {
                    return "not " + opString;
                }
            }
            int opPos = expr.indexOf( opString );
            if ( opPos >= 0 && !isInQuote( expr, opPos ) &&
                    !( Character.isLetter( opString.charAt( 0 ) ) &&
                            ( expr.length() == opPos + opString.length() || Character.isLetter( expr.charAt( opPos + opString.length() ) ) ||
                                    ( opPos > 0 && Character.isLetter( expr.charAt( opPos - 1 ) ) ) ) ) ) {
                potentialOperators.add( opString );
            }
        }
        String operator = "";
        if ( !potentialOperators.isEmpty() ) {
            for ( String potentialOperator : potentialOperators ) {
                if ( potentialOperator.length() > operator.length() ) {
                    operator = potentialOperator;
                }
            }
        }
        if ( !operator.isEmpty() ) {
            return operator;
        }

        if ( expr.contains( " not in " ) ) {
            return "not in";
        }
        if ( expr.contains( " in " ) ) {
            return "in";
        }
        return null;
    }

    private static boolean isInQuote( final String expr,
                                      final int pos ) {
        boolean isInQuote = false;
        for ( int i = pos - 1; i >= 0; i-- ) {
            if ( expr.charAt( i ) == '"' ) {
                isInQuote = !isInQuote;
            }
        }
        return isInQuote;
    }

    private static final String[] NULL_OPERATORS = new String[]{ "== null", "!= null" };

    private static String findNullOrNotNullOperator( final String expr ) {
        for ( String op : NULL_OPERATORS ) {
            if ( expr.contains( op ) ) {
                return op;
            }
        }
        return null;
    }

    private void parseRhs( final RuleModel m,
                           final String rhs,
                           final boolean isJavaDialect,
                           final Map<String, String> boundParams,
                           final ExpandedDRLInfo expandedDRLInfo,
                           final PackageDataModelOracle dmo ) {
        PortableWorkDefinition pwd = null;
        Map<String, List<String>> setStatements = new HashMap<String, List<String>>();
        Map<String, Integer> setStatementsPosition = new HashMap<String, Integer>();
        Map<String, String> factsType = new HashMap<String, String>();

        String modifiedVariable = null;
        String modifiers = null;

        int lineCounter = -1;
        String[] lines = rhs.split( "\n" );
        for ( String line : lines ) {
            lineCounter++;
            if ( expandedDRLInfo.hasDsl ) {
                String dslLine = expandedDRLInfo.dslStatementsInRhs.get( lineCounter );
                while ( dslLine != null ) {
                    m.addRhsItem( toDSLSentence( expandedDRLInfo.rhsDslPatterns,
                                                 dslLine ) );
                    dslLine = expandedDRLInfo.dslStatementsInRhs.get( ++lineCounter );
                }
            }
            line = line.trim();
            if ( modifiedVariable != null ) {
                int modifyBlockEnd = line.lastIndexOf( '}' );
                if ( modifiers == null ) {
                    modifiers = modifyBlockEnd > 0 ?
                            line.substring( line.indexOf( '{' ) + 1,
                                            modifyBlockEnd ).trim() :
                            line.substring( line.indexOf( '{' ) + 1 ).trim();
                } else if ( modifyBlockEnd != 0 ) {
                    modifiers += modifyBlockEnd > 0 ?
                            line.substring( 0,
                                            modifyBlockEnd ).trim() :
                            line;
                }
                if ( modifyBlockEnd >= 0 ) {
                    ActionUpdateField action = new ActionUpdateField();
                    action.setVariable( modifiedVariable );
                    m.addRhsItem( action );
                    addModifiersToAction( modifiers,
                                          action,
                                          boundParams,
                                          dmo,
                                          m.getImports(),
                                          isJavaDialect );
                    modifiedVariable = null;
                    modifiers = null;
                }
            } else if ( line.startsWith( "insertLogical" ) ) {
                String fact = unwrapParenthesis( line );
                String type = getStatementType( fact,
                                                factsType );
                if ( type != null ) {
                    ActionInsertLogicalFact action = new ActionInsertLogicalFact( type );
                    m.addRhsItem( action );
                    if ( factsType.containsKey( fact ) ) {
                        addSettersToAction( setStatements,
                                            fact,
                                            action,
                                            boundParams,
                                            dmo,
                                            m.getImports(),
                                            isJavaDialect );
                    }
                }
            } else if ( line.startsWith( "insert" ) ) {
                String fact = unwrapParenthesis( line );
                String type = getStatementType( fact,
                                                factsType );
                if ( type != null ) {
                    ActionInsertFact action = new ActionInsertFact( type );
                    m.addRhsItem( action );
                    if ( factsType.containsKey( fact ) ) {
                        action.setBoundName( fact );
                        addSettersToAction( setStatements,
                                            fact,
                                            action,
                                            boundParams,
                                            dmo,
                                            m.getImports(),
                                            isJavaDialect );
                    }
                }
            } else if ( line.startsWith( "update" ) ) {
                String variable = unwrapParenthesis( line );
                ActionUpdateField action = new ActionUpdateField();
                action.setVariable( variable );
                m.addRhsItem( action );
                addSettersToAction( setStatements,
                                    variable,
                                    action,
                                    boundParams,
                                    dmo,
                                    m.getImports(),
                                    isJavaDialect );
            } else if ( line.startsWith( "modify" ) ) {
                int modifyBlockEnd = line.lastIndexOf( '}' );
                if ( modifyBlockEnd > 0 ) {
                    String variable = line.substring( line.indexOf( '(' ) + 1,
                                                      line.indexOf( ')' ) ).trim();
                    ActionUpdateField action = new ActionUpdateField();
                    action.setVariable( variable );
                    m.addRhsItem( action );
                    addModifiersToAction( line.substring( line.indexOf( '{' ) + 1,
                                                          modifyBlockEnd ).trim(),
                                          action,
                                          boundParams,
                                          dmo,
                                          m.getImports(),
                                          isJavaDialect );
                } else {
                    modifiedVariable = line.substring( line.indexOf( '(' ) + 1,
                                                       line.indexOf( ')' ) ).trim();
                    int modifyBlockStart = line.indexOf( '{' );
                    if ( modifyBlockStart > 0 ) {
                        modifiers = line.substring( modifyBlockStart + 1 ).trim();
                    }
                }
            } else if ( line.startsWith( "retract" ) || line.startsWith( "delete" ) ) {
                String variable = unwrapParenthesis( line );
                m.addRhsItem( new ActionRetractFact( variable ) );
            } else if ( line.startsWith( "org.drools.core.process.instance.impl.WorkItemImpl wiWorkItem" ) ) {
                ActionExecuteWorkItem awi = new ActionExecuteWorkItem();
                pwd = new PortableWorkDefinition();
                pwd.setName( "WorkItem" );
                awi.setWorkDefinition( pwd );
                m.addRhsItem( awi );
            } else if ( line.startsWith( "wiWorkItem.getParameters().put" ) ) {
                String statement = line.substring( "wiWorkItem.getParameters().put".length() );
                statement = unwrapParenthesis( statement );
                int commaPos = statement.indexOf( ',' );
                String name = statement.substring( 0,
                                                   commaPos ).trim();
                String value = statement.substring( commaPos + 1 ).trim();
                pwd.addParameter( buildPortableParameterDefinition( name,
                                                                    value,
                                                                    boundParams ) );
            } else if ( line.startsWith( "wim.internalExecuteWorkItem" ) || line.startsWith( "wiWorkItem.setName" ) ) {
                // ignore
            } else {
                int dotPos = line.indexOf( '.' );
                int argStart = line.indexOf( '(' );
                if ( dotPos > 0 && argStart > dotPos ) {
                    String variable = line.substring( 0,
                                                      dotPos ).trim();
                    if ( isJavaIdentifier( variable ) ) {
                        String methodName = line.substring( dotPos + 1,
                                                            argStart ).trim();
                        if ( isJavaIdentifier( methodName ) ) {
                            if ( getSettedField( methodName ) != null ) {
                                List<String> setters = setStatements.get( variable );
                                if ( setters == null ) {
                                    setters = new ArrayList<String>();
                                    setStatements.put( variable,
                                                       setters );
                                }
                                setStatementsPosition.put( variable,
                                                           lineCounter );
                                setters.add( line );
                            } else if ( methodName.equals( "add" ) && expandedDRLInfo.hasGlobal( variable ) ) {
                                String factName = line.substring( argStart + 1,
                                                                  line.lastIndexOf( ')' ) ).trim();
                                ActionGlobalCollectionAdd actionGlobalCollectionAdd = new ActionGlobalCollectionAdd();
                                actionGlobalCollectionAdd.setGlobalName( variable );
                                actionGlobalCollectionAdd.setFactName( factName );
                                m.addRhsItem( actionGlobalCollectionAdd );
                            } else {
                                m.addRhsItem( getActionCallMethod( m,
                                                                   isJavaDialect,
                                                                   boundParams,
                                                                   dmo,
                                                                   line,
                                                                   variable,
                                                                   methodName ) );
                            }
                            continue;
                        }
                    }
                }

                int eqPos = line.indexOf( '=' );
                boolean addFreeFormLine = line.trim().length() > 0;
                if ( eqPos > 0 ) {
                    String field = line.substring( 0,
                                                   eqPos ).trim();
                    if ( "java.text.SimpleDateFormat sdf".equals( field ) || "org.drools.core.process.instance.WorkItemManager wim".equals( field ) ) {
                        addFreeFormLine = false;
                    }
                    String[] split = field.split( " " );
                    if ( split.length == 2 ) {
                        factsType.put( split[ 1 ],
                                       split[ 0 ] );
                        addFreeFormLine &= !isInsertedFact( lines,
                                                            lineCounter,
                                                            split[ 1 ] );
                    }
                }
                if ( addFreeFormLine ) {
                    FreeFormLine ffl = new FreeFormLine();
                    ffl.setText( line );
                    m.addRhsItem( ffl );
                }
            }
        }

        for ( Map.Entry<String, List<String>> entry : setStatements.entrySet() ) {
            ActionSetField action = new ActionSetField( entry.getKey() );
            addSettersToAction( entry.getValue(),
                                action,
                                boundParams,
                                dmo,
                                m.getImports(),
                                isJavaDialect );
            m.addRhsItem( action,
                          setStatementsPosition.get( entry.getKey() ) );
        }

        if ( expandedDRLInfo.hasDsl ) {
            String dslLine = expandedDRLInfo.dslStatementsInRhs.get( ++lineCounter );
            while ( dslLine != null ) {
                m.addRhsItem( toDSLSentence( expandedDRLInfo.rhsDslPatterns,
                                             dslLine ) );
                dslLine = expandedDRLInfo.dslStatementsInRhs.get( ++lineCounter );
            }
        }
    }

    private ActionCallMethod getActionCallMethod( final RuleModel model,
                                                  final boolean isJavaDialect,
                                                  final Map<String, String> boundParams,
                                                  final PackageDataModelOracle dmo,
                                                  final String line,
                                                  final String variable,
                                                  final String methodName ) {

        return new ActionCallMethodBuilder( model,
                                            dmo,
                                            isJavaDialect,
                                            boundParams ).get( variable,
                                                               methodName,
                                                               unwrapParenthesis( line ).split( "," ) );
    }

    private boolean isInsertedFact( final String[] lines,
                                    final int lineCounter,
                                    final String fact ) {
        for ( int i = lineCounter; i < lines.length; i++ ) {
            String line = lines[ i ].trim();
            if ( line.startsWith( "insert" ) ) {
                if ( fact.equals( unwrapParenthesis( line ) ) ) {
                    return true;
                }
            }
        }
        return false;
    }

    private DSLSentence toDSLSentence( final List<String> dslPatterns,
                                       final String dslLine ) {
        DSLSentence dslSentence = new DSLSentence();
        for ( String dslPattern : dslPatterns ) {
            // Dollar breaks the matcher, need to escape them.
            dslPattern = dslPattern.replace( "$",
                                             "\\$" );
            //A DSL Pattern can contain Regex itself, for example "When the ages is less than {num:1?[0-9]?[0-9]}"
            String regex = dslPattern.replaceAll( "\\{.*?\\}",
                                                  "(.*)" );
            Matcher matcher = Pattern.compile( regex ).matcher( dslLine );
            if ( matcher.matches() ) {
                dslPattern = dslPattern.replace( "\\$",
                                                 "$" );
                dslSentence.setDefinition( dslPattern );
                for ( int i = 0; i < matcher.groupCount(); i++ ) {
                    dslSentence.getValues().get( i ).setValue( matcher.group( i + 1 ) );
                }
                return dslSentence;
            }
        }
        dslSentence.setDefinition( dslLine );
        return dslSentence;
    }

    private PortableParameterDefinition buildPortableParameterDefinition( final String name,
                                                                          final String value,
                                                                          final Map<String, String> boundParams ) {
        PortableParameterDefinition paramDef;
        String type = boundParams.get( value );
        if ( type != null ) {
            if ( type.equals( "Boolean" ) ) {
                paramDef = new PortableBooleanParameterDefinition();
            } else if ( type.equals( "String" ) ) {
                paramDef = new PortableStringParameterDefinition();
            } else if ( type.equals( "Float" ) ) {
                paramDef = new PortableBooleanParameterDefinition();
            } else if ( type.equals( "Integer" ) ) {
                paramDef = new PortableIntegerParameterDefinition();
            } else {
                paramDef = new PortableObjectParameterDefinition();
            }
            ( (HasBinding) paramDef ).setBinding( value );
        } else if ( value.equals( "true" ) || value.equals( "false" ) || value.equals( "Boolean.TRUE" ) || value.equals( "Boolean.FALSE" ) ) {
            paramDef = new PortableBooleanParameterDefinition();
            boolean b = value.equals( "true" ) || value.equals( "Boolean.TRUE" );
            ( (PortableBooleanParameterDefinition) paramDef ).setValue( b );
        } else if ( value.startsWith( "\"" ) ) {
            paramDef = new PortableStringParameterDefinition();
            ( (PortableStringParameterDefinition) paramDef ).setValue( value.substring( 1,
                                                                                        value.length() - 1 ) );
        } else if ( Character.isDigit( value.charAt( 0 ) ) ) {
            if ( value.endsWith( "f" ) ) {
                paramDef = new PortableFloatParameterDefinition();
                ( (PortableFloatParameterDefinition) paramDef ).setValue( Float.parseFloat( value ) );
            } else {
                paramDef = new PortableIntegerParameterDefinition();
                ( (PortableIntegerParameterDefinition) paramDef ).setValue( Integer.parseInt( value ) );
            }
        } else {
            throw new RuntimeException( "Unknown parameter " + value );
        }
        paramDef.setName( name.substring( 1,
                                          name.length() - 1 ) );
        return paramDef;
    }

    private void addSettersToAction( final Map<String, List<String>> setStatements,
                                     final String variable,
                                     final ActionFieldList action,
                                     final Map<String, String> boundParams,
                                     final PackageDataModelOracle dmo,
                                     final Imports imports,
                                     final boolean isJavaDialect ) {
        addSettersToAction( setStatements.remove( variable ),
                            action,
                            boundParams,
                            dmo,
                            imports,
                            isJavaDialect );
    }

    private void addSettersToAction( final List<String> setters,
                                     final ActionFieldList action,
                                     final Map<String, String> boundParams,
                                     final PackageDataModelOracle dmo,
                                     final Imports imports,
                                     final boolean isJavaDialect ) {
        if ( setters != null ) {
            for ( String statement : setters ) {
                int dotPos = statement.indexOf( '.' );
                int argStart = statement.indexOf( '(' );
                String methodName = statement.substring( dotPos + 1,
                                                         argStart ).trim();
                addSetterToAction( action,
                                   boundParams,
                                   dmo,
                                   imports,
                                   isJavaDialect,
                                   statement,
                                   methodName );
            }
        }
    }

    private void addModifiersToAction( final String modifiers,
                                       final ActionFieldList action,
                                       final Map<String, String> boundParams,
                                       final PackageDataModelOracle dmo,
                                       final Imports imports,
                                       final boolean isJavaDialect ) {
        for ( String statement : splitArgumentsList( modifiers ) ) {
            int argStart = statement.indexOf( '(' );
            String methodName = statement.substring( 0,
                                                     argStart ).trim();
            addSetterToAction( action,
                               boundParams,
                               dmo,
                               imports,
                               isJavaDialect,
                               statement,
                               methodName );
        }
    }

    private void addSetterToAction( final ActionFieldList action,
                                    final Map<String, String> boundParams,
                                    final PackageDataModelOracle dmo,
                                    final Imports imports,
                                    final boolean isJavaDialect,
                                    final String statement,
                                    final String methodName ) {
        String field = getSettedField( methodName );
        String value = unwrapParenthesis( statement );
        String dataType = inferDataType( action,
                                         field,
                                         boundParams,
                                         dmo,
                                         imports );
        if ( dataType == null ) {
            dataType = inferDataType( value,
                                      boundParams,
                                      isJavaDialect );
        }
        action.addFieldValue( buildFieldValue( isJavaDialect,
                                               field,
                                               value,
                                               dataType,
                                               boundParams ) );
    }

    private ActionFieldValue buildFieldValue( final boolean isJavaDialect,
                                              String field,
                                              final String value,
                                              final String dataType,
                                              final Map<String, String> boundParams ) {
        if ( value.contains( "wiWorkItem.getResult" ) ) {
            field = field.substring( 0, 1 ).toUpperCase() + field.substring( 1 );
            String wiParam = field.substring( "Results".length() );
            if ( wiParam.equals( "BooleanResult" ) ) {
                return new ActionWorkItemFieldValue( field,
                                                     DataType.TYPE_BOOLEAN,
                                                     "WorkItem",
                                                     wiParam,
                                                     Boolean.class.getName() );
            } else if ( wiParam.equals( "StringResult" ) ) {
                return new ActionWorkItemFieldValue( field,
                                                     DataType.TYPE_STRING,
                                                     "WorkItem",
                                                     wiParam,
                                                     String.class.getName() );
            } else if ( wiParam.equals( "IntegerResult" ) ) {
                return new ActionWorkItemFieldValue( field,
                                                     DataType.TYPE_NUMERIC_INTEGER,
                                                     "WorkItem",
                                                     wiParam,
                                                     Integer.class.getName() );
            } else if ( wiParam.equals( "FloatResult" ) ) {
                return new ActionWorkItemFieldValue( field,
                                                     DataType.TYPE_NUMERIC_FLOAT,
                                                     "WorkItem",
                                                     wiParam,
                                                     Float.class.getName() );
            }
        }
        ActionFieldValue fieldValue = new ActionFieldValue( field,
                                                            adjustParam( dataType,
                                                                         value,
                                                                         boundParams,
                                                                         isJavaDialect ),
                                                            dataType );
        fieldValue.setNature( inferFieldNature( dataType,
                                                value,
                                                boundParams,
                                                isJavaDialect ) );
        return fieldValue;
    }

    private boolean isJavaIdentifier( final String name ) {
        if ( name == null || name.length() == 0 || !Character.isJavaIdentifierStart( name.charAt( 0 ) ) ) {
            return false;
        }
        for ( int i = 1; i < name.length(); i++ ) {
            if ( !Character.isJavaIdentifierPart( name.charAt( i ) ) ) {
                return false;
            }
        }
        return true;
    }

    private String getSettedField( final String methodName ) {
        if ( methodName.length() > 3 && methodName.startsWith( "set" ) ) {
            String field = methodName.substring( 3 );
            if ( Character.isUpperCase( field.charAt( 0 ) ) ) {
                return field.substring( 0,
                                        1 ).toLowerCase() + field.substring( 1 );
            } else {
                return field;
            }
        }
        return null;
    }

    private String getStatementType( final String fact,
                                     final Map<String, String> factsType ) {
        String type = null;
        if ( fact.startsWith( "new " ) ) {
            String inserted = fact.substring( 4 ).trim();
            if ( inserted.endsWith( "()" ) ) {
                type = inserted.substring( 0,
                                           inserted.length() - 2 ).trim();
            }
        } else {
            type = factsType.get( fact );
        }
        return type;
    }

    private Expr parseExpr( final String expr,
                            final boolean isJavaDialect,
                            final Map<String, String> boundParams,
                            final PackageDataModelOracle dmo ) {
        List<String> splittedExpr = splitExpression( expr );
        if ( splittedExpr.size() == 1 ) {
            String singleExpr = splittedExpr.get( 0 );
            if ( singleExpr.startsWith( "(" ) ) {
                return parseExpr( singleExpr.substring( 1 ),
                                  isJavaDialect,
                                  boundParams,
                                  dmo );
            } else if ( singleExpr.startsWith( "eval" ) ) {
                return new EvalExpr( unwrapParenthesis( singleExpr ) );
            } else {
                return new SimpleExpr( singleExpr,
                                       isJavaDialect,
                                       boundParams,
                                       dmo );
            }
        }
        ComplexExpr complexExpr = new ComplexExpr( splittedExpr.get( 1 ) );
        for ( int i = 0; i < splittedExpr.size(); i += 2 ) {
            complexExpr.subExprs.add( parseExpr( splittedExpr.get( i ),
                                                 isJavaDialect,
                                                 boundParams,
                                                 dmo ) );
        }
        return complexExpr;
    }

    private enum SplitterState {
        START, EXPR, PIPE, OR, AMPERSAND, AND, NESTED
    }

    private List<String> splitExpression( final String expr ) {
        List<String> splittedExpr = new ArrayList<String>();
        int nestingLevel = 0;
        SplitterState status = SplitterState.START;

        StringBuilder sb = new StringBuilder();
        for ( char ch : expr.toCharArray() ) {
            switch ( status ) {
                case START:
                    if ( ch == '(' ) {
                        status = SplitterState.NESTED;
                        nestingLevel++;
                    } else {
                        status = SplitterState.EXPR;
                        sb.append( ch );
                    }
                    break;
                case EXPR:
                    if ( ch == '|' ) {
                        status = SplitterState.PIPE;
                    } else if ( ch == '&' ) {
                        status = SplitterState.AMPERSAND;
                    } else {
                        sb.append( ch );
                    }
                    break;
                case PIPE:
                    if ( ch == '|' ) {
                        status = SplitterState.OR;
                    } else {
                        status = SplitterState.EXPR;
                        sb.append( '|' ).append( ch );
                    }
                    break;
                case AMPERSAND:
                    if ( ch == '&' ) {
                        status = SplitterState.AND;
                    } else {
                        status = SplitterState.EXPR;
                        sb.append( '&' ).append( ch );
                    }
                    break;
                case OR:
                case AND:
                    if ( ch == '=' || ch == '!' || ch == '<' || ch == '>' ) {
                        status = SplitterState.EXPR;
                        sb.append( status == SplitterState.AND ? "&& " : "|| " );
                        sb.append( ch );
                    } else if ( Character.isJavaIdentifierStart( ch ) ) {
                        String currentExpr = sb.toString().trim();
                        if ( currentExpr.length() > 0 ) {
                            splittedExpr.add( currentExpr );
                        }
                        splittedExpr.add( status == SplitterState.AND ? "&&" : "||" );
                        status = SplitterState.EXPR;
                        sb = new StringBuilder();
                        sb.append( ch );
                    } else if ( ch == '(' ) {
                        String currentExpr = sb.toString().trim();
                        if ( currentExpr.length() > 0 ) {
                            splittedExpr.add( currentExpr );
                        }
                        splittedExpr.add( status == SplitterState.AND ? "&&" : "||" );
                        status = SplitterState.NESTED;
                        nestingLevel++;
                        sb = new StringBuilder();
                        sb.append( ch );
                    }
                    break;
                case NESTED:
                    if ( ch == '(' ) {
                        nestingLevel++;
                        sb.append( ch );
                    } else if ( ch == ')' ) {
                        nestingLevel--;
                        if ( nestingLevel == 0 ) {
                            String currentExpr = sb.toString().trim();
                            if ( currentExpr.length() > 0 ) {
                                splittedExpr.add( "(" + currentExpr );
                            }
                            status = SplitterState.EXPR;
                            sb = new StringBuilder();
                        } else {
                            sb.append( ch );
                        }
                    } else {
                        sb.append( ch );
                    }
                    break;
            }
        }
        String currentExpr = sb.toString().trim();
        if ( currentExpr.length() > 0 ) {
            splittedExpr.add( currentExpr );
        }
        return splittedExpr;
    }

    private interface Expr {

        FieldConstraint asFieldConstraint( final RuleModel m,
                                           final FactPattern factPattern );
    }

    private static class SimpleExpr implements Expr {

        private final String expr;
        private final boolean isJavaDialect;
        private final Map<String, String> boundParams;
        private final PackageDataModelOracle dmo;

        private SimpleExpr( final String expr,
                            final boolean isJavaDialect,
                            final Map<String, String> boundParams,
                            final PackageDataModelOracle dmo ) {
            this.expr = expr;
            this.isJavaDialect = isJavaDialect;
            this.boundParams = boundParams;
            this.dmo = dmo;
        }

        public FieldConstraint asFieldConstraint( final RuleModel m,
                                                  final FactPattern factPattern ) {
            String fieldName = expr;

            String value = null;
            String operator = findNullOrNotNullOperator( expr );
            if ( operator != null ) {
                int opPos = expr.indexOf( operator );
                fieldName = expr.substring( 0,
                                            opPos ).trim();
            } else {
                operator = findOperator( expr );
                if ( operator != null ) {
                    int opPos = expr.lastIndexOf( operator );
                    fieldName = expr.substring( 0,
                                                opPos ).trim();
                    value = expr.substring( opPos + operator.length(),
                                            expr.length() ).trim();
                }
            }

            boolean isExpression = fieldName.contains( "." ) || fieldName.endsWith( "()" );
            return createFieldConstraint( m,
                                          factPattern,
                                          fieldName,
                                          value,
                                          operator,
                                          isExpression );
        }

        private SingleFieldConstraint createNullCheckFieldConstraint( final RuleModel m,
                                                                      final FactPattern factPattern,
                                                                      final String fieldName ) {
            return createFieldConstraint( m,
                                          factPattern,
                                          fieldName,
                                          null,
                                          null,
                                          true );
        }

        private SingleFieldConstraint createFieldConstraint( final RuleModel m,
                                                             final FactPattern factPattern,
                                                             final String fieldName,
                                                             String value,
                                                             final String operator,
                                                             final boolean isExpression ) {
            String operatorParams = null;
            if ( value != null && value.startsWith( "[" ) ) {
                int endSquare = value.indexOf( ']' );
                operatorParams = value.substring( 1,
                                                  endSquare ).trim();
                value = value.substring( endSquare + 1 ).trim();
            }

            SingleFieldConstraint fieldConstraint = isExpression ?
                    createExpressionBuilderConstraint( m,
                                                       factPattern,
                                                       fieldName,
                                                       operator,
                                                       value ) :
                    createSingleFieldConstraint( m,
                                                 factPattern,
                                                 fieldName,
                                                 operator,
                                                 value );

            if ( operatorParams != null ) {
                int i = 0;
                for ( String param : operatorParams.split( "," ) ) {
                    fieldConstraint.setParameter( "" + i++,
                                                  param.trim() );
                }
                fieldConstraint.setParameter( "org.drools.workbench.models.commons.backend.rule.visibleParameterSet",
                                              "" + i );
                fieldConstraint.setParameter( "org.drools.workbench.models.commons.backend.rule.operatorParameterGenerator",
                                              "org.drools.workbench.models.commons.backend.rule.CEPOperatorParameterDRLBuilder" );
            }

            if ( fieldName.equals( "this" ) && ( operator == null || operator.equals( "!= null" ) ) ) {
                fieldConstraint.setFieldType( DataType.TYPE_THIS );
            }
            fieldConstraint.setFactType( factPattern.getFactType() );

            ModelField field = findField( findFields( m,
                                                      dmo,
                                                      factPattern.getFactType() ),
                                          fieldConstraint.getFieldName() );

            if ( field != null && ( fieldConstraint.getFieldType() == null || fieldConstraint.getFieldType().trim().length() == 0 ) ) {
                fieldConstraint.setFieldType( field.getType() );
            }
            return fieldConstraint;
        }

        private SingleFieldConstraint createExpressionBuilderConstraint( final RuleModel m,
                                                                         final FactPattern factPattern,
                                                                         final String fieldName,
                                                                         final String operator,
                                                                         final String value ) {
            // TODO: we should find a way to know when the expression uses a getter and in this case create a plain SingleFieldConstraint
            //int dotPos = fieldName.lastIndexOf('.');
            //SingleFieldConstraint con = createSingleFieldConstraint(dotPos > 0 ? fieldName.substring(dotPos+1) : fieldName, operator, value);

            SingleFieldConstraint con = createSingleFieldConstraintEBLeftSide( m,
                                                                               factPattern,
                                                                               fieldName,
                                                                               operator,
                                                                               value );

            return con;
        }

        private SingleFieldConstraint createSingleFieldConstraint( final RuleModel m,
                                                                   final FactPattern factPattern,
                                                                   String fieldName,
                                                                   final String operator,
                                                                   final String value ) {
            SingleFieldConstraint con = new SingleFieldConstraint();
            fieldName = setFieldBindingOnContraint( factPattern.getFactType(),
                                                    fieldName,
                                                    m,
                                                    con,
                                                    boundParams );
            con.setFieldName( fieldName );
            setOperatorAndValueOnConstraint( m,
                                             operator,
                                             value,
                                             factPattern,
                                             con );

            //Setup parent relationships for SingleFieldConstraints
            for ( FieldConstraint fieldConstraint : factPattern.getFieldConstraints() ) {
                if ( fieldConstraint instanceof SingleFieldConstraint ) {
                    SingleFieldConstraint sfc = (SingleFieldConstraint) fieldConstraint;
                    if ( sfc.getOperator() != null && sfc.getOperator().equals( "!= null" ) ) {
                        int parentPos = fieldName.indexOf( sfc.getFieldName() + "." );
                        if ( parentPos >= 0 && !fieldName.substring( parentPos + sfc.getFieldName().length() + 1 ).contains( "." ) ) {
                            con.setParent( sfc );
                            break;
                        }
                    }
                }
            }

            if ( con.getParent() == null ) {
                con.setParent( createParentFor( m, factPattern, fieldName ) );
            }

            return con;
        }

        private SingleFieldConstraintEBLeftSide createSingleFieldConstraintEBLeftSide( final RuleModel m,
                                                                                       final FactPattern factPattern,
                                                                                       String fieldName,
                                                                                       final String operator,
                                                                                       final String value ) {
            SingleFieldConstraintEBLeftSide con = new SingleFieldConstraintEBLeftSide();

            fieldName = setFieldBindingOnContraint( factPattern.getFactType(),
                                                    fieldName,
                                                    m,
                                                    con,
                                                    boundParams );
            String classType = getFQFactType( m,
                                              factPattern.getFactType() );
            con.getExpressionLeftSide().appendPart( new ExpressionUnboundFact( factPattern ) );

            parseExpression( m,
                             classType,
                             fieldName,
                             con.getExpressionLeftSide() );

            setOperatorAndValueOnConstraint( m,
                                             operator,
                                             value,
                                             factPattern,
                                             con );

            return con;
        }

        private ExpressionFormLine parseExpression( final RuleModel m,
                                                    String factType,
                                                    final String fieldName,
                                                    final ExpressionFormLine expression ) {
            String[] splits = fieldName.split( "\\." );

            boolean isBoundParam = false;
            if ( factType == null ) {
                factType = getFQFactType( m,
                                          boundParams.get( splits[ 0 ].trim() ) );
                isBoundParam = true;
            }

            //An ExpressionPart can be a Field or a Method
            ModelField[] typeFields = findFields( m,
                                                  dmo,
                                                  factType );
            List<MethodInfo> methodInfos = getMethodInfosForType( m,
                                                                  dmo,
                                                                  factType );

            //Handle all but last expression part
            for ( int i = 0; i < splits.length - 1; i++ ) {
                String expressionPart = splits[ i ];

                //The first part of the expression may be a bound variable
                if ( boundParams.containsKey( expressionPart ) ) {
                    factType = getFQFactType( m,
                                              boundParams.get( expressionPart ) );
                    isBoundParam = true;

                    typeFields = findFields( m,
                                             dmo,
                                             factType );
                    methodInfos = getMethodInfosForType( m,
                                                         dmo,
                                                         factType );
                }
                if ( "this".equals( expressionPart ) ) {
                    expression.appendPart( new ExpressionField( expressionPart,
                                                                factType,
                                                                DataType.TYPE_THIS ) );

                } else if ( isBoundParam ) {
                    ModelField currentFact = findFact( dmo.getProjectModelFields(),
                                                       factType );
                    expression.appendPart( new ExpressionVariable( expressionPart,
                                                                   currentFact.getClassName(),
                                                                   currentFact.getType() ) );
                    isBoundParam = false;

                } else {
                    //An ExpressionPart can be a Field or a Method
                    String currentClassName = null;
                    ModelField currentField = findField( typeFields,
                                                         expressionPart );
                    if ( currentField != null ) {
                        currentClassName = currentField.getClassName();
                    }
                    MethodInfo currentMethodInfo = findMethodInfo( methodInfos,
                                                                   expressionPart );
                    if ( currentMethodInfo != null ) {
                        currentClassName = currentMethodInfo.getReturnClassType();
                    }

                    processExpressionPart( m,
                                           factType,
                                           currentField,
                                           currentMethodInfo,
                                           expression,
                                           expressionPart );

                    //Refresh field and method information based on current expression part
                    typeFields = findFields( m,
                                             dmo,
                                             currentClassName );
                    methodInfos = getMethodInfosForType( m,
                                                         dmo,
                                                         currentClassName );
                }
            }

            //Handle last expression part
            String expressionPart = splits[ splits.length - 1 ];
            ModelField currentField = findField( typeFields,
                                                 expressionPart );
            MethodInfo currentMethodInfo = findMethodInfo( methodInfos,
                                                           expressionPart );

            processExpressionPart( m,
                                   factType,
                                   currentField,
                                   currentMethodInfo,
                                   expression,
                                   expressionPart );

            return expression;
        }

        private void processExpressionPart( final RuleModel m,
                                            final String factType,
                                            final ModelField currentField,
                                            final MethodInfo currentMethodInfo,
                                            final ExpressionFormLine expression,
                                            final String expressionPart ) {
            if ( currentField == null ) {
                boolean isMethod = currentMethodInfo != null;
                if ( isMethod ) {
                    final ExpressionMethod em = new ExpressionMethod( currentMethodInfo.getName(),
                                                                      currentMethodInfo.getReturnClassType(),
                                                                      currentMethodInfo.getGenericType(),
                                                                      currentMethodInfo.getParametricReturnType() );
                    //Add applicable parameter values
                    final List<String> parameters = parseExpressionParameters( expressionPart );
                    for ( int index = 0; index < currentMethodInfo.getParams().size(); index++ ) {
                        final String paramDataType = currentMethodInfo.getParams().get( index );
                        final String paramValue = getParameterValue( paramDataType,
                                                                     parameters,
                                                                     index );
                        if ( paramValue != null ) {
                            final ExpressionFormLine param = new ExpressionFormLine( index );
                            param.appendPart( new ExpressionMethodParameter( paramValue,
                                                                             paramDataType,
                                                                             paramDataType ) );
                            em.putParam( paramDataType,
                                         param );
                        }
                    }
                    expression.appendPart( em );
                } else {
                    expression.appendPart( new ExpressionText( expressionPart ) );
                }

            } else if ( "Collection".equals( currentField.getType() ) ) {
                expression.appendPart( new ExpressionCollection( expressionPart,
                                                                 currentField.getClassName(),
                                                                 currentField.getType(),
                                                                 dmo.getProjectFieldParametersType().get( factType + "#" + expressionPart ) )
                                     );
            } else {
                expression.appendPart( new ExpressionField( expressionPart,
                                                            currentField.getClassName(),
                                                            currentField.getType() ) );
            }

        }

        private String getParameterValue( final String paramDataType,
                                          final List<String> parameters,
                                          final int index ) {
            if ( parameters == null || parameters.isEmpty() ) {
                return null;
            }
            if ( index < 0 || index > parameters.size() - 1 ) {
                return null;
            }

            return RuleModelPersistenceHelper.adjustParam( paramDataType,
                                                           parameters.get( index ).trim(),
                                                           boundParams,
                                                           isJavaDialect );
        }

        private String getFQFactType( final RuleModel ruleModel,
                                      final String factType ) {

            Set<String> factTypes = dmo.getProjectModelFields().keySet();

            if ( factTypes.contains( ruleModel.getPackageName() + "." + factType ) ) {
                return ruleModel.getPackageName() + "." + factType;
            }

            for ( String item : ruleModel.getImports().getImportStrings() ) {
                if ( item.endsWith( "." + factType ) ) {
                    return item;
                }
            }

            for ( String type : factTypes ) {
                if ( type.endsWith( "." + factType ) ) {
                    return type;
                }
            }

            return factType;
        }

        private ModelField findFact( final Map<String, ModelField[]> modelFields,
                                     final String factType ) {
            final ModelField[] typeFields = modelFields.get( factType );
            if ( typeFields == null ) {
                return null;
            }
            for ( ModelField typeField : typeFields ) {
                if ( typeField.getType().equals( DataType.TYPE_THIS ) ) {
                    return typeField;
                }
            }
            return null;
        }

        private SingleFieldConstraint createParentFor( final RuleModel m,
                                                       final FactPattern factPattern,
                                                       final String fieldName ) {
            int dotPos = fieldName.lastIndexOf( '.' );
            if ( dotPos > 0 ) {
                SingleFieldConstraint constraint = createNullCheckFieldConstraint( m,
                                                                                   factPattern,
                                                                                   fieldName.substring( 0,
                                                                                                        dotPos ) );
                factPattern.addConstraint( constraint );
                return constraint;
            }
            return null;
        }

        private String setFieldBindingOnContraint( final String factType,
                                                   String fieldName,
                                                   final RuleModel model,
                                                   final SingleFieldConstraint con,
                                                   final Map<String, String> boundParams ) {
            int colonPos = fieldName.indexOf( ':' );
            if ( colonPos > 0 ) {
                String fieldBinding = fieldName.substring( 0,
                                                           colonPos ).trim();
                con.setFieldBinding( fieldBinding );
                fieldName = fieldName.substring( colonPos + 1 ).trim();

                ModelField[] fields = findFields( model,
                                                  dmo,
                                                  factType );
                if ( fields != null ) {
                    for ( ModelField field : fields ) {
                        if ( field.getName().equals( fieldName ) ) {
                            boundParams.put( fieldBinding,
                                             field.getType() );
                        }
                    }
                }

            }
            return fieldName;
        }

        private String setOperatorAndValueOnConstraint( final RuleModel m,
                                                        final String operator,
                                                        final String value,
                                                        final FactPattern factPattern,
                                                        final SingleFieldConstraint con ) {
            con.setOperator( operator );
            String type = null;
            boolean isAnd = false;
            String[] splittedValue = new String[ 0 ];
            if ( value != null ) {
                isAnd = value.contains( "&&" );
                splittedValue = isAnd ? value.split( "\\&\\&" ) : value.split( "\\|\\|" );
                type = setValueOnConstraint( m,
                                             operator,
                                             factPattern,
                                             con,
                                             splittedValue[ 0 ].trim() );
            }

            if ( splittedValue.length > 1 ) {
                ConnectiveConstraint[] connectiveConstraints = new ConnectiveConstraint[ splittedValue.length - 1 ];
                for ( int i = 0; i < connectiveConstraints.length; i++ ) {
                    String constraint = splittedValue[ i + 1 ].trim();
                    String connectiveOperator = findOperator( constraint );
                    String connectiveValue = constraint.substring( connectiveOperator.length() ).trim();

                    connectiveConstraints[ i ] = new ConnectiveConstraint();
                    connectiveConstraints[ i ].setOperator( ( isAnd ? "&& " : "|| " ) + connectiveOperator );
                    setValueOnConstraint( m,
                                          operator,
                                          factPattern,
                                          connectiveConstraints[ i ],
                                          connectiveValue );
                }
                con.setConnectives( connectiveConstraints );
            }
            return type;
        }

        private String setValueOnConstraint( final RuleModel m,
                                             final String operator,
                                             final FactPattern factPattern,
                                             final BaseSingleFieldConstraint con,
                                             String value ) {
            String type = null;
            if ( value.startsWith( "\"" ) ) {
                type = DataType.TYPE_STRING;
                con.setConstraintValueType( SingleFieldConstraint.TYPE_LITERAL );
                con.setValue( value.substring( 1,
                                               value.length() - 1 ) );
            } else if ( value.startsWith( "(" ) ) {
                if ( operator != null && operator.contains( "in" ) ) {
                    value = unwrapParenthesis( value );
                    type = value.startsWith( "\"" ) ? DataType.TYPE_STRING : DataType.TYPE_NUMERIC_INTEGER;
                    con.setConstraintValueType( SingleFieldConstraint.TYPE_LITERAL );
                    con.setValue( value );
                } else {
                    con.setConstraintValueType( SingleFieldConstraint.TYPE_RET_VALUE );
                    con.setValue( unwrapParenthesis( value ) );
                }
            } else {
                if ( !Character.isDigit( value.charAt( 0 ) ) ) {
                    if ( value.equals( "true" ) || value.equals( "false" ) ) {
                        type = DataType.TYPE_BOOLEAN;
                        con.setConstraintValueType( BaseSingleFieldConstraint.TYPE_ENUM );
                    } else if ( isEnumerationValue( m,
                                                    factPattern,
                                                    con ) ) {
                        type = DataType.TYPE_COMPARABLE;
                        con.setConstraintValueType( SingleFieldConstraint.TYPE_ENUM );
                    } else if ( value.indexOf( '.' ) > 0 && boundParams.containsKey( value.substring( 0,
                                                                                                      value.indexOf( '.' ) ).trim() ) ) {
                        con.setExpressionValue( parseExpression( m,
                                                                 null,
                                                                 value,
                                                                 new ExpressionFormLine() ) );
                        con.setConstraintValueType( BaseSingleFieldConstraint.TYPE_EXPR_BUILDER_VALUE );
                        value = "";
                    } else {
                        con.setConstraintValueType( SingleFieldConstraint.TYPE_VARIABLE );
                    }
                } else {
                    if ( value.endsWith( "I" ) ) {
                        type = DataType.TYPE_NUMERIC_BIGINTEGER;
                        value = value.substring( 0,
                                                 value.length() - 1 );
                    } else if ( value.endsWith( "B" ) ) {
                        type = DataType.TYPE_NUMERIC_BIGDECIMAL;
                        value = value.substring( 0,
                                                 value.length() - 1 );
                    } else if ( value.endsWith( "f" ) ) {
                        type = DataType.TYPE_NUMERIC_FLOAT;
                    } else if ( value.endsWith( "d" ) ) {
                        type = DataType.TYPE_NUMERIC_DOUBLE;
                    } else {
                        type = DataType.TYPE_NUMERIC_INTEGER;
                    }
                    con.setConstraintValueType( SingleFieldConstraint.TYPE_LITERAL );
                }
                con.setValue( value );
            }
            if ( con instanceof SingleFieldConstraint ) {
                ( (SingleFieldConstraint) con ).setFieldType( type );
            } else if ( con instanceof ConnectiveConstraint ) {
                ( (ConnectiveConstraint) con ).setFieldType( type );
            }
            return type;
        }

        private boolean isEnumerationValue( final RuleModel ruleModel,
                                            final FactPattern factPattern,
                                            final BaseSingleFieldConstraint con ) {
            String factType = null;
            String fieldName = null;
            if ( con instanceof SingleFieldConstraintEBLeftSide ) {
                SingleFieldConstraintEBLeftSide sfcex = (SingleFieldConstraintEBLeftSide) con;
                List<ExpressionPart> sfcexParts = sfcex.getExpressionLeftSide().getParts();
                factType = sfcexParts.get( sfcexParts.size() - 1 ).getPrevious().getClassType();
                fieldName = sfcex.getFieldName();
            } else if ( con instanceof SingleFieldConstraint ) {
                factType = factPattern.getFactType();
                fieldName = ( (SingleFieldConstraint) con ).getFieldName();
            } else if ( con instanceof ConnectiveConstraint ) {
                factType = factPattern.getFactType();
                fieldName = ( (ConnectiveConstraint) con ).getFieldName();
            }

            if ( factType == null || fieldName == null ) {
                return false;
            }

            final String fullyQualifiedFactType = getFQFactType( ruleModel,
                                                                 factType );
            final String key = fullyQualifiedFactType + "#" + fieldName;
            final Map<String, String[]> projectJavaEnumDefinitions = dmo.getProjectJavaEnumDefinitions();

            return projectJavaEnumDefinitions.containsKey( key );
        }
    }

    private static class ComplexExpr implements Expr {

        private final List<Expr> subExprs = new ArrayList<Expr>();
        private final String connector;

        private ComplexExpr( final String connector ) {
            this.connector = connector;
        }

        public FieldConstraint asFieldConstraint( final RuleModel m,
                                                  final FactPattern factPattern ) {
            CompositeFieldConstraint comp = new CompositeFieldConstraint();
            comp.setCompositeJunctionType( connector.equals( "&&" ) ? CompositeFieldConstraint.COMPOSITE_TYPE_AND : CompositeFieldConstraint.COMPOSITE_TYPE_OR );
            for ( Expr expr : subExprs ) {
                comp.addConstraint( expr.asFieldConstraint( m,
                                                            factPattern ) );
            }
            return comp;
        }
    }

    private static class EvalExpr implements Expr {

        private final String expr;

        private EvalExpr( final String expr ) {
            this.expr = expr;
        }

        public FieldConstraint asFieldConstraint( final RuleModel m,
                                                  final FactPattern factPattern ) {
            SingleFieldConstraint con = new SingleFieldConstraint();
            con.setConstraintValueType( SingleFieldConstraint.TYPE_PREDICATE );
            con.setValue( expr );
            return con;
        }
    }

}
TOP

Related Classes of org.drools.workbench.models.commons.backend.rule.RuleModelDRLPersistenceImpl$RHSActionVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.