/*
***************************************************************************************
* Copyright (C) 2006 EsperTech, Inc. All rights reserved. *
* http://www.espertech.com/esper *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
***************************************************************************************
*/
package com.espertech.esper.filter;
import com.espertech.esper.client.EventType;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.util.ExecutionPathDebugLog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayDeque;
import java.util.Collections;
/**
* Builder manipulates a tree structure consisting of {@link FilterHandleSetNode} and {@link FilterParamIndexBase} instances.
* Filters can be added to a top node (an instance of FilterHandleSetNode) via the add method. This method returns
* an instance of {@link EventTypeIndexBuilderIndexLookupablePair} which represents an element in the tree path (list of indizes) that the filter callback was
* added to. To remove filters the same IndexTreePath instance must be passed in.
* <p>The implementation is designed to be multithread-safe in conjunction with the node classes manipulated by this class.
*/
public final class IndexTreeBuilder {
private IndexTreeBuilder() {
}
/**
* Add a filter callback according to the filter specification to the top node returning
* information to be used to remove the filter callback.
*
* @param filterValueSet is the filter definition
* @param filterCallback is the callback to be added
* @param topNode node to be added to any subnode beneath it
* @param lockFactory lock factory
* @return an encapsulation of information need to allow for safe removal of the filter tree.
*/
public static ArrayDeque<EventTypeIndexBuilderIndexLookupablePair>[] add(FilterValueSet filterValueSet,
FilterHandle filterCallback,
FilterHandleSetNode topNode,
FilterServiceGranularLockFactory lockFactory) {
if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled())) {
log.debug(".add (" + Thread.currentThread().getId() + ") Adding filter callback, " +
" topNode=" + topNode +
" filterCallback=" + filterCallback);
}
ArrayDeque<EventTypeIndexBuilderIndexLookupablePair>[] treePathInfo;
if (filterValueSet.getParameters().length == 0) {
treePathInfo = allocateTreePath(1);
treePathInfo[0] = new ArrayDeque<EventTypeIndexBuilderIndexLookupablePair>(1);
addToNode(new ArrayDeque<FilterValueSetParam>(1), filterCallback, topNode, treePathInfo[0], lockFactory);
} else {
treePathInfo = allocateTreePath(filterValueSet.getParameters().length);
ArrayDeque<FilterValueSetParam> remainingParameters = new ArrayDeque<FilterValueSetParam>(4);
for (int i = 0; i < filterValueSet.getParameters().length; i++) {
treePathInfo[i] = new ArrayDeque<EventTypeIndexBuilderIndexLookupablePair>(filterValueSet.getParameters()[i].length);
remainingParameters.clear();
Collections.addAll(remainingParameters, filterValueSet.getParameters()[i]);
addToNode(remainingParameters, filterCallback, topNode, treePathInfo[i], lockFactory);
}
}
return treePathInfo;
}
/**
* Remove an filterCallback from the given top node. The IndexTreePath instance passed in must be the
* same as obtained when the same filterCallback was added.
*
* @param filterCallback filter callback to be removed
* @param treePathInfo encapsulates information need to allow for safe removal of the filterCallback
* @param topNode The top tree node beneath which the filterCallback was added
* @param eventType event type
*/
public static void remove(
EventType eventType,
FilterHandle filterCallback,
EventTypeIndexBuilderIndexLookupablePair[] treePathInfo,
FilterHandleSetNode topNode) {
if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled())) {
log.debug(".remove (" + Thread.currentThread().getId() + ") Removing filterCallback " +
" type " + eventType.getName() +
" topNode=" + topNode +
" filterCallback=" + filterCallback);
}
removeFromNode(filterCallback, topNode, treePathInfo, 0);
}
/**
* Add to the current node building up the tree path information.
*
* @param currentNode is the node to add to
* @param treePathInfo is filled with information about which indizes were chosen to add the filter to
*/
private static void addToNode(ArrayDeque<FilterValueSetParam> remainingParameters,
FilterHandle filterCallback,
FilterHandleSetNode currentNode,
ArrayDeque<EventTypeIndexBuilderIndexLookupablePair> treePathInfo,
FilterServiceGranularLockFactory lockFactory) {
if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled())) {
log.debug(".addToNode (" + Thread.currentThread().getId() + ") Adding filterCallback, node=" + currentNode +
" remainingParameters=" + printRemainingParameters(remainingParameters));
}
// If no parameters are specified, add to current node, and done
if (remainingParameters.isEmpty()) {
currentNode.getNodeRWLock().writeLock().lock();
try {
currentNode.add(filterCallback);
} finally {
currentNode.getNodeRWLock().writeLock().unlock();
}
return;
}
// Need to find an existing index that matches one of the filter parameters
currentNode.getNodeRWLock().readLock().lock();
Pair<FilterValueSetParam, FilterParamIndexBase> pair;
try {
pair = IndexHelper.findIndex(remainingParameters, currentNode.getIndizes());
// Found an index matching a filter parameter
if (pair != null) {
remainingParameters.remove(pair.getFirst());
Object filterForValue = pair.getFirst().getFilterForValue();
FilterParamIndexBase index = pair.getSecond();
treePathInfo.add(new EventTypeIndexBuilderIndexLookupablePair(index, filterForValue));
addToIndex(remainingParameters, filterCallback, index, filterForValue, treePathInfo, lockFactory);
return;
}
} finally {
currentNode.getNodeRWLock().readLock().unlock();
}
// An index for any of the filter parameters was not found, create one
currentNode.getNodeRWLock().writeLock().lock();
try {
pair = IndexHelper.findIndex(remainingParameters, currentNode.getIndizes());
// Attempt to find an index again this time under a write lock
if (pair != null) {
remainingParameters.remove(pair.getFirst());
Object filterForValue = pair.getFirst().getFilterForValue();
FilterParamIndexBase index = pair.getSecond();
treePathInfo.add(new EventTypeIndexBuilderIndexLookupablePair(index, filterForValue));
addToIndex(remainingParameters, filterCallback, index, filterForValue, treePathInfo, lockFactory);
return;
}
// No index found that matches any parameters, create a new one
// Pick the next parameter for an index
FilterValueSetParam parameterPickedForIndex = remainingParameters.removeFirst();
FilterParamIndexBase index = IndexFactory.createIndex(parameterPickedForIndex.getLookupable(), lockFactory, parameterPickedForIndex.getFilterOperator());
currentNode.getIndizes().add(index);
treePathInfo.add(new EventTypeIndexBuilderIndexLookupablePair(index, parameterPickedForIndex.getFilterForValue()));
addToIndex(remainingParameters, filterCallback, index, parameterPickedForIndex.getFilterForValue(), treePathInfo, lockFactory);
} finally {
currentNode.getNodeRWLock().writeLock().unlock();
}
}
// Remove an filterCallback from the current node, return true if the node is the node is empty now
private static boolean removeFromNode(FilterHandle filterCallback,
FilterHandleSetNode currentNode,
EventTypeIndexBuilderIndexLookupablePair[] treePathInfo,
int treePathPosition) {
EventTypeIndexBuilderIndexLookupablePair nextPair = treePathPosition < treePathInfo.length ? treePathInfo[treePathPosition++] : null;
// No remaining filter parameters
if (nextPair == null) {
currentNode.getNodeRWLock().writeLock().lock();
try {
boolean isRemoved = currentNode.remove(filterCallback);
boolean isEmpty = currentNode.isEmpty();
if (!isRemoved) {
log.warn(".removeFromNode (" + Thread.currentThread().getId() + ") Could not find the filterCallback to be removed within the supplied node , node=" +
currentNode + " filterCallback=" + filterCallback);
}
return isEmpty;
} finally {
currentNode.getNodeRWLock().writeLock().unlock();
}
}
// Remove from index
FilterParamIndexBase nextIndex = nextPair.getIndex();
Object filteredForValue = nextPair.getLookupable();
currentNode.getNodeRWLock().writeLock().lock();
try {
boolean isEmpty = removeFromIndex(filterCallback, nextIndex, treePathInfo, treePathPosition, filteredForValue);
if (!isEmpty) {
return false;
}
// Remove the index if the index is now empty
if (nextIndex.isEmpty()) {
boolean isRemoved = currentNode.remove(nextIndex);
if (!isRemoved) {
log.warn(".removeFromNode (" + Thread.currentThread().getId() + ") Could not find the index in index list for removal, index=" +
nextIndex.toString() + " filterCallback=" + filterCallback);
return false;
}
}
return currentNode.isEmpty();
} finally {
currentNode.getNodeRWLock().writeLock().unlock();
}
}
// Remove filterCallback from index, returning true if index empty after removal
private static boolean removeFromIndex(FilterHandle filterCallback,
FilterParamIndexBase index,
EventTypeIndexBuilderIndexLookupablePair[] treePathInfo,
int treePathPosition,
Object filterForValue) {
index.getReadWriteLock().writeLock().lock();
try {
EventEvaluator eventEvaluator = index.get(filterForValue);
if (eventEvaluator == null) {
log.warn(".removeFromIndex (" + Thread.currentThread().getId() + ") Could not find the filterCallback value in index, index=" +
index.toString() + " value=" + filterForValue.toString() + " filterCallback=" + filterCallback);
return false;
}
if (eventEvaluator instanceof FilterHandleSetNode) {
FilterHandleSetNode node = (FilterHandleSetNode) eventEvaluator;
boolean isEmpty = removeFromNode(filterCallback, node, treePathInfo, treePathPosition);
if (isEmpty) {
// Since we are holding a write lock to this index, there should not be a chance that
// another thread had been adding anything to this FilterHandleSetNode
index.remove(filterForValue);
}
return index.isEmpty();
}
FilterParamIndexBase nextIndex = (FilterParamIndexBase) eventEvaluator;
EventTypeIndexBuilderIndexLookupablePair nextPair = treePathPosition < treePathInfo.length ? treePathInfo[treePathPosition++] : null;
if (nextPair == null) {
log.error(".removeFromIndex Expected an inner index to this index, this=" + filterCallback.toString());
assert false;
return false;
}
if (nextPair.getIndex() != nextIndex) {
log.error(".removeFromIndex Expected an index for filterCallback that differs from the found index, this=" + filterCallback.toString() +
" expected=" + nextPair.getIndex());
assert false;
return false;
}
Object nextExpressionValue = nextPair.getLookupable();
boolean isEmpty = removeFromIndex(filterCallback, nextPair.getIndex(), treePathInfo, treePathPosition, nextExpressionValue);
if (isEmpty) {
// Since we are holding a write lock to this index, there should not be a chance that
// another thread had been adding anything to this FilterHandleSetNode
index.remove(filterForValue);
}
return index.isEmpty();
} finally {
index.getReadWriteLock().writeLock().unlock();
}
}
/**
* Add to an index the value to filter for.
*
* @param index is the index to add to
* @param filterForValue is the filter parameter value to add
* @param treePathInfo is the specification to fill on where is was added
*/
private static void addToIndex(ArrayDeque<FilterValueSetParam> remainingParameters,
FilterHandle filterCallback,
FilterParamIndexBase index,
Object filterForValue,
ArrayDeque<EventTypeIndexBuilderIndexLookupablePair> treePathInfo,
FilterServiceGranularLockFactory lockFactory) {
if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled())) {
log.debug(".addToIndex (" + Thread.currentThread().getId() + ") Adding to index " +
index.toString() +
" expressionValue=" + filterForValue);
}
index.getReadWriteLock().readLock().lock();
EventEvaluator eventEvaluator;
try {
eventEvaluator = index.get(filterForValue);
// The filter parameter value already existed in bean, add and release locks
if (eventEvaluator != null) {
boolean added = addToEvaluator(remainingParameters, filterCallback, eventEvaluator, treePathInfo, lockFactory);
if (added) {
return;
}
}
} finally {
index.getReadWriteLock().readLock().unlock();
}
// new filter parameter value, need a write lock
index.getReadWriteLock().writeLock().lock();
try {
eventEvaluator = index.get(filterForValue);
// It may exist now since another thread could have added the entry
if (eventEvaluator != null) {
boolean added = addToEvaluator(remainingParameters, filterCallback, eventEvaluator, treePathInfo, lockFactory);
if (added) {
return;
}
// The found eventEvaluator must be converted to a new FilterHandleSetNode
FilterParamIndexBase nextIndex = (FilterParamIndexBase) eventEvaluator;
FilterHandleSetNode newNode = new FilterHandleSetNode(lockFactory.obtainNew());
newNode.add(nextIndex);
index.remove(filterForValue);
index.put(filterForValue, newNode);
addToNode(remainingParameters, filterCallback, newNode, treePathInfo, lockFactory);
return;
}
// The index does not currently have this filterCallback value,
// if there are no remaining parameters, create a node
if (remainingParameters.isEmpty()) {
FilterHandleSetNode node = new FilterHandleSetNode(lockFactory.obtainNew());
addToNode(remainingParameters, filterCallback, node, treePathInfo, lockFactory);
index.put(filterForValue, node);
return;
}
// If there are remaining parameters, create a new index for the next parameter
FilterValueSetParam parameterPickedForIndex = remainingParameters.removeFirst();
FilterParamIndexBase nextIndex = IndexFactory.createIndex(parameterPickedForIndex.getLookupable(), lockFactory, parameterPickedForIndex.getFilterOperator());
index.put(filterForValue, nextIndex);
treePathInfo.add(new EventTypeIndexBuilderIndexLookupablePair(nextIndex, parameterPickedForIndex.getFilterForValue()));
addToIndex(remainingParameters, filterCallback, nextIndex, parameterPickedForIndex.getFilterForValue(), treePathInfo, lockFactory);
} finally {
index.getReadWriteLock().writeLock().unlock();
}
}
/**
* Add filter callback to an event evaluator, which could be either an index node or a set node.
*
* @param eventEvaluator to add the filterCallback to.
* @param treePathInfo is for holding the information on where the add occured
* @return boolean indicating if the eventEvaluator was successfully added
*/
private static boolean addToEvaluator(ArrayDeque<FilterValueSetParam> remainingParameters,
FilterHandle filterCallback,
EventEvaluator eventEvaluator,
ArrayDeque<EventTypeIndexBuilderIndexLookupablePair> treePathInfo,
FilterServiceGranularLockFactory lockFactory) {
if (eventEvaluator instanceof FilterHandleSetNode) {
FilterHandleSetNode node = (FilterHandleSetNode) eventEvaluator;
addToNode(remainingParameters, filterCallback, node, treePathInfo, lockFactory);
return true;
}
// Check if the next index matches any of the remaining filterCallback parameters
FilterParamIndexBase nextIndex = (FilterParamIndexBase) eventEvaluator;
FilterValueSetParam parameter = IndexHelper.findParameter(remainingParameters, nextIndex);
if (parameter != null) {
remainingParameters.remove(parameter);
treePathInfo.add(new EventTypeIndexBuilderIndexLookupablePair(nextIndex, parameter.getFilterForValue()));
addToIndex(remainingParameters, filterCallback, nextIndex, parameter.getFilterForValue(), treePathInfo, lockFactory);
return true;
}
// This eventEvaluator does not work with any of the remaining filter parameters
return false;
}
private static String printRemainingParameters(ArrayDeque<FilterValueSetParam> remainingParameters) {
StringBuilder buffer = new StringBuilder();
int count = 0;
for (FilterValueSetParam parameter : remainingParameters) {
buffer.append(" param(").append(count).append(')');
buffer.append(" property=").append(parameter.getLookupable());
buffer.append(" operator=").append(parameter.getFilterOperator());
buffer.append(" value=").append(parameter.getFilterForValue());
count++;
}
return buffer.toString();
}
private static ArrayDeque<EventTypeIndexBuilderIndexLookupablePair>[] allocateTreePath(int size) {
return (ArrayDeque<EventTypeIndexBuilderIndexLookupablePair>[])
new ArrayDeque[size];
}
private static final Logger log = LoggerFactory.getLogger(IndexTreeBuilder.class);
}