alluxio.collections.IndexedSet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of alluxio-core-common Show documentation
Show all versions of alluxio-core-common Show documentation
Common utilities shared in Alluxio core modules
/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/
package alluxio.collections;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.annotation.concurrent.ThreadSafe;
/**
* A set of objects that are indexed and thus can be queried by specific fields of the object.
* Different {@link IndexedSet} instances may specify different fields to index. The field type
* must be comparable. The field value must not be changed after an object is added to the set,
* otherwise, behavior for all operations is not specified.
*
* If concurrent adds or removes for objects which are equivalent, but not the same exact object,
* the behavior is undefined. Therefore, do not add or remove "clones" objects in the
* {@link IndexedSet}.
*
*
* Example usage:
*
* We have a set of puppies:
*
*
* class Puppy {
* private final String mName;
* private final long mId;
*
* public Puppy(String name, long id) {
* mName = name;
* mId = id;
* }
*
* public String name() {
* return mName;
* }
*
* public long id() {
* return mId;
* }
* }
*
*
* We want to be able to retrieve the set of puppies via a puppy's id or name, one way is to have
* two maps like {@code Map nameToPuppy} and {@code Map idToPuppy},
* another way is to use a single instance of {@link IndexedSet}!
*
* First, define the fields to be indexed:
*
* IndexDefinition<Puppy> idIndex = new IndexDefinition<Puppy>(true) {
* {@literal @Override}
* Object getFieldValue(Puppy o) {
* return o.id();
* }
* }
*
* IndexDefinition<Puppy> nameIndex = new IndexDefinition<Puppy>(true) {
* {@literal @Override}
* Object getFieldValue(Puppy o) {
* return o.name();
* }
* }
*
*
* Then create an {@link IndexedSet} and add puppies:
*
* IndexedSet<Puppy> puppies = new IndexedSet<Puppy>(idIndex, nameIndex);
* puppies.add(new Puppy("sweet", 0));
* puppies.add(new Puppy("heart", 1));
*
*
* Then retrieve the puppy named sweet:
*
* Puppy sweet = puppies.getFirstByField(nameIndex, "sweet");
*
* and retrieve the puppy with id 1:
*
* Puppy heart = puppies.getFirstByField(idIndex, 1L);
*
*
* @param the type of object
*/
@ThreadSafe
public class IndexedSet extends AbstractSet {
/**
* The first index of the indexed set. This index is used to guarantee uniqueness of objects,
* support iterator and provide quick lookup.
*/
private final FieldIndex mPrimaryIndex;
/**
* Map from index definition to the index. An index is a map from index value to one or a set of
* objects with that index value.
*/
private final Map, FieldIndex> mIndices;
/**
* Constructs a new {@link IndexedSet} instance with at least one field as the index.
*
* @param primaryIndexDefinition at least one field is needed to index the set of objects. This
* primaryIndexDefinition is used to initialize {@link IndexedSet#mPrimaryIndex} and is
* recommended to be unique in consideration of performance.
* @param otherIndexDefinitions other index definitions to index the set
*/
@SafeVarargs
public IndexedSet(IndexDefinition primaryIndexDefinition,
IndexDefinition... otherIndexDefinitions) {
Iterable> indexDefinitions =
Iterables.concat(Collections.singletonList(primaryIndexDefinition),
Arrays.asList(otherIndexDefinitions));
// initialization
Map, FieldIndex> indices = new HashMap<>();
for (IndexDefinition indexDefinition : indexDefinitions) {
FieldIndex index = indexDefinition.isUnique()
? new UniqueFieldIndex<>(indexDefinition) : new NonUniqueFieldIndex<>(indexDefinition);
indices.put(indexDefinition, index);
}
mPrimaryIndex = indices.get(primaryIndexDefinition);
mIndices = Collections.unmodifiableMap(indices);
}
/**
* Removes all the entries in this set.
*
* This is an expensive operation, and concurrent adds are permitted.
*/
@Override
public void clear() {
for (T obj : mPrimaryIndex) {
remove(obj);
}
}
/**
* Adds an object o to the set if there is no other object o2 such that
* {@code (o == null ? o2 == null : o.equals(o2))}. If this set already contains the object, the
* call leaves the set unchanged.
*
* @param object the object to add
* @return true if this set did not already contain the specified element
*/
@Override
public boolean add(T object) {
Preconditions.checkNotNull(object, "object");
// Locking this object protects against removing the exact object, but does not protect against
// removing a distinct, but equivalent object.
synchronized (object) {
// add() will atomically add the object to the index, if it doesn't exist.
if (!mPrimaryIndex.add(object)) {
// This object is already added, possibly by another concurrent thread.
return false;
}
for (FieldIndex fieldIndex : mIndices.values()) {
fieldIndex.add(object);
}
}
return true;
}
/**
* Returns an iterator over the elements in this set. The elements are returned in no particular
* order. It is to implement {@link Iterable} so that users can foreach the {@link IndexedSet}
* directly.
*
* Note that the behavior of the iterator is unspecified if the underlying collection is
* modified while a thread is going through the iterator.
*
* @return an iterator over the elements in this {@link IndexedSet}
*/
@Override
public Iterator iterator() {
return new IndexedSetIterator();
}
/**
* Specialized iterator for {@link IndexedSet}.
*
* This is needed to support consistent removal from the set and the indices.
*/
private class IndexedSetIterator implements Iterator {
private final Iterator mSetIterator;
private T mObject;
public IndexedSetIterator() {
mSetIterator = mPrimaryIndex.iterator();
mObject = null;
}
@Override
public boolean hasNext() {
return mSetIterator.hasNext();
}
@Override
public T next() {
final T next = mSetIterator.next();
mObject = next;
return next;
}
@Override
public void remove() {
if (mObject != null) {
IndexedSet.this.remove(mObject);
mObject = null;
} else {
throw new IllegalStateException("next() was not called before remove()");
}
}
}
/**
* Whether there is an object with the specified index field value in the set.
*
* @param indexDefinition the field index definition
* @param value the field value
* @param the field type
* @return true if there is one such object, otherwise false
*/
public boolean contains(IndexDefinition indexDefinition, V value) {
FieldIndex index = (FieldIndex) mIndices.get(indexDefinition);
if (index == null) {
throw new IllegalStateException("the given index isn't defined for this IndexedSet");
}
return index.containsField(value);
}
/**
* Gets a subset of objects with the specified field value. If there is no object with the
* specified field value, a newly created empty set is returned.
*
* @param indexDefinition the field index definition
* @param value the field value to be satisfied
* @param the field type
* @return the set of objects or an empty set if no such object exists
*/
public Set getByField(IndexDefinition indexDefinition, V value) {
FieldIndex index = (FieldIndex) mIndices.get(indexDefinition);
if (index == null) {
throw new IllegalStateException("the given index isn't defined for this IndexedSet");
}
return index.getByField(value);
}
/**
* Gets the object from the set of objects with the specified field value.
*
* @param indexDefinition the field index definition
* @param value the field value
* @param the field type
* @return the object or null if there is no such object
*/
public T getFirstByField(IndexDefinition indexDefinition, V value) {
FieldIndex index = (FieldIndex) mIndices.get(indexDefinition);
if (index == null) {
throw new IllegalStateException("the given index isn't defined for this IndexedSet");
}
return index.getFirst(value);
}
/**
* Removes an object from the set.
*
* @param object the object to remove
* @return true if the object is in the set and removed successfully, otherwise false
*/
@Override
public boolean remove(Object object) {
if (object == null) {
return false;
}
// Locking this object protects against removing the exact object that might be in the
// process of being added, but does not protect against removing a distinct, but equivalent
// object.
synchronized (object) {
if (mPrimaryIndex.containsObject((T) object)) {
// This isn't technically typesafe. However, given that success is true, it's very unlikely
// that the object passed to remove is not of type .
@SuppressWarnings("unchecked")
T tObj = (T) object;
removeFromIndices(tObj);
return true;
} else {
return false;
}
}
}
/**
* Helper method that removes an object from the indices.
*
* @param object the object to be removed
*/
private void removeFromIndices(T object) {
for (FieldIndex fieldValue : mIndices.values()) {
fieldValue.remove(object);
}
}
/**
* Removes the subset of objects with the specified index field value.
*
* @param indexDefinition the field index
* @param value the field value
* @param the field type
* @return the number of objects removed
*/
public int removeByField(IndexDefinition indexDefinition, V value) {
Set toRemove = getByField(indexDefinition, value);
int removed = 0;
for (T o : toRemove) {
if (remove(o)) {
removed++;
}
}
return removed;
}
/**
* @return the number of objects in this indexed set
*/
@Override
public int size() {
return mPrimaryIndex.size();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy