/* * Copyright 2006 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.rule.builder; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.drools.base.ClassObjectType; import org.drools.base.DroolsQuery; import org.drools.base.FieldFactory; import org.drools.base.ValueType; import org.drools.base.evaluators.EvaluatorDefinition; import org.drools.base.evaluators.EvaluatorDefinition.Target; import org.drools.base.field.ObjectFieldImpl; import org.drools.compiler.DescrBuildError; import org.drools.compiler.Dialect; import org.drools.core.util.StringUtils; import org.drools.facttemplates.FactTemplate; import org.drools.facttemplates.FactTemplateFieldExtractor; import org.drools.facttemplates.FactTemplateObjectType; import org.drools.lang.MVELDumper; import org.drools.lang.descr.AndDescr; import org.drools.lang.descr.BaseDescr; import org.drools.lang.descr.BehaviorDescr; import org.drools.lang.descr.FieldBindingDescr; import org.drools.lang.descr.FieldConstraintDescr; import org.drools.lang.descr.LiteralRestrictionDescr; import org.drools.lang.descr.OrDescr; import org.drools.lang.descr.PatternDescr; import org.drools.lang.descr.PredicateDescr; import org.drools.lang.descr.QualifiedIdentifierRestrictionDescr; import org.drools.lang.descr.RestrictionConnectiveDescr; import org.drools.lang.descr.RestrictionDescr; import org.drools.lang.descr.ReturnValueRestrictionDescr; import org.drools.lang.descr.SlidingWindowDescr; import org.drools.lang.descr.VariableRestrictionDescr; import org.drools.rule.AbstractCompositeConstraint; import org.drools.rule.AbstractCompositeRestriction; import org.drools.rule.AndCompositeRestriction; import org.drools.rule.AndConstraint; import org.drools.rule.Behavior; import org.drools.rule.Declaration; import org.drools.rule.LiteralConstraint; import org.drools.rule.LiteralRestriction; import org.drools.rule.MultiRestrictionFieldConstraint; import org.drools.rule.MutableTypeConstraint; import org.drools.rule.OrCompositeRestriction; import org.drools.rule.OrConstraint; import org.drools.rule.Pattern; import org.drools.rule.PatternSource; import org.drools.rule.PredicateConstraint; import org.drools.rule.Query; import org.drools.rule.ReturnValueConstraint; import org.drools.rule.ReturnValueRestriction; import org.drools.rule.Rule; import org.drools.rule.RuleConditionElement; import org.drools.rule.SlidingLengthWindow; import org.drools.rule.SlidingTimeWindow; import org.drools.rule.UnificationRestriction; import org.drools.rule.VariableConstraint; import org.drools.rule.VariableRestriction; import org.drools.rule.builder.dialect.mvel.MVELDialect; import org.drools.spi.AcceptsReadAccessor; import org.drools.spi.Constraint; import org.drools.spi.Evaluator; import org.drools.spi.FieldValue; import org.drools.spi.InternalReadAccessor; import org.drools.spi.ObjectType; import org.drools.spi.PatternExtractor; import org.drools.spi.Restriction; import org.drools.spi.Constraint.ConstraintType; import org.mvel2.ParserContext; import org.mvel2.compiler.ExpressionCompiler; /** * A builder for patterns * * @author etirelli */ public class PatternBuilder implements RuleConditionBuilder { public PatternBuilder() { } public RuleConditionElement build(RuleBuildContext context, BaseDescr descr) { return this.build( context, descr, null ); } /** * Build a pattern for the given descriptor in the current * context and using the given utils object * * @param context * @param utils * @param patternDescr * @return */ public RuleConditionElement build(RuleBuildContext context, BaseDescr descr, Pattern prefixPattern) { final PatternDescr patternDescr = (PatternDescr) descr; if ( patternDescr.getObjectType() == null || patternDescr.getObjectType().equals( "" ) ) { context.getErrors().add( new DescrBuildError( context.getParentDescr(), patternDescr, null, "ObjectType not correctly defined" ) ); return null; } ObjectType objectType = null; final FactTemplate factTemplate = context.getPkg().getFactTemplate( patternDescr.getObjectType() ); if ( factTemplate != null ) { objectType = new FactTemplateObjectType( factTemplate ); } else { try { final Class userProvidedClass = context.getDialect().getTypeResolver().resolveType( patternDescr.getObjectType() ); final boolean isEvent = context.getPkg().isEvent( userProvidedClass ); objectType = new ClassObjectType( userProvidedClass, isEvent ); } catch ( final ClassNotFoundException e ) { // swallow as we'll do another check in a moment and then record the problem } } // lets see if it maps to a query if ( objectType == null ) { Rule rule = context.getPkg().getRule( patternDescr.getObjectType() ); if ( rule != null && rule instanceof Query ) { // it's a query so delegate to the QueryElementBuilder QueryElementBuilder qeBuilder = new QueryElementBuilder(); return qeBuilder.build( context, descr, prefixPattern ); } else { // this isn't a query either, so log an error context.getErrors().add( new DescrBuildError( context.getParentDescr(), patternDescr, null, "Unable to resolve ObjectType '" + patternDescr.getObjectType() + "'" ) ); return null; } } Pattern pattern; if ( patternDescr.getIdentifier() != null && !patternDescr.getIdentifier().equals( "" ) ) { if ( context.getDeclarationResolver().isDuplicated( context.getRule(), patternDescr.getIdentifier() ) ) { // This declaration already exists, so throw an Exception context.getErrors().add( new DescrBuildError( context.getParentDescr(), patternDescr, null, "Duplicate declaration for variable '" + patternDescr.getIdentifier() + "' in the rule '" + context.getRule().getName() + "'" ) ); } pattern = new Pattern( context.getNextPatternId(), 0, // offset is 0 by default objectType, patternDescr.getIdentifier(), patternDescr.isInternalFact() ); if ( objectType instanceof ClassObjectType ) { // make sure PatternExtractor is wired up to correct ClassObjectType and set as a target for rewiring context.getPkg().getClassFieldAccessorStore().getClassObjectType( ((ClassObjectType) objectType), (PatternExtractor) pattern.getDeclaration().getExtractor() ); } } else { pattern = new Pattern( context.getNextPatternId(), 0, // offset is 0 by default objectType, null ); } if ( objectType instanceof ClassObjectType ) { // make sure the Pattern is wired up to correct ClassObjectType and set as a target for rewiring context.getPkg().getClassFieldAccessorStore().getClassObjectType( ((ClassObjectType) objectType), pattern ); } //context.getPkg().getClassFieldAccessorStore().get // adding the newly created pattern to the build stack // this is necessary in case of local declaration usage context.getBuildStack().push( pattern ); for ( final Iterator it = patternDescr.getDescrs().iterator(); it.hasNext(); ) { final Object object = it.next(); buildConstraint( context, pattern, object, null ); } if ( patternDescr.getSource() != null ) { // we have a pattern source, so build it RuleConditionBuilder builder = (RuleConditionBuilder) context.getDialect().getBuilder( patternDescr.getSource().getClass() ); PatternSource source = (PatternSource) builder.build( context, patternDescr.getSource() ); pattern.setSource( source ); } for ( BehaviorDescr behaviorDescr : patternDescr.getBehaviors() ) { if( pattern.getObjectType().isEvent() ) { if ( Behavior.BehaviorType.TIME_WINDOW.matches( behaviorDescr.getType() ) ) { SlidingWindowDescr swd = (SlidingWindowDescr) behaviorDescr; SlidingTimeWindow window = new SlidingTimeWindow( swd.getLength() ); pattern.addBehavior( window ); } else if ( Behavior.BehaviorType.LENGTH_WINDOW.matches( behaviorDescr.getType() ) ) { SlidingWindowDescr swd = (SlidingWindowDescr) behaviorDescr; SlidingLengthWindow window = new SlidingLengthWindow( (int) swd.getLength() ); pattern.addBehavior( window ); } } else { // Some behaviors can only be assigned to patterns declared as events context.getErrors().add( new DescrBuildError( context.getParentDescr(), patternDescr, null, "A Sliding Window behavior can only be assigned to patterns declared with @role( event ). The pattern '" + pattern.getObjectType() + "' in the rule '" + context.getRule().getName() + "' is not declared as an Event." ) ); } } // poping the pattern context.getBuildStack().pop(); return pattern; } private void buildConstraint(final RuleBuildContext context, final Pattern pattern, final Object constraint, final AbstractCompositeConstraint container) { if ( constraint instanceof FieldBindingDescr ) { build( context, pattern, (FieldBindingDescr) constraint ); } else if ( constraint instanceof FieldConstraintDescr ) { build( context, pattern, (FieldConstraintDescr) constraint, container ); } else if ( constraint instanceof PredicateDescr ) { build( context, pattern, (PredicateDescr) constraint, container ); } else if ( constraint instanceof AndDescr ) { AndConstraint and = new AndConstraint(); for ( Iterator it = ((AndDescr) constraint).getDescrs().iterator(); it.hasNext(); ) { this.buildConstraint( context, pattern, it.next(), and ); } if ( container == null ) { pattern.addConstraint( and ); } else { if ( and.getType().equals( Constraint.ConstraintType.UNKNOWN ) ) { this.setConstraintType( pattern, (MutableTypeConstraint) and ); } container.addConstraint( and ); } } else if ( constraint instanceof OrDescr ) { OrConstraint or = new OrConstraint(); for ( Iterator it = ((OrDescr) constraint).getDescrs().iterator(); it.hasNext(); ) { this.buildConstraint( context, pattern, it.next(), or ); } if ( container == null ) { pattern.addConstraint( or ); } else { if ( or.getType().equals( Constraint.ConstraintType.UNKNOWN ) ) { this.setConstraintType( pattern, (MutableTypeConstraint) or ); } container.addConstraint( or ); } } else { context.getErrors().add( new DescrBuildError( context.getParentDescr(), (BaseDescr) constraint, null, "This is a bug: unable to build constraint descriptor: '" + constraint + "' in rule '" + context.getRule().getName() + "'" ) ); } } private void build(final RuleBuildContext context, final Pattern pattern, final FieldConstraintDescr fieldConstraintDescr, final AbstractCompositeConstraint container) { String fieldName = fieldConstraintDescr.getFieldName(); if ( fieldName.indexOf( '[' ) > -1 ) { rewriteToEval( context, pattern, fieldConstraintDescr, container ); // after building the predicate, we are done, so return return; } if ( fieldName.indexOf( '.' ) > -1 ) { // we have a composite field name String[] identifiers = fieldName.split( "\\." ); if ( identifiers.length == 2 && ((pattern.getDeclaration() != null && identifiers[0].equals( pattern.getDeclaration().getIdentifier() )) || ("this".equals( identifiers[0] ))) ) { // we have a self reference, so, it is fine to do direct access fieldName = identifiers[1]; } else { rewriteToEval( context, pattern, fieldConstraintDescr, container ); // after building the predicate, we are done, so return return; } } // if it is not a complex expression, just build a simple field constraint final InternalReadAccessor extractor = getFieldReadAccessor( context, fieldConstraintDescr, pattern.getObjectType(), fieldName, null, false ); if ( extractor == null ) { if ( fieldConstraintDescr.getFieldName().startsWith( "this." ) ) { // it may still be MVEL special syntax, like map key syntax, so try eval rewriteToEval( context, pattern, fieldConstraintDescr, container ); // after building the predicate, we are done, so return return; } else { context.getErrors().add( new DescrBuildError( context.getParentDescr(), fieldConstraintDescr, null, "Unable to create Field Extractor for '" + fieldName + "' of '" + pattern.getObjectType().toString() + "' in rule '" + context.getRule().getName() + "'" ) ); return; } } Restriction restriction = createRestriction( context, pattern, fieldConstraintDescr, fieldConstraintDescr.getRestriction(), extractor ); if ( restriction == null ) { // error was already logged during restriction creation failure return; } Constraint constraint = null; if ( restriction instanceof AbstractCompositeRestriction ) { constraint = new MultiRestrictionFieldConstraint( extractor, restriction ); } else if ( restriction instanceof LiteralRestriction ) { constraint = new LiteralConstraint( extractor, (LiteralRestriction) restriction ); registerReadAccessor( context, pattern.getObjectType(), fieldName, (LiteralConstraint) constraint ); registerReadAccessor( context, pattern.getObjectType(), fieldName, (LiteralRestriction) restriction ); } else if ( restriction instanceof VariableRestriction || restriction instanceof UnificationRestriction ) { constraint = new VariableConstraint( extractor, restriction ); registerReadAccessor( context, pattern.getObjectType(), fieldName, (AcceptsReadAccessor) restriction ); registerReadAccessor( context, pattern.getObjectType(), fieldName, (AcceptsReadAccessor) restriction ); } else if ( restriction instanceof ReturnValueRestriction ) { constraint = new ReturnValueConstraint( extractor, (ReturnValueRestriction) restriction ); registerReadAccessor( context, pattern.getObjectType(), fieldName, (ReturnValueConstraint) constraint ); registerReadAccessor( context, pattern.getObjectType(), fieldName, (ReturnValueRestriction) restriction ); } else { context.getErrors().add( new DescrBuildError( context.getParentDescr(), fieldConstraintDescr, null, "This is a bug: Unkown restriction type '" + restriction.getClass() + "' for pattern '" + pattern.getObjectType().toString() + "' in rule '" + context.getRule().getName() + "'" ) ); } if ( container == null ) { pattern.addConstraint( constraint ); } else { if ( constraint.getType().equals( Constraint.ConstraintType.UNKNOWN ) ) { this.setConstraintType( pattern, (MutableTypeConstraint) constraint ); } container.addConstraint( constraint ); } } /** * @param pattern * @param constraint */ private void setConstraintType(final Pattern container, final MutableTypeConstraint constraint) { final Declaration[] declarations = constraint.getRequiredDeclarations(); boolean isAlphaConstraint = true; for ( int i = 0; isAlphaConstraint && i < declarations.length; i++ ) { if ( !declarations[i].isGlobal() && declarations[i].getPattern() != container ) { isAlphaConstraint = false; } } ConstraintType type = isAlphaConstraint ? ConstraintType.ALPHA : ConstraintType.BETA; constraint.setType( type ); } private void rewriteToEval(final RuleBuildContext context, final Pattern pattern, final FieldConstraintDescr fieldConstraintDescr, final AbstractCompositeConstraint container) { // it is a complex expression, so we need to turn it into an MVEL predicate Dialect dialect = context.getDialect(); // switch to MVEL dialect MVELDialect mvelDialect = (MVELDialect) context.getDialect( "mvel" ); boolean strictMode = mvelDialect.isStrictMode(); mvelDialect.setStrictMode( false ); context.setDialect( mvelDialect ); // analyze field type: Class resultType = getFieldReturnType( pattern, fieldConstraintDescr ); PredicateDescr predicateDescr = new PredicateDescr(); MVELDumper dumper = new MVELDumper(context); predicateDescr.setContent( dumper.dump( fieldConstraintDescr, Date.class.isAssignableFrom( resultType ) ) ); build( context, pattern, predicateDescr, container ); mvelDialect.setStrictMode( strictMode ); // fall back to original dialect context.setDialect( dialect ); } /** * @param pattern * @param fieldConstraintDescr * @return */ private Class getFieldReturnType(final Pattern pattern, final FieldConstraintDescr fieldConstraintDescr) { String dummyField = "__DUMMY__"; String dummyExpr = dummyField + "." + fieldConstraintDescr.getFieldName(); ExpressionCompiler compiler = new ExpressionCompiler( dummyExpr ); ParserContext mvelcontext = new ParserContext(); mvelcontext.addInput( dummyField, ((ClassObjectType) pattern.getObjectType()).getClassType() ); compiler.compile( mvelcontext ); Class resultType = compiler.getReturnType(); return resultType; } private Restriction createRestriction(final RuleBuildContext context, final Pattern pattern, final FieldConstraintDescr fieldConstraintDescr, final RestrictionConnectiveDescr top, final InternalReadAccessor extractor) { Restriction[] restrictions = new Restriction[top.getRestrictions().size()]; int index = 0; for ( Iterator it = top.getRestrictions().iterator(); it.hasNext(); ) { RestrictionDescr restrictionDescr = (RestrictionDescr) it.next(); if ( restrictionDescr instanceof RestrictionConnectiveDescr ) { restrictions[index++] = this.createRestriction( context, pattern, fieldConstraintDescr, (RestrictionConnectiveDescr) restrictionDescr, extractor ); } else { restrictions[index] = buildRestriction( context, pattern, extractor, fieldConstraintDescr, restrictionDescr ); if ( restrictions[index] == null ) { context.getErrors().add( new DescrBuildError( context.getParentDescr(), fieldConstraintDescr, null, "Unable to create restriction '" + restrictionDescr.toString() + "' for field '" + fieldConstraintDescr.getFieldName() + "' in the rule '" + context.getRule().getName() + "'" ) ); } index++; } } if ( restrictions.length > 1 ) { AbstractCompositeRestriction composite = null; if ( top.getConnective() == RestrictionConnectiveDescr.AND ) { composite = new AndCompositeRestriction( restrictions ); } else if ( top.getConnective() == RestrictionConnectiveDescr.OR ) { composite = new OrCompositeRestriction( restrictions ); } else { context.getErrors().add( new DescrBuildError( context.getParentDescr(), fieldConstraintDescr, null, "This is a bug: Impossible to create a composite restriction for connective: " + top.getConnective() + "' for field '" + fieldConstraintDescr.getFieldName() + "' in the rule '" + context.getRule().getName() + "'" ) ); } return composite; } else if ( restrictions.length == 1 ) { return restrictions[0]; } context.getErrors().add( new DescrBuildError( context.getParentDescr(), fieldConstraintDescr, null, "This is a bug: trying to create a restriction for an empty restriction list for field '" + fieldConstraintDescr.getFieldName() + "' in the rule '" + context.getRule().getName() + "'" ) ); return null; } private void build(final RuleBuildContext context, final Pattern pattern, final FieldBindingDescr fieldBindingDescr) { if ( context.getDeclarationResolver().isDuplicated( context.getRule(), fieldBindingDescr.getIdentifier() ) ) { // This declaration already exists, so throw an Exception context.getErrors().add( new DescrBuildError( context.getParentDescr(), fieldBindingDescr, null, "Duplicate declaration for variable '" + fieldBindingDescr.getIdentifier() + "' in the rule '" + context.getRule().getName() + "'" ) ); return; } Declaration declr = pattern.addDeclaration( fieldBindingDescr.getIdentifier() ); final InternalReadAccessor extractor = getFieldReadAccessor( context, fieldBindingDescr, pattern.getObjectType(), fieldBindingDescr.getFieldName(), declr, true ); } @SuppressWarnings("unchecked") private void build(final RuleBuildContext context, final Pattern pattern, final PredicateDescr predicateDescr, final AbstractCompositeConstraint container) { Map<String, Class< ? >> declarations = getDeclarationsMap( predicateDescr, context ); Map<String, Class< ? >> globals = context.getPackageBuilder().getGlobals(); final Dialect.AnalysisResult analysis = context.getDialect().analyzeExpression( context, predicateDescr, predicateDescr.getContent(), new Map[]{declarations, globals} ); if ( analysis == null ) { // something bad happened return; } // this will return an array with 2 lists // where first list is from rule local variables // second list is from global variables final List[] usedIdentifiers = analysis.getBoundIdentifiers(); final List tupleDeclarations = new ArrayList(); final List factDeclarations = new ArrayList(); for ( int i = 0, size = usedIdentifiers[0].size(); i < size; i++ ) { final Declaration decl = context.getDeclarationResolver().getDeclaration( context.getRule(), (String) usedIdentifiers[0].get( i ) ); if ( decl.getPattern() == pattern ) { factDeclarations.add( decl ); } else { tupleDeclarations.add( decl ); } } this.createImplicitBindings( context, pattern, analysis.getNotBoundedIdentifiers(), factDeclarations ); final Declaration[] previousDeclarations = (Declaration[]) tupleDeclarations.toArray( new Declaration[tupleDeclarations.size()] ); final Declaration[] localDeclarations = (Declaration[]) factDeclarations.toArray( new Declaration[factDeclarations.size()] ); final String[] requiredGlobals = (String[]) usedIdentifiers[1].toArray( new String[usedIdentifiers[1].size()] ); final PredicateConstraint predicateConstraint = new PredicateConstraint( null, previousDeclarations, localDeclarations, requiredGlobals ); if ( container == null ) { pattern.addConstraint( predicateConstraint ); } else { if ( predicateConstraint.getType().equals( Constraint.ConstraintType.UNKNOWN ) ) { this.setConstraintType( pattern, (MutableTypeConstraint) predicateConstraint ); } container.addConstraint( predicateConstraint ); } final PredicateBuilder builder = context.getDialect().getPredicateBuilder(); builder.build( context, usedIdentifiers, previousDeclarations, localDeclarations, predicateConstraint, predicateDescr ); } private Map<String, Class< ? >> getDeclarationsMap(final BaseDescr baseDescr, final RuleBuildContext context) { Map<String, Class< ? >> declarations = new HashMap<String, Class< ? >>(); for ( Map.Entry<String, Declaration> entry : context.getDeclarationResolver().getDeclarations( context.getRule() ).entrySet() ) { if ( entry.getValue().getExtractor() == null ) { context.getErrors().add( new DescrBuildError( context.getParentDescr(), baseDescr, null, "Field Reader does not exist for declaration '" + entry.getKey() + "' in'" + baseDescr + "' in the rule '" + context.getRule().getName() + "'" ) ); continue; } declarations.put( entry.getKey(), entry.getValue().getExtractor().getExtractToClass() ); } return declarations; } /** * @param context * @param utils * @param pattern * @param usedIdentifiers * @param NOT_BOUND_INDEX * @param factDeclarations */ private void createImplicitBindings(final RuleBuildContext context, final Pattern pattern, final List unboundIdentifiers, final List factDeclarations) { // the following will create the implicit bindings for ( int i = 0, size = unboundIdentifiers.size(); i < size; i++ ) { final String identifier = (String) unboundIdentifiers.get( i ); Declaration declaration = createDeclarationObject( context, identifier, pattern ); if ( declaration != null ) { factDeclarations.add( declaration ); } } } /** * Creates a declaration object for the field identified by the given identifier * on the give pattern object * * @param context * @param identifier * @param pattern * @return */ private Declaration createDeclarationObject(final RuleBuildContext context, final String identifier, final Pattern pattern) { final FieldBindingDescr implicitBinding = new FieldBindingDescr( identifier, identifier ); final Declaration declaration = new Declaration( identifier, pattern ); final InternalReadAccessor extractor = getFieldReadAccessor( context, implicitBinding, pattern.getObjectType(), implicitBinding.getFieldName(), declaration, false ); if ( extractor == null ) { return null; } return declaration; } private Restriction buildRestriction(final RuleBuildContext context, final Pattern pattern, final InternalReadAccessor extractor, final FieldConstraintDescr fieldConstraintDescr, final RestrictionDescr restrictionDescr) { Restriction restriction = null; if ( restrictionDescr instanceof LiteralRestrictionDescr ) { restriction = buildRestriction( context, extractor, fieldConstraintDescr, (LiteralRestrictionDescr) restrictionDescr ); } else if ( restrictionDescr instanceof QualifiedIdentifierRestrictionDescr ) { restriction = buildRestriction( context, extractor, fieldConstraintDescr, (QualifiedIdentifierRestrictionDescr) restrictionDescr ); } else if ( restrictionDescr instanceof VariableRestrictionDescr ) { restriction = buildRestriction( context, extractor, fieldConstraintDescr, (VariableRestrictionDescr) restrictionDescr ); } else if ( restrictionDescr instanceof ReturnValueRestrictionDescr ) { restriction = buildRestriction( context, pattern, extractor, fieldConstraintDescr, (ReturnValueRestrictionDescr) restrictionDescr ); } return restriction; } private Restriction buildRestriction(final RuleBuildContext context, final InternalReadAccessor extractor, final FieldConstraintDescr fieldConstraintDescr, final VariableRestrictionDescr variableRestrictionDescr) { if ( variableRestrictionDescr.getIdentifier() == null || variableRestrictionDescr.getIdentifier().equals( "" ) ) { context.getErrors().add( new DescrBuildError( context.getParentDescr(), variableRestrictionDescr, null, "Identifier not defined for binding field '" + fieldConstraintDescr.getFieldName() + "'" ) ); return null; } Declaration declaration = context.getDeclarationResolver().getDeclaration( context.getRule(), variableRestrictionDescr.getIdentifier() ); if ( declaration == null ) { // trying to create implicit declaration final Pattern thisPattern = (Pattern) context.getBuildStack().peek(); final Declaration implicit = this.createDeclarationObject( context, variableRestrictionDescr.getIdentifier(), thisPattern ); if ( implicit != null ) { declaration = implicit; } else { context.getErrors().add( new DescrBuildError( context.getParentDescr(), variableRestrictionDescr, null, "Unable to return Declaration for identifier '" + variableRestrictionDescr.getIdentifier() + "'" ) ); return null; } } Target right = getRightTarget( extractor ); Target left = (declaration.isPatternDeclaration() && !(Date.class.isAssignableFrom( declaration.getExtractor().getExtractToClass() ) || Number.class.isAssignableFrom( declaration.getExtractor().getExtractToClass() ))) ? Target.HANDLE : Target.FACT; final Evaluator evaluator = getEvaluator( context, variableRestrictionDescr, extractor.getValueType(), variableRestrictionDescr.getEvaluator(), variableRestrictionDescr.isNegated(), variableRestrictionDescr.getParameterText(), left, right ); if ( evaluator == null ) { return null; } Restriction restriction = new VariableRestriction( extractor, declaration, evaluator ); // TODO: FIXME: Implement proper support for Unifications // if ( declaration.getPattern().getObjectType().equals( new ClassObjectType( DroolsQuery.class ) ) ) { // // declaration is query argument, so allow for unification. // restriction = new UnificationRestriction( ( VariableRestriction ) restriction ); // } return restriction; } private LiteralRestriction buildRestriction(final RuleBuildContext context, final InternalReadAccessor extractor, final FieldConstraintDescr fieldConstraintDescr, final LiteralRestrictionDescr literalRestrictionDescr) { FieldValue field = null; try { Object value = literalRestrictionDescr.getValue(); if ( literalRestrictionDescr.getType() == LiteralRestrictionDescr.TYPE_STRING && context.getConfiguration().isProcessStringEscapes() ) { value = StringUtils.unescapeJava( (String) value ); } field = FieldFactory.getFieldValue( value, extractor.getValueType(), context.getPackageBuilder().getDateFormats() ); } catch ( final Exception e ) { context.getErrors().add( new DescrBuildError( context.getParentDescr(), literalRestrictionDescr, e, "Unable to create a Field value of type '" + extractor.getValueType() + "' and value '" + literalRestrictionDescr.getText() + "'" ) ); } if ( field == null ) { return null; } Target right = getRightTarget( extractor ); Target left = Target.FACT; final Evaluator evaluator = getEvaluator( context, literalRestrictionDescr, extractor.getValueType(), literalRestrictionDescr.getEvaluator(), literalRestrictionDescr.isNegated(), literalRestrictionDescr.getParameterText(), left, right ); if ( evaluator == null ) { return null; } return new LiteralRestriction( field, evaluator, extractor ); } private Restriction buildRestriction(final RuleBuildContext context, final InternalReadAccessor extractor, final FieldConstraintDescr fieldConstraintDescr, final QualifiedIdentifierRestrictionDescr qiRestrictionDescr) { FieldValue field = null; final String[] parts = qiRestrictionDescr.getText().split( "\\." ); // if only 2 parts, it may be a composed direct property access if ( parts.length == 2 ) { Declaration implicit = null; if ( "this".equals( parts[0] ) ) { implicit = this.createDeclarationObject( context, parts[1], (Pattern) context.getBuildStack().peek() ); } else { final Declaration decl = context.getDeclarationResolver().getDeclaration( context.getRule(), parts[0] ); // if a declaration exists, then it may be a variable direct property access, not an enum if ( decl != null ) { if ( decl.isPatternDeclaration() ) { implicit = this.createDeclarationObject( context, parts[1], decl.getPattern() ); } else { context.getErrors().add( new DescrBuildError( context.getParentDescr(), qiRestrictionDescr, "", "Not possible to directly access the property '" + parts[1] + "' of declaration '" + parts[0] + "' since it is not a pattern" ) ); return null; } } } if ( implicit != null ) { Target right = getRightTarget( extractor ); Target left = (implicit.isPatternDeclaration() && !(Date.class.isAssignableFrom( implicit.getExtractor().getExtractToClass() ) || Number.class.isAssignableFrom( implicit.getExtractor().getExtractToClass() ))) ? Target.HANDLE : Target.FACT; final Evaluator evaluator = getEvaluator( context, qiRestrictionDescr, extractor.getValueType(), qiRestrictionDescr.getEvaluator(), qiRestrictionDescr.isNegated(), qiRestrictionDescr.getParameterText(), left, right ); if ( evaluator == null ) { return null; } return new VariableRestriction( extractor, implicit, evaluator ); } } final int lastDot = qiRestrictionDescr.getText().lastIndexOf( '.' ); final String className = qiRestrictionDescr.getText().substring( 0, lastDot ); final String fieldName = qiRestrictionDescr.getText().substring( lastDot + 1 ); try { final Class staticClass = context.getDialect().getTypeResolver().resolveType( className ); field = FieldFactory.getFieldValue( staticClass.getField( fieldName ).get( null ), extractor.getValueType(), context.getPackageBuilder().getDateFormats() ); if ( field.isObjectField() ) { ((ObjectFieldImpl) field).setEnum( true ); ((ObjectFieldImpl) field).setEnumName( staticClass.getName() ); ((ObjectFieldImpl) field).setFieldName( fieldName ); } } catch ( final ClassNotFoundException e ) { // nothing to do, as it is not a class name with static field } catch ( final Exception e ) { context.getErrors().add( new DescrBuildError( context.getParentDescr(), qiRestrictionDescr, e, "Unable to create a Field value of type '" + extractor.getValueType() + "' and value '" + qiRestrictionDescr.getText() + "'" ) ); } if ( field == null ) { return null; } Target right = getRightTarget( extractor ); Target left = Target.FACT; final Evaluator evaluator = getEvaluator( context, qiRestrictionDescr, extractor.getValueType(), qiRestrictionDescr.getEvaluator(), qiRestrictionDescr.isNegated(), qiRestrictionDescr.getParameterText(), left, right ); if ( evaluator == null ) { return null; } return new LiteralRestriction( field, evaluator, extractor ); } private Target getRightTarget(final InternalReadAccessor extractor) { Target right = (extractor.isSelfReference() && !(Date.class.isAssignableFrom( extractor.getExtractToClass() ) || Number.class.isAssignableFrom( extractor.getExtractToClass() ))) ? Target.HANDLE : Target.FACT; return right; } private ReturnValueRestriction buildRestriction(final RuleBuildContext context, final Pattern pattern, final InternalReadAccessor extractor, final FieldConstraintDescr fieldConstraintDescr, final ReturnValueRestrictionDescr returnValueRestrictionDescr) { Map<String, Class< ? >> declarations = getDeclarationsMap( returnValueRestrictionDescr, context ); Map<String, Class< ? >> globals = context.getPackageBuilder().getGlobals(); Dialect.AnalysisResult analysis = context.getDialect().analyzeExpression( context, returnValueRestrictionDescr, returnValueRestrictionDescr.getContent(), new Map[]{declarations, globals} ); final List[] usedIdentifiers = analysis.getBoundIdentifiers(); final List tupleDeclarations = new ArrayList(); final List factDeclarations = new ArrayList(); for ( int i = 0, size = usedIdentifiers[0].size(); i < size; i++ ) { final Declaration declaration = context.getDeclarationResolver().getDeclaration( context.getRule(), (String) usedIdentifiers[0].get( i ) ); if ( declaration.getPattern() == pattern ) { factDeclarations.add( declaration ); } else { tupleDeclarations.add( declaration ); } } createImplicitBindings( context, pattern, analysis.getNotBoundedIdentifiers(), factDeclarations ); Target right = getRightTarget( extractor ); Target left = Target.FACT; final Evaluator evaluator = getEvaluator( context, returnValueRestrictionDescr, extractor.getValueType(), returnValueRestrictionDescr.getEvaluator(), returnValueRestrictionDescr.isNegated(), returnValueRestrictionDescr.getParameterText(), left, right ); if ( evaluator == null ) { return null; } final Declaration[] previousDeclarations = (Declaration[]) tupleDeclarations.toArray( new Declaration[tupleDeclarations.size()] ); final Declaration[] localDeclarations = (Declaration[]) factDeclarations.toArray( new Declaration[factDeclarations.size()] ); final String[] requiredGlobals = (String[]) usedIdentifiers[1].toArray( new String[usedIdentifiers[1].size()] ); final ReturnValueRestriction returnValueRestriction = new ReturnValueRestriction( extractor, previousDeclarations, localDeclarations, requiredGlobals, evaluator ); final ReturnValueBuilder builder = context.getDialect().getReturnValueBuilder(); builder.build( context, usedIdentifiers, previousDeclarations, localDeclarations, returnValueRestriction, returnValueRestrictionDescr ); return returnValueRestriction; } public static void registerReadAccessor(final RuleBuildContext context, final ObjectType objectType, final String fieldName, final AcceptsReadAccessor target) { if ( !ValueType.FACTTEMPLATE_TYPE.equals( objectType.getValueType() ) ) { InternalReadAccessor reader = context.getPkg().getClassFieldAccessorStore().getReader( ((ClassObjectType) objectType).getClassName(), fieldName, target ); } } public static InternalReadAccessor getFieldReadAccessor(final RuleBuildContext context, final BaseDescr descr, final ObjectType objectType, final String fieldName, final AcceptsReadAccessor target, final boolean reportError) { InternalReadAccessor reader = null; if ( ValueType.FACTTEMPLATE_TYPE.equals( objectType.getValueType() ) ) { //@todo use accessor cache final FactTemplate factTemplate = ((FactTemplateObjectType) objectType).getFactTemplate(); reader = new FactTemplateFieldExtractor( factTemplate, factTemplate.getFieldTemplateIndex( fieldName ) ); if ( target != null ) { target.setReadAccessor( reader ); } } else { try { reader = context.getPkg().getClassFieldAccessorStore().getReader( ((ClassObjectType) objectType).getClassName(), fieldName, target ); } catch ( final Exception e ) { if ( reportError ) { context.getErrors().add( new DescrBuildError( context.getParentDescr(), descr, e, "Unable to create Field Extractor for '" + fieldName + "'" ) ); } } } return reader; } private Evaluator getEvaluator(final RuleBuildContext context, final BaseDescr descr, final ValueType valueType, final String evaluatorString, final boolean isNegated, final String parameterText, final Target left, final Target right) { final EvaluatorDefinition def = context.getConfiguration().getEvaluatorRegistry().getEvaluatorDefinition( evaluatorString ); if ( def == null ) { context.getErrors().add( new DescrBuildError( context.getParentDescr(), descr, null, "Unable to determine the Evaluator for ID '" + evaluatorString + "'" ) ); return null; } final Evaluator evaluator = def.getEvaluator( valueType, evaluatorString, isNegated, parameterText, left, right ); if ( evaluator == null ) { context.getErrors().add( new DescrBuildError( context.getParentDescr(), descr, null, "Evaluator '" + (isNegated ? "not " : "") + evaluatorString + "' does not support type '" + valueType ) ); } return evaluator; } }