/*
* Copyright 2012 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.workbench.models.datamodel.rule;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.drools.workbench.models.datamodel.imports.HasImports;
import org.drools.workbench.models.datamodel.imports.Imports;
import org.drools.workbench.models.datamodel.packages.HasPackageName;
public class RuleModel implements HasImports,
HasPackageName {
/**
* This name is generally not used - the asset name or the file name is
* preferred (ie it could get out of sync with the name of the file it is
* in).
*/
public String name;
public String parentName;
public String modelVersion = "1.0";
public RuleAttribute[] attributes = new RuleAttribute[ 0 ];
public RuleMetadata[] metadataList = new RuleMetadata[ 0 ];
public IPattern[] lhs = new IPattern[ 0 ];
public IAction[] rhs = new IAction[ 0 ];
private Imports imports = new Imports();
private String packageName;
//Is the Rule to be negated (i.e. "not ( PatternX, PatternY... )"
private boolean isNegated;
public RuleModel() {
}
/**
* This will return a List<String> of all FactPattern bindings
* @return The bindings or an empty list if no bindings are found.
*/
public List<String> getLHSBoundFacts() {
if ( this.lhs == null ) {
return Collections.emptyList();
}
final List<String> list = new ArrayList<String>();
for ( int i = 0; i < this.lhs.length; i++ ) {
IPattern pat = this.lhs[ i ];
if ( pat instanceof FromCompositeFactPattern ) {
pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
}
if ( pat instanceof FactPattern ) {
final FactPattern p = (FactPattern) pat;
if ( p.getBoundName() != null ) {
list.add( p.getBoundName() );
}
}
}
return list;
}
/**
* This will return the FactPattern that a variable is bound Eto.
* @param var The bound fact variable (NOT bound field).
* @return null or the FactPattern found.
*/
public FactPattern getLHSBoundFact( final String var ) {
if ( this.lhs == null ) {
return null;
}
for ( int i = 0; i < this.lhs.length; i++ ) {
IPattern pat = this.lhs[ i ];
if ( pat instanceof FromCompositeFactPattern ) {
pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
}
if ( pat instanceof FactPattern ) {
final FactPattern p = (FactPattern) pat;
if ( p.getBoundName() != null && var.equals( p.getBoundName() ) ) {
return p;
}
}
}
return null;
}
/**
* This will return the FieldConstraint that a variable is bound to.
* @param var The bound field variable (NOT bound fact).
* @return null or the FieldConstraint found.
*/
public SingleFieldConstraint getLHSBoundField( final String var ) {
if ( this.lhs == null ) {
return null;
}
for ( int i = 0; i < this.lhs.length; i++ ) {
IPattern pat = this.lhs[ i ];
SingleFieldConstraint fieldConstraint = getLHSBoundField( pat, var );
if ( fieldConstraint != null ) {
return fieldConstraint;
}
}
return null;
}
private SingleFieldConstraint getLHSBoundField( IPattern pat,
String var ) {
if ( pat instanceof FromCompositeFactPattern ) {
pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
}
if ( pat instanceof CompositeFactPattern ) {
for ( IPattern iPattern : ( (CompositeFactPattern) pat ).getPatterns() ) {
SingleFieldConstraint fieldConstraint = getLHSBoundField( iPattern, var );
if ( fieldConstraint != null ) {
return fieldConstraint;
}
}
}
if ( pat instanceof FactPattern ) {
final FactPattern p = (FactPattern) pat;
for ( int j = 0; j < p.getFieldConstraints().length; j++ ) {
if ( p.getFieldConstraints()[ j ] instanceof SingleFieldConstraint ) {
SingleFieldConstraint fc = (SingleFieldConstraint) p.getFieldConstraints()[ j ];
List<String> fieldBindings = getFieldBinding( fc );
if ( fieldBindings.contains( var ) ) {
return fc;
}
}
}
}
return null;
}
/**
* Get the data-type associated with the binding
* @param var
* @return The data-type, or null if the binding could not be found
*/
public String getLHSBindingType( final String var ) {
if ( this.lhs == null ) {
return null;
}
for ( int i = 0; i < this.lhs.length; i++ ) {
String type = getLHSBindingType( this.lhs[ i ], var );
if ( type != null ) {
return type;
}
}
return null;
}
private String getLHSBindingType( IPattern pat,
String var ) {
if ( pat instanceof FromCompositeFactPattern ) {
pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
}
if ( pat instanceof CompositeFactPattern ) {
for ( IPattern iPattern : ( (CompositeFactPattern) pat ).getPatterns() ) {
String type = getLHSBindingType( iPattern, var );
if ( type != null ) {
return type;
}
}
}
if ( pat instanceof FactPattern ) {
final FactPattern p = (FactPattern) pat;
if ( p.isBound() && var.equals( p.getBoundName() ) ) {
return p.getFactType();
}
for ( FieldConstraint fc : p.getFieldConstraints() ) {
String type = getFieldBinding( fc,
var );
if ( type != null ) {
return type;
}
}
}
return null;
}
public String getFieldBinding( FieldConstraint fc,
String var ) {
String fieldType = null;
if ( fc instanceof SingleFieldConstraint ) {
SingleFieldConstraint s = (SingleFieldConstraint) fc;
if ( s.isBound() && var.equals( s.getFieldBinding() ) ) {
fieldType = s.getFieldType();
}
}
if ( fc instanceof SingleFieldConstraintEBLeftSide ) {
SingleFieldConstraintEBLeftSide s = (SingleFieldConstraintEBLeftSide) fc;
if ( s.isBound() && var.equals( s.getFieldBinding() ) ) {
fieldType = s.getExpressionLeftSide().getGenericType();
}
}
if ( fc instanceof CompositeFieldConstraint ) {
CompositeFieldConstraint s = (CompositeFieldConstraint) fc;
if ( s.getConstraints() != null ) {
for ( FieldConstraint ss : s.getConstraints() ) {
fieldType = getFieldBinding( ss,
var );
}
}
}
return fieldType;
}
/**
* This will return a List<String> of all ActionInsertFact bindings
* @return The bindings or an empty list if no bindings are found.
*/
public List<String> getRHSBoundFacts() {
if ( this.rhs == null ) {
return null;
}
final List<String> list = new ArrayList<String>();
for ( int i = 0; i < this.rhs.length; i++ ) {
if ( this.rhs[ i ] instanceof ActionInsertFact ) {
final ActionInsertFact p = (ActionInsertFact) this.rhs[ i ];
if ( p.getBoundName() != null ) {
list.add( p.getBoundName() );
}
}
}
return list;
}
/**
* This will return the ActionInsertFact that a variable is bound to.
* @param var The bound fact variable (NOT bound field).
* @return null or the ActionInsertFact found.
*/
public ActionInsertFact getRHSBoundFact( final String var ) {
if ( this.rhs == null ) {
return null;
}
for ( int i = 0; i < this.rhs.length; i++ ) {
if ( this.rhs[ i ] instanceof ActionInsertFact ) {
final ActionInsertFact p = (ActionInsertFact) this.rhs[ i ];
if ( p.getBoundName() != null && var.equals( p.getBoundName() ) ) {
return p;
}
}
}
return null;
}
/**
* This will return the FactPattern that a variable is bound to. If the
* variable is bound to a FieldConstraint the parent FactPattern will be
* returned.
* @param var The variable binding
* @return null or the FactPattern found.
*/
public FactPattern getLHSParentFactPatternForBinding( final String var ) {
if ( this.lhs == null ) {
return null;
}
for ( int i = 0; i < this.lhs.length; i++ ) {
IPattern pat = this.lhs[ i ];
if ( pat instanceof FromCompositeFactPattern ) {
pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
}
if ( pat instanceof FactPattern ) {
final FactPattern p = (FactPattern) pat;
if ( p.getBoundName() != null && var.equals( p.getBoundName() ) ) {
return p;
}
for ( int j = 0; j < p.getFieldConstraints().length; j++ ) {
FieldConstraint fc = p.getFieldConstraints()[ j ];
List<String> fieldBindings = getFieldBinding( fc );
if ( fieldBindings.contains( var ) ) {
return p;
}
}
}
}
return null;
}
/**
* This will get a list of all LHS bound variables, including bound fields..
*/
public List<String> getAllLHSVariables() {
List<String> result = new ArrayList<String>();
for ( int i = 0; i < this.lhs.length; i++ ) {
IPattern pat = this.lhs[ i ];
if ( pat instanceof FromCompositeFactPattern ) {
pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
}
if ( pat instanceof FactPattern ) {
FactPattern fact = (FactPattern) pat;
if ( fact.isBound() ) {
result.add( fact.getBoundName() );
}
for ( int j = 0; j < fact.getFieldConstraints().length; j++ ) {
FieldConstraint fc = fact.getFieldConstraints()[ j ];
if ( fc instanceof SingleFieldConstraintEBLeftSide ) {
SingleFieldConstraintEBLeftSide exp = (SingleFieldConstraintEBLeftSide) fc;
if ( exp.getExpressionLeftSide() != null && exp.getExpressionLeftSide().isBound() ) {
result.add( exp.getExpressionLeftSide().getBinding() );
}
} else if ( fc instanceof SingleFieldConstraint ) {
SingleFieldConstraint con = (SingleFieldConstraint) fc;
if ( con.isBound() ) {
result.add( con.getFieldBinding() );
}
if ( con.getExpressionValue() != null && con.getExpressionValue().isBound() ) {
result.add( con.getExpressionValue().getBinding() );
}
}
}
}
}
return result;
}
/**
* This will get a list of all RHS bound variables.
*/
public List<String> getAllRHSVariables() {
List<String> result = new ArrayList<String>();
for ( int i = 0; i < this.rhs.length; i++ ) {
IAction pat = this.rhs[ i ];
if ( pat instanceof ActionInsertFact ) {
ActionInsertFact fact = (ActionInsertFact) pat;
if ( fact.isBound() ) {
result.add( fact.getBoundName() );
}
}
}
return result;
}
/**
* This will get a list of all bound variables (LHS and RHS), including bound fields..
*/
public List<String> getAllVariables() {
List<String> result = new ArrayList<String>();
result.addAll( this.getAllLHSVariables() );
result.addAll( this.getAllRHSVariables() );
return result;
}
public List<String> getFieldBinding( FieldConstraint f ) {
List<String> result = new ArrayList<String>();
if ( f instanceof SingleFieldConstraint ) {
SingleFieldConstraint con = (SingleFieldConstraint) f;
if ( con.isBound() ) {
result.add( con.getFieldBinding() );
}
if ( con.getExpressionValue() != null && con.getExpressionValue().isBound() ) {
result.add( con.getExpressionValue().getBinding() );
}
if ( con instanceof SingleFieldConstraintEBLeftSide ) {
SingleFieldConstraintEBLeftSide exp = (SingleFieldConstraintEBLeftSide) con;
if ( exp.getExpressionLeftSide() != null && exp.getExpressionLeftSide().isBound() ) {
result.add( exp.getExpressionLeftSide().getBinding() );
}
}
} else if ( f instanceof CompositeFieldConstraint ) {
CompositeFieldConstraint cfc = (CompositeFieldConstraint) f;
if ( cfc.getConstraints() != null ) {
for ( FieldConstraint ss : cfc.getConstraints() ) {
List<String> t = getFieldBinding( ss );
result.addAll( t );
}
}
}
return result;
}
/**
* @param idx Remove this index from the LHS. returns false if it was NOT
* allowed to remove this item (ie it is used on the RHS).
*/
public boolean removeLhsItem( final int idx ) {
final IPattern[] newList = new IPattern[ this.lhs.length - 1 ];
int newIdx = 0;
for ( int i = 0; i < this.lhs.length; i++ ) {
if ( i != idx ) {
newList[ newIdx ] = this.lhs[ i ];
newIdx++;
} else {
IPattern pat = this.lhs[ i ];
if ( pat instanceof FromCompositeFactPattern ) {
pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
}
if ( pat instanceof FactPattern ) {
final FactPattern p = (FactPattern) pat;
if ( p.getBoundName() != null && isBoundFactUsed( p.getBoundName() ) ) {
return false;
}
}
}
}
this.lhs = newList;
return true;
}
/**
* @param binding The name of the LHS fact binding.
* @return Returns true if the specified binding is used on the RHS.
*/
public boolean isBoundFactUsed( final String binding ) {
if ( this.rhs == null ) {
return false;
}
for ( int i = 0; i < this.rhs.length; i++ ) {
if ( this.rhs[ i ] instanceof ActionSetField ) {
final ActionSetField set = (ActionSetField) this.rhs[ i ];
if ( set.getVariable().equals( binding ) ) {
return true;
}
} else if ( this.rhs[ i ] instanceof ActionRetractFact ) {
final ActionRetractFact ret = (ActionRetractFact) this.rhs[ i ];
if ( ret.getVariableName().equals( binding ) ) {
return true;
}
}
}
return false;
}
public void addLhsItem( final IPattern pat ) {
this.addLhsItem( pat,
true );
}
public void addLhsItem( final IPattern pat,
boolean append ) {
this.addLhsItem( pat,
append ? this.lhs.length : 0 );
}
public void addLhsItem( final IPattern pat,
int position ) {
if ( this.lhs == null ) {
this.lhs = new IPattern[ 0 ];
}
if ( position < 0 ) {
position = 0;
} else if ( position > this.lhs.length ) {
position = this.lhs.length;
}
final IPattern[] list = this.lhs;
final IPattern[] newList = new IPattern[ list.length + 1 ];
for ( int i = 0; i < newList.length; i++ ) {
if ( i < position ) {
newList[ i ] = list[ i ];
} else if ( i > position ) {
newList[ i ] = list[ i - 1 ];
} else {
newList[ i ] = pat;
}
}
this.lhs = newList;
}
public void moveLhsItemDown( int itemIndex ) {
if ( this.lhs == null ) {
this.lhs = new IPattern[ 0 ];
}
this.moveItemDown( this.lhs,
itemIndex );
}
public void moveLhsItemUp( int itemIndex ) {
if ( this.lhs == null ) {
this.lhs = new IPattern[ 0 ];
}
this.moveItemUp( this.lhs,
itemIndex );
}
public void moveRhsItemDown( int itemIndex ) {
if ( this.rhs == null ) {
this.rhs = new IAction[ 0 ];
}
this.moveItemDown( this.rhs,
itemIndex );
}
public void moveRhsItemUp( int itemIndex ) {
if ( this.rhs == null ) {
this.rhs = new IAction[ 0 ];
}
this.moveItemUp( this.rhs,
itemIndex );
}
private void moveItemDown( Object[] array,
int itemIndex ) {
for ( int i = 0; i < array.length; i++ ) {
if ( i == itemIndex ) {
if ( array.length > ( i + 1 ) ) {
Object tmp = array[ i + 1 ];
array[ i + 1 ] = array[ i ];
array[ i ] = tmp;
i++;
}
}
}
}
private void moveItemUp( Object[] array,
int itemIndex ) {
if ( itemIndex == 0 ) {
return;
}
for ( int i = 0; i < array.length; i++ ) {
if ( i == itemIndex ) {
Object tmp = array[ i - 1 ];
array[ i - 1 ] = array[ i ];
array[ i ] = tmp;
}
}
}
public void addRhsItem( final IAction action ) {
this.addRhsItem( action,
true );
}
public void addRhsItem( final IAction action,
boolean append ) {
this.addRhsItem( action,
append ? this.rhs.length : 0 );
}
public void addRhsItem( final IAction action,
int position ) {
if ( this.rhs == null ) {
this.rhs = new IAction[ 0 ];
}
if ( position < 0 ) {
position = 0;
} else if ( position > this.rhs.length ) {
position = this.rhs.length;
}
final IAction[] list = this.rhs;
final IAction[] newList = new IAction[ list.length + 1 ];
for ( int i = 0; i < newList.length; i++ ) {
if ( i < position ) {
newList[ i ] = list[ i ];
} else if ( i > position ) {
newList[ i ] = list[ i - 1 ];
} else {
newList[ i ] = action;
}
}
this.rhs = newList;
}
public void removeRhsItem( final int idx ) {
final IAction[] newList = new IAction[ this.rhs.length - 1 ];
int newIdx = 0;
for ( int i = 0; i < this.rhs.length; i++ ) {
if ( i != idx ) {
newList[ newIdx ] = this.rhs[ i ];
newIdx++;
}
}
this.rhs = newList;
}
public void addAttribute( final RuleAttribute attribute ) {
final RuleAttribute[] list = this.attributes;
final RuleAttribute[] newList = new RuleAttribute[ list.length + 1 ];
for ( int i = 0; i < list.length; i++ ) {
newList[ i ] = list[ i ];
}
newList[ list.length ] = attribute;
this.attributes = newList;
}
public void removeAttribute( final int idx ) {
final RuleAttribute[] newList = new RuleAttribute[ this.attributes.length - 1 ];
int newIdx = 0;
for ( int i = 0; i < this.attributes.length; i++ ) {
if ( i != idx ) {
newList[ newIdx ] = this.attributes[ i ];
newIdx++;
}
}
this.attributes = newList;
}
/**
* Add metaData
* @param metadata
*/
public void addMetadata( final RuleMetadata metadata ) {
final RuleMetadata[] newList = new RuleMetadata[ this.metadataList.length + 1 ];
for ( int i = 0; i < this.metadataList.length; i++ ) {
newList[ i ] = this.metadataList[ i ];
}
newList[ this.metadataList.length ] = metadata;
this.metadataList = newList;
}
public void removeMetadata( final int idx ) {
final RuleMetadata[] newList = new RuleMetadata[ this.metadataList.length - 1 ];
int newIdx = 0;
for ( int i = 0; i < this.metadataList.length; i++ ) {
if ( i != idx ) {
newList[ newIdx ] = this.metadataList[ i ];
newIdx++;
}
}
this.metadataList = newList;
}
/**
* Locate metadata element
* @param attributeName - value to look for
* @return null if not found
*/
public RuleMetadata getMetaData( String attributeName ) {
if ( metadataList != null && attributeName != null ) {
for ( int i = 0; i < metadataList.length; i++ ) {
if ( attributeName.equals( metadataList[ i ].getAttributeName() ) ) {
return metadataList[ i ];
}
}
}
return null;
}
/**
* Update metaData element if it exists or add it otherwise
* @param target
* @return true on update of existing element false on added of element
*/
public boolean updateMetadata( final RuleMetadata target ) {
RuleMetadata metaData = getMetaData( target.getAttributeName() );
if ( metaData != null ) {
metaData.setValue( target.getValue() );
return true;
}
addMetadata( target );
return false;
}
/**
* This uses a deceptively simple algorithm to determine what bound
* variables are in scope for a given constraint (including connectives).
* Does not take into account globals.
*/
public List<String> getBoundVariablesInScope( final BaseSingleFieldConstraint con ) {
final List<String> result = new ArrayList<String>();
for ( int i = 0; i < this.lhs.length; i++ ) {
IPattern pat = this.lhs[ i ];
if ( findBoundVariableNames( con, result, pat ) ) {
return result;
}
}
return result;
}
private boolean findBoundVariableNames( BaseSingleFieldConstraint con,
List<String> result,
IPattern pat ) {
if ( pat instanceof FromCompositeFactPattern ) {
pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
}
if ( pat instanceof CompositeFactPattern ) {
for ( IFactPattern p : ( (CompositeFactPattern) pat ).getPatterns() ) {
findBoundVariableNames( con, result, p );
}
}
if ( pat instanceof FactPattern ) {
final FactPattern fact = (FactPattern) pat;
if ( findBoundVariableNames( con, result, fact ) ) {
return true;
}
}
return false;
}
private boolean findBoundVariableNames( BaseSingleFieldConstraint con,
List<String> result,
FactPattern fact ) {
if ( fact.getConstraintList() != null ) {
final FieldConstraint[] cons = fact.getConstraintList().getConstraints();
if ( cons != null ) {
for ( int k = 0; k < cons.length; k++ ) {
FieldConstraint fc = cons[ k ];
if ( fc instanceof SingleFieldConstraint ) {
final SingleFieldConstraint c = (SingleFieldConstraint) fc;
if ( c == con ) {
return true;
}
if ( c.getConnectives() != null ) {
for ( int j = 0; j < c.getConnectives().length; j++ ) {
if ( con == c.getConnectives()[ j ] ) {
return true;
}
}
}
if ( c.isBound() ) {
result.add( c.getFieldBinding() );
}
}
}
}
if ( fact.isBound() ) {
result.add( fact.getBoundName() );
}
} else {
if ( fact.isBound() ) {
result.add( fact.getBoundName() );
}
}
return false;
}
/**
* Checks to see if a variable is used or not, includes fields as well as
* facts.
*/
public boolean isVariableNameUsed( String s ) {
return getAllVariables().contains( s );
}
/**
* Returns true if any DSLSentences are used.
*/
public boolean hasDSLSentences() {
if ( this.lhs != null ) {
for ( IPattern pattern : this.lhs ) {
if ( pattern instanceof DSLSentence ) {
return true;
}
}
}
if ( this.rhs != null ) {
for ( IAction action : this.rhs ) {
if ( action instanceof DSLSentence ) {
return true;
}
}
}
return false;
}
/**
* Is the Rule to be negated, i.e. "not ( PatternX, PatternY... )"
* @return
*/
public boolean isNegated() {
return isNegated;
}
/**
* Set whether the Rule is to be negated
* @param isNegated
*/
public void setNegated( boolean isNegated ) {
this.isNegated = isNegated;
}
public Imports getImports() {
return imports;
}
@Override
public void setImports( final Imports imports ) {
this.imports = imports;
}
public String getPackageName() {
return packageName;
}
public void setPackageName( String packageName ) {
this.packageName = packageName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RuleModel ruleModel = (RuleModel) o;
if (isNegated != ruleModel.isNegated) return false;
if (!Arrays.equals(attributes, ruleModel.attributes)) return false;
if (imports != null ? !imports.equals(ruleModel.imports) : ruleModel.imports != null) return false;
if (!Arrays.equals(lhs, ruleModel.lhs)) return false;
if (!Arrays.equals(metadataList, ruleModel.metadataList)) return false;
if (modelVersion != null ? !modelVersion.equals(ruleModel.modelVersion) : ruleModel.modelVersion != null)
return false;
if (name != null ? !name.equals(ruleModel.name) : ruleModel.name != null) return false;
if (packageName != null ? !packageName.equals(ruleModel.packageName) : ruleModel.packageName != null)
return false;
if (parentName != null ? !parentName.equals(ruleModel.parentName) : ruleModel.parentName != null) return false;
if (!Arrays.equals(rhs, ruleModel.rhs)) return false;
return true;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = ~~result;
result = 31 * result + (parentName != null ? parentName.hashCode() : 0);
result = ~~result;
result = 31 * result + (modelVersion != null ? modelVersion.hashCode() : 0);
result = ~~result;
result = 31 * result + (attributes != null ? Arrays.hashCode(attributes) : 0);
result = ~~result;
result = 31 * result + (metadataList != null ? Arrays.hashCode(metadataList) : 0);
result = ~~result;
result = 31 * result + (lhs != null ? Arrays.hashCode(lhs) : 0);
result = ~~result;
result = 31 * result + (rhs != null ? Arrays.hashCode(rhs) : 0);
result = ~~result;
result = 31 * result + (imports != null ? imports.hashCode() : 0);
result = ~~result;
result = 31 * result + (packageName != null ? packageName.hashCode() : 0);
result = ~~result;
result = 31 * result + (isNegated ? 1 : 0);
result = ~~result;
return result;
}
}