com.googlecode.cqengine.index.unique.UniqueIndex 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.unique;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.googlecode.cqengine.attribute.Attribute;
import com.googlecode.cqengine.index.support.AbstractAttributeIndex;
import com.googlecode.cqengine.index.support.Factory;
import com.googlecode.cqengine.index.hash.HashIndex;
import com.googlecode.cqengine.query.Query;
import com.googlecode.cqengine.query.option.QueryOptions;
import com.googlecode.cqengine.query.simple.Equal;
import com.googlecode.cqengine.resultset.ResultSet;
import com.googlecode.cqengine.resultset.iterator.UnmodifiableIterator;
/**
* An index backed by a {@link ConcurrentHashMap}, which can be more efficient than {@link HashIndex} when used with
* (and only with) attributes which uniquely identify objects (primary key-type attributes).
*
* This type of index does not store a set of objects matching each attribute value, but instead stores only a
* single object for each value. This results in faster query performance, and often lower memory usage, but has some
* trade-offs.
*
* This index will throw an exception if a duplicate object is detected for an existing attribute value. That condition
* means however that inconsistencies might already have arisen between this and other indexes as a result of the
* application's misuse of this index.
*
* Trade-offs: {@code UniqueIndex} versus {@code HashIndex}
*
* -
* {@code UniqueIndex} will always use less memory than a non-quantized {@code HashIndex}
*
* -
* {@code UniqueIndex} will not necessarily use less memory than a quantized {@code HashIndex}, i.e.
* configured with a {@link com.googlecode.cqengine.quantizer.Quantizer}
*
* -
* In all cases, {@code UniqueIndex} will answer queries faster than a {@code HashIndex}
*
* -
* It is important that {@code UniqueIndex} only be used with attributes which uniquely identify objects
*
*
*
* Supports query types:
*
* -
* {@link Equal}
*
*
*
* @author Kinz Liu
* @author Niall Gallagher
*/
public class UniqueIndex extends AbstractAttributeIndex {
protected static final int INDEX_RETRIEVAL_COST = 25;
protected final Factory> indexMapFactory;
protected final ConcurrentMap indexMap;
/**
* Package-private constructor, used by static factory methods. Creates a new UniqueIndex initialized to index the
* supplied attribute.
*
* @param indexMapFactory A factory used to create the main map-based data structure used by the index
* @param attribute The attribute on which the index will be built
*/
protected UniqueIndex(Factory> indexMapFactory, Attribute attribute) {
super(attribute, new HashSet>() {{
add(Equal.class);
}});
this.indexMapFactory = indexMapFactory;
this.indexMap = indexMapFactory.create();
}
/**
* {@inheritDoc}
*
* This index is mutable.
*
* @return true
*/
@Override
public boolean isMutable() {
return true;
}
@Override
public ResultSet retrieve(Query query, QueryOptions queryOptions) {
Class> queryClass = query.getClass();
if (queryClass.equals(Equal.class))
{
final ConcurrentMap indexMap = this.indexMap;
final Equal equal = (Equal) query;
final O obj = indexMap.get(equal.getValue());
return new ResultSet() {
@Override
public Iterator iterator() {
return new UnmodifiableIterator() {
boolean hasNext = (obj != null);
@Override
public boolean hasNext() {
return this.hasNext;
}
@Override
public O next() {
this.hasNext=false;
return obj;
}
};
}
@Override
public boolean contains(O object) {
return (object != null && obj != null && object.equals(obj));
}
@Override
public int size() {
return obj == null ? 0 : 1;
}
@Override
public int getRetrievalCost() {
return INDEX_RETRIEVAL_COST;
}
@Override
public int getMergeCost() {
return obj == null ? 0 : 1;
}
@Override
public void close() {
// No op.
}
};
}
throw new IllegalArgumentException("Unsupported query: " + query);
}
/**
* {@inheritDoc}
*/
@Override
public boolean addAll(Collection objects, QueryOptions queryOptions) {
boolean modified = false;
ConcurrentMap indexMap = this.indexMap;
for (O object : objects) {
Iterable attributeValues = getAttribute().getValues(object, queryOptions);
for (A attributeValue : attributeValues) {
O existingValue = indexMap.put(attributeValue, object);
if (existingValue != null && !existingValue.equals(object)) {
throw new UniqueConstraintViolatedException(
"The application has attempted to add a duplicate object to the UniqueIndex on attribute '"
+ attribute.getAttributeName() +
"', potentially causing inconsistencies between indexes. " +
"UniqueIndex should not be used with attributes which do not uniquely identify objects. " +
"Problematic attribute value: '" + attributeValue + "', " +
"problematic duplicate object: " + object);
}
modified = true;
}
}
return modified;
}
/**
* {@inheritDoc}
*/
@Override
public boolean removeAll(Collection objects, QueryOptions queryOptions) {
boolean modified = false;
ConcurrentMap indexMap = this.indexMap;
for (O object : objects) {
Iterable attributeValues = getAttribute().getValues(object, queryOptions);
for (A attributeValue : attributeValues) {
modified |= (indexMap.remove(attributeValue) != null);
}
}
return modified;
}
/**
* {@inheritDoc}
*/
@Override
public void init(Set collection, QueryOptions queryOptions) {
addAll(collection, queryOptions);
}
/**
* {@inheritDoc}
*/
@Override
public void clear(QueryOptions queryOptions) {
this.indexMap.clear();
}
public static class UniqueConstraintViolatedException extends RuntimeException {
public UniqueConstraintViolatedException(String message) {
super(message);
}
}
/**
* Creates an index map using default settings.
*/
public static class DefaultIndexMapFactory implements Factory> {
@Override
public ConcurrentMap create() {
return new ConcurrentHashMap();
}
}
// ---------- Static factory methods to create UniqueIndexes ----------
/**
* Creates a new {@link UniqueIndex} 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 UniqueIndex} on this attribute
*/
public static UniqueIndex onAttribute(Attribute attribute) {
return onAttribute(new DefaultIndexMapFactory(), attribute);
}
/**
* Creates a new {@link UniqueIndex} on the specified attribute.
*
* @param indexMapFactory A factory used to create the main map-based data structure used by the index
* @param attribute The attribute on which the index will be built
* @param The type of the object containing the attribute
* @return A {@link UniqueIndex} on this attribute
*/
public static UniqueIndex onAttribute(Factory> indexMapFactory, Attribute attribute) {
return new UniqueIndex(indexMapFactory, attribute);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy