/** * 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.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.drools.WorkingMemory; import org.drools.base.EnabledBoolean; import org.drools.base.SalienceInteger; import org.drools.io.Resource; import org.drools.spi.AgendaGroup; import org.drools.spi.CompiledInvoker; import org.drools.spi.Consequence; import org.drools.spi.Enabled; import org.drools.spi.Salience; import org.drools.spi.Tuple; import org.drools.spi.Wireable; import org.drools.time.impl.Timer; /** * A <code>Rule</code> contains a set of <code>Test</code>s and a * <code>Consequence</code>. * <p> * The <code>Test</code>s describe the circumstances that representrepresent * a match for this rule. The <code>Consequence</code> gets fired when the * Conditions match. * * @author <a href="mailto:bob@eng.werken.com"> bob mcwhirter </a> * @author <a href="mailto:simon@redhillconsulting.com.au"> Simon Harris </a> * @author <a href="mailto:mproctor@codehaus.org"> mark proctor </a> */ public class Rule implements Externalizable, Wireable, Dialectable, org.drools.definition.rule.Rule, org.drools.definition.rule.Query { /** * */ private static final long serialVersionUID = 510l; /** */ // ------------------------------------------------------------ // Instance members // ------------------------------------------------------------ /** The parent pkg */ private String pkg; /** Name of the rule. */ private String name; /** Parent Rule Name, optional */ private Rule parent; /** Salience value. */ private Salience salience; /** The Rule is dirty after patterns have been added */ private boolean dirty; private Map<String, Declaration> declarations; private Declaration[] declarationArray; private GroupElement lhsRoot; private String dialect; private String agendaGroup; private Map<String, Object> metaAttributes; /** Consequence. */ private Consequence consequence; private Map<String, Consequence> namedConsequence; /** Timer semantics that controls the firing of a rule */ private Timer timer; /** Load order in Package */ private long loadOrder; /** Is recursion of this rule allowed */ private boolean noLoop; /** makes the rule's much the current focus */ private boolean autoFocus; private String activationGroup; private String ruleFlowGroup; private boolean lockOnActive; private boolean hasLogicalDependency; /** indicates that the rule is semantically correct. */ private boolean semanticallyValid; private String[] calendars; private Calendar dateEffective; private Calendar dateExpires; private Enabled enabled; private Resource resource; public void writeExternal(ObjectOutput out) throws IOException { out.writeObject( pkg ); out.writeObject( name ); out.writeObject( parent ); out.writeObject( salience ); out.writeBoolean( dirty ); out.writeObject( declarations ); out.writeObject( declarationArray ); out.writeObject( lhsRoot ); out.writeObject( dialect ); out.writeObject( agendaGroup ); out.writeObject( metaAttributes ); if ( this.consequence instanceof CompiledInvoker ) { out.writeObject( null ); out.writeObject( null ); } else { out.writeObject( this.consequence ); out.writeObject( this.namedConsequence ); } out.writeObject( timer ); out.writeLong( loadOrder ); out.writeBoolean( noLoop ); out.writeBoolean( autoFocus ); out.writeObject( activationGroup ); out.writeObject( ruleFlowGroup ); out.writeBoolean( lockOnActive ); out.writeBoolean( hasLogicalDependency ); out.writeBoolean( semanticallyValid ); out.writeObject( dateEffective ); out.writeObject( dateExpires ); out.writeObject( enabled ); out.writeObject( resource ); } @SuppressWarnings("unchecked") public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { pkg = (String) in.readObject(); name = (String) in.readObject(); parent = (Rule) in.readObject(); salience = (Salience) in.readObject(); dirty = in.readBoolean(); declarations = (Map<String, Declaration>) in.readObject(); declarationArray = (Declaration[]) in.readObject(); lhsRoot = (GroupElement) in.readObject(); dialect = (String) in.readObject(); agendaGroup = (String) in.readObject(); metaAttributes = (Map<String, Object>) in.readObject(); consequence = (Consequence) in.readObject(); namedConsequence = (Map<String, Consequence>) in.readObject(); timer = (Timer) in.readObject(); loadOrder = in.readLong(); noLoop = in.readBoolean(); autoFocus = in.readBoolean(); activationGroup = (String) in.readObject(); ruleFlowGroup = (String) in.readObject(); lockOnActive = in.readBoolean(); hasLogicalDependency = in.readBoolean(); semanticallyValid = in.readBoolean(); dateEffective = (Calendar) in.readObject(); dateExpires = (Calendar) in.readObject(); enabled = (Enabled) in.readObject(); resource = (Resource) in.readObject(); } // ------------------------------------------------------------ // Constructors // ------------------------------------------------------------ public Rule() { } /** * Construct a * <code>Rule<code> with the given name for the specified pkg parent * * @param name * The name of this rule. */ public Rule(final String name, final String pkg, final String agendaGroup) { this.name = name; this.pkg = pkg; this.agendaGroup = agendaGroup; this.lhsRoot = GroupElementFactory.newAndInstance(); this.semanticallyValid = true; this.enabled = EnabledBoolean.ENABLED_TRUE; this.salience = SalienceInteger.DEFAULT_SALIENCE; this.metaAttributes = new HashMap<String, Object>(); } /** * Construct a * <code>Rule<code> with the given name for the specified pkg parent * * @param name * The name of this rule. */ public Rule(final String name, final String agendaGroup) { this( name, null, agendaGroup ); } public Rule(final String name) { this( name, null, AgendaGroup.MAIN ); } public Resource getResource() { return resource; } public void setResource(Resource resource) { this.resource = resource; } public String getDialect() { return dialect; } public void setDialect(String dialect) { this.dialect = dialect; } /** * Returns the Timer semantics for a rule. Timer based rules are not added directly to the Agenda * instead they are scheduled for Agenda addition, based on the timer. * @return */ public Timer getTimer() { return timer; } /** * Sets the timer semantics for a rule. Timer based rules are not added directly to the Agenda * instead they are scheduled for Agenda addition, based on the timer. * @param timer */ public void setTimer(Timer timer) { this.timer = timer; } /** * Determine if this rule is internally consistent and valid. * This will include checks to make sure the rules semantic components (actions and predicates) * are valid. * * No exception is thrown. * <p> * A <code>Rule</code> must include at least one parameter declaration and * one condition. * </p> * * @return <code>true</code> if this rule is valid, else * <code>false</code>. */ public boolean isValid() { //if ( this.patterns.size() == 0 ) { // return false; //} if ( this.consequence == null || !isSemanticallyValid() ) { return false; } return true; } public String getPackage() { return this.pkg; } public void setPackage(String pkg) { this.pkg = pkg; } public String getPackageName() { return this.pkg; } /** * Retrieve the name of this rule. * * @return The name of this rule. */ public String getName() { return this.name; } /** * Retrieve the <code>Rule</code> salience. * * @return The salience. */ public Salience getSalience() { return this.salience; } /** * Set the <code>Rule<code> salience. * * @param salience The salience. */ public void setSalience(final Salience salience) { this.salience = salience; } public String getAgendaGroup() { if ( this.agendaGroup == null || this.agendaGroup.equals( "" ) ) { return AgendaGroup.MAIN; } return this.agendaGroup; } public void setAgendaGroup(final String agendaGroup) { this.agendaGroup = agendaGroup; } public boolean isNoLoop() { return this.noLoop; } /** * This returns true is the rule is effective. * If the rule is not effective, it cannot activate. * * This uses the dateEffective, dateExpires and enabled flag to decide this. */ public boolean isEffective(Tuple tuple, WorkingMemory workingMemory) { if ( !this.enabled.getValue( tuple, this, workingMemory ) ) { return false; } if ( this.dateEffective == null && this.dateExpires == null ) { return true; } else { Calendar now = Calendar.getInstance(); now.setTimeInMillis( workingMemory.getSessionClock().getCurrentTime() ); if ( this.dateEffective != null && this.dateExpires != null ) { return (now.after( this.dateEffective ) && now.before( this.dateExpires )); } else if ( this.dateEffective != null ) { return (now.after( this.dateEffective )); } else { return (now.before( this.dateExpires )); } } } public void setNoLoop(final boolean noLoop) { this.noLoop = noLoop; } public boolean getAutoFocus() { return this.autoFocus; } public void setAutoFocus(final boolean autoFocus) { this.autoFocus = autoFocus; } public String getActivationGroup() { return this.activationGroup; } public void setActivationGroup(final String activationGroup) { this.activationGroup = activationGroup; } public String getRuleFlowGroup() { return this.ruleFlowGroup; } public void setRuleFlowGroup(final String ruleFlowGroup) { this.ruleFlowGroup = ruleFlowGroup; } /** * Retrieve a parameter <code>Declaration</code> by identifier. * * @param identifier * The identifier. * * @return The declaration or <code>null</code> if no declaration matches * the <code>identifier</code>. */ @SuppressWarnings("unchecked") public Declaration getDeclaration(final String identifier) { if ( this.dirty || (this.declarations == null) ) { this.declarations = (Map<String, Declaration>) this.getExtendedLhs( this, null ).getOuterDeclarations(); this.declarationArray = (Declaration[]) this.declarations.values().toArray( new Declaration[this.declarations.values().size()] ); this.dirty = false; } return this.declarations.get( identifier ); } /** * This field is updated at runtime, when the first logical assertion is done. I'm currently not too happy about having this determine at runtime * but its currently easier than trying to do this at compile time, although eventually this should be changed * @return */ public boolean hasLogicalDependency() { return this.hasLogicalDependency; } public void setHasLogicalDependency(boolean hasLogicalDependency) { this.hasLogicalDependency = hasLogicalDependency; } public boolean isLockOnActive() { return this.lockOnActive; } public void setLockOnActive(final boolean lockOnActive) { this.lockOnActive = lockOnActive; } /** * Retrieve the set of all <i>root fact object </i> parameter * <code>Declarations</code>. * * @return The Set of <code>Declarations</code> in order which specify the * <i>root fact objects</i>. */ @SuppressWarnings("unchecked") public Declaration[] getDeclarations() { if ( this.dirty || (this.declarationArray == null) ) { this.declarations = (Map<String, Declaration>) this.getExtendedLhs( this, null ).getOuterDeclarations(); this.declarationArray = (Declaration[]) this.declarations.values().toArray( new Declaration[this.declarations.values().size()] ); this.dirty = false; } return this.declarationArray; } /** * Add a pattern to the rule. All patterns are searched for bindings which are then added to the rule * as declarations * * @param condition * The <code>Test</code> to add. * @throws InvalidRuleException */ public void addPattern(final RuleConditionElement element) { this.dirty = true; this.lhsRoot.addChild( element ); } /** * Retrieve the <code>List</code> of <code>Conditions</code> for this * rule. * * @return The <code>List</code> of <code>Conditions</code>. */ public GroupElement getLhs() { return this.lhsRoot; } public void setLhs(final GroupElement lhsRoot) { this.dirty = true; this.lhsRoot = lhsRoot; } private GroupElement getExtendedLhs(Rule rule, GroupElement fromChild) { //combine rules LHS with Parent "Extends" final GroupElement lhs = (GroupElement) rule.lhsRoot.clone(); //use the children passed from prior child rules, and combine with current LHS (at the end) if ( null != fromChild ) { //Have GroupElement from a child rule, so combine it lhs.getChildren().addAll( fromChild.getChildren() ); } //move recursively up the tree if ( rule.parent != null ) { return getExtendedLhs( rule.parent, lhs ); } //at the top of the tree, return combined LHS //TODO Merge LHS for performace return lhs; } /** * Uses the LogicTransformer to process the Rule patters - if no ORs are * used this will return an array of a single AND element. If there are Ors * it will return an And element for each possible logic branch. The * processing uses as a clone of the Rule's patterns, so they are not * changed. * * @return * @throws InvalidPatternException */ public GroupElement[] getTransformedLhs() throws InvalidPatternException { //Moved to getExtendedLhs --final GroupElement cloned = (GroupElement) this.lhsRoot.clone(); return LogicTransformer.getInstance().transform( getExtendedLhs( this, null ) ); } public int getSpecifity() { return getSpecifity( this.lhsRoot ); } private int getSpecifity(final GroupElement ce) { int specificity = 0; for ( final Iterator it = ce.getChildren().iterator(); it.hasNext(); ) { final Object object = it.next(); if ( object instanceof Pattern ) { specificity += getSpecifity( (Pattern) object ); } else if ( object instanceof GroupElement ) { specificity += getSpecifity( (GroupElement) object ); } } return specificity; } private int getSpecifity(final Pattern pattern) { int specificity = 0; for ( final Iterator it = pattern.getConstraints().iterator(); it.hasNext(); ) { if ( !(it.next() instanceof Declaration) ) { specificity++; } } return specificity; } public void wire(Object object) { if ( object instanceof Salience ) { setSalience( (Salience) object ); } else if ( object instanceof Enabled ) { setEnabled( (Enabled) object ); } else { Consequence c = (Consequence) object; if ( "default".equals( c.getName() ) ) { setConsequence( c ); } else { getNamedConsequences().put( c.getName(), c ); } } } /** * Set the <code>Consequence</code> that is associated with the successful * match of this rule. * * @param consequence * The <code>Consequence</code> to attach to this * <code>Rule</code>. */ public void setConsequence(final Consequence consequence) { this.consequence = consequence; } /** * Retrieve the <code>Consequence</code> associated with this * <code>Rule</code>. * * @return The <code>Consequence</code>. */ public Consequence getConsequence() { return this.consequence; } public Map<String, Consequence> getNamedConsequences() { if ( this.namedConsequence == null ) { this.namedConsequence = new HashMap<String, Consequence>(); } return this.namedConsequence; } public long getLoadOrder() { return this.loadOrder; } public void setLoadOrder(final long loadOrder) { this.loadOrder = loadOrder; } public String toString() { return "[Rule name=" + this.name + ", agendaGroup=" + this.agendaGroup + ", salience=" + this.salience + ", no-loop=" + this.noLoop + "]"; } public int hashCode() { final int PRIME = 31; int result = 1; result = PRIME * result + ((name == null) ? 0 : name.hashCode()); result = PRIME * result + ((pkg == null) ? 0 : pkg.hashCode()); return result; } public boolean equals(Object obj) { if ( this == obj ) return true; if ( obj == null || getClass() != obj.getClass() ) return false; final Rule other = (Rule) obj; if ( name == null ) { if ( other.name != null ) return false; } else if ( !name.equals( other.name ) ) return false; if ( pkg == null ) { if ( other.pkg != null ) return false; } else if ( !pkg.equals( other.pkg ) ) return false; return true; } public void setSemanticallyValid(final boolean valid) { this.semanticallyValid = valid; } /** * This will return if the semantic actions or predicates in the rules * are valid. * This is provided so that lists of rules can be provided even if their semantic actions * do not "compile" etc. */ public boolean isSemanticallyValid() { return this.semanticallyValid; } public String[] getCalendars() { return calendars; } public void setCalendars(String[] calendars) { this.calendars = calendars; } /** * Sets the date from which this rule takes effect (can include time to the millisecond). * @param effectiveDate */ public void setDateEffective(final Calendar effectiveDate) { this.dateEffective = effectiveDate; } /** * Sets the date after which the rule will no longer apply (can include time to the millisecond). * @param expiresDate */ public void setDateExpires(final Calendar expiresDate) { this.dateExpires = expiresDate; } public Calendar getDateEffective() { return this.dateEffective; } public Calendar getDateExpires() { return this.dateExpires; } /** * A rule is enabled by default. This can explicitly disable it in which case it will never activate. */ public void setEnabled(final Enabled b) { this.enabled = b; } public boolean isEnabled(Tuple tuple, WorkingMemory workingMemory) { return this.enabled.getValue( tuple, this, workingMemory ); } public void addMetaAttribute(String key, Object value) { this.metaAttributes.put( key, value ); } public Map<String, Object> getMetaData() { return Collections.unmodifiableMap( metaAttributes ); } @Deprecated public Map<String, Object> getMetaAttributes() { return Collections.unmodifiableMap( metaAttributes ); } @Deprecated public String getMetaAttribute(final String identifier) { return this.metaAttributes.get( identifier ).toString(); } @Deprecated public Collection<String> listMetaAttributes() { return this.metaAttributes.keySet(); } public void setParent(Rule parent) { this.parent = parent; } public Rule getParent() { return parent; } }