com.persistit.PersistitMap Maven / Gradle / Ivy
Show all versions of akiban-persistit Show documentation
/**
* Copyright © 2005-2012 Akiban Technologies, Inc. All rights reserved.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* This program may also be available under different license terms.
* For more information, see www.akiban.com or contact [email protected].
*
* Contributors:
* Akiban Technologies, Inc.
*/
package com.persistit;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import com.persistit.exception.PersistitException;
/**
*
* A persistent java.util.SortedMap
over a Persistit database. Keys
* and values inserted into this map are serialized and stored within Persistit
* using the encoding methods of {@link Key} and {@link Value}.
*
*
* To construct a PersistitMap
you supply an {@link Exchange}. The
* keys inserted into this map are appended to the key within the {@link Tree}
* originally supplied by the Exchange
. For example, the code
*
*
*
* Exchange ex = new Exchange("demo", "composers", true);
* ex.append("My Favorite Johns");
* Map<String, String> map = new PersistitMap<String, String>(ex);
* map.put("Brahms", "Johannes");
* map.put("Bach", "Johann");
*
*
*
* is equivalent to
*
*
*
* Exchange ex = new Exchange("demo", "composers", true);
* ex.getValue().put("Johannes");
* ex.clear().append("My Favorite Johns", "Brahms").store();
* ex.getValue().put("Johann");
* ex.clear().append("My Favorite Johns", "Bach").store();
*
*
*
*
*
* By default any Iterator
s created by PersistitMap
's
* collection views implement fail-fast behavior, meaning that methods of
* the the Iterator
will throw a
* ConcurrentModificationException
if the backing store has changed
* after the Iterator
was created. However, in a large persistent
* database designed for concurrent access, it may be preferable to allow the
* Iterator
to function continuously even when the underlying
* database is changing. The {@link #setAllowConcurrentModification} method
* provides control over this behavior.
*
*
* PersistitMap departs from the general contract for SortedMap
* with respect to the {@link Comparable} interface. The ordering of items
* within the map is determined by the encoding of key values into their
* underlying serialized byte array representation (see {@link Key Key Ordering}
* ). Generally this ordering corresponds to the default Comparable
* implementation for each supported type. However, PersistitMap
* permits storage of key values that may not permit comparison under their
* compareTo
implementations, does not require key values to
* implement Comparable
, and ignores the ordering implemented by
* Comparable
. Similarly, PersistitMap
does not allow
* installation of a custom {@link java.util.Comparator Comparator}. To
* customize the ordering of key values in a PersistitMap
,
* implement and register a custom {@link com.persistit.encoding.KeyCoder
* KeyCoder}.
*
*
* Unlike other Map
implementations, the methods of this class
* are thread-safe. Each of the Map
methods is synchronized
* on this PersistitMap
instance, and each of the methods of any
* collection view Iterator
created by this Map is synchronized on
* that iterator. Thus this class enforces serialized access to its internal
* memory structures. However, for maximum concurrency an application that
* shares data among multiple threads should create a separate instance of
* PersistitMap for each thread backed by the same tree rather than sharing a
* single PersistitMap object. These PersistitMap
instances can
* concurrently modify and query the same collection of data.
*
*
* Unlike an in-memory implementation of SortedMap
, this
* implementation serializes and deserializes object values into and from byte
* arrays stored as Persistit key/value pairs. Thus the {@link #put} and
* {@link #remove} methods not only read the former value from the database, but
* also incur the overhead of deserializing it. In many cases applications do
* not use the former value, and therefore this computation is often
* unnecessary. For maximum performance an application can use the
* {@link #putFast} and {@link #removeFast} methods. These methods are analogous
* to {@link #put} and {@link #remove} but do not deserialize or return the
* former object value.
*
*
* The Iterator
implementations returned by the {@link #keySet},
* {@link #values()} and {@link #entrySet()} provide methods to set and access a
* {@link KeyFilter} for the iterator's traversal of keys in the map. With a
* filter in place, the iterator only returns keys, values or entries for
* records whose keys are selected by the filter.
*
*
* Note on generic types: compile-time type verification cannot check the
* contents of the data stored in the backing B-Tree. Therefore the types of Key
* and Value objects stored in the B-Tree are not guaranteed to match the
* expected types. For example, the following will compile correctly but throw a
* ClassCastException at runtime:
*
*
*
* Exchange ex = new Exchange("demo", "composers", true);
* PersistitMap<String, Integer> map1 = new PersistitMap<String, Integer>();
* map1.put("Adams", Integer.valueOf(1));
* PersistitMap<String, String> map2 = new PersistitMap<String, String>();
* String lastName = map2.get("Adams");
*
*
*
*
* Note that any {@link PersistitException} that may occur during execution of
* one of the SortedMap
methods is thrown within the unchecked
* wrapper class {@link PersistitMapException}. Applications using
* PersistitMap
should catch and handled
* PersistitMapException
.
*
*
* @version 1.0
*/
public class PersistitMap extends AbstractMap implements SortedMap {
private final Exchange _ex;
private long _sizeGeneration;
private int _size;
private boolean _allowConcurrentModification = false;
//
// These fields are used for subMaps.
//
private Key _fromKey;
private Key _toKey;
/**
* Each of these fields are initialized to contain an instance of the
* appropriate view the first time this view is requested. The views are
* stateless, so there's no reason to create more than one of each.
*/
private transient volatile Set _keySet = null;
private transient volatile Collection _values = null;
private transient volatile Set> _entrySet;
/**
* Construct a PersistitMap over a particular Exchange.
*
* @param ex
* A Exchange
that serves as the parent of the Map's
* keys. This constructor makes a copy of the
* Exchange
. The original Exchange is unchanged, and
* may be reused by the caller.
*/
public PersistitMap(final Exchange ex) {
_ex = new Exchange(ex);
_ex.append(Key.BEFORE);
_sizeGeneration = -1; // Unknown
}
/**
* Construct a PersistitMap over the range of keys from fromKey (inclusive)
* to toKey (exclusive)
*
* @param pm
* @param useFrom
* @param fromKey
* @param useTo
* @param toKey
*
* @throws IllegalArgumentException
* if fromKey is after toKey or if either fromKey or toKey is
* outside the range of the supplied base PersistitMap
*/
private PersistitMap(final PersistitMap pm, final boolean useFrom, final Object fromKey, final boolean useTo,
final Object toKey) {
_ex = new Exchange(pm._ex);
_sizeGeneration = -1; // Unknown
if (useFrom) {
final Key key = new Key(_ex.getKey());
try {
key.to(fromKey);
} catch (final UnsupportedOperationException use) {
throw new ClassCastException(fromKey != null ? fromKey.getClass().getName() : null);
}
if (pm._fromKey != null && pm._fromKey.compareTo(key) > 0 || pm._toKey != null
&& pm._toKey.compareTo(key) < 0) {
throw new IllegalArgumentException("Key " + fromKey + " is outside submap range");
}
_fromKey = key;
} else {
_fromKey = pm._fromKey;
}
if (useTo) {
final Key key = new Key(_ex.getKey());
try {
key.to(toKey);
} catch (final UnsupportedOperationException use) {
throw new ClassCastException(toKey != null ? toKey.getClass().getName() : null);
}
if (pm._fromKey != null && pm._fromKey.compareTo(key) > 0 || pm._toKey != null
&& pm._toKey.compareTo(key) < 0) {
throw new IllegalArgumentException("Key " + toKey + " is outside submap range");
}
_toKey = key;
} else {
_toKey = pm._toKey;
}
}
/**
* Indicate whether a new Iterator
s should throw a
* ConcurrentModificationException
in the event the Persistit
* Tree backing this Map changes.
*
* @return true
if concurrent modifications are currently
* allowed, otherwise false
.
*/
public boolean isAllowConcurrentModification() {
return _allowConcurrentModification;
}
/**
* Control whether iterators of the collection views of this Map will throw
* ConcurrentModificationException
in the event the underlying
* physical database changes. This property is used in constructing an new
* Iterator
on a collection view. Changing this property after
* an Iterator
has been constructed does not change that
* Iterator
's behavior.
*
* @param allow
* Specify true to allow changes in the backing Tree.
* Specify false to cause newly constructed
* Iterator
s to throw a
* ConcurrentModificationException
if the Persistit
* Tree backing this Map changes.
*/
public void setAllowConcurrentModification(final boolean allow) {
_allowConcurrentModification = allow;
}
private void toLeftEdge() {
if (_fromKey == null) {
_ex.to(Key.BEFORE);
} else {
_fromKey.copyTo(_ex.getKey());
}
}
private void toRightEdge() {
if (_toKey == null) {
_ex.to(Key.AFTER);
} else {
_toKey.copyTo(_ex.getKey());
}
}
private boolean toKey(final Object key) {
try {
_ex.to(key);
if (_fromKey != null && _fromKey.compareTo(_ex.getKey()) > 0)
return false;
if (_toKey != null && _toKey.compareTo(_ex.getKey()) < 0)
return false;
return true;
} catch (final UnsupportedOperationException uoe) {
throw new ClassCastException(key != null ? key.getClass().getName() : null);
}
}
// Query Operations
/**
* Return the number of key-value mappings in this map. In the unlikely
* event the map contains more than Integer.MAX_VALUE
elements,
* the value returned is Integer.MAX_VALUE
.
*
* This implementation enumerates all the members of the Map, which for a
* large database could be time-consuming.
*
* @return the number of key-value mappings in this map.
*/
@Override
public synchronized int size() {
if (_ex.getChangeCount() == _sizeGeneration) {
return _size;
}
int size = 0;
try {
toLeftEdge();
while (_ex.traverse(size == 0 ? Key.GTEQ : Key.GT, false, 0)) {
if (_toKey != null && _ex.getKey().compareTo(_toKey) >= 0) {
break;
}
//
// The following code fails (!) in HotSpot if you remove the -1.
// If you remove the -1, then the value of size does not get
// incremented, and the iteration through traverse above
// uses GTEQ rather than GT in an infinite loop.
//
if (size < Integer.MAX_VALUE - 1) {
size++;
}
}
_size = size;
_sizeGeneration = _ex.getChangeCount();
return _size;
} catch (final PersistitException de) {
throw new PersistitMapException(de);
}
}
/**
* Return true
if this map contains no key-value mappings.
*
* This implementation is relatively efficient because it enumerates at most
* one child node of the Exchange to determine whether there are any
* children.
*
* @return true
if this map contains no key-value mappings.
*/
@Override
public synchronized boolean isEmpty() {
if (_ex.getChangeCount() == _sizeGeneration) {
return _size == 0;
}
try {
toLeftEdge();
if (_ex.traverse(Key.GTEQ, false)) {
if (_toKey != null && _ex.getKey().compareTo(_toKey) >= 0) {
_size = 0;
_sizeGeneration = _ex.getChangeCount();
return true;
} else {
return false;
}
} else {
return true;
}
} catch (final PersistitException de) {
throw new PersistitMapException(de);
}
}
/**
* Determine whether this map maps one or more keys to this value. More
* formally, returns true
if and only if this map contains at
* least one mapping to a value v
such that
*
*
* (value==null ? v==null : value.equals(v))
*
*
* This implementation implements a linear search across the child nodes of
* the Exchange, and can therefore be extremely time-consuming and
* resource-intensive for large databases.
*
* @param value
* value whose presence in this map is to be tested.
*
* @return true
if this map maps one or more keys to this
* value.
*/
@Override
public synchronized boolean containsValue(final Object value) {
final Value lookupValue = new Value(_ex.getPersistitInstance());
try {
lookupValue.put(value);
toLeftEdge();
while (_ex.next()) {
if (_toKey != null && _ex.getKey().compareTo(_toKey) >= 0) {
return false;
} else if (lookupValue.equals(_ex.getValue())) {
return true;
}
}
return false;
} catch (final Exception exception) {
throw new PersistitMapException(exception);
}
}
/**
* Determine whether this map contains a mapping for the specified key. This
* implementation performs a direct B-Tree lookup and is designed to be
* fast.
*
* @param key
* key whose presence in this map is to be tested.
* @return true
if this map contains a mapping for the
* specified key.
*
* @throws NullPointerException
* key is null
and this map does not not permit
* null
keys.
*/
@Override
public synchronized boolean containsKey(final Object key) {
try {
if (!toKey(key)) {
return false;
}
return _ex.isValueDefined();
} catch (final PersistitException de) {
throw new PersistitMapException(de);
}
}
/**
*
* Return the value to which this map maps the specified key. Returns
* null
if the map contains no mapping for this key. A return
* value of null
does not necessarily indicate that the
* map contains no mapping for the key; it's also possible that the map
* explicitly maps the key to null
. The containsKey operation
* may be used to distinguish these two cases.
*
*
* This implementation iterates over entrySet()
searching for
* an entry with the specified key. If such an entry is found, the entry's
* value is returned. If the iteration terminates without finding such an
* entry, null
is returned. Note that this implementation
* requires linear time in the size of the map; many implementations will
* override this method.
*
*
* @param key
* key whose associated value is to be returned.
* @return the value to which this map maps the specified key.
*
* @throws NullPointerException
* if the key is null
and this map does not not
* permit null
keys.
*
* @see #containsKey(Object)
*/
@Override
public synchronized V get(final Object key) {
try {
if (!toKey(key))
return null;
_ex.fetch();
if (!_ex.getValue().isDefined()) {
return null;
}
@SuppressWarnings("unchecked")
final V value = (V) _ex.getValue().get();
return value;
} catch (final PersistitException de) {
throw new PersistitMapException(de);
}
}
// Modification Operations
/**
*
* Stores the specified value with the specified key in this map. If the map
* previously contained a mapping for this key, the old value is replaced.
* This method returns the old value, if present, or null
if
* there was no former value. An application that does not require the
* former value can use {@link #putFast} to avoid incurring the cost of
* deserializing that value.
*
*
* @param key
* key with which the specified value is to be associated.
* @param value
* value to be associated with the specified key.
*
* @return previous value associated with specified key, or
* null
if there was no mapping for key. (A
* null
return can also indicate that the map
* previously associated null
with the specified key,
* if the implementation supports null
values.)
*
* @throws ClassCastException
* if the class of the specified key or value prevents it from
* being stored in this map.
*
* @throws IllegalArgumentException
* if some aspect of this key or value * prevents it from being
* stored in this map.
*
*/
@Override
@SuppressWarnings("unchecked")
public synchronized V put(final K key, final V value) {
try {
if (!toKey(key)) {
throw new IllegalArgumentException("Key " + key + " is out of submap range");
}
final long changeCount = _sizeGeneration;
Object result = null;
_ex.getValue().put(value);
_ex.fetchAndStore();
if (_ex.getValue().isDefined()) {
result = _ex.getValue().get();
} else {
adjustSize(changeCount, 1);
}
return (V) result;
} catch (final PersistitException de) {
throw new PersistitMapException(de);
}
}
/**
*
* Associates the specified value with the specified key in this map. If the
* map previously contained a mapping for this key, the old value is
* replaced.
*
*
* This method differs from {@link #put} by not returning the previous
* value. In order to return the previous value, put
must
* deserialize it, which can be costly. Applications that don't need that
* value can benefit from calling putFast
instead of
* put
.
*
* @param key
* key with which the specified value is to be associated.
*
* @param value
* value to be associated with the specified key.
*
* @throws ClassCastException
* if the class of the specified key or value prevents it from
* being stored in this map.
*
* @throws IllegalArgumentException
* if some aspect of this key or value * prevents it from being
* stored in this map.
*
*/
public synchronized void putFast(final Object key, final Object value) {
try {
if (!toKey(key)) {
throw new IllegalArgumentException("Key " + key + " is out of submap range");
}
_ex.getValue().put(value);
_ex.store();
_sizeGeneration = -1;
} catch (final PersistitException de) {
throw new PersistitMapException(de);
}
}
/**
*
* Removes the mapping for this key from this map if present, and returns
* the former value. Applications that do not require the former value can
* use {@link #removeFast} to improve performance.
*
*
* @param key
* key whose mapping is to be removed from the map.
*
* @return previous value associated with specified key, or
* null
if there was no entry for key. (A
* null
return can also indicate that the map
* previously associated null
with the specified key.)
*
*/
@Override
@SuppressWarnings("unchecked")
public synchronized V remove(final Object key) {
try {
if (!toKey(key)) {
throw new IllegalArgumentException("Key " + key + " is out of submap range");
}
final long changeCount = _sizeGeneration;
final boolean removed = _ex.fetchAndRemove();
Object result = null;
if (removed) {
adjustSize(changeCount, -1);
if (_ex.getValue().isDefined()) {
result = _ex.getValue().get();
}
}
return (V) result;
} catch (final PersistitException de) {
throw new PersistitMapException(de);
}
}
/**
*
* Removes the mapping for this key from this map if present. Unlike
* {@link #remove}, this method does not return the former value, and
* therefore does not incur the cost of deserializing it.
*
*
* @param key
* key whose mapping is to be removed from the map.
*
*/
public synchronized void removeFast(final Object key) {
try {
if (!toKey(key)) {
throw new IllegalArgumentException("Key " + key + " is out of submap range");
}
final long changeCount = _sizeGeneration;
final boolean removed = _ex.remove();
if (removed) {
adjustSize(changeCount, -1);
}
} catch (final PersistitException de) {
throw new PersistitMapException(de);
}
}
/**
* Called by operations that add or remove keys. This method attempts to
* track and update the current Map size, but if there are untracked
* modifications, then resets _sizeGeneration to -1, meaning that on the
* next call to size(), the entire collection will need to be counted.
*
* @param changeCount
* The changeCount of the backing tree prior to the operation
* that added or removed a key
* @param delta
* 1 if a key was added, -1 if removed.
*/
private void adjustSize(final long changeCount, final int delta) {
if (_sizeGeneration >= 0) {
if (_ex.getChangeCount() == changeCount + 1) {
_size += delta;
_sizeGeneration = _ex.getChangeCount();
} else {
_sizeGeneration = -1;
}
}
}
// Bulk Operations
/**
* Copies all of the mappings from the specified map to this map. These
* mappings will replace any mappings that this map had for any of the keys
* currently in the specified map.
*
*
* This implementation iterates over the specified map's
* entrySet()
collection, and calls this map's put
* operation once for each entry returned by the iteration.
*
*
* @param t
* mappings to be stored in this map.
*
* @throws ClassCastException
* if the class of a key or value in the specified map prevents
* it from being stored in this map.
*
* @throws IllegalArgumentException
* if some aspect of a key or value in the specified map
* prevents it from being stored in this map.
*
* @throws NullPointerException
* the specified map is null
.
*/
@Override
public synchronized void putAll(final Map t) {
for (final Iterator iterator = t.entrySet().iterator(); iterator.hasNext();) {
final Map.Entry entry = (Map.Entry) iterator.next();
putFast(entry.getKey(), entry.getValue());
}
}
/**
* Removes all mappings from this map.
*
*
* This implementation calls entrySet().clear()
.
*/
@Override
public synchronized void clear() {
try {
toLeftEdge();
final Key key = new Key(_ex.getKey());
if (_toKey == null) {
key.to(Key.AFTER);
} else {
_toKey.copyTo(key);
}
_ex.removeKeyRange(_ex.getKey(), key);
_size = 0;
_sizeGeneration = _ex.getChangeCount();
} catch (final PersistitException de) {
throw new PersistitMapException(de);
}
}
/**
* Unchecked wrapper for {@link PersistitException}s that may be thrown by
* methods of {@link SortedMap}.
*/
public static class PersistitMapException extends RuntimeException {
private static final long serialVersionUID = 7257161738800744724L;
Exception _exception;
PersistitMapException(final Exception ee) {
_exception = ee;
}
@Override
public Throwable getCause() {
return _exception;
}
}
/**
* Implement java.util.Map.Entry
using a backing Persistit
* tree.
*/
public class ExchangeEntry implements Map.Entry {
private final K _key;
private final V _value;
private final ExchangeIterator> _iterator;
private ExchangeEntry(final K key, final V value, final ExchangeIterator> iterator) {
_key = key;
_value = value;
_iterator = iterator;
}
/**
* Returns the key field of this ExchangeEntry
.
*
* @return The key
*/
@Override
public K getKey() {
return _key;
}
/**
* Returns the value field of this ExchangeEntry
. This is
* the value that was present in the database at the time an
* ExchangeIterator
created this entry; because other
* threads may concurrently modify the database, the stored value may
* have changed.
*
* @return The value associated with this ExchangeEntry
*/
@Override
public V getValue() {
return _value;
}
/**
* Modifies the value field of this Entry and of the underlying
* Persistit database record associated with the key for this Entry.
* Returns the former value associated with this entry. This is the
* value that was present in the database at the time an
* ExchangeIterator
created this entry; because other
* threads may concurrently modify the database, the stored value may
* have changed.
*
* @param value
* The new value.
*
* @return The value formerly associated with this
* ExchangeEntry
.
*/
@Override
@SuppressWarnings("unchecked")
public synchronized V setValue(final V value) {
Object result = null;
try {
_ex.to(_key);
_ex.getValue().put(value);
long changeCount = _ex.getChangeCount();
_ex.fetchAndStore();
if (!_ex.getValue().isDefined())
changeCount++;
_iterator._changeCount = changeCount;
result = _value;
} catch (final PersistitException pe) {
throw new PersistitMapException(pe);
}
return (V) result;
}
/**
* Implements the contract for equals
. Two
* ExchangeEntr
instances are equal if their key and value
* fields are equal.
*/
@Override
public boolean equals(final Object o) {
if (o instanceof Map.Entry) {
final Map.Entry entry = (Map.Entry) o;
return (entry.getKey() == null ? _key == null : entry.getKey().equals(_key))
&& (entry.getValue() == null ? _value == null : entry.getValue().equals(_value));
} else
return false;
}
/**
* Implements the contract for hashCode
. The hash code is
* the XOR of the hashCodes of the key and value fields.
*/
@Override
public int hashCode() {
return (_key == null ? 0 : _key.hashCode()) ^ (_value == null ? 0 : _value.hashCode());
}
}
private final class ExchangeValueIterator extends ExchangeIterator {
protected ExchangeValueIterator(final PersistitMap pm, final boolean allowConcurrentModification) {
super(pm, allowConcurrentModification);
}
@Override
public V next() {
nextEntry();
return (V) _iteratorExchange.getValue().get();
}
}
private final class ExchangeKeyIterator extends ExchangeIterator {
protected ExchangeKeyIterator(final PersistitMap pm, final boolean allowConcurrentModification) {
super(pm, allowConcurrentModification);
}
@Override
public K next() {
nextEntry();
_iteratorExchange.getKey().indexTo(-1);
return (K) _iteratorExchange.getKey().decode();
}
}
private final class ExchangeEntryIterator extends ExchangeIterator> {
protected ExchangeEntryIterator(final PersistitMap pm, final boolean allowConcurrentModification) {
super(pm, allowConcurrentModification);
}
@Override
public Map.Entry next() {
nextEntry();
_iteratorExchange.getKey().indexTo(-1);
return (new ExchangeEntry(_iteratorExchange.getKey().decode(), _iteratorExchange.getValue().get(), this));
}
}
/**
* Implements java.util.Iterator
using an underlying Persistit
* tree as the source of keys and values to be traversed. In addition to the
* inherited methods of java.util.Iterator
, this class also
* implements a facility to apply a {@link KeyFilter} to restrict the set of
* keys over it will iterate. See
* {@link PersistitMap.ExchangeIterator#setFilterTerm(KeyFilter.Term)} and
* {@link PersistitMap.ExchangeIterator#getKeyFilter()} for details.
*/
public abstract class ExchangeIterator implements Iterator {
PersistitMap _pm;
Exchange _iteratorExchange;
boolean _allowCM;
Key.Direction _direction = Key.GT;
long _changeCount;
boolean _okToRemove;
KeyFilter _keyFilter;
boolean _traversed;
boolean _hasNext;
Key _trailingKey;
ExchangeIterator(final PersistitMap pm, final boolean allowConcurrentModification) {
_pm = pm;
_iteratorExchange = new Exchange(_ex);
_iteratorExchange.to(Key.BEFORE);
_trailingKey = new Key(_iteratorExchange.getKey());
_traversed = false;
if (_fromKey != null) {
_fromKey.copyTo(_iteratorExchange.getKey());
}
_changeCount = _iteratorExchange.getChangeCount();
_allowCM = allowConcurrentModification;
}
/**
* Set the desired traversal direction of this Iterator. Values
* {@link com.persistit.Key#GT} (ascending) and
* {@link com.persistit.Key#LT} (descending) are allowed.
*
* @param direction
* The direction
*/
public void setDirection(final Key.Direction direction) {
if (direction == Key.GT || direction == Key.LT) {
_direction = direction;
} else {
throw new IllegalArgumentException("Must be GT or LT");
}
}
/**
* @return The traversal direction (GT for increasing keys, LT for
* decreasing keys) of this Iterator.
*/
public Key.Direction getDirection() {
return _direction;
}
/**
* Returns the KeyFilter
for this iterator, or
* null
if there is none.
*
* @return A copy of the current KeyFilter
, or
* null
if there is none.
*/
public KeyFilter getKeyFilter() {
return _keyFilter;
}
/**
*
* Set up a KeyFilter
for this Iterator
. When
* set, the {@link #hasNext} and {@link #next} operations return only
* those keys, values or entries for records having keys selected by the
* supplied {@link KeyFilter.Term}. Note that setting the filter affects
* subsequent hasNext
or next
operations, but
* does not modify the current key, nor does it affect the behavior of
* {@link #remove} or
* {@link com.persistit.PersistitMap.ExchangeEntry#setValue} for the
* current entry.
*
*
* @param term
* If null
, clears the KeyFilter
* for this iterator. Otherwise, sets up a filter based on
* the supplied Term
.
*/
public void setFilterTerm(final KeyFilter.Term term) {
if (term == null) {
_keyFilter = null;
} else {
final Key key = _iteratorExchange.getKey();
final Key spareKey = _iteratorExchange.getAuxiliaryKey1();
key.copyTo(spareKey);
final int depth = spareKey.getDepth();
spareKey.cut();
_keyFilter = new KeyFilter(spareKey).append(term).limit(depth, depth);
}
}
@Override
public synchronized boolean hasNext() {
if (!_traversed) {
_hasNext = traverse();
_traversed = true;
}
return _hasNext;
}
protected synchronized void nextEntry() {
if (!_traversed && !traverse() || _traversed && !_hasNext) {
throw new NoSuchElementException();
}
if (!_allowCM && _iteratorExchange.getChangeCount() != _changeCount) {
_changeCount = _iteratorExchange.getChangeCount();
throw new ConcurrentModificationException();
}
_traversed = false;
_okToRemove = true;
}
@Override
public synchronized void remove() {
if (!_okToRemove) {
throw new IllegalStateException();
}
if (!_allowCM && _iteratorExchange.getChangeCount() != _changeCount) {
_changeCount = _iteratorExchange.getChangeCount();
throw new ConcurrentModificationException();
}
boolean removed = false;
final long changeCount = _iteratorExchange.getChangeCount();
if (_traversed) {
_trailingKey.copyTo(_iteratorExchange.getKey());
_traversed = false;
}
try {
removed = _iteratorExchange.remove();
} catch (final PersistitException e) {
throw new PersistitMapException(e);
}
if (!removed) {
throw new NoSuchElementException();
} else {
_pm.adjustSize(changeCount, -1);
if (!_allowCM && _iteratorExchange.getChangeCount() != changeCount + 1) {
_changeCount = _iteratorExchange.getChangeCount();
throw new ConcurrentModificationException();
}
}
_changeCount = _iteratorExchange.getChangeCount();
_okToRemove = false;
}
private boolean traverse() {
try {
// Save the current Key before traversing so that in case
// user calls next(), hasNext(), remove() in that sequence,
// the original key to be removed is still available.
_iteratorExchange.getKey().copyTo(_trailingKey);
final boolean result = _iteratorExchange.traverse(_direction, _keyFilter, Integer.MAX_VALUE);
if (!result || _toKey == null) {
return result;
} else {
return _toKey.compareTo(_iteratorExchange.getKey()) <= 0;
}
} catch (final PersistitException de) {
throw new PersistitMapException(de);
}
}
}
// Views
/**
* Returns a Set view of the keys contained in this map. The Set is backed
* by the map, so changes to the map are reflected in the Set, and
* vice-versa. (If the map is modified while an iteration over the Set is in
* progress, the results of the iteration are undefined.) The Set supports
* element removal, which removes the corresponding entry from the map, via
* the Iterator.remove, Set.remove, removeAll retainAll, and clear
* operations. It does not support the add or addAll operations.
*
*
* This implementation returns a Set that subclasses AbstractSet. The
* subclass's iterator method returns a "wrapper object" over this map's
* entrySet() iterator. The size method delegates to this map's size method
* and the contains method delegates to this map's containsKey method.
*
*
* The Set is created the first time this method is called, and returned in
* response to all subsequent calls. No synchronization is performed, so
* there is a slight chance that multiple calls to this method will not all
* return the same Set.
*
* @return a Set view of the keys contained in this map.
*/
@Override
public Set keySet() {
if (_keySet == null) {
_keySet = new AbstractSet() {
@Override
public Iterator iterator() {
return new ExchangeKeyIterator(PersistitMap.this, _allowConcurrentModification);
}
@Override
public int size() {
return PersistitMap.this.size();
}
@Override
public boolean contains(final Object k) {
return PersistitMap.this.containsKey(k);
}
};
}
return _keySet;
}
/**
* Returns a collection view of the values contained in this map. The
* collection is backed by the map, so changes to the map are reflected in
* the collection, and vice-versa. (If the map is modified while an
* iteration over the collection is in progress, the results of the
* iteration are undefined.) The collection supports element removal, which
* removes the corresponding entry from the map, via the
* Iterator.remove
, Collection.remove
,
* removeAll
, retainAll
and clear
* operations. It does not support the add
or
* addAll
operations.
*
*
* This implementation returns a collection that subclasses abstract
* collection. The subclass's iterator method returns a "wrapper object"
* over this map's entrySet()
iterator. The size method
* delegates to this map's size method and the contains method delegates to
* this map's containsValue method.
*
*
* The collection is created the first time this method is called, and
* returned in response to all subsequent calls. No synchronization is
* performed, so there is a slight chance that multiple calls to this method
* will not all return the same Collection.
*
* @return a collection view of the values contained in this map.
*/
@Override
public Collection values() {
if (_values == null) {
_values = new AbstractSet() {
@Override
public Iterator iterator() {
return new ExchangeValueIterator(PersistitMap.this, _allowConcurrentModification);
}
@Override
public int size() {
return PersistitMap.this.size();
}
@Override
public boolean contains(final Object v) {
return PersistitMap.this.containsValue(v);
}
};
}
return _values;
}
/**
* Returns a Set of Map.Entry values corresponding with key-value pairs in
* the backing B-Tree.
*
* @return a set view of the mappings contained in this map.
*/
@Override
public Set> entrySet() {
if (_entrySet == null) {
_entrySet = new AbstractSet>() {
@Override
public Iterator> iterator() {
return new ExchangeEntryIterator(PersistitMap.this, _allowConcurrentModification);
}
@Override
public int size() {
return PersistitMap.this.size();
}
@Override
public boolean contains(final Object v) {
return PersistitMap.this.containsValue(v);
}
};
}
return _entrySet;
}
/**
* Returns null because PersistitMap always uses Persistit's natural
* ordering and no other Comparator is possible.
*
* @return null
*/
@Override
public Comparator comparator() {
return null;
}
/**
* Returns a view of the portion of this PersistitMap
whose
* keys range from fromKey
, inclusive, to toKey
,
* exclusive. The view is another PersistitMap
instance backed
* by this one; any change to the collection made by either by either
* instance is reflected in the other.
*
* @param fromKey
* low endpoint (inclusive) of the subMap.
*
* @param toKey
* high endpoint (exclusive) of the subMap.
*
* @return a view of the specified range within this sorted map.
*/
@Override
public SortedMap subMap(final Object fromKey, final Object toKey) {
return new PersistitMap(this, true, fromKey, true, toKey);
}
/**
* Returns a view of the portion of this PersistitMap
whose
* keys are strictly less than toKey
. The view is another
* PersistitMap
instance backed by this one; any change to the
* collection made by either by either instance is reflected in the other.
*
* @param toKey
* high endpoint (exclusive) of the subMap.
*
* @return a view of the specified range within this sorted map.
*/
@Override
public SortedMap headMap(final Object toKey) {
return new PersistitMap(this, false, null, true, toKey);
}
/**
* Returns a view of the portion of this PersistitMap
whose
* keys are greater than or equal to than fromKey
. The view is
* another PersistitMap
instance backed by this one; any change
* to the collection made by either by either instance is reflected in the
* other.
*
* @param fromKey
* low endpoint (inclusive) of the subMap.
*
* @return a view of the specified range within this sorted map.
*/
@Override
public SortedMap tailMap(final Object fromKey) {
return new PersistitMap(this, true, fromKey, false, null);
}
/**
* Returns the first (lowest) key currently in this
* PersistitMap
determined by the key ordering specification.
*
* @return the first (lowest) key currently in the backing store for this
* PersistitMap
.
*
* @throws NoSuchElementException
* if this map is empty.
*/
@Override
public K firstKey() {
toLeftEdge();
try {
if (_ex.traverse(_fromKey == null ? Key.GT : Key.GTEQ, false, -1)
&& (_toKey == null || _toKey.compareTo(_ex.getKey()) > 0)) {
return (K) _ex.getKey().decode();
} else
throw new NoSuchElementException();
} catch (final PersistitException de) {
throw new PersistitMapException(de);
}
}
/**
* Returns the last (highest) key currently in this
* PersistitMap
as determined by the key ordering specification.
*
* @return the last (highest) key currently in the backing store for this
* PersistitMap
.
*
* @throws NoSuchElementException
* if this map is empty.
*/
@Override
public K lastKey() {
toRightEdge();
try {
if (_ex.traverse(Key.LT, false, -1) && (_fromKey == null || _fromKey.compareTo(_ex.getKey()) <= 0)) {
return (K) _ex.getKey().decode();
} else
throw new NoSuchElementException();
} catch (final PersistitException de) {
throw new PersistitMapException(de);
}
}
// Comparison and hashing
/**
* If the supplied object is also a Map, then this method determines whether
* the supplied map and this map contain the same entries. This is
* implemented by testing set equality between the entrySets of the two
* maps. Generally this operation is not useful on a backing database that
* is large and/or changing is discouraged in most cases.
*
* @param o
* object to be compared for equality with this map.
* @return true
if the specified object is equal to this map.
*/
@Override
public boolean equals(final Object o) {
return super.equals(o);
}
/**
* Returns the hash code value for this map. The hash code is the sum of the
* hash codes of each Map.Entry value in the entry set. The hash code value
* is generally not useful on a backing database that is large and/or
* changing, and therefore use of this method is discouraged in most cases.
*
* @return the hash code value for this map.
* @see java.util.Map.Entry#hashCode()
* @see Object#hashCode()
* @see Object#equals(Object)
* @see Set#equals(Object)
*/
@Override
public int hashCode() {
return super.hashCode();
}
/**
* Returns a string representation of this map. The string representation
* consists of a list of key-value mappings in the order returned by the
* map's entrySet
view's iterator, enclosed in braces (
* "{}"
). Adjacent mappings are separated by the characters
* ", "
(comma and space). Each key-value mapping is rendered
* as the key followed by an equals sign ("="
) followed by the
* associated value. Keys and values are converted to strings as by
* String.valueOf(Object)
.
*
*
* @return a String representation of this map.
*/
@Override
public String toString() {
return "PersistitMap(" + _ex + ")";
}
/**
* Returns a shallow copy of this AbstractMap
instance: the
* keys and values themselves are not cloned.
*
* @return a shallow copy of this map.
*/
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}