package org.infinispan.objectfilter.impl.predicateindex;
import java.util.ArrayList;
import java.util.List;
import org.infinispan.objectfilter.impl.FilterSubscriptionImpl;
import org.infinispan.objectfilter.impl.predicateindex.be.PredicateNode;
import org.infinispan.objectfilter.impl.util.IntervalTree;
/**
* Holds all predicates that are subscribed for a certain attribute. This class is not thread safe and leaves this
* responsibility to the caller.
*
* @param <AttributeDomain> the type of the values of the attribute
* @author anistor@redhat.com
* @since 7.0
*/
public final class Predicates<AttributeDomain extends Comparable<AttributeDomain>, AttributeId extends Comparable<AttributeId>> {
/**
* Holds all subscriptions for a single predicate.
*/
private static class Subscriptions {
private final Predicate predicate;
/**
* The callbacks of the subscribed predicates.
*/
private final List<Subscription> subscriptions = new ArrayList<>();
private Subscriptions(Predicate predicate) {
this.predicate = predicate;
}
void add(Subscription subscription) {
subscriptions.add(subscription);
}
void remove(Subscription subscription) {
subscriptions.remove(subscription);
}
boolean isEmpty() {
return subscriptions.isEmpty();
}
//todo [anistor] this is an improvement but still does not eliminate precessing of attributes that have only suspended subscribers
boolean isActive(MatcherEvalContext<?, ?, ?> ctx) {
return !predicate.isRepeated() || ctx.getSuspendedSubscriptionsCounter(predicate) < subscriptions.size();
}
}
public static final class Subscription<AttributeId extends Comparable<AttributeId>> {
private final PredicateNode<AttributeId> predicateNode;
private final FilterSubscriptionImpl filterSubscription;
private Subscription(PredicateNode<AttributeId> predicateNode, FilterSubscriptionImpl filterSubscription) {
this.predicateNode = predicateNode;
this.filterSubscription = filterSubscription;
}
private void handleValue(MatcherEvalContext<?, ?, ?> ctx, boolean isMatching) {
FilterEvalContext filterEvalContext = ctx.getFilterEvalContext(filterSubscription);
if (!predicateNode.isEvaluationComplete(filterEvalContext)) {
if (predicateNode.isNegated()) {
isMatching = !isMatching;
}
if (isMatching || !predicateNode.getPredicate().isRepeated()) {
predicateNode.handleChildValue(null, isMatching, filterEvalContext);
}
}
}
public PredicateNode<AttributeId> getPredicateNode() {
return predicateNode;
}
}
private final boolean useIntervals;
/**
* The predicates that have a condition based on an order relation (ie. intervals). This allows them to be
* represented by an interval tree.
*/
private IntervalTree<AttributeDomain, Subscriptions> orderedPredicates;
/**
* The predicates that are based on an arbitrary condition that is not an order relation.
*/
private List<Subscriptions> unorderedPredicates;
Predicates(boolean useIntervals) {
this.useIntervals = useIntervals;
}
public void notifyMatchingSubscribers(final MatcherEvalContext<?, ?, ?> ctx, Object attributeValue) {
if (orderedPredicates != null && attributeValue instanceof Comparable) {
orderedPredicates.stab((AttributeDomain) attributeValue, new IntervalTree.NodeCallback<AttributeDomain, Subscriptions>() {
@Override
public void handle(IntervalTree.Node<AttributeDomain, Subscriptions> node) {
Subscriptions subscriptions = node.value;
if (subscriptions.isActive(ctx)) {
for (Subscription s : subscriptions.subscriptions) {
s.handleValue(ctx, true);
}
}
}
});
}
if (unorderedPredicates != null) {
for (int k = unorderedPredicates.size() - 1; k >= 0; k--) {
Subscriptions subscriptions = unorderedPredicates.get(k);
if (subscriptions.isActive(ctx)) {
boolean isMatching = subscriptions.predicate.match(attributeValue);
List<Subscription> s = subscriptions.subscriptions;
for (int i = s.size() - 1; i >= 0; i--) {
s.get(i).handleValue(ctx, isMatching);
}
}
}
}
}
public Predicates.Subscription<AttributeId> addPredicateSubscription(PredicateNode predicateNode, FilterSubscriptionImpl filterSubscription) {
Subscriptions subscriptions;
Predicate<AttributeDomain> predicate = predicateNode.getPredicate();
if (useIntervals && predicate instanceof IntervalPredicate) {
if (orderedPredicates == null) {
// in this case AttributeDomain extends Comparable for sure
orderedPredicates = new IntervalTree<AttributeDomain, Subscriptions>();
}
IntervalTree.Node<AttributeDomain, Subscriptions> n = orderedPredicates.add(((IntervalPredicate) predicate).getInterval());
if (n.value == null) {
subscriptions = new Subscriptions(predicate);
n.value = subscriptions;
} else {
subscriptions = n.value;
}
} else {
subscriptions = null;
if (unorderedPredicates == null) {
unorderedPredicates = new ArrayList<>();
} else {
for (int i = 0; i < unorderedPredicates.size(); i++) {
Subscriptions s = unorderedPredicates.get(i);
if (s.predicate.equals(predicate)) {
subscriptions = s;
break;
}
}
}
if (subscriptions == null) {
subscriptions = new Subscriptions(predicate);
unorderedPredicates.add(subscriptions);
}
}
Subscription<AttributeId> subscription = new Subscription<AttributeId>(predicateNode, filterSubscription);
subscriptions.add(subscription);
return subscription;
}
public void removePredicateSubscription(Subscription subscription) {
Predicate<AttributeDomain> predicate = (Predicate<AttributeDomain>) subscription.predicateNode.getPredicate();
if (useIntervals && predicate instanceof IntervalPredicate) {
if (orderedPredicates != null) {
IntervalTree.Node<AttributeDomain, Subscriptions> n = orderedPredicates.findNode(((IntervalPredicate) predicate).getInterval());
if (n != null) {
n.value.remove(subscription);
if (n.value.isEmpty()) {
orderedPredicates.remove(n);
}
} else {
throwIllegalStateException();
}
} else {
throwIllegalStateException();
}
} else {
if (unorderedPredicates != null) {
for (int i = 0; i < unorderedPredicates.size(); i++) {
Predicates.Subscriptions subscriptions = unorderedPredicates.get(i);
if (subscriptions.predicate.equals(predicate)) {
subscriptions.remove(subscription);
if (subscriptions.isEmpty()) {
unorderedPredicates.remove(i);
}
break;
}
}
} else {
throwIllegalStateException();
}
}
}
public boolean isEmpty() {
return (unorderedPredicates == null || unorderedPredicates.isEmpty())
&& (orderedPredicates == null || orderedPredicates.isEmpty());
}
private static void throwIllegalStateException() throws IllegalStateException {
// this is not expected to happen unless a programming error slipped through
throw new IllegalStateException("Reached an invalid state");
}
}