/* * 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.definitions.rule.impl.RuleImpl; import org.drools.core.spi.ObjectType; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import static org.drools.core.util.ClassUtils.findCommonSuperClass; public class GroupElement extends ConditionalElement implements Externalizable { private static final long serialVersionUID = 510l; public static final Type AND = Type.AND; public static final Type OR = Type.OR; public static final Type NOT = Type.NOT; public static final Type EXISTS = Type.EXISTS; private Type type = null; private List<RuleConditionElement> children = new ArrayList<RuleConditionElement>(); private ObjectType forallBaseObjectType = null; private boolean root; private Map<String, Declaration> outerDeclrarations; public GroupElement() { this( Type.AND ); } public GroupElement(final Type type) { this.type = type; } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.type = (Type) in.readObject(); children = (List) in.readObject(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeObject( type ); out.writeObject( children ); } /** * Adds a child to the current GroupElement. * * Restrictions are: * NOT/EXISTS: can have only one child, either a single Pattern or another CE */ public void addChild(final RuleConditionElement child) { if ( (this.isNot() || this.isExists()) && (this.children.size() > 0) ) { throw new RuntimeException( this.type.toString() + " can have only a single child element. Either a single Pattern or another CE." ); } this.children.add( child ); } /** * Adds the given child as the (index)th child of the this GroupElement */ public void addChild(final int index, final RuleConditionElement rce) { this.children.add( index, rce ); } public List<RuleConditionElement> getChildren() { return this.children; } /** * @inheritDoc */ public Map<String,Declaration> getInnerDeclarations() { return this.type.getInnerDeclarations( this.children ); } public Map<String,Declaration> getInnerDeclarations(String consequenceName) { return this.type.getInnerDeclarations( this.children, consequenceName ); } /** * @inheritDoc */ public Map<String, Declaration> getOuterDeclarations() { return getOuterDeclarations( RuleImpl.DEFAULT_CONSEQUENCE_NAME ); } public Map<String, Declaration> getOuterDeclarations(String consequenceName) { if ( outerDeclrarations != null ) { return outerDeclrarations; } else if ( root ) { outerDeclrarations = this.type.getOuterDeclarations( this.children, consequenceName ); return outerDeclrarations; } return this.type.getOuterDeclarations( this.children, consequenceName ); } /** * @inheritDoc */ public Declaration resolveDeclaration(final String identifier) { return this.type.getInnerDeclarations( this.children ).get( identifier ); } public void setForallBaseObjectType(ObjectType objectType) { this.forallBaseObjectType = objectType; } /** * Optimize the group element subtree by removing redundancies * like an AND inside another AND, OR inside OR, single branches * AND/OR, etc. * * LogicTransformer does further, more complicated, transformations */ public void pack() { // we must clone, since we want to iterate only over the original list final Object[] clone = this.children.toArray(); for (Object aClone : clone) { // if child is also a group element, there may be // some possible clean up / optimizations to be done if (aClone instanceof GroupElement) { final GroupElement childGroup = (GroupElement) aClone; childGroup.pack(this); } } // if after packing, this is an AND or OR GE with a single // child GE, then clone child into current node eliminating child if ( (this.isAnd() || this.isOr()) && (this.children.size() == 1) ) { final Object child = this.getChildren().get( 0 ); if ( child instanceof GroupElement ) { mergeGroupElements( this, (GroupElement) child ); } } // if after packing, this is a NOT GE with an EXISTS child // or this is an EXISTS GE with a NOT child, eliminate the redundant // child and make this a NOT GE if ( this.isNot() && this.children.size() == 1 && this.getChildren().get( 0 ) instanceof GroupElement ) { final GroupElement child = (GroupElement) this.getChildren().get( 0 ); if ( child.isExists() ) { this.children.clear(); this.children.addAll( child.getChildren() ); } } if ( this.isExists() && this.children.size() == 1 && this.getChildren().get( 0 ) instanceof GroupElement ) { final GroupElement child = (GroupElement) this.getChildren().get( 0 ); if ( child.isNot() ) { this.setType( NOT ); this.children.clear(); this.children.addAll( child.getChildren() ); } } } protected void mergeGroupElements(GroupElement parent, GroupElement child) { parent.type = child.getType(); parent.children.clear(); parent.children.addAll( child.getChildren() ); } public void pack(final GroupElement parent) { if ( this.children.size() == 0 ) { // if there is no child, just remove this node parent.children.remove( this ); return; } // If this is an AND or OR or EXISTS, there are some possible merges if ( this.isAnd() || this.isOr() || this.isExists() ) { // if parent is of the same type as current node, // then merge this children with parent children if ( parent.getType() == this.getType() ) { // we must keep the order so, save index int index = parent.getChildren().indexOf( this ); parent.getChildren().remove( this ); // for each child, pack it and add it to parent for ( RuleConditionElement child : children ) { // we must keep the order, so add in the same place were parent was before parent.addChild( index++, child ); if ( child instanceof GroupElement ) { final int previousSize = parent.getChildren().size(); ((GroupElement) child).pack( parent ); // in case the child also added elements to the parent, // we need to compensate index += (parent.getChildren().size() - previousSize); } } // if current node has a single child, then move it to parent and pack it } else if ( (!this.isExists()) && (this.children.size() == 1) ) { // we must keep the order so, save index final int index = parent.getChildren().indexOf( this ); parent.getChildren().remove( this ); final RuleConditionElement child = this.children.get( 0 ); parent.addChild( index, child ); if ( child instanceof GroupElement ) { ((GroupElement) child).pack( parent ); } // otherwise pack itself } else { this.pack(); } // also pack itself if it is a NOT } else { this.pack(); } } public boolean equals(final Object object) { // Return false if its null or not an instance of ConditionalElement if ( object == null || !(object instanceof GroupElement) ) { return false; } // Return true if they are the same reference if ( this == object ) { return true; } // Now try a recurse manual check final GroupElement e2 = (GroupElement) object; if ( !this.type.equals( e2.type ) ) { return false; } final List e1Children = this.getChildren(); final List e2Children = e2.getChildren(); if ( e1Children.size() != e2Children.size() ) { return false; } for ( int i = 0; i < e1Children.size(); i++ ) { final Object e1Object1 = e1Children.get( i ); final Object e2Object1 = e2Children.get( i ); if ( !e1Object1.equals( e2Object1 ) ) { return false; } } return true; } public int hashCode() { return this.type.hashCode() + this.children.hashCode(); } /** * Clones all Conditional Elements but references the non ConditionalElement children */ public GroupElement clone() { return clone(true); } public GroupElement cloneOnlyGroup() { return clone(false); } protected GroupElement clone(boolean deepClone) { GroupElement cloned = new GroupElement(); cloned.setType( this.getType() ); for ( RuleConditionElement re : children ) { cloned.addChild( deepClone ? re.clone() : re ); } return cloned; } public Type getType() { return this.type; } public void setType(final Type type) { this.type = type; } public boolean isAnd() { return AND.equals( this.type ); } public boolean isOr() { return OR.equals( this.type ); } public boolean isNot() { return NOT.equals( this.type ); } public boolean isExists() { return EXISTS.equals( this.type ); } public String toString() { return this.type.toString() + this.children.toString(); } public List<RuleConditionElement> getNestedElements() { return this.children; } public boolean isPatternScopeDelimiter() { return this.type.isPatternScopeDelimiter(); } public boolean isRoot() { return root; } public void setRoot(boolean root) { this.root = root; } public boolean containesNode(Type node) { return containesNode( node, this ); } private static boolean containesNode(Type node, GroupElement groupElement) { for( RuleConditionElement rce : groupElement.getChildren() ) { if ( rce instanceof GroupElement ) { return ( (GroupElement) rce ).getType() == node || containesNode( node, (GroupElement) rce ); } } return false; } /** * A public enum for CE types */ public enum Type { AND(ScopeDelimiter.NEVER), OR(ScopeDelimiter.CONSENSUS), NOT(ScopeDelimiter.ALWAYS), EXISTS(ScopeDelimiter.ALWAYS); enum ScopeDelimiter { NEVER, CONSENSUS, // it isn't a scope delimiter only if a given Declaration is present on ALL branches ALWAYS } private final ScopeDelimiter scopeDelimiter; Type(final ScopeDelimiter scopeDelimiter) { this.scopeDelimiter = scopeDelimiter; } /** * Returns a map of declarations that are * visible inside of an element of this type */ private Map<String, Declaration> getInnerDeclarations(List<RuleConditionElement> children) { return getInnerDeclarations(children, RuleImpl.DEFAULT_CONSEQUENCE_NAME); } /** * Returns a map of declarations that are * visible inside of an element of this type * for the consequence with the given name */ private Map<String, Declaration> getInnerDeclarations(List<RuleConditionElement> children, String consequenceName) { return getDeclarations(children, ScopeDelimiter.NEVER, consequenceName); } /** * Returns a map of declarations that are * visible outside of an element of this type */ private Map<String, Declaration> getOuterDeclarations(List<RuleConditionElement> children, String consequenceName) { return getDeclarations(children, this.scopeDelimiter, consequenceName); } private Map<String, Declaration> getDeclarations(List<RuleConditionElement> children, ScopeDelimiter scopeDelimiter, String consequenceName) { if ( scopeDelimiter == ScopeDelimiter.ALWAYS || children.isEmpty() ) { return Collections.EMPTY_MAP; } else if ( children.size() == 1 ) { return getOuterDeclarations(children.get(0), consequenceName); } else { Map<String, Declaration> declarations = new HashMap<String, Declaration>(); if ( scopeDelimiter == ScopeDelimiter.NEVER ) { for ( RuleConditionElement rce : children ) { declarations.putAll( getOuterDeclarations( rce, consequenceName ) ); if ( isConsequenceInvoker(rce, consequenceName) ) { break; } } } else if ( scopeDelimiter == ScopeDelimiter.CONSENSUS ) { Iterator<RuleConditionElement> i = children.iterator(); RuleConditionElement rce = i.next(); Map<String, Declaration> elementDeclarations = getOuterDeclarations( rce, consequenceName ); if ( isConsequenceInvoker(rce, consequenceName) ) { return elementDeclarations; } declarations.putAll( elementDeclarations ); while ( i.hasNext() ) { rce = i.next(); elementDeclarations = getOuterDeclarations( rce, consequenceName ); if ( isConsequenceInvoker(rce, consequenceName) ) { return elementDeclarations; } declarations.keySet().retainAll( elementDeclarations.keySet() ); findCommonDeclarationClasses(declarations, elementDeclarations); } } return declarations; } } private void findCommonDeclarationClasses(Map<String, Declaration> original, Map<String, Declaration> merged) { for (Map.Entry<String, Declaration> entry : original.entrySet()) { Declaration declaration = entry.getValue(); declaration.setDeclarationClass( findCommonSuperClass( declaration.getDeclarationClass(), merged.get(entry.getKey()).getDeclarationClass() ) ); } } private Map<String, Declaration> getOuterDeclarations(RuleConditionElement rce, String consequenceName) { return rce instanceof GroupElement ? ((GroupElement) rce).getOuterDeclarations(consequenceName) : rce.getOuterDeclarations(); } private boolean isConsequenceInvoker(RuleConditionElement rce, String consequenceName) { if ( RuleImpl.DEFAULT_CONSEQUENCE_NAME.equals( consequenceName ) ) { return false; } if ( rce instanceof NamedConsequenceInvoker && ((NamedConsequenceInvoker)rce).invokesConsequence(consequenceName) ) { return true; } if ( rce instanceof GroupElement ) { for ( RuleConditionElement child : ((GroupElement) rce).getChildren() ) { if ( isConsequenceInvoker(child, consequenceName) ) { return true; } } } return false; } /** * Returns true in case this RuleConditionElement delimits * a pattern visibility scope. * * For instance, AND CE is not a scope delimiter, while * NOT CE is a scope delimiter */ public boolean isPatternScopeDelimiter() { return this.scopeDelimiter == ScopeDelimiter.ALWAYS; } } }