All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jsimpledb.kv.util.AbstractKVNavigableMap Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package org.jsimpledb.kv.util;

import com.google.common.base.Preconditions;

import java.util.AbstractMap;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Set;

import org.jsimpledb.kv.KVPair;
import org.jsimpledb.kv.KVStore;
import org.jsimpledb.kv.KeyFilter;
import org.jsimpledb.kv.KeyFilterUtil;
import org.jsimpledb.kv.KeyRange;
import org.jsimpledb.util.AbstractIterationSet;
import org.jsimpledb.util.AbstractNavigableMap;
import org.jsimpledb.util.Bounds;
import org.jsimpledb.util.ByteReader;
import org.jsimpledb.util.ByteUtil;
import org.jsimpledb.util.ByteWriter;

/**
 * {@link java.util.NavigableMap} support superclass for maps backed by keys and values encoded as {@code byte[]}
 * arrays in a {@link KVStore}, and whose key sort order is consistent with the {@code byte[]} key encoding.
 * There must be an equivalence between map keys and {@code byte[]} key encodings (i.e., there must be
 * only one valid encoding per map key).
 *
 * 

Subclass Methods

* *

* Subclasses must implement the {@linkplain #encodeKey encodeKey()}, {@linkplain #decodeKey decodeKey()}, * and {@linkplain #decodeValue decodeValue()} methods to convert keys and value to/from {@link KVStore} keys and values, * and {@link #createSubMap(boolean, KeyRange, KeyFilter, Bounds) createSubMap()} * to allow creating reversed and restricted range sub-maps. * *

* Subclasses must also implement {@link #comparator comparator()}, and the resulting sort order must be consistent with * the sort order of the encoded {@code byte[]} keys (possibly {@link #reversed}). * *

* This class provides a read-only implementation; for a mutable implementation, subclasses should also implement * {@link #put put()}, {@link #remove remove()}, and {@link #clear}; note, these methods must verify the key * {@link #isVisible isVisible()} before making any changes. * *

* Additional subclass notes: *

    *
  • {@link #navigableKeySet} returns a {@link Set} for which: *
      *
    • The {@link Set#add add()} method is not supported.
    • *
    • The {@link Set#remove remove()} method and the {@link Iterator#remove remove()} method of the * associated {@link Iterator} delegate to this instance's {@link #remove remove()} method.
    • *
    • The {@link Set#clear clear()} method delegates to this instance's {@link #clear} method.
    • *
    *
  • {@link #entrySet} returns a {@link Set} for which: *
      *
    • The {@link Set#add add()} method is not supported.
    • *
    • The {@link Set#remove remove()} method and the {@link Iterator#remove remove()} method of the * associated {@link Iterator} delegate to this instance's {@link #remove remove()} method (but * only when the {@link java.util.Map.Entry} is still contained in the map). *
    • The {@link java.util.Map.Entry} elements' {@link java.util.Map.Entry#setValue setValue()} method delegates to * this instance's {@link #put put()} method (but only when the {@link java.util.Map.Entry} is still contained in the map). *
    • The {@link Set#clear clear()} method delegates to this instance's {@link #clear} method.
    • *
    *
* *

Prefix Mode

* *

* Instances support "prefix mode" where the {@code byte[]} keys may have arbitrary trailing garbage, which is ignored, * and so by definition no key can be a prefix of any other key. The length of the prefix is determined implicitly by the * number of bytes produced by {@link #encodeKey encodeKey()} or consumed by {@link #decodeKey decodeKey()}. * When not in prefix mode, {@link #decodeKey decodeKey()} must consume the entire key to preserve correct semantics. * *

Key Restrictions

* *

* Instances are configured with an (optional) {@link KeyRange}; when {@linkplain #bounds range restriction} is in * effect, this key range corresponds to the bounds. * *

* Instances also support filtering visible keys using a {@link KeyFilter}; see {@link #filterKeys filterKeys()}. * To be {@linkplain #isVisible} in the map, keys must both be in the {@link KeyRange} and pass the {@link KeyFilter}. * *

Concurrent Modifications

* *

* This implementation never throws {@link java.util.ConcurrentModificationException}; instead, iterators always * see the most up-to-date state of the associated {@link KVStore}. * * @see AbstractKVNavigableSet * @param key type * @param value type */ @SuppressWarnings("serial") public abstract class AbstractKVNavigableMap extends AbstractNavigableMap { /** * The underlying {@link KVStore}. */ protected final KVStore kv; /** * Whether we are in "prefix" mode. */ protected final boolean prefixMode; /** * Whether the ordering of this instance is reversed. */ protected final boolean reversed; /** * Key range, or null for the entire range. */ protected final KeyRange keyRange; /** * Key filter, or null if all keys in the range should be visible. */ protected final KeyFilter keyFilter; // Constructors /** * Convenience constructor for when there are no range restrictions. * * @param kv underlying {@link KVStore} * @param prefixMode whether to allow keys to have trailing garbage * @throws IllegalArgumentException if {@code kv} is null */ protected AbstractKVNavigableMap(KVStore kv, boolean prefixMode) { this(kv, prefixMode, (KeyRange)null); } /** * Convenience constructor for when the range of visible {@link KVStore} keys is all keys sharing a given {@code byte[]} prefix. * * @param kv underlying {@link KVStore} * @param prefixMode whether to allow keys to have trailing garbage * @param prefix prefix defining minimum and maximum keys * @throws IllegalArgumentException if {@code kv} is null * @throws IllegalArgumentException if {@code prefix} is null or empty */ protected AbstractKVNavigableMap(KVStore kv, boolean prefixMode, byte[] prefix) { this(kv, prefixMode, KeyRange.forPrefix(prefix)); } /** * Primary constructor. * * @param kv underlying {@link KVStore} * @param prefixMode whether to allow keys to have trailing garbage * @param keyRange key range restriction, or null for none * @throws IllegalArgumentException if {@code kv} is null */ protected AbstractKVNavigableMap(KVStore kv, boolean prefixMode, KeyRange keyRange) { this(kv, prefixMode, false, keyRange, null, new Bounds<>()); } /** * Internal constructor. Used for creating sub-maps and reversed views. * *

* Note: if {@code bounds} are set, then {@code keyRange} must exclude all keys outside of those bounds. * * @param kv underlying {@link KVStore} * @param prefixMode whether to allow keys to have trailing garbage * @param reversed whether ordering is reversed (implies {@code bounds} are also inverted, but not {@code keyRange}) * @param keyRange key range restriction, or null for none * @param keyFilter key filter, or null for none * @param bounds range restriction * @throws IllegalArgumentException if {@code kv} or {@code bounds} is null */ protected AbstractKVNavigableMap(KVStore kv, boolean prefixMode, boolean reversed, KeyRange keyRange, KeyFilter keyFilter, Bounds bounds) { super(bounds); Preconditions.checkArgument(kv != null, "null kv"); this.kv = kv; this.prefixMode = prefixMode; this.reversed = reversed; this.keyRange = keyRange; this.keyFilter = keyFilter; } @Override public V get(Object obj) { // Encode key and check visibility final byte[] key = this.encodeVisibleKey(obj, false); if (key == null) return null; // Find key, or some longer key with the same prefix in prefix mode final KVPair pair; if (this.prefixMode) { byte[] maxKey; try { maxKey = ByteUtil.getKeyAfterPrefix(key); } catch (IllegalArgumentException e) { maxKey = null; } if ((pair = this.kv.getAtLeast(key, maxKey)) == null) return null; assert ByteUtil.isPrefixOf(key, pair.getKey()); } else { final byte[] value = this.kv.get(key); if (value == null) return null; pair = new KVPair(key, value); } // Decode value return this.decodeValue(pair); } @Override public Set> entrySet() { return this.new EntrySet(); } @Override public NavigableSet navigableKeySet() { return new KeySet(); } /** * Create a view of this instance with additional filtering applied to the underlying {@code byte[]} keys. * Any map entry for which the corresponding key does not pass {@code keyFilter} will be effectively hidden from view. * *

* The restrictions of the given {@link KeyFilter} will be added to any current {@link KeyFilter} restrictions on this instance. * The {@link #bounds} associated with this instance will not change. * * @param keyFilter additional key filtering to apply * @return filtered view of this instance * @throws IllegalArgumentException if {@code keyFilter} is null */ public NavigableMap filterKeys(KeyFilter keyFilter) { Preconditions.checkArgument(keyFilter != null, "null keyFilter"); if (this.keyFilter != null) keyFilter = KeyFilterUtil.intersection(keyFilter, this.keyFilter); return this.createSubMap(this.reversed, this.keyRange, keyFilter, this.bounds); } @Override protected boolean isWithinLowerBound(K key) { if (!super.isWithinLowerBound(key)) return false; if (this.keyRange == null) return true; final ByteWriter writer = new ByteWriter(); this.encodeKey(writer, key); return KeyRange.compare(writer.getBytes(), this.keyRange.getMin()) >= 0; } @Override protected boolean isWithinUpperBound(K key) { if (!super.isWithinUpperBound(key)) return false; if (this.keyRange == null) return true; final ByteWriter writer = new ByteWriter(); this.encodeKey(writer, key); return KeyRange.compare(writer.getBytes(), this.keyRange.getMax()) < 0; } @Override protected final NavigableMap createSubMap(boolean reverse, Bounds newBounds) { // Determine the direction of the new sub-map final boolean newReversed = this.reversed ^ reverse; // Determine new min and max keys final KeyRange newKeyRange = this.buildKeyRange(newReversed ? newBounds.reverse() : newBounds); // Create submap return this.createSubMap(newReversed, newKeyRange, this.keyFilter, newBounds); } /** * Create a (possibly reversed) view of this instance with (possibly) tighter lower and/or upper bounds and * the given {@link KeyFilter}, if any. * The bounds are consistent with the reversed ordering (i.e., reversed if {@code reverse} is true) * and have already been range-checked against this instance's bounds. * * @param newReversed whether the new map's ordering should be reversed (implies {@code newBounds} are also inverted, * but not {@code keyRange}); note: means "absolutely" reversed, not relative to this instance * @param newKeyRange new key range, or null for none; will be consistent with {@code bounds}, if any * @param newKeyFilter new key filter, or null for none * @param newBounds new bounds * @return restricted and/or filtered view of this instance * @throws IllegalArgumentException if {@code newBounds} is null */ protected abstract NavigableMap createSubMap(boolean newReversed, KeyRange newKeyRange, KeyFilter newKeyFilter, Bounds newBounds); /** * Encode the given key object into a {@code byte[]} key. * Note that this method must throw {@link IllegalArgumentException}, not {@link ClassCastException} * or {@code NullPointerException}, if {@code obj} does not have the correct type or is an illegal null value. * * @param writer output for encoded {@code byte[]} key corresponding to {@code obj} * @param obj map key object * @throws IllegalArgumentException if {@code obj} is not of the required Java type supported by this set * @throws IllegalArgumentException if {@code obj} is null and this set does not support null elements */ protected abstract void encodeKey(ByteWriter writer, Object obj); /** * Decode a key object from an encoded {@code byte[]} key. * *

* If not in prefix mode, all of {@code reader} must be consumed; otherwise, the consumed portion * is the prefix and any following keys with the same prefix are ignored. * * @param reader input for encoded bytes * @return decoded map key */ protected abstract K decodeKey(ByteReader reader); /** * Decode a value object from an encoded {@code byte[]} key/value pair. * * @param pair key/value pair * @return decoded map value */ protected abstract V decodeValue(KVPair pair); /** * Determine if the given {@code byte[]} key is visible in this map according to the configured * {@link KeyRange} and/or {@link KeyFilter}, if any. * * @param key key to test * @return true if key is visible * @throws IllegalArgumentException if {@code key} is null * @see #filterKeys filterKeys() */ protected boolean isVisible(byte[] key) { return (this.keyRange == null || this.keyRange.contains(key)) && (this.keyFilter == null || this.keyFilter.contains(key)); } /** * Encode the given key object, if possible, and verify the corresponding {@code byte[]} key is visible, * otherwise return null or throw an exception. * Delegates to {@link #encodeKey(ByteWriter, Object)} to attempt the actual encoding. * * @param obj key object to encode, possibly null * @param fail whether, if {@code obj} can't be encoded, to throw an exception (true) or return null (false) * @return encoed key for {@code obj}, or null if {@code fail} is false and {@code obj} has the wrong type or is out of bounds * @throws IllegalArgumentException if {@code fail} is true and {@code obj} has the wrong type * @throws IllegalArgumentException if {@code fail} is true and the resulting key is not {@linkplain #isVisible visible} */ protected byte[] encodeVisibleKey(Object obj, boolean fail) { final ByteWriter writer = new ByteWriter(); try { this.encodeKey(writer, obj); } catch (IllegalArgumentException e) { if (!fail) return null; throw e; } final byte[] key = writer.getBytes(); if (this.keyRange != null && !this.keyRange.contains(key)) { if (fail) throw new IllegalArgumentException("key is out of bounds: " + obj); return null; } if (this.keyFilter != null && !this.keyFilter.contains(key)) { if (fail) throw new IllegalArgumentException("key is filtered out: " + obj); return null; } return key; } /** * Derive a new {@link KeyRange} from (possibly) new element bounds. The given bounds must not ever be reversed. */ private KeyRange buildKeyRange(Bounds bounds) { final byte[] minKey = this.keyRange != null ? this.keyRange.getMin() : null; final byte[] maxKey = this.keyRange != null ? this.keyRange.getMax() : null; byte[] newMinKey; byte[] newMaxKey; switch (bounds.getLowerBoundType()) { case NONE: newMinKey = minKey; break; default: final ByteWriter writer = new ByteWriter(); this.encodeKey(writer, bounds.getLowerBound()); newMinKey = writer.getBytes(); if (!bounds.getLowerBoundType().isInclusive()) newMinKey = this.prefixMode ? ByteUtil.getKeyAfterPrefix(newMinKey) : ByteUtil.getNextKey(newMinKey); if (minKey != null) newMinKey = ByteUtil.max(newMinKey, minKey); if (maxKey != null) newMinKey = ByteUtil.min(newMinKey, maxKey); break; } switch (bounds.getUpperBoundType()) { case NONE: newMaxKey = maxKey; break; default: final ByteWriter writer = new ByteWriter(); this.encodeKey(writer, bounds.getUpperBound()); newMaxKey = writer.getBytes(); if (bounds.getUpperBoundType().isInclusive()) newMaxKey = this.prefixMode ? ByteUtil.getKeyAfterPrefix(newMaxKey) : ByteUtil.getNextKey(newMaxKey); if (maxKey != null) newMaxKey = ByteUtil.min(newMaxKey, maxKey); if (minKey != null) newMaxKey = ByteUtil.max(newMaxKey, minKey); break; } return new KeyRange(newMinKey != null ? newMinKey : ByteUtil.EMPTY, newMaxKey); } // KeySet private class KeySet extends AbstractKVNavigableSet { KeySet() { super(AbstractKVNavigableMap.this.kv, AbstractKVNavigableMap.this.prefixMode, AbstractKVNavigableMap.this.reversed, AbstractKVNavigableMap.this.keyRange, AbstractKVNavigableMap.this.keyFilter, AbstractKVNavigableMap.this.bounds); } @Override public Comparator comparator() { return AbstractKVNavigableMap.this.comparator(); } @Override public boolean remove(Object obj) { final boolean existed = AbstractKVNavigableMap.this.containsKey(obj); AbstractKVNavigableMap.this.remove(obj); return existed; } @Override public void clear() { AbstractKVNavigableMap.this.clear(); } @Override protected void encode(ByteWriter writer, Object obj) { AbstractKVNavigableMap.this.encodeKey(writer, obj); } @Override protected K decode(ByteReader reader) { return AbstractKVNavigableMap.this.decodeKey(reader); } @Override protected NavigableSet createSubSet(boolean newReversed, KeyRange newKeyRange, KeyFilter newKeyFilter, Bounds newBounds) { return AbstractKVNavigableMap.this.createSubMap(newReversed, newKeyRange, newKeyFilter, newBounds).navigableKeySet(); } } // EntrySet private class EntrySet extends AbstractIterationSet> { @Override public Iterator> iterator() { return new AbstractKVIterator>(AbstractKVNavigableMap.this.kv, AbstractKVNavigableMap.this.prefixMode, AbstractKVNavigableMap.this.reversed, AbstractKVNavigableMap.this.keyRange, AbstractKVNavigableMap.this.keyFilter) { @Override protected Map.Entry decodePair(KVPair pair, ByteReader keyReader) { final K key = AbstractKVNavigableMap.this.decodeKey(keyReader); final V value = AbstractKVNavigableMap.this.decodeValue(pair); return new MapEntry(key, value); } @Override protected void doRemove(Map.Entry entry, KVPair pair) { AbstractKVNavigableMap.this.entrySet().remove(entry); } }; } @Override @SuppressWarnings("unchecked") public boolean contains(Object obj) { // Check type if (!(obj instanceof Map.Entry)) return false; final Map.Entry entry = (Map.Entry)obj; // Find key if (!AbstractKVNavigableMap.this.containsKey(entry.getKey())) return false; // Compare key/value pair final K key = (K)entry.getKey(); final V value = AbstractKVNavigableMap.this.get(entry.getKey()); return new MapEntry(key, value).equals(entry); } @Override @SuppressWarnings("unchecked") public boolean remove(Object obj) { // Check type if (!(obj instanceof Map.Entry)) return false; final Map.Entry entry = (Map.Entry)obj; // Find key if (!AbstractKVNavigableMap.this.containsKey(entry.getKey())) return false; // Compare key/value pair and remove entry (if contained) final K key = (K)entry.getKey(); final V value = AbstractKVNavigableMap.this.get(entry.getKey()); if (new MapEntry(key, value).equals(entry)) { AbstractKVNavigableMap.this.remove(key); return true; } // Not found return false; } @Override public void clear() { AbstractKVNavigableMap.this.clear(); } } // MapEntry private class MapEntry extends AbstractMap.SimpleEntry { MapEntry(K key, V value) { super(key, value); } @Override public V setValue(V value) { AbstractKVNavigableMap.this.put(this.getKey(), value); return super.setValue(value); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy