/** * 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.reteoo; import org.drools.base.DroolsQuery; import org.drools.common.BetaConstraints; import org.drools.common.InternalFactHandle; import org.drools.common.InternalWorkingMemory; import org.drools.core.util.Iterator; import org.drools.reteoo.builder.BuildContext; import org.drools.rule.Behavior; import org.drools.spi.PropagationContext; /** * */ public class JoinNode extends BetaNode { /** * */ private static final long serialVersionUID = 510l; public JoinNode() { } public JoinNode(final int id, final LeftTupleSource leftInput, final ObjectSource rightInput, final BetaConstraints binder, final Behavior[] behaviors, final BuildContext context) { super( id, context.getPartitionId(), context.getRuleBase().getConfiguration().isMultithreadEvaluation(), leftInput, rightInput, binder, behaviors ); this.tupleMemoryEnabled = context.isTupleMemoryEnabled(); } public void assertLeftTuple(final LeftTuple leftTuple, final PropagationContext context, final InternalWorkingMemory workingMemory) { final BetaMemory memory = (BetaMemory) workingMemory.getNodeMemory( this ); boolean useLeftMemory = true; if ( this.tupleMemoryEnabled ) { memory.getLeftTupleMemory().add( leftTuple ); } else { // This is a hack, to not add closed DroolsQuery objects Object object = ((InternalFactHandle) context.getFactHandle()).getObject(); if ( object instanceof DroolsQuery && !((DroolsQuery) object).isOpen() ) { useLeftMemory = false; } else if ( memory.getLeftTupleMemory() != null ) { // LeftMemory will be null for sequential (still created for queries). memory.getLeftTupleMemory().add( leftTuple ); } } this.constraints.updateFromTuple( memory.getContext(), workingMemory, leftTuple ); for ( RightTuple rightTuple = memory.getRightTupleMemory().getFirst( leftTuple, (InternalFactHandle) context.getFactHandle() ); rightTuple != null; rightTuple = (RightTuple) rightTuple.getNext() ) { final InternalFactHandle handle = rightTuple.getFactHandle(); if ( this.constraints.isAllowedCachedLeft( memory.getContext(), handle ) ) { this.sink.propagateAssertLeftTuple( leftTuple, rightTuple, null, null, context, workingMemory, useLeftMemory ); } } this.constraints.resetTuple( memory.getContext() ); } public void assertObject(final InternalFactHandle factHandle, final PropagationContext context, final InternalWorkingMemory workingMemory) { final BetaMemory memory = (BetaMemory) workingMemory.getNodeMemory( this ); RightTuple rightTuple = createRightTuple( factHandle, this ); if ( !behavior.assertRightTuple( memory.getBehaviorContext(), rightTuple, workingMemory ) ) { // destroy right tuple rightTuple.unlinkFromRightParent(); return; } memory.getRightTupleMemory().add( rightTuple ); if ( memory.getLeftTupleMemory() == null || memory.getLeftTupleMemory().size() == 0 ) { // do nothing here, as no left memory return; } this.constraints.updateFromFactHandle( memory.getContext(), workingMemory, factHandle ); int i = 0; for ( LeftTuple leftTuple = memory.getLeftTupleMemory().getFirst( rightTuple ); leftTuple != null; leftTuple = (LeftTuple) leftTuple.getNext() ) { if ( this.constraints.isAllowedCachedRight( memory.getContext(), leftTuple ) ) { // wm.marshaller.write( i, leftTuple ) this.sink.propagateAssertLeftTuple( leftTuple, rightTuple, null, null, context, workingMemory, true ); } i++; } this.constraints.resetFactHandle( memory.getContext() ); } public void retractRightTuple(final RightTuple rightTuple, final PropagationContext context, final InternalWorkingMemory workingMemory) { final BetaMemory memory = (BetaMemory) workingMemory.getNodeMemory( this ); behavior.retractRightTuple( memory.getBehaviorContext(), rightTuple, workingMemory ); memory.getRightTupleMemory().remove( rightTuple ); if ( rightTuple.firstChild != null ) { this.sink.propagateRetractRightTuple( rightTuple, context, workingMemory ); } } public void retractLeftTuple(final LeftTuple leftTuple, final PropagationContext context, final InternalWorkingMemory workingMemory) { final BetaMemory memory = (BetaMemory) workingMemory.getNodeMemory( this ); memory.getLeftTupleMemory().remove( leftTuple ); if ( leftTuple.firstChild != null ) { this.sink.propagateRetractLeftTuple( leftTuple, context, workingMemory ); } } public void modifyRightTuple(final RightTuple rightTuple, final PropagationContext context, final InternalWorkingMemory workingMemory) { final BetaMemory memory = (BetaMemory) workingMemory.getNodeMemory( this ); // WTD here // if ( !behavior.assertRightTuple( memory.getBehaviorContext(), // rightTuple, // workingMemory ) ) { // // destroy right tuple // rightTuple.unlinkFromRightParent(); // return; // } // 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.getRightTupleMemory().remove( rightTuple ); memory.getRightTupleMemory().add( rightTuple ); if ( memory.getLeftTupleMemory() != null && memory.getLeftTupleMemory().size() == 0 ) { // do nothing here, as we know there are no left tuples. return; } LeftTuple childLeftTuple = rightTuple.firstChild; LeftTupleMemory leftMemory = memory.getLeftTupleMemory(); LeftTuple leftTuple = leftMemory.getFirst( rightTuple ); this.constraints.updateFromFactHandle( memory.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 == null || (leftTuple.getMemory() != childLeftTuple.getLeftParent().getMemory())) ) { // our index has changed, so delete all the previous propagations this.sink.propagateRetractRightTuple( rightTuple, context, workingMemory ); childLeftTuple = null; // null so the next check will attempt matches for new bucket } // we can't do anything if LeftTupleMemory is empty 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.getContext(), leftTuple ) ) { this.sink.propagateAssertLeftTuple( leftTuple, rightTuple, null, null, context, workingMemory, true ); } } } else { // in the same bucket, so iterate and compare for ( ; leftTuple != null; leftTuple = (LeftTuple) leftTuple.getNext() ) { if ( this.constraints.isAllowedCachedRight( memory.getContext(), leftTuple ) ) { if ( childLeftTuple == null || childLeftTuple.getLeftParent() != leftTuple ) { this.sink.propagateAssertLeftTuple( leftTuple, rightTuple, null, childLeftTuple, context, workingMemory, true ); } else { // preserve the current LeftTuple, as we need to iterate to the next before re-adding LeftTuple temp = childLeftTuple; childLeftTuple = this.sink.propagateModifyChildLeftTuple( childLeftTuple, leftTuple, context, workingMemory, true ); // we must re-add this to ensure deterministic iteration temp.reAddLeft(); } } else if ( childLeftTuple != null && childLeftTuple.getLeftParent() == leftTuple ) { childLeftTuple = this.sink.propagateRetractChildLeftTuple( childLeftTuple, leftTuple, context, workingMemory ); } // else do nothing, was false before and false now. } } } this.constraints.resetFactHandle( memory.getContext() ); } public void modifyLeftTuple(final LeftTuple leftTuple, final PropagationContext context, final InternalWorkingMemory workingMemory) { final BetaMemory memory = (BetaMemory) 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.getLeftTupleMemory().remove( leftTuple ); memory.getLeftTupleMemory().add( leftTuple ); this.constraints.updateFromTuple( memory.getContext(), workingMemory, leftTuple ); LeftTuple childLeftTuple = leftTuple.firstChild; RightTupleMemory rightMemory = memory.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 == null || (rightTuple.getMemory() != childLeftTuple.getRightParent().getMemory())) ) { // our index has changed, so delete all the previous propagations this.sink.propagateRetractLeftTuple( leftTuple, context, workingMemory ); 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.getContext(), handle ) ) { this.sink.propagateAssertLeftTuple( leftTuple, rightTuple, null, null, context, workingMemory, true ); } } } else { // 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.getContext(), handle ) ) { if ( childLeftTuple == null || childLeftTuple.getRightParent() != rightTuple ) { this.sink.propagateAssertLeftTuple( leftTuple, rightTuple, childLeftTuple, null, context, workingMemory, true ); } else { // preserve the current LeftTuple, as we need to iterate to the next before re-adding LeftTuple temp = childLeftTuple; childLeftTuple = this.sink.propagateModifyChildLeftTuple( childLeftTuple, rightTuple, context, workingMemory, true ); // we must re-add this to ensure deterministic iteration temp.reAddRight(); } } else if ( childLeftTuple != null && childLeftTuple.getRightParent() == rightTuple ) { childLeftTuple = this.sink.propagateRetractChildLeftTuple( childLeftTuple, rightTuple, context, workingMemory ); } // else do nothing, was false before and false now. } } } this.constraints.resetTuple( memory.getContext() ); } /* (non-Javadoc) * @see org.drools.reteoo.BaseNode#updateNewNode(org.drools.reteoo.WorkingMemoryImpl, org.drools.spi.PropagationContext) */ public void updateSink(final LeftTupleSink sink, final PropagationContext context, final InternalWorkingMemory workingMemory) { final BetaMemory memory = (BetaMemory) workingMemory.getNodeMemory( this ); final Iterator tupleIter = memory.getLeftTupleMemory().iterator(); for ( LeftTuple leftTuple = (LeftTuple) tupleIter.next(); leftTuple != null; leftTuple = (LeftTuple) tupleIter.next() ) { this.constraints.updateFromTuple( memory.getContext(), workingMemory, leftTuple ); for ( RightTuple rightTuple = memory.getRightTupleMemory().getFirst( leftTuple, (InternalFactHandle) context.getFactHandle() ); rightTuple != null; rightTuple = (RightTuple) rightTuple.getNext() ) { if ( this.constraints.isAllowedCachedLeft( memory.getContext(), rightTuple.getFactHandle() ) ) { sink.assertLeftTuple( new LeftTuple( leftTuple, rightTuple, null, null, sink, true ), context, workingMemory ); } } this.constraints.resetTuple( memory.getContext() ); } } public short getType() { return NodeTypeEnums.JoinNode; } public String toString() { ObjectSource source = this.rightInput; while ( !(source instanceof ObjectTypeNode) ) { source = source.source; } return "[JoinNode(" + this.getId() + ") - " + ((ObjectTypeNode) source).getObjectType() + "]"; } }