/* * Copyright 2005 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.core.common; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; import org.drools.core.WorkingMemoryEntryPoint; import org.drools.core.base.TraitHelper; import org.drools.core.datasources.InternalDataSource; import org.drools.core.factmodel.traits.TraitFactory; import org.drools.core.factmodel.traits.TraitTypeEnum; import org.drools.core.impl.InternalKnowledgeBase; import org.drools.core.reteoo.LeftTuple; import org.drools.core.reteoo.ObjectTypeNode; import org.drools.core.reteoo.RightTuple; import org.drools.core.spi.Tuple; import org.drools.core.util.AbstractBaseLinkedListNode; import org.drools.core.util.StringUtils; import org.kie.api.runtime.rule.FactHandle; /** * Implementation of <code>FactHandle</code>. */ @XmlRootElement(name = "fact-handle") @XmlAccessorType(XmlAccessType.NONE) public class DefaultFactHandle extends AbstractBaseLinkedListNode<DefaultFactHandle> implements InternalFactHandle { // ---------------------------------------------------------------------- // Instance members // ---------------------------------------------------------------------- private static final long serialVersionUID = 510l; /** Handle id. */ static final String FACT_FORMAT_VERSION = "0"; private int id; private long recency; private Object object; private EqualityKey key; private int objectHashCode; private int identityHashCode; private WorkingMemoryEntryPoint entryPoint; private boolean disconnected; private TraitTypeEnum traitType; private boolean valid = true; private boolean negated; private String objectClassName; protected LinkedTuples linkedTuples; private InternalFactHandle parentHandle; // ---------------------------------------------------------------------- // Constructors // ---------------------------------------------------------------------- public DefaultFactHandle() { } public DefaultFactHandle(final int id, final Object object) { // this is only used by tests, left as legacy as so many test rely on it. this( id, object, id, null, false); } /** * Construct. * * @param id * Handle id. */ public DefaultFactHandle(final int id, final Object object, final long recency, final WorkingMemoryEntryPoint wmEntryPoint) { this( id, determineIdentityHashCode( object ), object, recency, wmEntryPoint, false ); } public DefaultFactHandle(final int id, final Object object, final long recency, final WorkingMemoryEntryPoint wmEntryPoint, final boolean isTraitOrTraitable ) { this( id, determineIdentityHashCode( object ), object, recency, wmEntryPoint, isTraitOrTraitable ); } public DefaultFactHandle(final int id, final int identityHashCode, final Object object, final long recency, final WorkingMemoryEntryPoint wmEntryPoint, final boolean isTraitOrTraitable ) { this.id = id; setEntryPoint( wmEntryPoint ); this.recency = recency; setObject( object ); this.identityHashCode = identityHashCode; this.traitType = isTraitOrTraitable ? determineTraitType() : TraitTypeEnum.NON_TRAIT; } public DefaultFactHandle(int id, String wmEntryPointId, int identityHashCode, int objectHashCode, long recency, Object object) { this.id = id; setEntryPoint( ( wmEntryPointId == null ) ? null : new DisconnectedWorkingMemoryEntryPoint( wmEntryPointId ) ); this.recency = recency; setObject( object ); this.identityHashCode = identityHashCode; this.objectHashCode = objectHashCode; this.disconnected = true; this.traitType = TraitTypeEnum.NON_TRAIT; } // ---------------------------------------------------------------------- // Instance members // ---------------------------------------------------------------------- /** * @see Object */ public boolean equals( final Object object ) { return this == object || ( object instanceof DefaultFactHandle && this.id == ( (DefaultFactHandle) object ).id ); } public void disconnect() { this.key = null; this.linkedTuples = null; this.entryPoint = ( this.entryPoint == null ) ? null : new DisconnectedWorkingMemoryEntryPoint( this.entryPoint.getEntryPointId() ); this.disconnected = true; } public boolean isNegated() { return negated; } public void setNegated(boolean negated) { this.negated = negated; } @Override public <K> K as( Class<K> klass ) throws ClassCastException { if ( klass.isAssignableFrom( object.getClass() ) ) { return (K) object; } else if ( this.isTraitOrTraitable() ) { K k = TraitHelper.extractTrait( this, klass ); if ( k != null ) { return k; } } throw new ClassCastException( "The Handle's Object can't be cast to " + klass ); } public boolean isDisconnected() { return disconnected; } protected void setDisconnected( boolean disconnected ) { this.disconnected = disconnected; } public int getObjectHashCode() { return this.objectHashCode; } public int getIdentityHashCode() { return this.identityHashCode; } public static int determineIdentityHashCode( Object object ) { return System.identityHashCode( object ); } protected void setIdentityHashCode( int identityHashCode ) { this.identityHashCode = identityHashCode; } protected void setObjectHashCode( int hashCode ) { this.objectHashCode = hashCode; } /** * @see Object */ public int hashCode() { return this.id; } /** * format_version:id:identity:hashcode:recency * * @see FactHandle */ public final String toExternalForm() { return getFormatVersion() + ":" + this.id + ":" + getIdentityHashCode() + ":" + getObjectHashCode() + ":" + getRecency() + ":" + ( ( this.entryPoint != null ) ? this.entryPoint.getEntryPointId() : "null" ) + ":" + this.traitType.name() + ":" + this.objectClassName; } protected String getFormatVersion() { return FACT_FORMAT_VERSION; } @XmlAttribute(name = "external-form") public String getExternalForm() { return toExternalForm(); } public void setExternalForm(String externalForm) { populateFactHandleFromExternalForm( externalForm, this ); } /** * @see Object */ public String toString() { return "[fact " + toExternalForm() + ":" + this.object + "]"; } public long getRecency() { return this.recency; } public void setRecency( final long recency ) { this.recency = recency; } public int getId() { return this.id; } public void invalidate() { valid = false; } public boolean isValid() { return valid; } public Object getObject() { return this.object; } public String getObjectClassName() { return this.objectClassName; } public void setObject( final Object object ) { this.object = object; if (object != null) { this.objectClassName = object.getClass().getName(); this.objectHashCode = object.hashCode(); } else { this.objectHashCode = 0; } if ( isTraitOrTraitable() ) { TraitTypeEnum newType = determineTraitType(); if ( ! ( this.traitType == TraitTypeEnum.LEGACY_TRAITABLE && newType != TraitTypeEnum.LEGACY_TRAITABLE ) ) { this.identityHashCode = determineIdentityHashCode( object ); } else { // we are replacing a non-traitable object with its proxy, so we need to preserve the identity hashcode } this.traitType = newType; } else { this.identityHashCode = determineIdentityHashCode( object ); } } /** * @return the key */ public EqualityKey getEqualityKey() { return this.key; } /** * @param key the key to set */ public void setEqualityKey( final EqualityKey key ) { this.key = key; } /** * Always returns false, since the DefaultFactHandle is * only used for regular Facts, and not for Events */ public boolean isEvent() { return false; } public boolean isTraitOrTraitable() { return traitType != TraitTypeEnum.NON_TRAIT; } public WorkingMemoryEntryPoint getEntryPoint() { return entryPoint; } public void setEntryPoint( WorkingMemoryEntryPoint sourceNode ) { this.entryPoint = sourceNode; setLinkedTuples( entryPoint != null ? entryPoint.getKnowledgeBase() : null ); } private void setLinkedTuples( InternalKnowledgeBase kbase ) { linkedTuples = kbase != null && kbase.getConfiguration().isMultithreadEvaluation() ? new CompositeLinkedTuples() : new SingleLinkedTuples(); } public void addFirstLeftTuple( LeftTuple leftTuple ) { linkedTuples.addFirstLeftTuple( leftTuple ); } public void addLastLeftTuple( LeftTuple leftTuple ) { linkedTuples.addLastLeftTuple( leftTuple ); } public void addTupleInPosition( Tuple tuple ) { linkedTuples.addTupleInPosition( tuple ); } public void removeLeftTuple( LeftTuple leftTuple ) { linkedTuples.removeLeftTuple( leftTuple ); } public void addFirstRightTuple( RightTuple rightTuple ) { linkedTuples.addFirstRightTuple( rightTuple ); } public void addLastRightTuple( RightTuple rightTuple ) { linkedTuples.addLastRightTuple( rightTuple ); } public void removeRightTuple( RightTuple rightTuple ) { linkedTuples.removeRightTuple( rightTuple ); } public void clearLeftTuples() { linkedTuples.clearLeftTuples(); } public void clearRightTuples() { linkedTuples.clearRightTuples(); } public DefaultFactHandle clone() { DefaultFactHandle clone = new DefaultFactHandle( this.id, this.object, this.recency, this.entryPoint ); clone.key = this.key; clone.linkedTuples = this.linkedTuples.clone(); clone.objectHashCode = this.objectHashCode; clone.identityHashCode = System.identityHashCode( clone.object ); clone.disconnected = this.disconnected; clone.traitType = this.traitType; clone.negated = this.negated; return clone; } public static DefaultFactHandle createFromExternalFormat( String externalFormat ) { String[] elements = splitExternalForm( externalFormat ); DefaultFactHandle handle; if (FACT_FORMAT_VERSION.equals( elements[0]) ) { handle = new DefaultFactHandle(); } else if (EventFactHandle.EVENT_FORMAT_VERSION.equals( elements[0])) { handle = new EventFactHandle(); } else { throw new RuntimeException( "Unknown fact handle version format: " + elements[0]); } populateFactHandleFromExternalForm( elements, handle ); return handle; } private static String[] splitExternalForm( String externalFormat ) { String[] elements = externalFormat.split( ":" ); if (elements.length < 6) { throw new IllegalArgumentException( "externalFormat did not have enough elements ["+externalFormat+"]" ); } return elements; } private static void populateFactHandleFromExternalForm( String externalFormat, DefaultFactHandle handle ) { populateFactHandleFromExternalForm( splitExternalForm( externalFormat ), handle ); } private static void populateFactHandleFromExternalForm( String[] elements, DefaultFactHandle handle ) { handle.id = Integer.parseInt( elements[1] ); handle.identityHashCode = Integer.parseInt( elements[2] ); handle.objectHashCode = Integer.parseInt( elements[3] ); handle.recency = Long.parseLong( elements[4] ); handle.setEntryPoint( ( StringUtils.isEmpty( elements[5] ) || "null".equals( elements[5].trim() ) ) ? null : new DisconnectedWorkingMemoryEntryPoint( elements[5].trim() ) ); handle.disconnected = true; handle.traitType = elements.length > 6 ? TraitTypeEnum.valueOf( elements[6] ) : TraitTypeEnum.NON_TRAIT; handle.objectClassName = elements.length > 7 ? elements[7] : null; } private TraitTypeEnum determineTraitType() { if ( isTraitOrTraitable() ) { return TraitFactory.determineTraitType( object ); } else { return TraitTypeEnum.NON_TRAIT; } } public boolean isTraitable() { return traitType == TraitTypeEnum.TRAITABLE || traitType == TraitTypeEnum.WRAPPED_TRAITABLE; } public boolean isTraiting() { return traitType == TraitTypeEnum.TRAIT; } public TraitTypeEnum getTraitType() { return traitType; } protected void setTraitType( TraitTypeEnum traitType ) { this.traitType = traitType; } public boolean isExpired() { return false; } public boolean isPendingRemoveFromStore() { return false; } protected static class SingleLinkedTuples implements LinkedTuples { private RightTuple firstRightTuple; private RightTuple lastRightTuple; private LeftTuple firstLeftTuple; private LeftTuple lastLeftTuple; public SingleLinkedTuples clone() { SingleLinkedTuples clone = new SingleLinkedTuples(); clone.firstLeftTuple = this.firstLeftTuple; clone.lastLeftTuple = this.lastLeftTuple; clone.firstRightTuple = this.firstRightTuple; clone.lastRightTuple = this.lastRightTuple; return clone; } public void addFirstLeftTuple( LeftTuple leftTuple ) { LeftTuple previous = firstLeftTuple; if ( previous == null ) { // no other LeftTuples, just add. leftTuple.setHandlePrevious( null ); leftTuple.setHandleNext( null ); firstLeftTuple = leftTuple; lastLeftTuple = leftTuple; } else { leftTuple.setHandlePrevious( null ); leftTuple.setHandleNext( previous ); previous.setHandlePrevious( leftTuple ); firstLeftTuple = leftTuple; } } public void addLastLeftTuple( LeftTuple leftTuple ) { LeftTuple previous = lastLeftTuple; if ( previous == null ) { // no other LeftTuples, just add. leftTuple.setHandlePrevious( null ); leftTuple.setHandleNext( null ); firstLeftTuple = leftTuple; lastLeftTuple = leftTuple; } else { leftTuple.setHandlePrevious( previous ); leftTuple.setHandleNext( null ); previous.setHandleNext( leftTuple ); lastLeftTuple = leftTuple; } } public void addTupleInPosition( Tuple tuple ) { boolean left = tuple instanceof LeftTuple; ObjectTypeNode.Id otnId = tuple.getInputOtnId(); if (otnId == null) { // can happen only in tests addLastTuple( tuple, left ); return; } Tuple previous = left ? lastLeftTuple : lastRightTuple; if ( previous == null ) { // no other LeftTuples, just add. tuple.setHandlePrevious( null ); tuple.setHandleNext( null ); setFirstTuple( tuple, left ); setLastTuple( tuple, left ); return; } else if ( previous.getTupleSink() == null || !otnId.before( previous.getInputOtnId() ) ) { // the last LeftTuple comes before the new one so just add it at the end tuple.setHandlePrevious( previous ); tuple.setHandleNext( null ); previous.setHandleNext( tuple ); setLastTuple( tuple, left ); return; } Tuple next = previous; previous = previous.getHandlePrevious(); while (previous != null && otnId.before( previous.getInputOtnId() ) ) { next = previous; previous = previous.getHandlePrevious(); } tuple.setHandleNext( next ); next.setHandlePrevious( tuple ); tuple.setHandlePrevious( previous ); if ( previous != null ) { previous.setHandleNext( tuple ); } else { setFirstTuple( tuple, left ); } } private void addLastTuple(Tuple tuple, boolean left) { if (left) { addLastLeftTuple( ( (LeftTuple) tuple ) ); } else { addLastRightTuple( ( (RightTuple) tuple ) ); } } private void setFirstTuple(Tuple tuple, boolean left) { if (left) { firstLeftTuple = ( (LeftTuple) tuple ); } else { firstRightTuple = ( (RightTuple) tuple ); } } private void setLastTuple(Tuple tuple, boolean left) { if (left) { lastLeftTuple = ( (LeftTuple) tuple ); } else { lastRightTuple = ( (RightTuple) tuple ); } } public void removeLeftTuple( LeftTuple leftTuple ) { LeftTuple previous = leftTuple.getHandlePrevious(); LeftTuple next = leftTuple.getHandleNext(); if ( previous != null && next != null ) { // remove from middle previous.setHandleNext( next ); next.setHandlePrevious( previous ); } else if ( next != null ) { // remove from first next.setHandlePrevious( null ); firstLeftTuple = next; } else if ( previous != null ) { // remove from end previous.setHandleNext( null ); lastLeftTuple = previous; } else { // single remaining item, no previous or next firstLeftTuple = null; lastLeftTuple = null; } leftTuple.setHandlePrevious( null ); leftTuple.setHandleNext( null ); } public void addFirstRightTuple( RightTuple rightTuple ) { RightTuple previousFirst = firstRightTuple; firstRightTuple = rightTuple; if ( previousFirst == null ) { rightTuple.setHandlePrevious( null ); rightTuple.setHandleNext( null ); lastRightTuple = rightTuple; } else { rightTuple.setHandlePrevious( null ); rightTuple.setHandleNext( previousFirst ); previousFirst.setHandlePrevious( rightTuple ); } } public void addLastRightTuple( RightTuple rightTuple ) { RightTuple previousLast = lastRightTuple; if( previousLast == null ){ rightTuple.setHandlePrevious( null ); rightTuple.setHandleNext( null ); firstRightTuple = rightTuple; lastRightTuple = rightTuple; } else { rightTuple.setHandlePrevious( previousLast ); rightTuple.setHandleNext( null ); previousLast.setHandleNext( rightTuple ); lastRightTuple = rightTuple; } } public void removeRightTuple( RightTuple rightTuple ) { RightTuple previous = rightTuple.getHandlePrevious(); RightTuple next = rightTuple.getHandleNext(); if ( previous != null && next != null ) { // remove from middle previous.setHandleNext( next ); next.setHandlePrevious( previous ); } else if ( next != null ) { // remove from first next.setHandlePrevious( null ); firstRightTuple = next; } else if ( previous != null ) { // remove from end previous.setHandleNext( null ); lastRightTuple = previous; } else { // single remaining item, no previous or next firstRightTuple = null; lastRightTuple = null; } rightTuple.setHandlePrevious( null ); rightTuple.setHandleNext( null ); } public void clearLeftTuples() { firstLeftTuple = null; lastLeftTuple = null; } public void clearRightTuples() { firstRightTuple = null; lastRightTuple = null; } public void forEachRightTuple(Consumer<RightTuple> rightTupleConsumer) { for (RightTuple rightTuple = firstRightTuple; rightTuple != null; ) { RightTuple nextRightTuple = rightTuple.getHandleNext(); rightTupleConsumer.accept( rightTuple ); rightTuple = nextRightTuple; } } public RightTuple findFirstRightTuple(Predicate<RightTuple> rightTuplePredicate ) { for (RightTuple rightTuple = firstRightTuple; rightTuple != null; ) { RightTuple nextRightTuple = rightTuple.getHandleNext(); if (rightTuplePredicate.test( rightTuple )) { return rightTuple; } rightTuple = nextRightTuple; } return null; } public void forEachLeftTuple(Consumer<LeftTuple> leftTupleConsumer) { for ( LeftTuple leftTuple = firstLeftTuple; leftTuple != null; ) { LeftTuple nextLeftTuple = leftTuple.getHandleNext(); leftTupleConsumer.accept( leftTuple ); leftTuple = nextLeftTuple; } } public LeftTuple findFirstLeftTuple(Predicate<LeftTuple> lefttTuplePredicate ) { for ( LeftTuple leftTuple = firstLeftTuple; leftTuple != null; ) { LeftTuple nextLeftTuple = leftTuple.getHandleNext(); if (lefttTuplePredicate.test( leftTuple )) { return leftTuple; } leftTuple = nextLeftTuple; } return null; } public LeftTuple getFirstLeftTuple(int partition) { return getFirstLeftTuple(); } LeftTuple getFirstLeftTuple() { return firstLeftTuple; } public void setFirstLeftTuple( LeftTuple firstLeftTuple, int partition ) { setFirstLeftTuple( firstLeftTuple ); } void setFirstLeftTuple( LeftTuple firstLeftTuple ) { this.firstLeftTuple = firstLeftTuple; } public RightTuple getFirstRightTuple(int partition) { return getFirstRightTuple(); } RightTuple getFirstRightTuple() { return firstRightTuple; } } public static class CompositeLinkedTuples implements LinkedTuples { private final SingleLinkedTuples[] partitionedTuples = new SingleLinkedTuples[RuleBasePartitionId.PARALLEL_PARTITIONS_NUMBER]; public CompositeLinkedTuples() { for (int i = 0; i < partitionedTuples.length; i++) { partitionedTuples[i] = new SingleLinkedTuples(); } } @Override public LinkedTuples clone() { CompositeLinkedTuples clone = new CompositeLinkedTuples(); for (int i = 0; i < partitionedTuples.length; i++) { clone.partitionedTuples[i] = partitionedTuples[i].clone(); } return clone; } private LinkedTuples getPartitionTuples(Tuple tuple) { return partitionedTuples[tuple.getTupleSink().getPartitionId().getParallelEvaluationSlot()]; } @Override public void addFirstLeftTuple( LeftTuple leftTuple ) { getPartitionTuples(leftTuple).addFirstLeftTuple( leftTuple ); } @Override public void addLastLeftTuple( LeftTuple leftTuple ) { getPartitionTuples(leftTuple).addLastLeftTuple( leftTuple ); } @Override public void addTupleInPosition( Tuple tuple ) { getPartitionTuples(tuple).addTupleInPosition( tuple ); } @Override public void removeLeftTuple( LeftTuple leftTuple ) { getPartitionTuples(leftTuple).removeLeftTuple( leftTuple ); } @Override public void addFirstRightTuple( RightTuple rightTuple ) { getPartitionTuples(rightTuple).addFirstRightTuple( rightTuple ); } @Override public void addLastRightTuple( RightTuple rightTuple ) { getPartitionTuples(rightTuple).addLastRightTuple( rightTuple ); } @Override public void removeRightTuple( RightTuple rightTuple ) { if (rightTuple.getTupleSink() != null) { getPartitionTuples( rightTuple ).removeRightTuple( rightTuple ); } } @Override public void clearLeftTuples() { for (int i = 0; i < partitionedTuples.length; i++) { clearLeftTuples(i); } } public void clearLeftTuples(int partition) { partitionedTuples[partition].clearLeftTuples(); } @Override public void clearRightTuples() { for (int i = 0; i < partitionedTuples.length; i++) { clearRightTuples(i); } } public void clearRightTuples(int partition) { partitionedTuples[partition].clearRightTuples(); } @Override public void forEachRightTuple( Consumer<RightTuple> rightTupleConsumer ) { for (int i = 0; i < partitionedTuples.length; i++) { forEachRightTuple( i, rightTupleConsumer ); } } public void forEachRightTuple( int partition, Consumer<RightTuple> rightTupleConsumer ) { partitionedTuples[partition].forEachRightTuple( rightTupleConsumer ); } @Override public RightTuple findFirstRightTuple( Predicate<RightTuple> rightTuplePredicate ) { return Stream.of( partitionedTuples ) .map( t -> t.findFirstRightTuple( rightTuplePredicate ) ) .filter( Objects::nonNull ) .findFirst() .orElse( null ); } @Override public void forEachLeftTuple( Consumer<LeftTuple> leftTupleConsumer ) { for (int i = 0; i < partitionedTuples.length; i++) { forEachLeftTuple( i, leftTupleConsumer ); } } public void forEachLeftTuple( int partition, Consumer<LeftTuple> leftTupleConsumer ) { partitionedTuples[partition].forEachLeftTuple( leftTupleConsumer ); } @Override public LeftTuple findFirstLeftTuple( Predicate<LeftTuple> lefttTuplePredicate ) { return Stream.of( partitionedTuples ) .map( t -> t.findFirstLeftTuple( lefttTuplePredicate ) ) .filter( Objects::nonNull ) .findFirst() .orElse( null ); } @Override public LeftTuple getFirstLeftTuple(int partition) { return partitionedTuples[partition].getFirstLeftTuple(); } @Override public void setFirstLeftTuple( LeftTuple firstLeftTuple, int partition ) { partitionedTuples[partition].setFirstLeftTuple(firstLeftTuple); } @Override public RightTuple getFirstRightTuple(int partition) { return partitionedTuples[partition].getFirstRightTuple(); } } @Override public void forEachRightTuple(Consumer<RightTuple> rightTupleConsumer) { linkedTuples.forEachRightTuple( rightTupleConsumer ); } @Override public RightTuple findFirstRightTuple(Predicate<RightTuple> rightTuplePredicate ) { return linkedTuples.findFirstRightTuple( rightTuplePredicate ); } @Override public void forEachLeftTuple(Consumer<LeftTuple> leftTupleConsumer) { linkedTuples.forEachLeftTuple( leftTupleConsumer ); } @Override public LeftTuple findFirstLeftTuple(Predicate<LeftTuple> lefttTuplePredicate ) { return linkedTuples.findFirstLeftTuple( lefttTuplePredicate ); } @Override public LeftTuple getFirstLeftTuple() { if (linkedTuples instanceof SingleLinkedTuples) { return ( (SingleLinkedTuples) linkedTuples ).getFirstLeftTuple(); } throw new UnsupportedOperationException(); } @Override public void setFirstLeftTuple( LeftTuple firstLeftTuple ) { if (linkedTuples instanceof SingleLinkedTuples) { ( (SingleLinkedTuples) linkedTuples ).setFirstLeftTuple( firstLeftTuple ); } else { throw new UnsupportedOperationException(); } } @Override public RightTuple getFirstRightTuple() { if (linkedTuples instanceof SingleLinkedTuples) { return ( (SingleLinkedTuples) linkedTuples ).getFirstRightTuple(); } throw new UnsupportedOperationException(); } @Override public LinkedTuples getLinkedTuples() { return linkedTuples; } @Override public LinkedTuples detachLinkedTuples() { LinkedTuples detached = linkedTuples; linkedTuples = new SingleLinkedTuples(); return detached; } @Override public LinkedTuples detachLinkedTuplesForPartition(int i) { LinkedTuples detached = ( (CompositeLinkedTuples) linkedTuples ).partitionedTuples[i]; ( (CompositeLinkedTuples) linkedTuples ).partitionedTuples[i] = new SingleLinkedTuples(); return detached; } @Override public InternalDataSource<?> getDataSource() { return parentHandle != null ? parentHandle.getDataSource() : null; } public InternalFactHandle getParentHandle() { return parentHandle; } public void setParentHandle( InternalFactHandle parentHandle ) { this.parentHandle = parentHandle; } }