/*******************************************************************************
* Copyright 2010 Cees De Groot, Alex Boisvert, Jan Kotek
*
* 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 org.apache.jdbm;
import java.io.IOError;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentNavigableMap;
/**
* Wrapper for BTree
which implements ConcurrentNavigableMap
interface
*
* @param key type
* @param value type
*
* @author Jan Kotek
*/
class BTreeMap extends AbstractMap implements ConcurrentNavigableMap {
protected BTree tree;
protected final K fromKey;
protected final K toKey;
protected final boolean readonly;
protected NavigableSet keySet2;
private final boolean toInclusive;
private final boolean fromInclusive;
public BTreeMap(BTree tree, boolean readonly) {
this(tree, readonly, null, false, null, false);
}
protected BTreeMap(BTree tree, boolean readonly, K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
this.tree = tree;
this.fromKey = fromKey;
this.fromInclusive = fromInclusive;
this.toKey = toKey;
this.toInclusive = toInclusive;
this.readonly = readonly;
}
@Override
public Set> entrySet() {
return _entrySet;
}
private final Set> _entrySet = new AbstractSet>() {
protected Entry newEntry(K k, V v) {
return new SimpleEntry(k, v) {
private static final long serialVersionUID = 978651696969194154L;
public V setValue(V arg0) {
BTreeMap.this.put(getKey(), arg0);
return super.setValue(arg0);
}
};
}
public boolean add(java.util.Map.Entry e) {
if (readonly)
throw new UnsupportedOperationException("readonly");
try {
if (e.getKey() == null)
throw new NullPointerException("Can not add null key");
if (!inBounds(e.getKey()))
throw new IllegalArgumentException("key outside of bounds");
return tree.insert(e.getKey(), e.getValue(), true) == null;
} catch (IOException e1) {
throw new IOError(e1);
}
}
@SuppressWarnings("unchecked")
public boolean contains(Object o) {
if (o instanceof Entry) {
Entry e = (java.util.Map.Entry) o;
try {
if (!inBounds(e.getKey()))
return false;
if (e.getKey() != null && tree.get(e.getKey()) != null)
return true;
} catch (IOException e1) {
throw new IOError(e1);
}
}
return false;
}
public Iterator> iterator() {
try {
final BTree.BTreeTupleBrowser br = fromKey == null ?
tree.browse() : tree.browse(fromKey, fromInclusive);
return new Iterator>() {
private Entry next;
private K lastKey;
void ensureNext() {
try {
BTree.BTreeTuple t = new BTree.BTreeTuple();
if (br.getNext(t) && inBounds(t.key))
next = newEntry(t.key, t.value);
else
next = null;
} catch (IOException e1) {
throw new IOError(e1);
}
}
{
ensureNext();
}
public boolean hasNext() {
return next != null;
}
public java.util.Map.Entry next() {
if (next == null)
throw new NoSuchElementException();
Entry ret = next;
lastKey = ret.getKey();
//move to next position
ensureNext();
return ret;
}
public void remove() {
if (readonly)
throw new UnsupportedOperationException("readonly");
if (lastKey == null)
throw new IllegalStateException();
try {
br.remove(lastKey);
lastKey = null;
} catch (IOException e1) {
throw new IOError(e1);
}
}
};
} catch (IOException e) {
throw new IOError(e);
}
}
@SuppressWarnings("unchecked")
public boolean remove(Object o) {
if (readonly)
throw new UnsupportedOperationException("readonly");
if (o instanceof Entry) {
Entry e = (java.util.Map.Entry) o;
try {
//check for nulls
if (e.getKey() == null || e.getValue() == null)
return false;
if (!inBounds(e.getKey()))
throw new IllegalArgumentException("out of bounds");
//get old value, must be same as item in entry
V v = get(e.getKey());
if (v == null || !e.getValue().equals(v))
return false;
V v2 = tree.remove(e.getKey());
return v2 != null;
} catch (IOException e1) {
throw new IOError(e1);
}
}
return false;
}
public int size() {
return BTreeMap.this.size();
}
public void clear(){
if(fromKey!=null || toKey!=null)
super.clear();
else
try {
tree.clear();
} catch (IOException e) {
throw new IOError(e);
}
}
};
public boolean inBounds(K e) {
if(fromKey == null && toKey == null)
return true;
Comparator comp = comparator();
if (comp == null) comp = Utils.COMPARABLE_COMPARATOR;
if(fromKey!=null){
final int compare = comp.compare(e, fromKey);
if(compare<0) return false;
if(!fromInclusive && compare == 0) return false;
}
if(toKey!=null){
final int compare = comp.compare(e, toKey);
if(compare>0)return false;
if(!toInclusive && compare == 0) return false;
}
return true;
}
@SuppressWarnings("unchecked")
@Override
public V get(Object key) {
try {
if (key == null)
return null;
if (!inBounds((K) key))
return null;
return tree.get((K) key);
} catch (ClassCastException e) {
return null;
} catch (IOException e) {
throw new IOError(e);
}
}
@SuppressWarnings("unchecked")
@Override
public V remove(Object key) {
if (readonly)
throw new UnsupportedOperationException("readonly");
try {
if (key == null || tree.get((K) key) == null)
return null;
if (!inBounds((K) key))
throw new IllegalArgumentException("out of bounds");
return tree.remove((K) key);
} catch (ClassCastException e) {
return null;
} catch (IOException e) {
throw new IOError(e);
}
}
public V put(K key, V value) {
if (readonly)
throw new UnsupportedOperationException("readonly");
try {
if (key == null || value == null)
throw new NullPointerException("Null key or value");
if (!inBounds(key))
throw new IllegalArgumentException("out of bounds");
return tree.insert(key, value, true);
} catch (IOException e) {
throw new IOError(e);
}
}
public void clear(){
entrySet().clear();
}
@SuppressWarnings("unchecked")
@Override
public boolean containsKey(Object key) {
if (key == null)
return false;
try {
if (!inBounds((K) key))
return false;
V v = tree.get((K) key);
return v != null;
} catch (IOException e) {
throw new IOError(e);
} catch (ClassCastException e) {
return false;
}
}
public Comparator super K> comparator() {
return tree._comparator;
}
public K firstKey() {
if (isEmpty())
return null;
try {
BTree.BTreeTupleBrowser b = fromKey == null ? tree.browse() : tree.browse(fromKey,fromInclusive);
BTree.BTreeTuple t = new BTree.BTreeTuple();
b.getNext(t);
return t.key;
} catch (IOException e) {
throw new IOError(e);
}
}
public K lastKey() {
if (isEmpty())
return null;
try {
BTree.BTreeTupleBrowser b = toKey == null ? tree.browse(null,true) : tree.browse(toKey,false);
BTree.BTreeTuple t = new BTree.BTreeTuple();
b.getPrevious(t);
if(!toInclusive && toKey!=null){
//make sure we wont return last key
Comparator c = comparator();
if(c==null) c=Utils.COMPARABLE_COMPARATOR;
if(c.compare(t.key,toKey)==0)
b.getPrevious(t);
}
return t.key;
} catch (IOException e) {
throw new IOError(e);
}
}
public ConcurrentNavigableMap headMap(K toKey2, boolean inclusive) {
K toKey3 = Utils.min(this.toKey,toKey2,comparator());
boolean inclusive2 = toKey3 == toKey? toInclusive : inclusive;
return new BTreeMap(tree, readonly, this.fromKey, this.fromInclusive, toKey3, inclusive2);
}
public ConcurrentNavigableMap headMap(K toKey) {
return headMap(toKey,false);
}
public Entry lowerEntry(K key) {
K k = lowerKey(key);
return k==null? null : new SimpleEntry(k,get(k));
}
public K lowerKey(K key) {
if (isEmpty())
return null;
K key2 = Utils.min(key,toKey,comparator());
try {
BTree.BTreeTupleBrowser b = tree.browse(key2,true) ;
BTree.BTreeTuple t = new BTree.BTreeTuple();
b.getPrevious(t);
return t.key;
} catch (IOException e) {
throw new IOError(e);
}
}
public Entry floorEntry(K key) {
K k = floorKey(key);
return k==null? null : new SimpleEntry(k,get(k));
}
public K floorKey(K key) {
if (isEmpty())
return null;
K key2 = Utils.max(key,fromKey,comparator());
try {
BTree.BTreeTupleBrowser b = tree.browse(key2,true) ;
BTree.BTreeTuple t = new BTree.BTreeTuple();
b.getNext(t);
Comparator comp = comparator();
if (comp == null) comp = Utils.COMPARABLE_COMPARATOR;
if(comp.compare(t.key,key2) == 0)
return t.key;
b.getPrevious(t);
b.getPrevious(t);
return t.key;
} catch (IOException e) {
throw new IOError(e);
}
}
public Entry ceilingEntry(K key) {
K k = ceilingKey(key);
return k==null? null : new SimpleEntry(k,get(k));
}
public K ceilingKey(K key) {
if (isEmpty())
return null;
K key2 = Utils.min(key,toKey,comparator());
try {
BTree.BTreeTupleBrowser b = tree.browse(key2,true) ;
BTree.BTreeTuple t = new BTree.BTreeTuple();
b.getNext(t);
return t.key;
} catch (IOException e) {
throw new IOError(e);
}
}
public Entry higherEntry(K key) {
K k = higherKey(key);
return k==null? null : new SimpleEntry(k,get(k));
}
public K higherKey(K key) {
if (isEmpty())
return null;
K key2 = Utils.max(key,fromKey,comparator());
try {
BTree.BTreeTupleBrowser b = tree.browse(key2,false) ;
BTree.BTreeTuple t = new BTree.BTreeTuple();
b.getNext(t);
return t.key;
} catch (IOException e) {
throw new IOError(e);
}
}
public Entry firstEntry() {
K k = firstKey();
return k==null? null : new SimpleEntry(k,get(k));
}
public Entry lastEntry() {
K k = lastKey();
return k==null? null : new SimpleEntry(k,get(k));
}
public Entry pollFirstEntry() {
Entry first = firstEntry();
if(first!=null)
remove(first.getKey());
return first;
}
public Entry pollLastEntry() {
Entry last = lastEntry();
if(last!=null)
remove(last.getKey());
return last;
}
public ConcurrentNavigableMap descendingMap() {
throw new UnsupportedOperationException("not implemented yet");
//TODO implement descending (reverse order) map
}
public NavigableSet keySet() {
return navigableKeySet();
}
public NavigableSet navigableKeySet() {
if(keySet2 == null)
keySet2 = new BTreeSet((BTreeMap) this);
return keySet2;
}
public NavigableSet descendingKeySet() {
return descendingMap().navigableKeySet();
}
public ConcurrentNavigableMap tailMap(K fromKey) {
return tailMap(fromKey,true);
}
public ConcurrentNavigableMap tailMap(K fromKey2, boolean inclusive) {
K fromKey3 = Utils.max(this.fromKey,fromKey2,comparator());
boolean inclusive2 = fromKey3 == toKey? toInclusive : inclusive;
return new BTreeMap(tree, readonly, fromKey3, inclusive2, toKey, toInclusive);
}
public ConcurrentNavigableMap subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
Comparator comp = comparator();
if (comp == null) comp = Utils.COMPARABLE_COMPARATOR;
if (comp.compare(fromKey, toKey) > 0)
throw new IllegalArgumentException("fromKey is bigger then toKey");
return new BTreeMap(tree, readonly, fromKey, fromInclusive, toKey, toInclusive);
}
public ConcurrentNavigableMap subMap(K fromKey, K toKey) {
return subMap(fromKey,true,toKey,false);
}
public BTree getTree() {
return tree;
}
public void addRecordListener(RecordListener listener) {
tree.addRecordListener(listener);
}
public DBAbstract getRecordManager() {
return tree.getRecordManager();
}
public void removeRecordListener(RecordListener listener) {
tree.removeRecordListener(listener);
}
public int size() {
if (fromKey == null && toKey == null)
return (int) tree._entries; //use fast counter on tree if Map has no bounds
else {
//had to count items in iterator
Iterator iter = keySet().iterator();
int counter = 0;
while (iter.hasNext()) {
iter.next();
counter++;
}
return counter;
}
}
public V putIfAbsent(K key, V value) {
tree.lock.writeLock().lock();
try{
if (!containsKey(key))
return put(key, value);
else
return get(key);
}finally {
tree.lock.writeLock().unlock();
}
}
public boolean remove(Object key, Object value) {
tree.lock.writeLock().lock();
try{
if (containsKey(key) && get(key).equals(value)) {
remove(key);
return true;
} else return false;
}finally {
tree.lock.writeLock().unlock();
}
}
public boolean replace(K key, V oldValue, V newValue) {
tree.lock.writeLock().lock();
try{
if (containsKey(key) && get(key).equals(oldValue)) {
put(key, newValue);
return true;
} else return false;
}finally {
tree.lock.writeLock().unlock();
}
}
public V replace(K key, V value) {
tree.lock.writeLock().lock();
try{
if (containsKey(key)) {
return put(key, value);
} else return null;
}finally {
tree.lock.writeLock().unlock();
}
}
}