/* * Copyright 2005 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.drools.core.rule; import org.drools.core.base.ClassObjectType; import org.drools.core.base.extractors.ArrayElementReader; import org.drools.core.base.extractors.SelfReferenceClassFieldReader; import org.drools.core.rule.constraint.MvelConstraint; import org.drools.core.spi.Constraint; import org.drools.core.spi.DataProvider; import org.drools.core.spi.DeclarationScopeResolver; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Stack; /** * LogicTransformation is reponsible for removing redundant nodes and move Or * nodes upwards. * * This class does not turn Exists into two Nots at this stage, that role is * delegated to the Builder. */ public class LogicTransformer { private final Map<GroupElement.Type, Transformation> orTransformations = new HashMap<GroupElement.Type, Transformation>(); private static LogicTransformer INSTANCE = new LogicTransformer(); public static LogicTransformer getInstance() { return LogicTransformer.INSTANCE; } protected LogicTransformer() { initialize(); } /** * sets up the parent->child transformations map */ private void initialize() { // these pairs will be transformed addTransformationPair( GroupElement.NOT, new NotOrTransformation() ); addTransformationPair( GroupElement.EXISTS, new ExistOrTransformation() ); addTransformationPair( GroupElement.AND, new AndOrTransformation() ); } private void addTransformationPair(final GroupElement.Type parent, final Transformation method) { this.orTransformations.put( parent, method ); } public GroupElement[] transform( final GroupElement cloned, Map<String, Class<?>> globals ) throws InvalidPatternException { //moved cloned to up //final GroupElement cloned = (GroupElement) and.clone(); boolean hasNamedConsequenceAndIsStream = processTree( cloned ); cloned.pack(); GroupElement[] ands; // is top element an AND? if ( cloned.isAnd() ) { // Yes, so just return it ands = new GroupElement[]{cloned}; } else if ( cloned.isOr() ) { // it is an OR, so each child is an AND branch ands = splitOr( cloned ); } else { // no, so just wrap into an AND final GroupElement wrapper = GroupElementFactory.newAndInstance(); wrapper.addChild( cloned ); ands = new GroupElement[]{wrapper}; } for ( GroupElement and : ands ) { // fix the cloned declarations this.fixClonedDeclarations( and, globals ); and.setRoot( true ); } return hasNamedConsequenceAndIsStream ? processNamedConsequences(ands) : ands; } private GroupElement[] processNamedConsequences(GroupElement[] ands) { List<GroupElement> result = new ArrayList<GroupElement>(); for (GroupElement and : ands) { List<RuleConditionElement> children = and.getChildren(); for (int i = 0; i < children.size(); i++) { RuleConditionElement child = children.get(i); if (child instanceof NamedConsequence) { GroupElement clonedAnd = GroupElementFactory.newAndInstance(); for (int j = 0; j < i; j++) { clonedAnd.addChild(children.get(j).clone()); } ((NamedConsequence) child).setTerminal(true); clonedAnd.addChild(child); children.remove(i--); result.add(clonedAnd); } } result.add(and); } return result.toArray(new GroupElement[result.size()]); } protected GroupElement[] splitOr( final GroupElement cloned ) { GroupElement[] ands = new GroupElement[cloned.getChildren().size()]; int i = 0; for ( final RuleConditionElement branch : cloned.getChildren() ) { if ( ( branch instanceof GroupElement ) && ( ( (GroupElement) branch ).isAnd() ) ) { ands[i++] = (GroupElement) branch; } else { ands[i] = GroupElementFactory.newAndInstance(); ands[i].addChild( branch ); i++; } } return ands; } /** * During the logic transformation, we eventually clone CEs, * specially patterns and corresponding declarations. So now * we need to fix any references to cloned declarations. */ protected void fixClonedDeclarations( GroupElement and, Map<String, Class<?>> globals ) { Stack<RuleConditionElement> contextStack = new Stack<RuleConditionElement>(); DeclarationScopeResolver resolver = new DeclarationScopeResolver( globals, contextStack ); contextStack.push( and ); processElement( resolver, contextStack, and ); contextStack.pop(); } /** * recurse through the rule condition elements updating the declaration objecs */ private void processElement(final DeclarationScopeResolver resolver, final Stack<RuleConditionElement> contextStack, final RuleConditionElement element) { if ( element instanceof Pattern ) { Pattern pattern = (Pattern) element; for ( RuleConditionElement ruleConditionElement : pattern.getNestedElements() ) { processElement( resolver, contextStack, ruleConditionElement ); } for (Constraint constraint : pattern.getConstraints()) { if (constraint instanceof Declaration) { continue; } replaceDeclarations( resolver, pattern, constraint ); } } else if ( element instanceof EvalCondition ) { processEvalCondition(resolver, (EvalCondition) element); } else if ( element instanceof Accumulate ) { for ( RuleConditionElement rce : element.getNestedElements() ) { processElement( resolver, contextStack, rce ); } Accumulate accumulate = (Accumulate)element; replaceDeclarations( resolver, accumulate ); } else if ( element instanceof From ) { DataProvider provider = ((From) element).getDataProvider(); Declaration[] decl = provider.getRequiredDeclarations(); for (Declaration aDecl : decl) { Declaration resolved = resolver.getDeclaration(aDecl.getIdentifier()); if (resolved != null && resolved != aDecl) { provider.replaceDeclaration(aDecl, resolved); } else if (resolved == null) { // it is probably an implicit declaration, so find the corresponding pattern Pattern old = aDecl.getPattern(); Pattern current = resolver.findPatternByIndex(old.getIndex()); if (current != null && old != current) { resolved = new Declaration(aDecl.getIdentifier(), aDecl.getExtractor(), current); provider.replaceDeclaration(aDecl, resolved); } } } } else if ( element instanceof QueryElement ) { QueryElement qe = ( QueryElement ) element; Pattern pattern = qe.getResultPattern(); for ( Entry<String, Declaration> entry : pattern.getInnerDeclarations().entrySet() ) { Declaration resolved = resolver.getDeclaration( entry.getValue().getIdentifier() ); if ( resolved != null && resolved != entry.getValue() && resolved.getPattern() != pattern ) { entry.setValue( resolved ); } } List<Integer> varIndexes = asList( qe.getVariableIndexes() ); for (int i = 0; i < qe.getArguments().length; i++) { if (!(qe.getArguments()[i] instanceof QueryArgument.Declr)) { continue; } Declaration declr = ((QueryArgument.Declr) qe.getArguments()[i]).getDeclaration(); Declaration resolved = resolver.getDeclaration( declr.getIdentifier() ); if ( resolved != declr && resolved.getPattern() != pattern ) { qe.getArguments()[i] = new QueryArgument.Declr(resolved); } if( ClassObjectType.DroolsQuery_ObjectType.isAssignableFrom( resolved.getPattern().getObjectType() ) ) { // if the resolved still points to DroolsQuery, we know this is the first unification pattern, so redeclare it as the visible Declaration declr = pattern.addDeclaration( declr.getIdentifier() ); // this bit is different, notice its the ArrayElementReader that we wire up to, not the declaration. ArrayElementReader reader = new ArrayElementReader( new SelfReferenceClassFieldReader(Object[].class), i, resolved.getDeclarationClass() ); declr.setReadAccessor( reader ); varIndexes.add( i ); } } qe.setVariableIndexes( toIntArray( varIndexes ) ); } else if ( element instanceof ConditionalBranch ) { processBranch( resolver, (ConditionalBranch) element ); } else { contextStack.push( element ); for (RuleConditionElement ruleConditionElement : element.getNestedElements()) { processElement(resolver, contextStack, ruleConditionElement); } contextStack.pop(); } } private void replaceDeclarations( DeclarationScopeResolver resolver, Pattern pattern, Constraint constraint ) { Declaration[] decl = constraint.getRequiredDeclarations(); for ( Declaration aDecl : decl ) { Declaration resolved = resolver.getDeclaration( aDecl.getIdentifier() ); if ( constraint instanceof MvelConstraint && ( (MvelConstraint) constraint ).isUnification() ) { if ( ClassObjectType.DroolsQuery_ObjectType.isAssignableFrom( resolved.getPattern().getObjectType() ) ) { Declaration redeclaredDeclr = new Declaration( resolved.getIdentifier(), ( (MvelConstraint) constraint ).getFieldExtractor(), pattern, false ); pattern.addDeclaration( redeclaredDeclr ); } else if ( resolved.getPattern() != pattern ) { ( (MvelConstraint) constraint ).unsetUnification(); } } if ( resolved != null && resolved != aDecl && resolved.getPattern() != pattern ) { constraint.replaceDeclaration( aDecl, resolved ); } else if ( resolved == null ) { // it is probably an implicit declaration, so find the corresponding pattern Pattern old = aDecl.getPattern(); Pattern current = resolver.findPatternByIndex( old.getIndex() ); if ( current != null && old != current ) { resolved = new Declaration( aDecl.getIdentifier(), aDecl.getExtractor(), current ); constraint.replaceDeclaration( aDecl, resolved ); } } } } private void replaceDeclarations( DeclarationScopeResolver resolver, Accumulate accumulate ) { Declaration[] decl = accumulate.getRequiredDeclarations(); for ( Declaration aDecl : decl ) { Declaration resolved = resolver.getDeclaration( aDecl.getIdentifier() ); if ( resolved != null && resolved != aDecl ) { accumulate.replaceDeclaration( aDecl, resolved ); } else if ( resolved == null ) { // it is probably an implicit declaration, so find the corresponding pattern Pattern old = aDecl.getPattern(); Pattern current = resolver.findPatternByIndex( old.getIndex() ); if ( current != null && old != current ) { resolved = new Declaration( aDecl.getIdentifier(), aDecl.getExtractor(), current ); accumulate.replaceDeclaration( aDecl, resolved ); } } } } private static List<Integer> asList(int[] ints) { List<Integer> list = new ArrayList<Integer>(ints.length); for ( int i : ints ) { list.add( i ); } return list; } public static int[] toIntArray(List<Integer> list) { int[] ints = new int[list.size()]; for ( int i = 0; i < list.size(); i++ ) { ints[i] = list.get( i ); } return ints; } private void processEvalCondition(DeclarationScopeResolver resolver, EvalCondition element) { Declaration[] decl = element.getRequiredDeclarations(); for (Declaration aDecl : decl) { Declaration resolved = resolver.getDeclaration(aDecl.getIdentifier()); if (resolved != null && resolved != aDecl) { element.replaceDeclaration( aDecl, resolved ); } } } private void processBranch(DeclarationScopeResolver resolver, ConditionalBranch branch) { processEvalCondition(resolver, branch.getEvalCondition()); if ( branch.getElseBranch() != null ) { processBranch(resolver, branch.getElseBranch()); } } protected boolean processTree(final GroupElement ce) throws InvalidPatternException { boolean[] hasNamedConsequenceAndIsStream = new boolean[2]; processTree(ce, hasNamedConsequenceAndIsStream); return hasNamedConsequenceAndIsStream[0] && hasNamedConsequenceAndIsStream[1]; } /** * Traverses a Tree, during the process it transforms Or nodes moving the * upwards and it removes duplicate logic statement, this does not include * Not nodes. * * Traversal involves three levels the graph for each iteration. The first * level is the current node, this node will not be transformed, instead * what we are interested in are the children of the current node (called * the parent nodes) and the children of those parents (call the child * nodes). */ private void processTree(final GroupElement ce, boolean[] result) throws InvalidPatternException { boolean hasChildOr = false; // first we elimininate any redundancy ce.pack(); for (Object child : ce.getChildren().toArray()) { if (child instanceof GroupElement) { final GroupElement group = (GroupElement) child; processTree(group, result); if ((group.isOr() || group.isAnd()) && group.getType() == ce.getType()) { group.pack(ce); } else if (group.isOr()) { hasChildOr = true; } } else if (child instanceof NamedConsequence) { result[0] = true; } else if (child instanceof Pattern && ((Pattern) child).getObjectType().isEvent()) { result[1] = true; } } if ( hasChildOr ) { applyOrTransformation( ce ); } } void applyOrTransformation(final GroupElement parent) throws InvalidPatternException { final Transformation transformation = this.orTransformations.get( parent.getType() ); if ( transformation == null ) { throw new RuntimeException( "applyOrTransformation could not find transformation for parent '" + parent.getType() + "' and child 'OR'" ); } transformation.transform( parent ); } interface Transformation { void transform(GroupElement element) throws InvalidPatternException; } /** * Takes any And that has an Or as a child and rewrites it to move the Or * upwards * * (a||b)&&c * * <pre> * and * / \ * or c * / \ * a b * </pre> * * Should become (a&&c)||(b&&c) * * <pre> * * or * / \ * / \ * / \ * and and * / \ / \ * a c b c * </pre> */ class AndOrTransformation implements Transformation { public void transform(final GroupElement parent) throws InvalidPatternException { final List<GroupElement> orsList = new ArrayList<GroupElement>(); // must keep order, so, using array final RuleConditionElement[] others = new RuleConditionElement[parent.getChildren().size()]; // first we split children as OR or not OR int permutations = 1; int index = 0; for (final RuleConditionElement child : parent.getChildren()) { if ((child instanceof GroupElement) && ((GroupElement) child).isOr()) { permutations *= ((GroupElement) child).getChildren().size(); orsList.add((GroupElement)child); } else { others[index] = child; } index++; } // transform parent into an OR parent.setType( GroupElement.OR ); parent.getChildren().clear(); // prepare arrays and indexes to calculate permutation final int[] indexes = new int[orsList.size()]; // now we know how many permutations we will have, so create it for ( int i = 1; i <= permutations; i++ ) { final GroupElement and = GroupElementFactory.newAndInstance(); // create the actual permutations int mod = 1; for ( int j = orsList.size() - 1; j >= 0; j-- ) { GroupElement or = orsList.get(j); // we must insert at the beginning to keep the order and.addChild( 0, or.getChildren().get(indexes[j]).clone() ); if ( (i % mod) == 0 ) { indexes[j] = (indexes[j] + 1) % or.getChildren().size(); } mod *= or.getChildren().size(); } // elements originally outside OR will be in every permutation, so add them // in their original position for ( int j = 0; j < others.length; j++ ) { if ( others[j] != null ) { // always add clone of them to avoid offset conflicts in declarations // HERE IS THE MESSY PROBLEM: need to change further references to the appropriate cloned ref and.addChild( j, others[j].clone() ); } } parent.addChild( and ); } // remove duplications parent.pack(); } } /** * (Exist (OR (A B) * * <pre> * Exist * | * or * / \ * a b * </pre> * * (Not Exist ( Not (a) And Not (b)) ) * * <pre> * Not * | * And * / \ * Not Not * | | * a b * </pre> */ class ExistOrTransformation implements Transformation { public void transform(final GroupElement parent) throws InvalidPatternException { if ( (!(parent.getChildren().get( 0 ) instanceof GroupElement)) || (!((GroupElement) parent.getChildren().get( 0 )).isOr()) ) { throw new RuntimeException( "ExistOrTransformation expected 'OR' but instead found '" + parent.getChildren().get( 0 ).getClass().getName() + "'" ); } // we know an Exists only ever has one child, and the previous algorithm // has confirmed the child is an OR final GroupElement or = (GroupElement) parent.getChildren().get( 0 ); parent.setType( GroupElement.NOT ); parent.getChildren().clear(); final GroupElement and = GroupElementFactory.newAndInstance(); for (RuleConditionElement ruleConditionElement : or.getChildren()) { final GroupElement newNot = GroupElementFactory.newNotInstance(); newNot.addChild(ruleConditionElement); and.addChild(newNot); } parent.addChild( and ); parent.pack(); } } /** * (Not (OR (A B) ) * * <pre> * Not * | * or * / \ * a b * </pre> * * (And ( Not (a) Not (b)) ) * * <pre> * And * / \ * Not Not * | | * a b * </pre> */ public class NotOrTransformation implements Transformation { public void transform(final GroupElement parent) throws InvalidPatternException { if ( (!(parent.getChildren().get( 0 ) instanceof GroupElement)) || (!((GroupElement) parent.getChildren().get( 0 )).isOr()) ) { throw new RuntimeException( "NotOrTransformation expected 'OR' but instead found '" + parent.getChildren().get( 0 ).getClass().getName() + "'" ); } // we know a Not only ever has one child, and the previous algorithm // has confirmed the child is an OR final GroupElement or = (GroupElement) parent.getChildren().get( 0 ); parent.setType( GroupElement.AND ); parent.getChildren().clear(); for (RuleConditionElement ruleConditionElement : or.getChildren()) { final GroupElement newNot = GroupElementFactory.newNotInstance(); newNot.addChild(ruleConditionElement); parent.addChild(newNot); } parent.pack(); } } }