package pt.ist.fenixframework.core.adt.bplustree;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pt.ist.fenixframework.core.AbstractDomainObject;
public class LeafNodeArray extends LeafNodeArray_Base {
private static final Logger logger = LoggerFactory.getLogger(LeafNodeArray.class);
public LeafNodeArray() {
setEntries(new DoubleArray<AbstractDomainObject>(AbstractDomainObject.class));
}
private LeafNodeArray(DoubleArray<AbstractDomainObject> entries) {
setEntries(entries);
}
public AbstractNodeArray insert(Comparable key, AbstractDomainObject value) {
DoubleArray<AbstractDomainObject> localArr = justInsert(key, value);
if (localArr == null) {
return null; // insert will return false
}
if (localArr.length() <= BPlusTreeArray.MAX_NUMBER_OF_ELEMENTS) { // it still fits :-)
return getRoot();
} else { // must split this node
// find middle position
Comparable keyToSplit = localArr.findRightMiddlePosition();
// split node in two
LeafNodeArray leftNode = new LeafNodeArray(localArr.leftPart(BPlusTreeArray.LOWER_BOUND + 1));
LeafNodeArray rightNode = new LeafNodeArray(localArr.rightPart(BPlusTreeArray.LOWER_BOUND + 1));
fixLeafNodeArraysListAfterSplit(leftNode, rightNode);
// propagate split to parent
if (getParent() == null) { // make new root node
InnerNodeArray newRoot = new InnerNodeArray(leftNode, rightNode, keyToSplit);
return newRoot;
} else {
return getParent().rebase(leftNode, rightNode, keyToSplit);
}
}
}
private DoubleArray<AbstractDomainObject> justInsert(Comparable key, AbstractDomainObject value) {
logger.trace("Getting 'entries' slot");
DoubleArray<AbstractDomainObject> 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.
AbstractDomainObject currentValue = localEntries.get(key);
if (currentValue != null && currentValue == value ) {
logger.trace("Existing key. No change required");
return null;
} else {
logger.trace("Will add new entry. Must duplicate 'entries'.");
DoubleArray<AbstractDomainObject> newArr = localEntries.addKeyValue(key, value);
setEntries(newArr);
return newArr;
}
}
private void fixLeafNodeArraysListAfterSplit(LeafNodeArray leftNode, LeafNodeArray rightNode) {
leftNode.setPrevious(this.getPrevious());
rightNode.setNext(this.getNext());
leftNode.setNext(rightNode);
}
public AbstractNodeArray remove(Comparable key) {
DoubleArray<AbstractDomainObject> localArr = justRemove(key);
if (localArr == null) {
return null; // remove will return false
}
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 (localArr.length() < BPlusTreeArray.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 DoubleArray<AbstractDomainObject> justRemove(Comparable key) {
DoubleArray<AbstractDomainObject> 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 null;
} else {
DoubleArray<AbstractDomainObject> newArr = localEntries.removeKey(key);
setEntries(newArr);
return newArr;
}
}
// 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 (BPlusTreeArray.COMPARATOR_SUPPORTING_LAST_KEY.compare(deletedKey, firstKey) < 0) {
return firstKey;
} else {
return null; // null means that key does not need replacement
}
}
DoubleArray<AbstractDomainObject>.KeyVal removeBiggestKeyValue() {
DoubleArray<AbstractDomainObject> entries = this.getEntries();
DoubleArray<AbstractDomainObject>.KeyVal lastEntry = entries.getBiggestKeyValue();
setEntries(entries.removeBiggestKeyValue());
return lastEntry;
}
DoubleArray<AbstractDomainObject>.KeyVal removeSmallestKeyValue() {
DoubleArray<AbstractDomainObject> entries = this.getEntries();
DoubleArray<AbstractDomainObject>.KeyVal firstEntry = entries.getSmallestKeyValue();
setEntries(entries.removeSmallestKeyValue());
return firstEntry;
}
Comparable getSmallestKey() {
return this.getEntries().firstKey();
}
void addKeyValue(DoubleArray.KeyVal keyValue) {
setEntries(this.getEntries().addKeyValue(keyValue));
}
void mergeWithLeftNode(AbstractNodeArray leftNode, Comparable splitKey) {
LeafNodeArray left = (LeafNodeArray)leftNode; // this node does not know how to merge with another kind
setEntries(getEntries().mergeWith(left.getEntries()));
LeafNodeArray 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 AbstractDomainObject get(Comparable key) {
return this.getEntries().get(key);
}
public AbstractDomainObject getIndex(int index) {
if (index < 0) {
throw new IndexOutOfBoundsException();
}
if (index < shallowSize()) { // the required position is here
return this.getEntries().values[index];
} else {
LeafNodeArray next = this.getNext();
if (next == null) {
throw new IndexOutOfBoundsException();
}
return next.getIndex(index - shallowSize());
}
}
public AbstractNodeArray removeIndex(int index) {
if (index < 0) {
throw new IndexOutOfBoundsException();
}
if (index < shallowSize()) { // the required position is here
return remove(this.getEntries().keys[index]);
} else {
LeafNodeArray 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().length();
}
public int size() {
return this.getEntries().length();
}
public Iterator<AbstractDomainObject> iterator() {
return new LeafNodeArrayIterator(this);
}
private class LeafNodeArrayIterator implements Iterator<AbstractDomainObject> {
private int index;
private AbstractDomainObject[] values;
private LeafNodeArray current;
LeafNodeArrayIterator(LeafNodeArray LeafNodeArray) {
this.index = 0;
this.values = LeafNodeArray.getEntries().values;
this.current = LeafNodeArray;
}
public boolean hasNext() {
if (index < values.length) {
return true;
} else {
return this.current.getNext() != null;
}
}
public AbstractDomainObject next() {
if (index >= values.length) {
LeafNodeArray nextNode = this.current.getNext();
if (nextNode != null) {
this.current = nextNode;
this.index = 0;
this.values = this.current.getEntries().values;
} else {
throw new NoSuchElementException();
}
}
index++;
return values[index - 1];
}
public void remove() {
throw new UnsupportedOperationException("This implementation does not allow element removal via the iterator");
}
}
public String dump(int level, boolean dumpKeysOnly, boolean dumpNodeIds) {
StringBuilder str = new StringBuilder();
str.append(BPlusTreeArray.spaces(level));
if (dumpNodeIds) {
str.append(this.getPrevious() + "<-[" + this + ": ");
} else {
str.append("[: ");
}
DoubleArray<AbstractDomainObject> subNodes = this.getEntries();
for (int i = 0; i < subNodes.length(); i++) {
Comparable key = subNodes.keys[i];
AbstractDomainObject value = subNodes.values[i];
str.append("(" + key);
str.append(dumpKeysOnly ? ") " : "," + value.getOid() + ") ");
}
if (dumpNodeIds) {
str.append("]->" + this.getNext() + " ^" + getParent() + "\n");
} else {
str.append("]\n");
}
return str.toString();
}
}