package org.infinispan.objectfilter.impl.predicateindex;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.infinispan.objectfilter.impl.FilterSubscriptionImpl;
import org.infinispan.objectfilter.impl.MetadataAdapter;
import org.infinispan.objectfilter.impl.predicateindex.be.PredicateNode;
/**
* An attribute node represents a single attribute and keeps track of subscribed predicates and projections.
*
* @author anistor@redhat.com
* @since 7.0
*/
public class AttributeNode<AttributeMetadata, AttributeId extends Comparable<AttributeId>> {
public static final Object DUMMY_VALUE = new Object();
private static final AttributeNode[] EMPTY_CHILDREN = new AttributeNode[0];
// this is never null, except for the root node
private final AttributeId attribute;
// property metadata for an intermediate or leaf node. This is never null, except for the root node
private final AttributeMetadata metadata;
private final MetadataAdapter<?, AttributeMetadata, AttributeId> metadataAdapter;
// this is never null, except for the root node
private final AttributeNode<AttributeMetadata, AttributeId> parent;
/**
* Child attributes.
*/
private Map<AttributeId, AttributeNode<AttributeMetadata, AttributeId>> children;
private AttributeNode<AttributeMetadata, AttributeId>[] childrenArray = EMPTY_CHILDREN;
/**
* Root node must not have predicates. This field is always null for root node. Non-leaf nodes can only have
* 'isEmpty' or 'isNull' predicates.
*/
private Predicates predicates;
/**
* The list of all subscribed projections. This field is always null for root node. Only leaf nodes can have
* projections.
*/
private Projections projections;
/**
* Constructor used only for the root node.
*/
protected AttributeNode(MetadataAdapter<?, AttributeMetadata, AttributeId> metadataAdapter) {
this.attribute = null;
this.parent = null;
this.metadataAdapter = metadataAdapter;
this.metadata = null;
}
private AttributeNode(AttributeId attribute, AttributeNode<AttributeMetadata, AttributeId> parent) {
this.attribute = attribute;
this.parent = parent;
metadataAdapter = parent.metadataAdapter;
metadata = metadataAdapter.makeChildAttributeMetadata(parent.metadata, attribute);
}
public AttributeId getAttribute() {
return attribute;
}
public AttributeMetadata getMetadata() {
return metadata;
}
public AttributeNode<AttributeMetadata, AttributeId> getParent() {
return parent;
}
public AttributeNode<AttributeMetadata, AttributeId>[] getChildren() {
return childrenArray;
}
/**
* @param attribute
* @return the child or null if not found
*/
public AttributeNode<AttributeMetadata, AttributeId> getChild(AttributeId attribute) {
if (children != null) {
return children.get(attribute);
}
return null;
}
public int getNumChildren() {
return childrenArray.length;
}
public boolean hasPredicates() {
return predicates != null && !predicates.isEmpty();
}
public boolean hasProjections() {
return projections != null && projections.hasProjections();
}
public void processValue(Object attributeValue, MatcherEvalContext<?, AttributeMetadata, AttributeId> ctx) {
if (projections != null && attributeValue != DUMMY_VALUE) {
projections.processProjections(ctx, attributeValue);
}
if (predicates != null) {
predicates.notifyMatchingSubscribers(ctx, attributeValue);
}
}
/**
* Add a child node. If the child already exists it just increments its usage counter and returns the existing
* child.
*
* @param attribute
* @return the added or existing child
*/
public AttributeNode<AttributeMetadata, AttributeId> addChild(AttributeId attribute) {
AttributeNode<AttributeMetadata, AttributeId> child;
if (children == null) {
children = new HashMap<>();
child = new AttributeNode<>(attribute, this);
children.put(attribute, child);
rebuildChildrenArray();
} else {
child = children.get(attribute);
if (child == null) {
child = new AttributeNode<>(attribute, this);
children.put(attribute, child);
rebuildChildrenArray();
}
}
return child;
}
/**
* Decrement the usage counter of a child node and remove it if no usages remain. The removal works recursively up
* the path to the root.
*
* @param attribute the attribute of the child to be removed
*/
public void removeChild(AttributeId attribute) {
if (children == null) {
throw new IllegalArgumentException("No child found : " + attribute);
}
AttributeNode<AttributeMetadata, AttributeId> child = children.get(attribute);
if (child == null) {
throw new IllegalArgumentException("No child found : " + attribute);
}
children.remove(attribute);
rebuildChildrenArray();
}
private void rebuildChildrenArray() {
Collection<AttributeNode<AttributeMetadata, AttributeId>> childrenCollection = children.values();
childrenArray = childrenCollection.toArray(new AttributeNode[childrenCollection.size()]);
}
public Predicates.Subscription<AttributeId> addPredicateSubscription(PredicateNode<AttributeId> predicateNode, FilterSubscriptionImpl filterSubscription) {
if (predicates == null) {
predicates = new Predicates(filterSubscription.useIntervals() && metadataAdapter.isComparableProperty(metadata));
}
return predicates.addPredicateSubscription(predicateNode, filterSubscription);
}
public void removePredicateSubscription(Predicates.Subscription<AttributeId> subscription) {
if (predicates != null) {
predicates.removePredicateSubscription(subscription);
} else {
throw new IllegalStateException("Reached illegal state");
}
}
public void addProjection(FilterSubscriptionImpl filterSubscription, int position) {
if (projections == null) {
projections = new Projections();
}
projections.addProjection(filterSubscription, position);
}
public void removeProjections(FilterSubscriptionImpl filterSubscription) {
if (projections != null) {
projections.removeProjections(filterSubscription);
} else {
throw new IllegalStateException("Reached illegal state");
}
}
@Override
public String toString() {
return "AttributeNode(" + attribute + ')';
}
}