com.googlecode.cqengine.index.suffix.SuffixTreeIndex Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cqengine Show documentation
Show all versions of cqengine Show documentation
Collection Query Engine: NoSQL indexing and query engine for Java collections with ultra-low latency
/**
* Copyright 2012-2015 Niall Gallagher
*
* 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 com.googlecode.cqengine.index.suffix;
import com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory;
import com.googlecode.concurrenttrees.suffix.ConcurrentSuffixTree;
import com.googlecode.concurrenttrees.suffix.SuffixTree;
import com.googlecode.cqengine.attribute.Attribute;
import com.googlecode.cqengine.attribute.SimpleAttribute;
import com.googlecode.cqengine.index.support.AbstractAttributeIndex;
import com.googlecode.cqengine.query.Query;
import com.googlecode.cqengine.query.option.DeduplicationOption;
import com.googlecode.cqengine.query.option.QueryOptions;
import com.googlecode.cqengine.query.simple.Equal;
import com.googlecode.cqengine.query.simple.StringContains;
import com.googlecode.cqengine.query.simple.StringEndsWith;
import com.googlecode.cqengine.resultset.ResultSet;
import com.googlecode.cqengine.resultset.connective.ResultSetUnion;
import com.googlecode.cqengine.resultset.connective.ResultSetUnionAll;
import com.googlecode.cqengine.resultset.stored.StoredResultSet;
import com.googlecode.cqengine.resultset.stored.StoredSetBasedResultSet;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* An index backed by a {@link ConcurrentSuffixTree}.
*
* Supports query types:
*
* -
* {@link Equal}
*
* -
* {@link StringEndsWith}
*
* -
* {@link StringContains}
*
*
*
* @author Niall Gallagher
*/
public class SuffixTreeIndex extends AbstractAttributeIndex {
private static final int INDEX_RETRIEVAL_COST = 53;
private volatile SuffixTree> tree = new ConcurrentSuffixTree>(new DefaultCharArrayNodeFactory());
/**
* Package-private constructor, used by static factory methods. Creates a new SuffixTreeIndex initialized to
* index the supplied attribute.
*
* @param attribute The attribute on which the index will be built
*/
protected SuffixTreeIndex(Attribute attribute) {
super(attribute, new HashSet>() {{
add(Equal.class);
add(StringEndsWith.class);
add(StringContains.class);
}});
}
@Override
public boolean isMutable() {
return true;
}
@Override
public ResultSet retrieve(Query query, final QueryOptions queryOptions) {
final SuffixTree> tree = this.tree;
Class> queryClass = query.getClass();
if (queryClass.equals(Equal.class)) {
final Equal equal = (Equal) query;
return new ResultSet() {
@Override
public Iterator iterator() {
ResultSet rs = tree.getValueForExactKey(equal.getValue());
return rs == null ? Collections.emptySet().iterator() : rs.iterator();
}
@Override
public boolean contains(O object) {
ResultSet rs = tree.getValueForExactKey(equal.getValue());
return rs != null && rs.contains(object);
}
@Override
public int size() {
ResultSet rs = tree.getValueForExactKey(equal.getValue());
return rs == null ? 0 : rs.size();
}
@Override
public int getRetrievalCost() {
return INDEX_RETRIEVAL_COST;
}
@Override
public int getMergeCost() {
// Return size of entire stored set as merge cost...
ResultSet rs = tree.getValueForExactKey(equal.getValue());
return rs == null ? 0 : rs.size();
}
@Override
public void close() {
// No op.
}
};
}
else if (queryClass.equals(StringEndsWith.class)) {
final StringEndsWith stringEndsWith = (StringEndsWith) query;
return new ResultSet() {
@Override
public Iterator iterator() {
Iterable extends ResultSet> resultSets = tree.getValuesForKeysEndingWith(stringEndsWith.getValue());
ResultSet rs = unionResultSets(resultSets, queryOptions);
return rs.iterator();
}
@Override
public boolean contains(O object) {
Iterable extends ResultSet> resultSets = tree.getValuesForKeysEndingWith(stringEndsWith.getValue());
ResultSet rs = unionResultSets(resultSets, queryOptions);
return rs.contains(object);
}
@Override
public int size() {
Iterable extends ResultSet> resultSets = tree.getValuesForKeysEndingWith(stringEndsWith.getValue());
ResultSet rs = unionResultSets(resultSets, queryOptions);
return rs.size();
}
@Override
public int getRetrievalCost() {
return INDEX_RETRIEVAL_COST;
}
@Override
public int getMergeCost() {
Iterable extends ResultSet> resultSets = tree.getValuesForKeysEndingWith(stringEndsWith.getValue());
ResultSet rs = unionResultSets(resultSets, queryOptions);
return rs.getMergeCost();
}
@Override
public void close() {
// No op.
}
};
}
else if (queryClass.equals(StringContains.class)) {
final StringContains stringContains = (StringContains) query;
return new ResultSet() {
@Override
public Iterator iterator() {
Iterable extends ResultSet> resultSets = tree.getValuesForKeysContaining(stringContains.getValue());
ResultSet rs = unionResultSets(resultSets, queryOptions);
return rs.iterator();
}
@Override
public boolean contains(O object) {
Iterable extends ResultSet> resultSets = tree.getValuesForKeysContaining(stringContains.getValue());
ResultSet rs = unionResultSets(resultSets, queryOptions);
return rs.contains(object);
}
@Override
public int size() {
Iterable extends ResultSet> resultSets = tree.getValuesForKeysContaining(stringContains.getValue());
ResultSet rs = unionResultSets(resultSets, queryOptions);
return rs.size();
}
@Override
public int getRetrievalCost() {
return INDEX_RETRIEVAL_COST;
}
@Override
public int getMergeCost() {
Iterable extends ResultSet> resultSets = tree.getValuesForKeysContaining(stringContains.getValue());
ResultSet rs = unionResultSets(resultSets, queryOptions);
return rs.getMergeCost();
}
@Override
public void close() {
// No op.
}
};
}
else {
throw new IllegalArgumentException("Unsupported query: " + query);
}
}
/**
* If a query option specifying logical deduplication was supplied, wrap the given result sets in
* {@link ResultSetUnion}, otherwise wrap in {@link ResultSetUnionAll}.
*
* An exception is if the index is built on a SimpleAttribute, we can avoid deduplication and always use
* {@link ResultSetUnionAll}, because the same object could not exist in more than one {@link StoredResultSet}.
*
* @param results Provides the result sets to union
* @param queryOptions Specifies whether or not logical deduplication is required
* @return A union view over the given result sets
*/
ResultSet unionResultSets(Iterable extends ResultSet> results, QueryOptions queryOptions) {
if (DeduplicationOption.isLogicalElimination(queryOptions) && !(getAttribute() instanceof SimpleAttribute)) {
return new ResultSetUnion(results, queryOptions) {
@Override
public int getRetrievalCost() {
return INDEX_RETRIEVAL_COST;
}
};
}
else {
return new ResultSetUnionAll(results) {
@Override
public int getRetrievalCost() {
return INDEX_RETRIEVAL_COST;
}
};
}
}
/**
* @return A {@link StoredSetBasedResultSet} based on a set backed by {@link ConcurrentHashMap}, as created via
* {@link java.util.Collections#newSetFromMap(java.util.Map)}
*/
public StoredResultSet createValueSet() {
return new StoredSetBasedResultSet(Collections.newSetFromMap(new ConcurrentHashMap()));
}
/**
* {@inheritDoc}
*/
@Override
public boolean addAll(Collection objects, QueryOptions queryOptions) {
boolean modified = false;
final SuffixTree> tree = this.tree;
for (O object : objects) {
Iterable attributeValues = getAttribute().getValues(object, queryOptions);
for (A attributeValue : attributeValues) {
// Look up StoredResultSet for the value...
StoredResultSet valueSet = tree.getValueForExactKey(attributeValue);
if (valueSet == null) {
// No StoredResultSet, create and add one...
valueSet = createValueSet();
StoredResultSet existingValueSet = tree.putIfAbsent(attributeValue, valueSet);
if (existingValueSet != null) {
// Another thread won race to add new value set, use that one...
valueSet = existingValueSet;
}
}
// Add the object to the StoredResultSet for this value...
modified |= valueSet.add(object);
}
}
return modified;
}
/**
* {@inheritDoc}
*/
@Override
public boolean removeAll(Collection objects, QueryOptions queryOptions) {
boolean modified = false;
final SuffixTree> tree = this.tree;
for (O object : objects) {
Iterable attributeValues = getAttribute().getValues(object, queryOptions);
for (A attributeValue : attributeValues) {
StoredResultSet valueSet = tree.getValueForExactKey(attributeValue);
if (valueSet == null) {
continue;
}
modified |= valueSet.remove(object);
if (valueSet.isEmpty()) {
tree.remove(attributeValue);
}
}
}
return modified;
}
/**
* {@inheritDoc}
*/
@Override
public void init(Set collection, QueryOptions queryOptions) {
addAll(collection, queryOptions);
}
/**
* {@inheritDoc}
*/
@Override
public void clear(QueryOptions queryOptions) {
this.tree = new ConcurrentSuffixTree>(new DefaultCharArrayNodeFactory());
}
// ---------- Static factory methods to create SuffixTreeIndexes ----------
/**
* Creates a new {@link SuffixTreeIndex} on the specified attribute.
*
* @param attribute The attribute on which the index will be built
* @param The type of the object containing the attribute
* @return A {@link SuffixTreeIndex} on this attribute
*/
public static SuffixTreeIndex onAttribute(Attribute attribute) {
return new SuffixTreeIndex(attribute);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy