/**
* Copyright 2005 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;
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 org.drools.RuntimeDroolsException;
import org.drools.spi.ObjectType;
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 children = new ArrayList();
private ObjectType forallBaseObjectType = null;
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
*
* @param child
*/
public void addChild(final RuleConditionElement child) {
if ( (this.isNot() || this.isExists()) && (this.children.size() > 0) ) {
throw new RuntimeDroolsException( 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
* @param index
* @param rce
*/
public void addChild(final int index,
final RuleConditionElement rce) {
this.children.add( index,
rce );
}
public List getChildren() {
return this.children;
}
/**
* @inheritDoc
*/
public Map getInnerDeclarations() {
return this.type.getInnerDeclarations( this.children );
}
/**
* @inheritDoc
*/
public Map getOuterDeclarations() {
return this.type.getOuterDeclarations( this.children );
}
/**
* @inheritDoc
*/
public Declaration resolveDeclaration(final String identifier) {
return (Declaration) this.type.getInnerDeclarations( this.children ).get( identifier );
}
public void setForallBaseObjectType(ObjectType objectType) {
this.forallBaseObjectType = objectType;
}
public ObjectType getForallBaseObjectType() {
return this.forallBaseObjectType;
}
/**
* 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 ( int i = 0; i < clone.length; i++ ) {
// if child is also a group element, there may be
// some possible clean up / optimizations to be done
if ( clone[i] instanceof GroupElement ) {
final GroupElement childGroup = (GroupElement) clone[i];
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 ) {
final GroupElement group = (GroupElement) child;
this.type = group.getType();
this.children.clear();
this.children.addAll( group.getChildren() );
}
}
// 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() );
}
}
}
/**
* @param parent
*/
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 ( final Iterator childIt = this.children.iterator(); childIt.hasNext(); ) {
final Object child = childIt.next();
// we must keep the order, so add in the same place were parent was before
parent.getChildren().add( 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 Object child = this.children.get( 0 );
parent.getChildren().add( 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();
}
}
/**
* Traverses two trees and checks that they are structurally equal at all
* levels
*
* @param e1
* @param e2
* @return
*/
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
*
* @param e1
* @param e2
* @return
*/
public Object clone() {
GroupElement cloned = null;
try {
cloned = (GroupElement) this.getClass().newInstance();
} catch ( final InstantiationException e ) {
throw new RuntimeException( "Could not clone '" + this.getClass().getName() + "'" );
} catch ( final IllegalAccessException e ) {
throw new RuntimeException( "Could not clone '" + this.getClass().getName() + "'" );
}
cloned.setType( this.getType() );
for ( final Iterator it = this.children.iterator(); it.hasNext(); ) {
RuleConditionElement re = (RuleConditionElement) it.next();
if ( re instanceof GroupElement ) {
re = (RuleConditionElement) ((GroupElement) re).clone();
}
cloned.addChild( 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 getNestedElements() {
return this.children;
}
public boolean isPatternScopeDelimiter() {
return this.type.isPatternScopeDelimiter();
}
/**
* A public enum for CE types
*/
public static enum Type {
AND(false),
OR(false),
NOT(true),
EXISTS(true);
private final boolean scopeDelimiter;
Type(final boolean scopeDelimiter) {
this.scopeDelimiter = scopeDelimiter;
}
/**
* Returns a map of declarations that are
* visible inside of an element of this type
*/
public Map getInnerDeclarations(List children) {
Map declarations = null;
if ( children.isEmpty() ) {
declarations = Collections.EMPTY_MAP;
} else if ( children.size() == 1 ) {
final RuleConditionElement re = (RuleConditionElement) children.get( 0 );
declarations = re.getOuterDeclarations();
} else {
declarations = new HashMap();
for ( final Iterator it = children.iterator(); it.hasNext(); ) {
declarations.putAll( ((RuleConditionElement) it.next()).getOuterDeclarations() );
}
}
return declarations;
}
/**
* Returns a map of declarations that are
* visible outside of an element of this type
*/
public Map getOuterDeclarations(List children) {
Map declarations = null;
if ( this.scopeDelimiter || children.isEmpty() ) {
declarations = Collections.EMPTY_MAP;
} else if ( children.size() == 1 ) {
final RuleConditionElement re = (RuleConditionElement) children.get( 0 );
declarations = re.getOuterDeclarations();
} else {
declarations = new HashMap();
for ( final Iterator it = children.iterator(); it.hasNext(); ) {
declarations.putAll( ((RuleConditionElement) it.next()).getOuterDeclarations() );
}
}
return declarations;
}
/**
* 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
* @return
*/
public boolean isPatternScopeDelimiter() {
return this.scopeDelimiter;
}
}
}