/* XXL: The eXtensible and fleXible Library for data processing Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger Head of the Database Research Group Department of Mathematics and Computer Science University of Marburg Germany This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; If not, see <http://www.gnu.org/licenses/>. http://code.google.com/p/xxl/ */ package xxl.core.indexStructures; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Stack; import xxl.core.collections.queues.DynamicHeap; import xxl.core.cursors.Cursors; import xxl.core.cursors.filters.Filter; import xxl.core.functions.AbstractFunction; import xxl.core.functions.Function; import xxl.core.predicates.AbstractPredicate; /** * This class extends the MVBTree with auto-delete functionality, i.e. * objects have to be inserted with a deletion version and will be * automatically deleted at that version. */ public class AutoDeleteMVBTree extends MVBTree { /** * This class extends the <tt>DynamicHeap</tt> by providing a * mechanism to notify a listener whenever an object in the heap * changes its position. * * <p>This can be used to inform each object about its current * position in the heap. Whenever an object's state changes in a way * that affects the ordering in the heap, that object can then call * the heap's <tt>update</tt> method, providing it's current position * as the parameter, to restore the heap structure.</p> * * @param <E> the type of the elements of this heap. */ protected static class NotifyHeap<E> extends DynamicHeap<E> { protected Function onUpdate; public NotifyHeap(Comparator<? super E> comparator, Function onUpdate) { super(comparator); this.onUpdate = onUpdate; } @Override protected void bubbleUp(E object, int i) { // prerequisite: 0 <= i <= last && array[0] <= object while (comparator.compare(object, (E)array[i/2]) < 0) { onUpdate.invoke(array[i/2], i); array[i] = array[i /= 2]; /* * Prevent infinite loop when array[0] > object * (this can happen if bubbleUp is called from update(int) */ if (i == 0) break; } onUpdate.invoke(object, i); array[i] = object; } @Override protected int sinkIn(int i) { // prerequisite: 1 <= i <= last onUpdate.invoke(array[i], i/2); array[i/2] = array[i]; while ((i *= 2) < size-1) { onUpdate.invoke(array[comparator.compare((E)array[i], (E)array[i+1]) < 0 ? i : i+1], i/2); array[(comparator.compare((E)array[i], (E)array[i+1]) < 0 ? i : i++)/2] = array[i]; } return i/2; } @Override public void enqueueObject(E object) { onUpdate.invoke(object, 0); super.enqueueObject(object); } @Override public E dequeueObject() { version++; E minimum = (E)array[0]; if (size > 1) { int index = sinkIn(1); if (index < size-1) bubbleUp((E)array[size-1], index); } array = resizer.resize(array, size()); onUpdate.invoke(minimum, new Integer(-1)); return minimum; } @Override public E replace(E object) throws NoSuchElementException { throw new UnsupportedOperationException(); } @Override protected void update(int index) { computedNext = false; bubbleUp((E)array[index], index); // sink without removal! int i=index; if (i==0) { if (comparator.compare((E)array[0], (E)array[1]) > 0 ) { onUpdate.invoke(array[0], 1); onUpdate.invoke(array[1], 0); Object tmp=array[0]; array[0] = array[1]; array[1] = tmp; i=1; } } if (i!=0) { while (2*i < size) { int smallerIndex=2*i; if (smallerIndex+1<size) if (comparator.compare((E)array[smallerIndex], (E)array[smallerIndex+1]) > 0 ) smallerIndex++; if (comparator.compare((E)array[smallerIndex], (E)array[i]) < 0 ) { onUpdate.invoke(array[i], smallerIndex); onUpdate.invoke(array[smallerIndex], i); Object tmp=array[i]; array[i] = array[smallerIndex]; array[smallerIndex] = tmp; i=smallerIndex; } else break; } } } /** * Removes the element at the given position in the heap and returns it. * * @param i object position in the underlying array * @return the removed element */ public E remove(int index) { if (index < 0 || index >= size) throw new IllegalArgumentException(); E object = (E)array[index]; if (size>1) { // array[size-1]>=array[0] array[index]=array[size-1]; size--; update(index); } else size=0; version++; onUpdate.invoke(object, new Integer(-1)); return object; } } /** * Contains information about one leaf node of the MVBTree. */ protected class LeafInfo { protected Object id; protected Object referenceKey; protected Version underflowVersion; protected int heapPos; /** * Creates a new <tt>LeafInfo</tt> object. * * @param key the leaf's reference key * @param underflowVersion the leaf's underflow version */ public LeafInfo(Object key, Version underflowVersion) { if (key == null || underflowVersion == null) throw new IllegalArgumentException(); this.referenceKey = key; this.underflowVersion = underflowVersion; } /** * Returns the leaf's ID in the tree's container. * * @return the leaf's ID in the tree's container */ public Object getId() { if (id == null) throw new IllegalStateException(); return id; } /** * Sets the leaf's ID in the tree's container. * * @param id the leaf's ID in the tree's container */ public void setId(Object id) { if (id == null) throw new IllegalArgumentException(); if (this.id != null) throw new IllegalStateException(); this.id = id; } /** * Returns the leaf's reference key. * * @return the leaf's reference key */ public Object getReferenceKey() { return referenceKey; } /** * Sets the leaf's reference key. * * @param key the leaf's reference key */ public void setReferenceKey(Object key) { if (key == null) throw new IllegalArgumentException(); this.referenceKey = key; } /** * Returns the leaf's underflow version. * * @return the leaf's underflow version */ public Version getUnderflowVersion() { return underflowVersion; } /** * Sets the leaf's underflow version. The new underflow version must * be greater than or equal to the old one. * * @param underflowVersion the leaf's underflow version */ public void setUnderflowVersion(Version underflowVersion) { if (underflowVersion == null) throw new IllegalArgumentException(); assert this.underflowVersion.compareTo(underflowVersion) <= 0; this.underflowVersion = underflowVersion; leafInfoHeap.update(heapPos); } /** * Returns this LeafInfo object's current position in the heap. * * @return this LeafInfo object's current position in the heap */ public int getHeapPos() { return heapPos; } /** * Sets this LeafInfo object's current position in the heap. * * @param heapPos this LeafInfo object's current position in the heap */ public void setHeapPos(int heapPos) { this.heapPos = heapPos; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("(id="); sb.append(id); sb.append(",key="); sb.append(referenceKey); sb.append(",underflow="); sb.append(underflowVersion); sb.append(")"); return sb.toString(); } } protected Map<Object,LeafInfo> leafInfoMap = new HashMap<Object,LeafInfo>(); protected NotifyHeap<LeafInfo> leafInfoHeap = new NotifyHeap<LeafInfo>( new Comparator<LeafInfo>() { public int compare(LeafInfo li1, LeafInfo li2) { return li1.getUnderflowVersion().compareTo(li2.getUnderflowVersion()); } }, new AbstractFunction() { public Object invoke(Object o1, Object o2) { LeafInfo leafInfo = (LeafInfo)o1; int pos = ((Integer)o2).intValue(); leafInfo.setHeapPos(pos); return null; } }); /** * Creates a new <tt>AutoDeleteMVBTree</tt>. * * @param blockSize the block size of the underlying <tt>Container</tt>. * @param minCapRatio the minimal capacity ratio of the tree's nodes. * @param e the epsilon of the strong version condition. */ public AutoDeleteMVBTree(int blockSize, float minCapRatio, float e) { super(blockSize, minCapRatio, e); } /** * Creates a new <tt>AutoDeleteMVBTree</tt>. The minimal capacity ratio of the * tree's nodes is set to 50%. * * @param blockSize the block size of the underlying <tt>Container</tt>. * @param e the epsilon of the strong version condition. */ public AutoDeleteMVBTree(int blockSize, float e) { super(blockSize, e); } /** * Inserts a data object into the tree. * This operation is unsupported and throws an UnsupportedOperationException. * Use <tt>insert(Version, Version, Object)</tt> instead. * * @param insertVersion the insertion version. * @param data the data object to be inserted. */ @Override public void insert(Version insertVersion, Object data) { throw new UnsupportedOperationException("Insertion without defining a deletion version is not supported. Use insert(Version, Version, Object) instead."); } /** * Inserts a data object into the tree. * * @param insertVersion the insertion version. * @param deleteVersion the deletion version. * @param data the data object to be inserted. */ public void insert(Version insertVersion, Version deleteVersion, Object data) { if (insertVersion == null || deleteVersion == null) throw new IllegalArgumentException(); if (insertVersion.compareTo(deleteVersion) >= 0) throw new IllegalArgumentException("insertVersion must be smaller than deleteVersion."); setCurrentVersion(insertVersion); while (!leafInfoHeap.isEmpty() && leafInfoHeap.peek().getUnderflowVersion().compareTo(currentVersion) <= 0) { // treat weak version underflow in leaf referenced by the the heap's root LeafInfo leafInfo = leafInfoHeap.peek(); Stack path = pathToNode(leafInfo.getReferenceKey(), currentVersion, 0); assert indexEntry(path).id().equals(leafInfo.getId()) : "Wrong leaf found. Actual ID: " + indexEntry(path).id() + ", expected ID: " + leafInfo.getId(); assert node(path).underflows(); treatUnderflow(path); // the heap's root is not removed here - this is done during the version // split when the leaf dies } LeafEntry leafEntry = createLeafEntry(new Lifespan(insertVersion, deleteVersion), data); insert(insertVersion, leafEntry,(MVSeparator)separator(leafEntry), 0); } /** * Replaces a data object in the tree. * This operation is unsupported and throws an UnsupportedOperationException. * * @param updateVersion the <tt>Version</tt> of this Operation. * @param oldData the data object stored in the tree which is to be replaced. * @param newData the new data object. */ @Override public void update(Version updateVersion, Object oldData, Object newData) { throw new UnsupportedOperationException("Updating is not supported."); } /** * Removes a data object from the tree. * This operation is unsupported and throws an UnsupportedOperationException. * Data objects are automatically deleted at their designated deletion version. * * @param deleteVersion the version of this operation. * @param data the data object to be removed. * @return the removed data object or null if no such element is stored in the tree. */ @Override public Object remove(Version deleteVersion, Object data) { throw new UnsupportedOperationException("Explicit object removal is not supported."); } /** * Creates a new <tt>Node</tt>. * * @return the newly created <tt>Node</tt>. */ @Override public Tree.Node createNode(int level) { return new Node(level); } /** * Adds a LeafInfo object for a newly created leaf. * * @param nodeId the leaf's ID in the tree's container * @param leafInfo leaf info for the leaf */ protected void addLeafInfo(Object nodeId, LeafInfo leafInfo) { leafInfo.setId(nodeId); LeafInfo old = leafInfoMap.put(nodeId, leafInfo); leafInfoHeap.enqueue(leafInfo); assert old == null; assert leafInfoMap.size() == leafInfoHeap.size(); } /** * Updates a LeafInfo object for an existing leaf. * * @param nodeId the leaf's ID in the tree's container * @param key the leaf's new reference key * @param underflowVersion the leaf's new underflow version */ protected void updateLeafInfo(Object nodeId, Object key, Version underflowVersion) { LeafInfo leafInfo = leafInfoMap.get(nodeId); leafInfo.setReferenceKey(key); leafInfo.setUnderflowVersion(underflowVersion); } /** * Removes a LeafInfo object for a leaf that died. * * @param nodeId the leaf's ID in the tree's container * @return leaf info that was removed */ protected LeafInfo removeLeafInfo(Object nodeId) { LeafInfo old = leafInfoMap.remove(nodeId); if (old != null) leafInfoHeap.remove(old.getHeapPos()); assert leafInfoMap.size() == leafInfoHeap.size(); return old; } /** * Retrieves a LeafInfo object for a leaf. * * @param nodeId the leaf's ID in the tree's container * @return leaf info */ protected LeafInfo getLeafInfo(Object nodeId) { return leafInfoMap.get(nodeId); } public class Node extends MVBTree.Node { /** * <tt>LeafInfo</tt> for this node. This field is only used temporarily: * it is only set when a new leaf node is created in the tree and only * read when the new node is inserted into the container. At that point * the field is set to null and is never used again. */ protected LeafInfo leafInfo; public Node(int level, Function createEntryList) { super(level, createEntryList); } public Node(int level) { super(level); } @Override public void onInsert(Object id) { if (leafInfo != null) { // System.out.println("adding ID "+id+", LI: "+leafInfo+"\n"); addLeafInfo(id, leafInfo); leafInfo = null; } } /** * Returns an <tt>Iterator</tt> pointing to all current entries of this * node, i.e. those entries whose deletion version is empty or greater * than the current version. * * @return an <tt>Iterator</tt> pointing to all entries of this node. */ @Override public Iterator getCurrentEntries() { return new Filter( iterator(), new AbstractPredicate() { public boolean invoke(Object entry) { return ((MVSeparator)separator(entry)).isAlive() || ((MVSeparator)separator(entry)).deleteVersion().compareTo(currentVersion()) > 0; } }); } protected int countEntriesFromVersion(final Version version) { return Cursors.count( new Filter( iterator(), new AbstractPredicate() { public boolean invoke(Object entry) { return ((MVSeparator)separator(entry)).lifespan().contains(version); } })); } /** * Computes the underflow version of this leaf node by choosing the d-largest * deletion version of this leaf's current entries. * * @return this leaf node's underflow version */ protected Version computeUnderflowVersion() { if (level() != 0) throw new IllegalArgumentException("Cannot compute underflow version of a non-leaf node."); // sort entries by deletion version List<LeafEntry> list = new ArrayList<LeafEntry>(); Iterator<LeafEntry> it = getCurrentEntries(); while (it.hasNext()) list.add(it.next()); Collections.sort(list, new Comparator<LeafEntry>() { public int compare(LeafEntry o1, LeafEntry o2) { return o1.getLifespan().endVersion().compareTo(o2.getLifespan().endVersion()); } }); // get d-largest deletion version Version delVersion = list.get(list.size() - D_LeafNode).getLifespan().endVersion(); assert delVersion != null; return delVersion; } /** * Computes a reference key for this leaf node by simply taking the first entry's key value. * * @return this leaf node's reference key */ protected Object computeReferenceKey() { if (level() != 0) throw new IllegalArgumentException("Cannot compute reference key of a non-leaf node."); return key(((LeafEntry)getEntry(0)).data()); } @Override protected void grow(Object entry, Stack path) { super.grow(entry, path); if (level() == 0 && path.size() > 1) { // is this a leaf but not a root? Object nodeId = indexEntry(path).id(); /* Version underflowVersion; Version oldUnderflowVersion = getLeafInfo(nodeId).getUnderflowVersion(); Version delVersion = ((LeafEntry)entry).getLifespan().endVersion(); if (delVersion.compareTo(oldUnderflowVersion) > 0 && countEntriesFromVersion(oldUnderflowVersion) == getD() - 1) { } else underflowVersion = oldUnderflowVersion; assert underflowVersion == computeUnderflowVersion(); */ updateLeafInfo(nodeId, computeReferenceKey(), computeUnderflowVersion()); /* System.out.println("\nGROW"); System.out.println("new: "+this); System.out.println("entry: "+entry); System.out.println("leaf info: "+getLeafInfo(indexEntry(path).id())+"\n");*/ } } @Override protected SplitInfo split(Version splitVersion, Stack path) { LeafInfo oldLeafInfo = null; if (level() == 0) { // remember old leaf info of this node oldLeafInfo = getLeafInfo(indexEntry(path).id()); } SplitInfo splitInfo = super.split(splitVersion, path); if (level() == 0) { Node newNode = (Node)splitInfo.newNode(); Node keySplitNode = (Node)splitInfo.keySplitNode; /* System.out.println("\nSPLIT at version " + splitVersion); System.out.println("isRoot: "+splitInfo.isRootSplit()); System.out.println("old: "+node(path)); System.out.println("new: "+newNode); System.out.println("key: "+keySplitNode); */ if (splitInfo.isKeySplit()) { // System.out.println("key split"); newNode.leafInfo = new LeafInfo(newNode.computeReferenceKey(), newNode.computeUnderflowVersion()); keySplitNode.leafInfo = new LeafInfo(keySplitNode.computeReferenceKey(), keySplitNode.computeUnderflowVersion()); } else if (!splitInfo.isRootSplit()) { // old node is not a root? if (splitInfo.isMergePerformed()) { // System.out.println("merge without key split"); newNode.leafInfo = new LeafInfo(newNode.computeReferenceKey(), newNode.computeUnderflowVersion()); } else { // just a version split // System.out.println("just version split"); Version underflowVersion = oldLeafInfo.getUnderflowVersion(); assert underflowVersion.equals(newNode.computeUnderflowVersion()); newNode.leafInfo = new LeafInfo(newNode.computeReferenceKey(), underflowVersion); } } // System.out.println(); } return splitInfo; } @Override protected SplitInfo versionSplit(Version splitVersion, Stack path, SplitInfo splitInfo) { SplitInfo newSplitInfo = super.versionSplit(splitVersion, path, splitInfo); // System.out.println("node "+indexEntry(path).id()+" died"); // this node is now dead so its leaf info is removed removeLeafInfo(indexEntry(path).id()); return newSplitInfo; } } public void printInfo() { System.out.println("current tree version: "+currentVersion); System.out.println("Hashtable: "+leafInfoMap); System.out.println("Heap: "+leafInfoHeap); System.out.println("number of current leafs: "+leafInfoMap.size()+"\n"); System.out.println(leafInfoMap.entrySet().size()); for(Map.Entry<Object,LeafInfo> entry : leafInfoMap.entrySet()) { LeafInfo leafInfo = entry.getValue(); System.out.println(leafInfo); Stack path = pathToNode(leafInfo.getReferenceKey(), currentVersion, 0); if (!indexEntry(path).id().equals(leafInfo.getId())) { System.out.println("wrong id: leaf info: "+entry.getKey()+", stack: "+indexEntry(path).id()); } } } }