/******************************************************************************* * 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 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(); } } }