package pt.ist.fenixframework.adt.bplustree; import java.io.Serializable; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.SortedMap; import java.util.TreeMap; public class LeafNode extends LeafNode_Base { public LeafNode() { setEntries(new TreeMap<Comparable,Serializable>(BPlusTree.COMPARATOR_SUPPORTING_LAST_KEY)); } private LeafNode(TreeMap<Comparable,Serializable> entries) { setEntries(entries); } private TreeMap<Comparable,Serializable> duplicateMap() { return new TreeMap<Comparable,Serializable>(getEntries()); } public AbstractNode insert(Comparable key, Serializable value) { TreeMap<Comparable,Serializable> localMap = justInsert(key, value); if (localMap.size() <= BPlusTree.MAX_NUMBER_OF_ELEMENTS) { // it still fits :-) return getRoot(); } else { // must split this node // find middle position Comparable keyToSplit = findRightMiddlePosition(localMap.keySet()); // split node in two LeafNode leftNode = new LeafNode(new TreeMap<Comparable,Serializable>(localMap.headMap(keyToSplit))); LeafNode rightNode = new LeafNode(new TreeMap<Comparable,Serializable>(localMap.tailMap(keyToSplit))); fixLeafNodesListAfterSplit(leftNode, rightNode); // propagate split to parent if (getParent() == null) { // make new root node InnerNode newRoot = new InnerNode(leftNode, rightNode, keyToSplit); return newRoot; } else { // leftNode.parent = getParent(); // rightNode.parent = getParent(); return getParent().rebase(leftNode, rightNode, keyToSplit); } } } private <T extends Comparable> Comparable findRightMiddlePosition(Collection<T> keys) { Iterator<T> keysIterator = keys.iterator(); for (int i = 0; i < BPlusTree.LOWER_BOUND + 1; i++) { keysIterator.next(); } return keysIterator.next(); } private TreeMap<Comparable,Serializable> justInsert(Comparable key, Serializable value) { TreeMap<Comparable,Serializable> localEntries = this.getEntries(); // this test is performed because we need to return a new structure in // case an update occurs. Value types must be immutable. Serializable currentValue = localEntries.get(key); if (currentValue == value && localEntries.containsKey(key)) { return localEntries; } else { TreeMap<Comparable,Serializable> newMap = duplicateMap(); newMap.put(key, value); setEntries(newMap); return newMap; } } private void fixLeafNodesListAfterSplit(LeafNode leftNode, LeafNode rightNode) { leftNode.setPrevious(this.getPrevious()); rightNode.setNext(this.getNext()); leftNode.setNext(rightNode); } public AbstractNode remove(Comparable key) { TreeMap<Comparable,Serializable> localMap = justRemove(key); if (getParent() == null) { return this; } else { // if the removed key was the first we need to replace it in some parent's index Comparable replacementKey = getReplacementKeyIfNeeded(key); if (localMap.size() < BPlusTree.LOWER_BOUND) { return getParent().underflowFromLeaf(key, replacementKey); } else if (replacementKey != null) { return getParent().replaceDeletedKey(key, replacementKey); } else { return getParent().getRoot(); // maybe a tiny faster than just getRoot() ?! } } } private TreeMap<Comparable,Serializable> justRemove(Comparable key) { TreeMap<Comparable,Serializable> localEntries = this.getEntries(); // this test is performed because we need to return a new structure in // case an update occurs. Value types must be immutable. if (!localEntries.containsKey(key)) { return localEntries; } else { TreeMap<Comparable,Serializable> newMap = duplicateMap(); newMap.remove(key); setEntries(newMap); return newMap; } } // This method assumes that there is at least one more key (which is // always true if this is not the root node) private Comparable getReplacementKeyIfNeeded(Comparable deletedKey) { Comparable firstKey = this.getEntries().firstKey(); if (BPlusTree.COMPARATOR_SUPPORTING_LAST_KEY.compare(deletedKey, firstKey) < 0) { return firstKey; } else { return null; // null means that key does not need replacement } } Map.Entry<Comparable,Serializable> removeBiggestKeyValue() { TreeMap<Comparable,Serializable> newMap = duplicateMap(); Map.Entry<Comparable,Serializable> lastEntry = newMap.pollLastEntry(); setEntries(newMap); return lastEntry; } Map.Entry<Comparable,Serializable> removeSmallestKeyValue() { TreeMap<Comparable,Serializable> newMap = duplicateMap(); Map.Entry<Comparable,Serializable> firstEntry = newMap.pollFirstEntry(); setEntries(newMap); return firstEntry; } Comparable getSmallestKey() { return this.getEntries().firstKey(); } void addKeyValue(Map.Entry keyValue) { TreeMap<Comparable,Serializable> newMap = duplicateMap(); newMap.put((Comparable)keyValue.getKey(), (Serializable)keyValue.getValue()); setEntries(newMap); } void mergeWithLeftNode(AbstractNode leftNode, Comparable splitKey) { LeafNode left = (LeafNode)leftNode; // this node does not know how to merge with another kind TreeMap<Comparable,Serializable> newMap = duplicateMap(); newMap.putAll(left.getEntries()); setEntries(newMap); LeafNode nodeBefore = left.getPrevious(); this.setPrevious(nodeBefore); if (nodeBefore != null) { nodeBefore.setNext(this); } // no need to update parents, because they are always the same for the two merging leaf nodes assert(this.getParent() == leftNode.getParent()); } public Serializable get(Comparable key) { return this.getEntries().get(key); } public Serializable getIndex(int index) { if (index < 0) { throw new IndexOutOfBoundsException(); } if (index < shallowSize()) { // the required position is here Iterator<Serializable> values = this.getEntries().values().iterator(); for (int i = 0; i < index; i++) { values.next(); } return values.next(); } else { LeafNode next = this.getNext(); if (next == null) { throw new IndexOutOfBoundsException(); } return next.getIndex(index - shallowSize()); } } public AbstractNode removeIndex(int index) { if (index < 0) { throw new IndexOutOfBoundsException(); } if (index < shallowSize()) { // the required position is here Iterator<Comparable> keys = this.getEntries().keySet().iterator(); for (int i = 0; i < index; i++) { keys.next(); } return this.remove(keys.next()); } else { LeafNode next = this.getNext(); if (next == null) { throw new IndexOutOfBoundsException(); } return next.removeIndex(index - shallowSize()); } } public boolean containsKey(Comparable key) { return this.getEntries().containsKey(key); } int shallowSize() { return this.getEntries().size(); } public int size() { return this.getEntries().size(); } @Override Iterator<? extends Comparable> keysIterator() { return new LeafNodeKeysIterator(this); } public Iterator<Serializable> iterator() { return new LeafNodeValuesIterator(this); } protected abstract class GenericLeafNodeIterator<T> implements Iterator<T> { private Iterator<T> iterator; private LeafNode current; GenericLeafNodeIterator(LeafNode leafNode) { this.iterator = getInternalIterator(leafNode); this.current = leafNode; } protected abstract Iterator<T> getInternalIterator(LeafNode leafNode); public boolean hasNext() { if (this.iterator.hasNext()) { return true; } else { return this.current.getNext() != null; } } public T next() { if (!this.iterator.hasNext()) { LeafNode nextNode = this.current.getNext(); if (nextNode != null) { this.current = nextNode; this.iterator = getInternalIterator(this.current); } else { throw new NoSuchElementException(); } } return this.iterator.next(); } public void remove() { throw new UnsupportedOperationException("This implementation does not allow element removal via the iterator"); } } private class LeafNodeValuesIterator extends GenericLeafNodeIterator<Serializable> { LeafNodeValuesIterator(LeafNode leafNode) { super(leafNode); } protected Iterator<Serializable> getInternalIterator(LeafNode leafNode) { return leafNode.getEntries().values().iterator(); } } private class LeafNodeKeysIterator extends GenericLeafNodeIterator<Comparable> { LeafNodeKeysIterator(LeafNode leafNode) { super(leafNode); } protected Iterator<Comparable> getInternalIterator(LeafNode leafNode) { return leafNode.getEntries().keySet().iterator(); } } public String dump(int level, boolean dumpKeysOnly, boolean dumpNodeIds) { StringBuilder str = new StringBuilder(); str.append(BPlusTree.spaces(level)); if (dumpNodeIds) { str.append(this.getPrevious() + "<-[" + this + ": "); } else { str.append("[: "); } for (Map.Entry<Comparable, Serializable> entry : this.getEntries().entrySet()) { Comparable key = entry.getKey(); Serializable value = entry.getValue(); str.append("(" + key); str.append(dumpKeysOnly ? ") " : "," + value + ") "); } if (dumpNodeIds) { str.append("]->" + this.getNext() + " ^" + getParent() + "\n"); } else { str.append("]\n"); } return str.toString(); } @Override Collection<? extends Comparable> getKeys() { return this.getEntries().keySet(); } }