/**
* Copyright 2010 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.reteoo;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.Arrays;
import org.drools.RuleBaseConfiguration;
import org.drools.base.DroolsQuery;
import org.drools.common.BetaConstraints;
import org.drools.common.InternalFactHandle;
import org.drools.common.InternalWorkingMemory;
import org.drools.common.PropagationContextImpl;
import org.drools.core.util.ArrayUtils;
import org.drools.core.util.Iterator;
import org.drools.core.util.ObjectHashMap.ObjectEntry;
import org.drools.reteoo.builder.BuildContext;
import org.drools.rule.Accumulate;
import org.drools.rule.Behavior;
import org.drools.rule.ContextEntry;
import org.drools.spi.AlphaNodeFieldConstraint;
import org.drools.spi.PropagationContext;
/**
* AccumulateNode
* A beta node capable of doing accumulate logic.
*
* Created: 04/06/2006
* @author <a href="mailto:tirelli@post.com">Edson Tirelli</a>
*
* @version $Id$
*/
public class AccumulateNode extends BetaNode {
private static final long serialVersionUID = 510l;
private boolean unwrapRightObject;
private Accumulate accumulate;
private AlphaNodeFieldConstraint[] resultConstraints;
private BetaConstraints resultBinder;
public AccumulateNode() {
}
public AccumulateNode(final int id,
final LeftTupleSource leftInput,
final ObjectSource rightInput,
final AlphaNodeFieldConstraint[] resultConstraints,
final BetaConstraints sourceBinder,
final BetaConstraints resultBinder,
final Behavior[] behaviors,
final Accumulate accumulate,
final boolean unwrapRightObject,
final BuildContext context) {
super( id,
context.getPartitionId(),
context.getRuleBase().getConfiguration().isMultithreadEvaluation(),
leftInput,
rightInput,
sourceBinder,
behaviors );
this.resultBinder = resultBinder;
this.resultConstraints = resultConstraints;
this.accumulate = accumulate;
this.unwrapRightObject = unwrapRightObject;
this.tupleMemoryEnabled = context.isTupleMemoryEnabled();
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
super.readExternal( in );
unwrapRightObject = in.readBoolean();
accumulate = (Accumulate) in.readObject();
resultConstraints = (AlphaNodeFieldConstraint[]) in.readObject();
resultBinder = (BetaConstraints) in.readObject();
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal( out );
out.writeBoolean( unwrapRightObject );
out.writeObject( accumulate );
out.writeObject( resultConstraints );
out.writeObject( resultBinder );
}
/**
* @inheritDoc
*
* When a new tuple is asserted into an AccumulateNode, do this:
*
* 1. Select all matching objects from right memory
* 2. Execute the initialization code using the tuple + matching objects
* 3. Execute the accumulation code for each combination of tuple+object
* 4. Execute the return code
* 5. Create a new CalculatedObjectHandle for the resulting object and add it to the tuple
* 6. Propagate the tuple
*
* The initialization, accumulation and return codes, in JBRules, are assembled
* into a generated method code and called once for the whole match, as you can see
* bellow:
*
* Object result = this.accumulator.accumulate( ... );
*
*/
public void assertLeftTuple(final LeftTuple leftTuple,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
final AccumulateMemory memory = (AccumulateMemory) workingMemory.getNodeMemory( this );
AccumulateContext accresult = new AccumulateContext();
boolean useLeftMemory = true;
if ( this.tupleMemoryEnabled ) {
memory.betaMemory.getLeftTupleMemory().add( leftTuple );
memory.betaMemory.getCreatedHandles().put( leftTuple,
accresult,
false );
} else {
// this is a hack, to not add closed DroolsQuery objects
Object object = ((InternalFactHandle) context.getFactHandle()).getObject();
if ( memory.betaMemory.getLeftTupleMemory() != null && !(object instanceof DroolsQuery && !((DroolsQuery) object).isOpen()) ) {
memory.betaMemory.getLeftTupleMemory().add( leftTuple );
memory.betaMemory.getCreatedHandles().put( leftTuple,
accresult,
false );
} else {
useLeftMemory = false;
}
}
accresult.context = this.accumulate.createContext();
this.accumulate.init( memory.workingMemoryContext,
accresult.context,
leftTuple,
workingMemory );
this.constraints.updateFromTuple( memory.betaMemory.getContext(),
workingMemory,
leftTuple );
for ( RightTuple rightTuple = memory.betaMemory.getRightTupleMemory().getFirst( leftTuple,
(InternalFactHandle) context.getFactHandle() ); rightTuple != null; rightTuple = (RightTuple) rightTuple.getNext() ) {
InternalFactHandle handle = rightTuple.getFactHandle();
if ( this.constraints.isAllowedCachedLeft( memory.betaMemory.getContext(),
handle ) ) {
// add a match
addMatch( leftTuple,
rightTuple,
null,
null,
workingMemory,
memory,
accresult,
useLeftMemory );
}
}
this.constraints.resetTuple( memory.betaMemory.getContext() );
evaluateResultConstraints( ActivitySource.LEFT,
leftTuple,
context,
workingMemory,
memory,
accresult,
useLeftMemory );
}
/**
* @inheritDoc
*
* As the accumulate node will always generate a resulting tuple,
* we must always destroy it
*
*/
public void retractLeftTuple(final LeftTuple leftTuple,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
final AccumulateMemory memory = (AccumulateMemory) workingMemory.getNodeMemory( this );
memory.betaMemory.getLeftTupleMemory().remove( leftTuple );
final AccumulateContext accctx = (AccumulateContext) memory.betaMemory.getCreatedHandles().remove( leftTuple );
removePreviousMatchesForLeftTuple( leftTuple,
workingMemory,
memory,
accctx );
if ( accctx.propagated ) {
// if tuple was previously propagated, retract it and destroy result fact handle
this.sink.propagateRetractLeftTupleDestroyRightTuple( leftTuple,
context,
workingMemory );
} else {
// if not propagated, just destroy the result fact handle
workingMemory.getFactHandleFactory().destroyFactHandle( accctx.result.getFactHandle() );
}
}
/**
* @inheritDoc
*
* When a new object is asserted into an AccumulateNode, do this:
*
* 1. Select all matching tuples from left memory
* 2. For each matching tuple, call a modify tuple
*
*/
public void assertObject(final InternalFactHandle factHandle,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
final AccumulateMemory memory = (AccumulateMemory) workingMemory.getNodeMemory( this );
final RightTuple rightTuple = new RightTuple( factHandle,
this );
if ( !behavior.assertRightTuple( memory.betaMemory.getBehaviorContext(),
rightTuple,
workingMemory ) ) {
// destroy right tuple
rightTuple.unlinkFromRightParent();
return;
}
memory.betaMemory.getRightTupleMemory().add( rightTuple );
if ( memory.betaMemory.getLeftTupleMemory() == null || memory.betaMemory.getLeftTupleMemory().size() == 0 ) {
// do nothing here, as we know there are no left tuples at this stage in sequential mode or for a query.
// unless it's an "Open Query" and thus that will have left memory, so continue as normal
return;
}
this.constraints.updateFromFactHandle( memory.betaMemory.getContext(),
workingMemory,
factHandle );
for ( LeftTuple leftTuple = memory.betaMemory.getLeftTupleMemory().getFirst( rightTuple ); leftTuple != null; leftTuple = (LeftTuple) leftTuple.getNext() ) {
if ( this.constraints.isAllowedCachedRight( memory.betaMemory.getContext(),
leftTuple ) ) {
final AccumulateContext accctx = (AccumulateContext) memory.betaMemory.getCreatedHandles().get( leftTuple );
addMatch( leftTuple,
rightTuple,
null,
null,
workingMemory,
memory,
accctx,
true );
evaluateResultConstraints( ActivitySource.RIGHT,
leftTuple,
context,
workingMemory,
memory,
accctx,
true );
}
}
this.constraints.resetFactHandle( memory.betaMemory.getContext() );
}
/**
* @inheritDoc
*
* If an object is retract, call modify tuple for each
* tuple match.
*/
public void retractRightTuple(final RightTuple rightTuple,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
final AccumulateMemory memory = (AccumulateMemory) workingMemory.getNodeMemory( this );
final InternalFactHandle origin = (InternalFactHandle) context.getFactHandleOrigin();
if ( context.getType() == PropagationContext.EXPIRATION ) {
((PropagationContextImpl) context).setFactHandle( null );
}
behavior.retractRightTuple( memory.betaMemory.getBehaviorContext(),
rightTuple,
workingMemory );
memory.betaMemory.getRightTupleMemory().remove( rightTuple );
removePreviousMatchesForRightTuple( rightTuple,
context,
workingMemory,
memory,
rightTuple.firstChild );
if ( context.getType() == PropagationContext.EXPIRATION ) {
((PropagationContextImpl) context).setFactHandle( origin );
}
}
public void modifyLeftTuple(LeftTuple leftTuple,
PropagationContext context,
InternalWorkingMemory workingMemory) {
final AccumulateMemory memory = (AccumulateMemory) workingMemory.getNodeMemory( this );
final AccumulateContext accctx = (AccumulateContext) memory.betaMemory.getCreatedHandles().get( leftTuple );
// Add and remove to make sure we are in the right bucket and at the end
// this is needed to fix for indexing and deterministic iteration
memory.betaMemory.getLeftTupleMemory().remove( leftTuple );
memory.betaMemory.getLeftTupleMemory().add( leftTuple );
this.constraints.updateFromTuple( memory.betaMemory.getContext(),
workingMemory,
leftTuple );
LeftTuple childLeftTuple = getFirstMatch( leftTuple,
accctx,
false );
RightTupleMemory rightMemory = memory.betaMemory.getRightTupleMemory();
RightTuple rightTuple = rightMemory.getFirst( leftTuple,
(InternalFactHandle) context.getFactHandle() );
// first check our index (for indexed nodes only) hasn't changed and we are returning the same bucket
if ( childLeftTuple != null && rightMemory.isIndexed() && rightTuple != rightMemory.getFirst( childLeftTuple.getRightParent() ) ) {
// our index has changed, so delete all the previous matchings
removePreviousMatchesForLeftTuple( leftTuple,
workingMemory,
memory,
accctx );
childLeftTuple = null; // null so the next check will attempt matches for new bucket
}
// we can't do anything if RightTupleMemory is empty
if ( rightTuple != null ) {
if ( childLeftTuple == null ) {
// either we are indexed and changed buckets or
// we had no children before, but there is a bucket to potentially match, so try as normal assert
for ( ; rightTuple != null; rightTuple = (RightTuple) rightTuple.getNext() ) {
final InternalFactHandle handle = rightTuple.getFactHandle();
if ( this.constraints.isAllowedCachedLeft( memory.betaMemory.getContext(),
handle ) ) {
// add a new match
addMatch( leftTuple,
rightTuple,
null,
null,
workingMemory,
memory,
accctx,
true );
}
}
} else {
boolean isDirty = false;
// in the same bucket, so iterate and compare
for ( ; rightTuple != null; rightTuple = (RightTuple) rightTuple.getNext() ) {
final InternalFactHandle handle = rightTuple.getFactHandle();
if ( this.constraints.isAllowedCachedLeft( memory.betaMemory.getContext(),
handle ) ) {
if ( childLeftTuple == null || childLeftTuple.getRightParent() != rightTuple ) {
// add a new match
addMatch( leftTuple,
rightTuple,
childLeftTuple,
null,
workingMemory,
memory,
accctx,
true );
} else {
// we must re-add this to ensure deterministic iteration
LeftTuple temp = childLeftTuple.getLeftParentNext();
childLeftTuple.reAddRight();
childLeftTuple = temp;
}
} else if ( childLeftTuple != null && childLeftTuple.getRightParent() == rightTuple ) {
LeftTuple temp = childLeftTuple.getLeftParentNext();
// remove the match
removeMatch( rightTuple,
childLeftTuple,
workingMemory,
memory,
accctx,
false );
childLeftTuple = temp;
// the next line means that when a match is removed from the current leftTuple
// and the accumulate does not support the reverse operation, then the whole
// result is dirty (since removeMatch above is not recalculating the total)
// and we need to do this later
isDirty = !accumulate.supportsReverse();
}
// else do nothing, was false before and false now.
}
if ( isDirty ) {
reaccumulateForLeftTuple( leftTuple,
workingMemory,
memory,
accctx );
}
}
}
this.constraints.resetTuple( memory.betaMemory.getContext() );
evaluateResultConstraints( ActivitySource.LEFT,
leftTuple,
context,
workingMemory,
memory,
accctx,
true );
}
public void modifyRightTuple(RightTuple rightTuple,
PropagationContext context,
InternalWorkingMemory workingMemory) {
final AccumulateMemory memory = (AccumulateMemory) workingMemory.getNodeMemory( this );
// Add and remove to make sure we are in the right bucket and at the end
// this is needed to fix for indexing and deterministic iteration
memory.betaMemory.getRightTupleMemory().remove( rightTuple );
memory.betaMemory.getRightTupleMemory().add( rightTuple );
if ( memory.betaMemory.getLeftTupleMemory() == null || memory.betaMemory.getLeftTupleMemory().size() == 0 ) {
// do nothing here, as we know there are no left tuples at this stage in sequential mode.
return;
}
// WTD here
// if ( !behavior.assertRightTuple( memory.getBehaviorContext(),
// rightTuple,
// workingMemory ) ) {
// // destroy right tuple
// rightTuple.unlinkFromRightParent();
// return;
// }
LeftTuple childLeftTuple = rightTuple.firstChild;
LeftTupleMemory leftMemory = memory.betaMemory.getLeftTupleMemory();
LeftTuple leftTuple = leftMemory.getFirst( rightTuple );
this.constraints.updateFromFactHandle( memory.betaMemory.getContext(),
workingMemory,
rightTuple.getFactHandle() );
// first check our index (for indexed nodes only) hasn't changed and we are returning the same bucket
if ( childLeftTuple != null && leftMemory.isIndexed() && leftTuple != leftMemory.getFirst( childLeftTuple.getLeftParent() ) ) {
// our index has changed, so delete all the previous matches
removePreviousMatchesForRightTuple( rightTuple,
context,
workingMemory,
memory,
childLeftTuple );
childLeftTuple = null; // null so the next check will attempt matches for new bucket
}
// if LeftTupleMemory is empty, there are no matches to modify
if ( leftTuple != null ) {
if ( childLeftTuple == null ) {
// either we are indexed and changed buckets or
// we had no children before, but there is a bucket to potentially match, so try as normal assert
for ( ; leftTuple != null; leftTuple = (LeftTuple) leftTuple.getNext() ) {
if ( this.constraints.isAllowedCachedRight( memory.betaMemory.getContext(),
leftTuple ) ) {
final AccumulateContext accctx = (AccumulateContext) memory.betaMemory.getCreatedHandles().get( leftTuple );
// add a new match
addMatch( leftTuple,
rightTuple,
null,
null,
workingMemory,
memory,
accctx,
true );
evaluateResultConstraints( ActivitySource.RIGHT,
leftTuple,
context,
workingMemory,
memory,
accctx,
true );
}
}
} else {
// in the same bucket, so iterate and compare
for ( ; leftTuple != null; leftTuple = (LeftTuple) leftTuple.getNext() ) {
if ( this.constraints.isAllowedCachedRight( memory.betaMemory.getContext(),
leftTuple ) ) {
final AccumulateContext accctx = (AccumulateContext) memory.betaMemory.getCreatedHandles().get( leftTuple );
LeftTuple temp = null;
if ( childLeftTuple != null && childLeftTuple.getLeftParent() == leftTuple ) {
temp = childLeftTuple.getRightParentNext();
// we must re-add this to ensure deterministic iteration
childLeftTuple.reAddLeft();
removeMatch( rightTuple,
childLeftTuple,
workingMemory,
memory,
accctx,
true );
childLeftTuple = childLeftTuple.getRightParentNext();
}
// add a new match
addMatch( leftTuple,
rightTuple,
null,
childLeftTuple,
workingMemory,
memory,
accctx,
true );
if ( temp != null ) {
childLeftTuple = temp;
}
evaluateResultConstraints( ActivitySource.RIGHT,
leftTuple,
context,
workingMemory,
memory,
accctx,
true );
} else if ( childLeftTuple != null && childLeftTuple.getLeftParent() == leftTuple ) {
LeftTuple temp = childLeftTuple.getRightParentNext();
final AccumulateContext accctx = (AccumulateContext) memory.betaMemory.getCreatedHandles().get( leftTuple );
// remove the match
removeMatch( rightTuple,
childLeftTuple,
workingMemory,
memory,
accctx,
true );
evaluateResultConstraints( ActivitySource.RIGHT,
leftTuple,
context,
workingMemory,
memory,
accctx,
true );
childLeftTuple = temp;
}
// else do nothing, was false before and false now.
}
}
}
this.constraints.resetFactHandle( memory.betaMemory.getContext() );
}
/**
* Evaluate result constraints and propagate assert in case they are true
*
* @param leftTuple
* @param context
* @param workingMemory
* @param memory
* @param accresult
* @param handle
*/
private void evaluateResultConstraints(final ActivitySource source,
final LeftTuple leftTuple,
final PropagationContext context,
final InternalWorkingMemory workingMemory,
final AccumulateMemory memory,
final AccumulateContext accctx,
final boolean useLeftMemory) {
// get the actual result
final Object[] result = this.accumulate.getResult( memory.workingMemoryContext,
accctx.context,
leftTuple,
workingMemory );
if ( accctx.result == null ) {
final InternalFactHandle handle = workingMemory.getFactHandleFactory().newFactHandle( result[0],
workingMemory.getObjectTypeConfigurationRegistry().getObjectTypeConf( context.getEntryPoint(),
result[0] ),
workingMemory,
null ); // so far, result is not an event
accctx.result = new RightTuple( handle,
this );
} else {
accctx.result.getFactHandle().setObject( result[0] );
}
// First alpha node filters
boolean isAllowed = result[0] != null;
for ( int i = 0, length = this.resultConstraints.length; isAllowed && i < length; i++ ) {
if ( !this.resultConstraints[i].isAllowed( accctx.result.getFactHandle(),
workingMemory,
memory.alphaContexts[i] ) ) {
isAllowed = false;
}
}
if ( isAllowed ) {
this.resultBinder.updateFromTuple( memory.resultsContext,
workingMemory,
leftTuple );
if ( !this.resultBinder.isAllowedCachedLeft( memory.resultsContext,
accctx.result.getFactHandle() ) ) {
isAllowed = false;
}
this.resultBinder.resetTuple( memory.resultsContext );
}
if ( accctx.propagated == true ) {
// temporarily break the linked list to avoid wrong interactions
LeftTuple[] matchings = splitList( leftTuple,
accctx,
false );
if ( isAllowed ) {
// modify
if ( ActivitySource.LEFT.equals( source ) ) {
this.sink.propagateModifyChildLeftTuple( leftTuple.firstChild,
leftTuple,
context,
workingMemory,
useLeftMemory );
} else {
this.sink.propagateModifyChildLeftTuple( leftTuple.firstChild,
accctx.result,
context,
workingMemory,
useLeftMemory );
}
} else {
// retract
this.sink.propagateRetractLeftTuple( leftTuple,
context,
workingMemory );
accctx.propagated = false;
}
// restore the matchings list
restoreList( leftTuple,
matchings );
} else if ( isAllowed ) {
// temporarily break the linked list to avoid wrong interactions
LeftTuple[] matchings = splitList( leftTuple,
accctx,
false );
// assert
this.sink.propagateAssertLeftTuple( leftTuple,
accctx.result,
null,
null,
context,
workingMemory,
useLeftMemory );
accctx.propagated = true;
// restore the matchings list
restoreList( leftTuple,
matchings );
}
}
public void updateSink(final LeftTupleSink sink,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
final AccumulateMemory memory = (AccumulateMemory) workingMemory.getNodeMemory( this );
final Iterator tupleIter = memory.betaMemory.getLeftTupleMemory().iterator();
for ( LeftTuple leftTuple = (LeftTuple) tupleIter.next(); leftTuple != null; leftTuple = (LeftTuple) tupleIter.next() ) {
AccumulateContext accctx = (AccumulateContext) memory.betaMemory.getCreatedHandles().get( leftTuple );
if ( accctx.propagated ) {
// temporarily break the linked list to avoid wrong interactions
LeftTuple[] matchings = splitList( leftTuple,
accctx,
true );
sink.assertLeftTuple( new LeftTuple( leftTuple,
accctx.result,
null,
null,
sink,
true ),
context,
workingMemory );
restoreList( leftTuple,
matchings );
}
}
}
protected void doRemove(final InternalWorkingMemory workingMemory,
final AccumulateMemory memory) {
Iterator it = memory.betaMemory.getCreatedHandles().iterator();
for ( ObjectEntry entry = (ObjectEntry) it.next(); entry != null; entry = (ObjectEntry) it.next() ) {
AccumulateContext ctx = (AccumulateContext) entry.getValue();
workingMemory.getFactHandleFactory().destroyFactHandle( ctx.result.getFactHandle() );
}
}
/* (non-Javadoc)
* @see org.drools.reteoo.BaseNode#hashCode()
*/
public int hashCode() {
return this.leftInput.hashCode() ^ this.rightInput.hashCode() ^ this.accumulate.hashCode() ^ this.resultBinder.hashCode() ^ ArrayUtils.hashCode( this.resultConstraints );
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(final Object object) {
if ( this == object ) {
return true;
}
if ( object == null || !(object instanceof AccumulateNode) ) {
return false;
}
final AccumulateNode other = (AccumulateNode) object;
if ( this.getClass() != other.getClass() || (!this.leftInput.equals( other.leftInput )) || (!this.rightInput.equals( other.rightInput )) || (!this.constraints.equals( other.constraints )) ) {
return false;
}
return this.accumulate.equals( other.accumulate ) && resultBinder.equals( other.resultBinder ) && Arrays.equals( this.resultConstraints,
other.resultConstraints );
}
/**
* Creates a BetaMemory for the BetaNode's memory.
*/
public Object createMemory(final RuleBaseConfiguration config) {
AccumulateMemory memory = new AccumulateMemory();
memory.betaMemory = this.constraints.createBetaMemory( config );
memory.workingMemoryContext = this.accumulate.createWorkingMemoryContext();
memory.resultsContext = this.resultBinder.createContext();
memory.alphaContexts = new ContextEntry[this.resultConstraints.length];
for ( int i = 0; i < this.resultConstraints.length; i++ ) {
memory.alphaContexts[i] = this.resultConstraints[i].createContextEntry();
}
memory.betaMemory.setBehaviorContext( this.behavior.createBehaviorContext() );
return memory;
}
public short getType() {
return NodeTypeEnums.AccumulateNode;
}
private void addMatch(final LeftTuple leftTuple,
final RightTuple rightTuple,
final LeftTuple currentLeftChild,
final LeftTuple currentRightChild,
final InternalWorkingMemory workingMemory,
final AccumulateMemory memory,
final AccumulateContext accresult,
final boolean useLeftMemory) {
LeftTuple tuple = leftTuple;
InternalFactHandle handle = rightTuple.getFactHandle();
if ( this.unwrapRightObject ) {
// if there is a subnetwork, handle must be unwrapped
tuple = (LeftTuple) handle.getObject();
handle = tuple.getLastHandle();
}
this.accumulate.accumulate( memory.workingMemoryContext,
accresult.context,
tuple,
handle,
workingMemory );
// in sequential mode, we don't need to keep record of matched tuples
if ( useLeftMemory ) {
// linking left and right by creating a new left tuple
new LeftTuple( leftTuple,
rightTuple,
currentLeftChild,
currentRightChild,
this,
true );
}
}
/**
* Removes a match between left and right tuple
*
* @param rightTuple
* @param match
* @param result
*/
private void removeMatch(final RightTuple rightTuple,
final LeftTuple match,
final InternalWorkingMemory workingMemory,
final AccumulateMemory memory,
final AccumulateContext accctx,
final boolean reaccumulate) {
// save the matching tuple
LeftTuple leftTuple = match.getLeftParent();
if ( match != null ) {
// removing link between left and right
match.unlinkFromLeftParent();
match.unlinkFromRightParent();
}
// if there is a subnetwork, we need to unwrap the object from inside the tuple
InternalFactHandle handle = rightTuple.getFactHandle();
LeftTuple tuple = leftTuple;
if ( this.unwrapRightObject ) {
tuple = (LeftTuple) handle.getObject();
handle = tuple.getLastHandle();
}
if ( this.accumulate.supportsReverse() ) {
// just reverse this single match
this.accumulate.reverse( memory.workingMemoryContext,
accctx.context,
tuple,
handle,
workingMemory );
} else {
// otherwise need to recalculate all matches for the given leftTuple
if ( reaccumulate ) {
reaccumulateForLeftTuple( leftTuple,
workingMemory,
memory,
accctx );
}
}
}
private void reaccumulateForLeftTuple(final LeftTuple leftTuple,
final InternalWorkingMemory workingMemory,
final AccumulateMemory memory,
final AccumulateContext accctx) {
this.accumulate.init( memory.workingMemoryContext,
accctx.context,
leftTuple,
workingMemory );
for ( LeftTuple childMatch = getFirstMatch( leftTuple,
accctx,
false ); childMatch != null; childMatch = childMatch.getLeftParentNext() ) {
InternalFactHandle childHandle = childMatch.getRightParent().getFactHandle();
LeftTuple tuple = leftTuple;
if ( this.unwrapRightObject ) {
tuple = (LeftTuple) childHandle.getObject();
childHandle = tuple.getLastHandle();
}
this.accumulate.accumulate( memory.workingMemoryContext,
accctx.context,
tuple,
childHandle,
workingMemory );
}
}
private void removePreviousMatchesForLeftTuple(final LeftTuple leftTuple,
final InternalWorkingMemory workingMemory,
final AccumulateMemory memory,
final AccumulateContext accctx) {
// so we just split the list keeping the head
LeftTuple[] matchings = splitList( leftTuple,
accctx,
false );
for ( LeftTuple match = matchings[0]; match != null; match = match.getLeftParentNext() ) {
// can't unlink from the left parent as it was already unlinked during the splitList call above
match.unlinkFromRightParent();
}
// since there are no more matches, the following call will just re-initialize the accumulation
this.accumulate.init( memory.workingMemoryContext,
accctx.context,
leftTuple,
workingMemory );
}
private void removePreviousMatchesForRightTuple(final RightTuple rightTuple,
final PropagationContext context,
final InternalWorkingMemory workingMemory,
final AccumulateMemory memory,
final LeftTuple firstChild) {
for ( LeftTuple match = firstChild; match != null; ) {
final LeftTuple tmp = match.getRightParentNext();
final LeftTuple parent = match.getLeftParent();
final AccumulateContext accctx = (AccumulateContext) memory.betaMemory.getCreatedHandles().get( parent );
removeMatch( rightTuple,
match,
workingMemory,
memory,
accctx,
true );
evaluateResultConstraints( ActivitySource.RIGHT,
parent,
context,
workingMemory,
memory,
accctx,
true );
match = tmp;
}
}
protected LeftTuple[] splitList(final LeftTuple parent,
final AccumulateContext accctx,
final boolean isUpdatingSink) {
LeftTuple[] matchings = new LeftTuple[2];
// save the matchings list
matchings[0] = getFirstMatch( parent,
accctx,
isUpdatingSink );
matchings[1] = matchings[0] != null ? parent.lastChild : null;
// update the tuple for the actual propagations
if ( matchings[0] != null ) {
if ( parent.firstChild == matchings[0] ) {
parent.firstChild = null;
}
parent.lastChild = matchings[0].getLeftParentPrevious();
if ( parent.lastChild != null ) {
parent.lastChild.setLeftParentNext( null );
matchings[0].setLeftParentPrevious( null );
}
}
return matchings;
}
private void restoreList(final LeftTuple parent,
final LeftTuple[] matchings) {
// concatenate matchings list at the end of the children list
if ( parent.firstChild == null ) {
parent.firstChild = matchings[0];
parent.lastChild = matchings[1];
} else if ( matchings[0] != null ) {
parent.lastChild.setLeftParentNext( matchings[0] );
matchings[0].setLeftParentPrevious( parent.lastChild );
parent.lastChild = matchings[1];
}
}
/**
* Skips the propagated tuple handles and return the first handle
* in the list that correspond to a match
*
* @param leftTuple
* @param accctx
* @return
*/
private LeftTuple getFirstMatch(final LeftTuple leftTuple,
final AccumulateContext accctx,
final boolean isUpdatingSink) {
// unlink all right matches
LeftTuple child = leftTuple.firstChild;
if ( accctx.propagated ) {
// To do that, we need to skip the first N children that are in fact
// the propagated tuples
int target = isUpdatingSink ? this.sink.size() - 1 : this.sink.size();
for ( int i = 0; i < target; i++ ) {
child = child.getLeftParentNext();
}
}
return child;
}
public static class AccumulateMemory
implements
Externalizable {
private static final long serialVersionUID = 510l;
public Object[] workingMemoryContext;
public BetaMemory betaMemory;
public ContextEntry[] resultsContext;
public ContextEntry[] alphaContexts;
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
workingMemoryContext = (Object[]) in.readObject();
betaMemory = (BetaMemory) in.readObject();
resultsContext = (ContextEntry[]) in.readObject();
alphaContexts = (ContextEntry[]) in.readObject();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject( workingMemoryContext );
out.writeObject( betaMemory );
out.writeObject( resultsContext );
out.writeObject( alphaContexts );
}
}
public static class AccumulateContext
implements
Externalizable {
public Serializable[] context;
public RightTuple result;
public boolean propagated;
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
context = (Serializable[]) in.readObject();
result = (RightTuple) in.readObject();
propagated = in.readBoolean();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject( context );
out.writeObject( result );
out.writeBoolean( propagated );
}
}
private static enum ActivitySource {
LEFT, RIGHT
}
}