/* 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.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Stack; import java.util.Map.Entry; import xxl.core.collections.MapEntry; import xxl.core.collections.MappedList; import xxl.core.collections.containers.Container; import xxl.core.collections.queues.DynamicHeap; import xxl.core.collections.queues.ListQueue; import xxl.core.collections.queues.Queue; import xxl.core.collections.queues.Queues; import xxl.core.cursors.Cursor; import xxl.core.cursors.Cursors; import xxl.core.cursors.filters.Filter; import xxl.core.cursors.mappers.Mapper; import xxl.core.cursors.sources.EmptyCursor; import xxl.core.functions.AbstractFunction; import xxl.core.functions.Constant; import xxl.core.functions.Function; import xxl.core.io.converters.BooleanConverter; import xxl.core.io.converters.IntegerConverter; import xxl.core.io.converters.MeasuredConverter; import xxl.core.predicates.AbstractPredicate; import xxl.core.predicates.Predicate; /** * This Class is an implementation of MVBT index structure * "An asymptotically optimal multiversion B-tree" * Becker Bruno, Gschwind Stephan, Ohler Thomas, Seeger Bernhard and Widmayer, Peter * VLDB 1996 * */ public class MVBTree extends BPlusTree { /** Defalut value for KeyDomain minValue*/ public static final Integer DEAFUALT_KEYDOMAIN_MINVALUE = Integer.MIN_VALUE; /** Is used to define the bounds of the <tt>strong version condition</tt>.*/ protected final float EPSILON; /**A <tt>BPlusTree</tt> to store the roots of the <tt>MVBTree</tt>.*/ public BPlusTree roots; /** The current version of the <tt>MVBTree</tt>.*/ public Version currentVersion; /**A <tt>MeasuredConverter</tt> to convert <tt>Version-Objects</tt>.*/ protected MeasuredConverter versionConverter; /**A <tt>MeasuredConverter</tt> to convert <tt>Lifespans</tt>.*/ protected MeasuredConverter lifespanConverter; /**A <tt>MeasuredConverter</tt> to convert <tt>Roots</tt>.*/ protected MeasuredConverter rootConverter; /** Oldest relevant version - all elements before that version can be purged */ protected Version cutoffVersion; public Comparable keyDomainMinValue; /** * Contains information about one block of the MVBTree, i.e. the block's * ID in the underlying container and the block's deletion version. */ protected static class BlockDelInfo implements Comparable { protected Object id; protected Version del; public BlockDelInfo(Object id, Version del) { this.id = id; this.del = del; } public Object getId() { return id; } public Version getDeletionVersion() { return del; } public int compareTo(Object obj) { return del.compareTo(((BlockDelInfo)obj).del); } } /** * This queue contains an entry for each dead block of the MVBTree. The heap's * root is the next block which will be purged. */ protected Queue<BlockDelInfo> purgeQueue = new ListQueue<BlockDelInfo>(); /**Creates a new <tt>MVBTree</tt>. * KeyDomainMinValue set to default value @see {@link MVBTree#DEAFUALT_KEYDOMAIN_MINVALUE} * @param blockSize the block size of the underlaying <tt>Container</tt>. * @param minCapRatio the minimal capacity ratio of the tree's nodes. * @param e the epsilon of the <tt>strong version condition</tt>. */ public MVBTree(int blockSize, float minCapRatio, float e) { super(blockSize, minCapRatio); EPSILON=e; roots=new BPlusTree(blockSize); this.keyDomainMinValue = MVBTree.DEAFUALT_KEYDOMAIN_MINVALUE; } /**Creates a new <tt>MVBTree</tt>. The minimal capacity ratio of the tree's nodes is set ot 50%. * @param blockSize the block size of the underlaying <tt>Container</tt>. * @param e the epsilon of the <tt>strong version condition</tt>. */ public MVBTree(int blockSize, float e) { this(blockSize, 0.5f, e); } /**Creates a new <tt>MVBTree</tt>. The minimal capacity ratio of the tree's nodes is set ot 50%. * @param blockSize the block size of the underlaying <tt>Container</tt>. * @param e the epsilon of the <tt>strong version condition</tt>. */ public MVBTree(int blockSize, float e, Comparable keyDomainMinValue) { this(blockSize, 0.5f, e, keyDomainMinValue); } public MVBTree(int blockSize, float minCapRatio, float e, Comparable keyDomainMinValue ) { super(blockSize, minCapRatio); EPSILON=e; roots=new BPlusTree(blockSize); this.keyDomainMinValue = keyDomainMinValue; } /** Initializes the <tt>MVBTree</tt> with the given parameters. * @param getKey a <tt>Function</tt> to extract the key of a data object. * @param rootsContainer the <tt>Container</tt> of the <tt>BPlusTree</tt> which is used to store the historical * roots of the <tt>MVBTree</tt>. * @param treeContainer the <tt>Container</tt> which is used by the <tt>MVBTree</tt> to store its <tt>Nodes</tt>. * @param versionConverter a <tt>Converter</tt> to convert <tt>Versions</tt>. * @param keyConverter a <tt>Converter</tt> to convert keys. * @param dataConverter a <tt>Converter</tt> to convert the data objects stored in the <tt>MVBTree</tt>. * @param createMVSeparator a <tt>Function</tt> to create <tt>MVSeparators</tt>. * @param createMVRegion a <tt>Function</tt> to create <tt>MVRegion</tt>. * @return the initialized <tt>MVBTree</tt>. */ public MVBTree initialize( Function getKey, final Container rootsContainer, final Container treeContainer, MeasuredConverter versionConverter, MeasuredConverter keyConverter, MeasuredConverter dataConverter, Function createMVSeparator, Function createMVRegion) { Function getRootsContainer = new Constant(rootsContainer); Function determineRootsContainer= getRootsContainer; Function getContainer = new Constant(treeContainer); Function determineContainer= new Constant(treeContainer); return initialize(getKey, getRootsContainer, determineRootsContainer, getContainer, determineContainer, versionConverter, keyConverter, dataConverter, createMVSeparator, createMVRegion); } /** * * @param rootEntry * @param rootsRootEntry * @param getKey * @param rootsContainer * @param treeContainer * @param versionConverter * @param keyConverter * @param dataConverter * @param createMVSeparator * @param createMVRegion * @return */ public MVBTree initialize(IndexEntry rootEntry, Descriptor liveRootDescriptor, IndexEntry rootsRootEntry, Descriptor rootsRootDescriptor, final Function getKey, final Container rootsContainer, final Container treeContainer, MeasuredConverter versionConverter, MeasuredConverter keyConverter, MeasuredConverter dataConverter, Function createMVSeparator, Function createMVRegion){ this.versionConverter=versionConverter; this.lifespanConverter=lifespanConverter(); Function getSplitMinRatio= new Constant(new Float((1+EPSILON)*minCapacityRatio)); Function getSplitMaxRatio= new Constant(new Float(1- EPSILON*minCapacityRatio)); Function newGetKey= new AbstractFunction() { public Object invoke(Object entry) { if(entry instanceof LeafEntry) return getKey.invoke(((LeafEntry)entry).data()); return getKey.invoke(entry); } }; super.initialize(rootEntry, liveRootDescriptor, newGetKey, treeContainer, keyConverter, dataConverter, createMVSeparator, createMVRegion, getSplitMinRatio, getSplitMaxRatio); this.getDescriptor= new AbstractFunction() { public Object invoke(Object entry) { if(entry instanceof MVSeparator) return entry; if(entry instanceof IndexEntry) return ((IndexEntry)entry).separator; if(entry instanceof LeafEntry) { LeafEntry leafEntry=(LeafEntry)entry; return createMVSeparator( leafEntry.lifespan.beginVersion(), leafEntry.lifespan.endVersion(), key(leafEntry.data)); } throw new IllegalArgumentException(entry.toString()); } }; this.rootConverter=rootConverter(lifespanConverter); roots.initialize(rootsRootEntry,rootsRootDescriptor, new AbstractFunction() { public Object invoke(Object object) { // Roots have always bounded intervals return ((Root)object).lifespan().endVersion(); } }, rootsContainer, //lifespanConverter, versionConverter, this.rootConverter, LifeSpanSeparator.FACTORY_FUNCTION, Lifespan.FACTORY_FUNCTION); // old code // due to changes in BPlusTree // roots.getDescriptor=new AbstractFunction() { // public Object invoke(Object object) { // if(object instanceof Root){ // //TODO new Code // LifeSpanSeparator separator = // new LifeSpanSeparator(((Root)object).lifespan().beginVersion(), // ((Root)object).lifespan().endVersion()); // return separator; // } // return ((IndexEntry)object).separator(); // } // }; this.underflows = new AbstractPredicate() { public boolean invoke(Object object) { Node node = (Node)object; return node.countCurrentEntries() < (node.level() == 0 ? D_LeafNode : D_IndexNode); } }; if (!checkFactors(B_LeafNode, D_LeafNode, EPSILON, B_LeafNode / D_LeafNode)) throw new IllegalArgumentException(); if (!checkFactors(B_IndexNode, D_IndexNode, EPSILON, B_IndexNode / D_IndexNode)) throw new IllegalArgumentException(); return this; } /** Initializes the <tt>MVBTree</tt> with the given parameters. * @param getKey a <tt>Function</tt> to extract the key of a data object. * @param getRootsContainer the new @link{BPlusTree#getContainer <tt>Function "getContainer"</tt>} of the * <tt>BPlusTree</tt> which is used to store the historical roots of the <tt>MVBTree</tt>. * @param determineRootsContainer the new @link{BPlusTree#determineContainer <tt>Function "determineContainer"</tt>} * of the <tt>BPlusTree</tt> which is used to store the historical roots of the <tt>MVBTree</tt>. * @param getContainer a <tt>Function</tt> which gives the <tt>Container</tt> in which a particular <tt>Node</tt> * of the <tt>MVBTree</tt> is sotred. * @param determineContainer a <tt>Function</tt> which gives the <tt>Container</tt> in which a new created <tt>Node</tt> * has to be stored. * @param versionConverter a <tt>Converter</tt> to convert <tt>Versions</tt>. * @param keyConverter a <tt>Converter</tt> to convert keys. * @param dataConverter a <tt>Converter</tt> to convert the data objects stored in the <tt>MVBTree</tt>. * @param createMVSeparator a <tt>Function</tt> to create <tt>MVSeparators</tt>. * @param createMVRegion a <tt>Function</tt> to create <tt>MVRegion</tt>. * @return the initialized <tt>MVBTree</tt>. */ public MVBTree initialize( final Function getKey, Function getRootsContainer, Function determineRootsContainer, Function getContainer, Function determineContainer, MeasuredConverter versionConverter, MeasuredConverter keyConverter, MeasuredConverter dataConverter, Function createMVSeparator, Function createMVRegion) { this.versionConverter=versionConverter; this.lifespanConverter=lifespanConverter(); Function getSplitMinRatio= new Constant(new Float((1+EPSILON)*minCapacityRatio)); Function getSplitMaxRatio= new Constant(new Float(1- EPSILON*minCapacityRatio)); Function newGetKey= new AbstractFunction() { public Object invoke(Object entry) { if(entry instanceof LeafEntry) return getKey.invoke(((LeafEntry)entry).data()); return getKey.invoke(entry); } }; super.initialize(newGetKey, getContainer, determineContainer, keyConverter, dataConverter, createMVSeparator, createMVRegion, getSplitMinRatio, getSplitMaxRatio); this.getDescriptor= new AbstractFunction() { public Object invoke(Object entry) { if(entry instanceof MVSeparator) return entry; if(entry instanceof IndexEntry) return ((IndexEntry)entry).separator; if(entry instanceof LeafEntry) { LeafEntry leafEntry=(LeafEntry)entry; return createMVSeparator( leafEntry.lifespan.beginVersion(), leafEntry.lifespan.endVersion(), key(leafEntry.data)); } throw new IllegalArgumentException(entry.toString()); } }; this.rootConverter=rootConverter(lifespanConverter); roots.initialize( new AbstractFunction() { public Object invoke(Object object) { // Roots have always bounded intervals return ((Root)object).lifespan().endVersion(); } }, getRootsContainer, determineRootsContainer, //lifespanConverter, versionConverter, this.rootConverter, LifeSpanSeparator.FACTORY_FUNCTION, Lifespan.FACTORY_FUNCTION); // old code // due to changes in BPlusTree // roots.getDescriptor=new AbstractFunction() { // public Object invoke(Object object) { // if(object instanceof Root){ // // // LifeSpanSeparator separator = // new LifeSpanSeparator(((Root)object).lifespan().beginVersion(), // ((Root)object).lifespan().endVersion()); // return separator; // } // return ((IndexEntry)object).separator(); // } // }; this.underflows = new AbstractPredicate() { public boolean invoke(Object object) { Node node = (Node)object; return node.countCurrentEntries() < (node.level() == 0 ? D_LeafNode : D_IndexNode); } }; if (!checkFactors(B_LeafNode, D_LeafNode, EPSILON, B_LeafNode / D_LeafNode)) throw new IllegalArgumentException(); if (!checkFactors(B_IndexNode, D_IndexNode, EPSILON, B_IndexNode / D_IndexNode)) throw new IllegalArgumentException(); return this; } private static boolean checkFactors(int b,int d,double e,int k) { return (k>= (2+(3*e)-(1/(double)d))) && (e<=1-1/(double)d); } /** Gives the <tt>Converter</tt> used by the <tt>MVBTree</tt> to convert the <tt>Lifespans</tt>. * @return the <tt>Converter</tt> used by the <tt>MVBTree</tt> to convert the <tt>Lifespans</tt>. */ protected MeasuredConverter lifespanConverter() { return new MeasuredConverter() { public Object read(DataInput input, Object object) throws IOException { Version in=(Version) versionConverter.read(input,null); Lifespan life=new Lifespan(in); boolean isDead= BooleanConverter.DEFAULT_INSTANCE.readBoolean(input); if(isDead) { Version del=(Version) versionConverter.read(input,null); life.delete(del); } return life; } public void write(DataOutput output, Object object) throws IOException { Lifespan lifespan=(Lifespan)object; //Version versionConverter.write(output,lifespan.beginVersion()); //Boolean BooleanConverter.DEFAULT_INSTANCE.writeBoolean(output, lifespan.isDead()); //Version if(lifespan.isDead())versionConverter.write(output,lifespan.endVersion()); } public int getMaxObjectSize() { return 2* versionConverter.getMaxObjectSize()+ BooleanConverter.SIZE; } }; } /** Gives the <tt>Converter</tt> used by the <tt>MVBTree</tt> to convert the historical <tt>Roots</tt>. * @return the <tt>Converter</tt> used by the <tt>MVBTree</tt> to convert the historical <tt>Roots</tt>. */ protected MeasuredConverter rootConverter(final MeasuredConverter lifespanConverter) { return new MeasuredConverter() { public Object read(DataInput input, Object object) throws IOException { Lifespan lifespan=(Lifespan)lifespanConverter.read(input,null); Comparable minBound=(Comparable) keyConverter.read(input,null); Comparable maxBound=(Comparable) keyConverter.read(input,null); MVRegion mvReg= createMVRegion(lifespan.beginVersion(), lifespan.endVersion(), minBound, maxBound); Object rootId= MVBTree.this.container().objectIdConverter().read(input,null); int parentLevel= IntegerConverter.DEFAULT_INSTANCE.readInt(input); return new Root(mvReg, rootId, parentLevel); } public void write(DataOutput output, Object object) throws IOException { Root root=(Root)object; //Lifespan lifespanConverter.write(output, root.lifespan()); //Key Range keyConverter.write(output, root.getRegion().minBound()); keyConverter.write(output, root.getRegion().maxBound()); //Id container().objectIdConverter().write(output, root.rootNodeId()); //parent Level IntegerConverter.DEFAULT_INSTANCE.writeInt(output, root.parentLevel()); } public int getMaxObjectSize() { return lifespanConverter.getMaxObjectSize()+2*keyConverter.getMaxObjectSize()+roots.container().getIdSize()+IntegerConverter.SIZE; //return 2*keyConverter.getMaxObjectSize()+roots.container().getIdSize()+IntegerConverter.SIZE; } }; } /** Creates a new <tt>Node</tt>. * @return the new created <tt>Node</tt>. */ public Tree.Node createNode(int level) { return new Node(level); } /** Creates a new <tt>LeafEntry</tt>. * @param lifespan the <tt>Lifespan</tt> of the new <tt>LeafEntry</tt>. * @param data the data object which is to store in the new <tt>LeafEntry</tt>. * @return the new created <tt>LeafEntry</tt>. */ protected LeafEntry createLeafEntry(Lifespan lifespan, Object data) { return new LeafEntry(lifespan, data); } /** Creates a new <tt>NodeConverter</tt>. * @return the new created <tt>NodeConverter</tt>. */ protected BPlusTree.NodeConverter createNodeConverter() { return new NodeConverter(); } /** Creates a new <tt>MVSeparator</tt> with the given parameters. * @param insertVersion the insertion <tt>Version</tt> of the associated entry. * @param deleteVersion the deletion <tt>Version</tt> of the associated entry. * @param sepValue the key used as a separator. * @return the new created <tt>MVSeparator</tt>. */ public MVSeparator createMVSeparator(Version insertVersion, Version deleteVersion, Comparable sepValue) { // CHANGE: convert array to list return (MVSeparator)createSeparator.invoke(java.util.Arrays.asList( new Object[] { insertVersion, deleteVersion, sepValue})); } /** Creates a new <tt>MVRegion</tt>. * @param beginVersion the begin <tt>Version</tt> of the <tt>MVRegion</tt>. * @param endVersion the end <tt>Version</tt> of the <tt>MVRegion</tt>. * @param minBound the minimal bound of the <tt>MVRegion</tt>. * @param maxBound the maximal bound of the <tt>MVRegion</tt>. * @return the new created <tt>MVRegion</tt>. */ public MVRegion createMVRegion(Version beginVersion, Version endVersion, Comparable minBound, Comparable maxBound) { // CHANGE: convert array to list return (MVRegion)createKeyRange.invoke(java.util.Arrays.asList( new Object[] { beginVersion, endVersion, minBound, maxBound})); } /** Gives the <tt>BPlusTree</tt> used to manage the historical <tt>Roots</tt> of the <tt>MVBTree</tt>. * @return the <tt>BPlusTree</tt> used to manage the historical <tt>Roots</tt> of the <tt>MVBTree</tt>. */ public BPlusTree rootsTree() { return roots; } /** Determines the <tt>Root</tt> which is appropriate to the given <tt>Version</tt>. * @param version the <tt>Version</tt> whose <tt>Root</tt> is required. * @return an <tt>IndexEntry</tt> pointing to the appropriate <tt>root Node</tt>. */ public IndexEntry determineRootEntry(Version version) { Version lastRootSplitVersion= ((MVSeparator)((IndexEntry)rootEntry).separator()).insertVersion(); if((version==null)||(version.compareTo(lastRootSplitVersion)>=0)){ return (IndexEntry)rootEntry; } // Daniar: new code IndexEntry indexEntry = null; Root root = null; Cursor rootsCursor = roots.rangeQuery(version, lastRootSplitVersion); if(rootsCursor.hasNext()){ root = (Root) rootsCursor.next(); indexEntry = root.toIndexEntry(); } // old code // ((Root)roots.exactMatchQuery(version)).toIndexEntry(); return indexEntry; } /** Gives the current <tt>Version</tt> of the <tt>MVBTree</tt>, i.e. the <tt>Version</tt> of the last update * operation (insertion, deletion or update). * @return the current <tt>Version</tt> of the <tt>MVBTree</tt>. */ public Version currentVersion() { return currentVersion; } /** This method is normaly used during an update operation (insertion, deletion or update) to update the current * <tt>Version</tt> of the <tt>MVTree</tt>. Also it checks whether this chage is valid, i.e. the new * <tt>Version</tt> has to be greater than the old current <tt>Version</tt>. * @param version the new current <tt>Version</tt>. */ public void setCurrentVersion(Version version) { if((currentVersion!=null)&&(version.compareTo(currentVersion)<0)) throw new UnsupportedOperationException("Update Operations into a past version " + "are not supported by MVBTree."); currentVersion=(Version)version.clone(); } /** * Sets the cutoff version. The cutoff version is the oldest version whose * elements are to be kept. All versions before the cutoff version can be purged. * * @param version cutoff version */ public void setCutoffVersion(Version version) { if (version == null) throw new IllegalArgumentException(); if (cutoffVersion != null && version.compareTo(cutoffVersion) < 0) throw new IllegalArgumentException("New cutoff version must be greater than or equal to the old cutoff version."); if (version.compareTo(currentVersion) > 0) throw new IllegalArgumentException("Cutoff version must be smaller than or equal to the current version."); cutoffVersion = (Version)version.clone(); purge(); } /** * Removes all blocks from the container which died before the cutoff version. */ protected void purge() { if (cutoffVersion != null) while (!purgeQueue.isEmpty() && purgeQueue.peek().getDeletionVersion().compareTo(cutoffVersion) <= 0) { BlockDelInfo info = purgeQueue.dequeue(); ((Container)getContainer.invoke()).remove(info.getId()); } } /** Converts a <tt>MVSeparator</tt> into a <tt>MVRegion</tt> on the following wise: * <ul> * <li> mvSeparator.insertVersion() --> begin <tt>Version</tt> of the <tt>MVRegion</tt> </li> * <li> mvSeparator.deleteVersion() --> end <tt>Version</tt> of the <tt>MVRegion</tt> </li> * <li> mvSeparator.sepValue() --> begin key of the <tt>MVRegion</tt> </li> * <li> mvSeparator.sepValue() --> end key of the <tt>MVRegion</tt> </li> * </ul> * @param mvSeparator the <tt>MVSeparator</tt> which is to convert. * @return the new <tt>MVRegion</tt>. */ public MVRegion toMVRegion(MVSeparator mvSeparator) { MVRegion mvReg=createMVRegion(mvSeparator.insertVersion(), mvSeparator.deleteVersion(), mvSeparator.sepValue(), mvSeparator.sepValue()); return mvReg; } /** Converts a <tt>MVRegion</tt> into a <tt>MVSeparator</tt> on the following wise: * <ul> * <li> region.beginVersion() --> insertion <tt>Version</tt> of the <tt>MVSeparator</tt> </li> * <li> region.endVersion() --> deletion <tt>Version</tt> of the <tt>MVSeparator</tt> </li> * <li> region.minBound() --> separation key of the <tt>MVSeparator</tt> </li> * <li> region.maxBound() --> will be ignored. </li> * </ul> * @param region the <tt>MVRegion</tt> which is to convert. * @return the new <tt>MVSeparator</tt>. */ public MVSeparator toMVSeparator(MVRegion region) { return createMVSeparator(region.beginVersion(), region.endVersion(), region.minBound()); } /** Computes the region of the subtree pointed by the given entry. * @param entry the root of the subtree. * @param path the path from the root to the <tt>Node</tt> in which the given entry is stored. * @return the region of the subtree pointed by the given entry. */ protected MVRegion computeRegion(IndexEntry entry, Stack path) { IndexEntry indexEntry=entry; MVRegion region=toMVRegion((MVSeparator)indexEntry.separator()); if(!path.isEmpty()) { Node parentNode=(Node)node(path); int index= parentNode.entries.indexOf(indexEntry); boolean adjusted=false; if(index<parentNode.number()-1) { MVSeparator nextSep= (MVSeparator)separator(parentNode.getEntry(index+1)); if(nextSep.lifespan().overlaps(region.lifespan()) && nextSep.sepValue().compareTo(region.minBound())>0) { region.updateMaxBound(nextSep.sepValue()); adjusted=true; } } if(!adjusted) { IndexEntry parent=(IndexEntry)indexEntry(path); Object pathEntry= path.pop(); MVRegion parentRegion= computeRegion(parent, path); path.push(pathEntry); region.updateMaxBound(parentRegion.maxBound()); } } else region.union(rootDescriptor, false); return region; } /** * */ protected Stack pathToNode(KeyRange nodeRegion, int level) { MVRegion region=(MVRegion)nodeRegion; IndexEntry indexEntry= determineRootEntry(region.beginVersion()); Comparable parentMax= ((MVRegion)rootDescriptor).maxBound(); //indexEntry.separator.maxBound(); Stack path= new Stack(); down(path, indexEntry); Node pathNode=(Node)node(path); while(pathNode.level() > level) { boolean found=false; for(int i = 0; !found && (i < pathNode.number()); i++) { MVRegion subRegion = toMVRegion((MVSeparator)separator(pathNode.getEntry(i))); subRegion.updateMaxBound(parentMax); if(i < pathNode.number() - 1) { MVSeparator nextSep = (MVSeparator)separator(pathNode.getEntry(i+1)); if (nextSep.lifespan.overlaps(subRegion.lifespan()) && (nextSep.sepValue().compareTo(subRegion.minBound()) > 0)) subRegion.updateMaxBound(separator(pathNode.getEntry(i+1)).sepValue()); } if(subRegion.contains((Descriptor)region)) { indexEntry=(IndexEntry)pathNode.getEntry(i); found=true; } } if(!found) throw new IllegalStateException(); down(path, indexEntry); pathNode=(Node)node(path); } return path; } /** * Builds a path from the appropriate root node to the node which * contains the given key in the given version. * * @param key key to query for. * @param version version to query for. * @param level tree level at which the search should stop. * @return path from a root node to the correct node at the given level. */ protected Stack pathToNode(Object key, Version version, int level) { IndexEntry indexEntry = determineRootEntry(version); if (indexEntry == null) throw new IllegalStateException(); Stack path = new Stack(); down(path, indexEntry); Node node = (Node)node(path); while (node.level() > level) { MVSeparator lastSep = null; indexEntry = null; for (int i = 0; i < node.number(); i++) { MVSeparator sep = (MVSeparator)separator(node.getEntry(i)); if (sep.lifespan().contains(version) && sep.sepValue().compareTo(key) <= 0 && (lastSep == null || sep.sepValue().compareTo(lastSep.sepValue()) > 0)) { lastSep = sep; indexEntry = (IndexEntry)node.getEntry(i); } } if (indexEntry == null) throw new IllegalStateException(); down(path, indexEntry); node = (Node)node(path); } return path; } /** This method is called when the root node overflows and has been splitted. * A new root node must be created. The tree's height increases by one. * NOTE: In the opposite to {Tree#grow(Object entry)} this method does not * write the new root node into the container. This redurces one access to the underlaying <tt>Container</tt>. * @param entry the entry which should be inserted to the new root node. * It is normally the old rootEntry. * @return a MapEntry whose key is the new rootEntry and whose value is the * new root node. */ protected Entry grow (Object entry) { boolean virgin= rootEntry==null; Node rootNode = (Node)createNode(height()); Tree.Node.SplitInfo splitInfo = rootNode.initialize(entry); rootEntry = createIndexEntry(height()+1).initialize(splitInfo); ((IndexEntry)rootEntry).initialize(toMVSeparator((MVRegion)rootDescriptor)); if(virgin) { Object id= rootEntry.container().insert(rootNode); rootNode.onInsert(id); rootEntry.initialize(id); } return new MapEntry(rootEntry, rootNode); } /** Unsupported Operation. * @throws UnsupportedOperationException */ public void insert(Object data) { throw new UnsupportedOperationException("You have to define the insertion version. \n" + "Please use the method: insert(Version, Object)"); } /** Unsupported Operation. * @throws UnsupportedOperationException */ public void update (Object oldData, Object newData) { throw new UnsupportedOperationException("You have to define the update version. \n" + "Please use the method: update(Version, Object, Object)"); } /** Unsupported Operation. * @throws UnsupportedOperationException */ public Object remove(Object data) { throw new UnsupportedOperationException("You have to define the deletion version. \n" + "Please use the method: remove(Version, Object)"); } /** Inserts a data object into the <tt>MVTree</tt>. * @param insertVersion the insertion <tt>Version</tt>. * @param data the data object which is to insert. */ public void insert(Version insertVersion, Object data) { setCurrentVersion(insertVersion); LeafEntry leafEntry = createLeafEntry(new Lifespan(insertVersion,null), data); insert(insertVersion, leafEntry, (MVSeparator)separator(leafEntry), 0); } /** Replaces the <tt>oldObject</tt> by the <tt>newObject</tt>. * @param updateVersion the <tt>Version</tt> of this Operation. * @param oldData the data object stored in the <tt>MVBTree</tt> which is to replace. * @param newData the new data object. */ public void update (Version updateVersion, Object oldData, Object newData) { Version inVer=currentVersion(); setCurrentVersion(updateVersion); LeafEntry oldEntry=createLeafEntry(new Lifespan(inVer, null), oldData); super.update(oldEntry, newData); } /** * Removes a data object from the <tt>MVBTree</tt>. * * @param removeVersion the <tt>Version</tt> of this Operation. * @param data the data object which is to remove. * @return the removed data object or null if no such element is stored in the <tt>MVBTree</tt>. */ public Object remove_old(Version removeVersion, Object data) { setCurrentVersion(removeVersion); Object obj = super.remove(data); return obj; } /** * Removes a data object from the <tt>MVBTree</tt>. * * @param removeVersion the <tt>Version</tt> of this Operation. * @param data the data object which is to remove. * @return the removed data object or null if no such element is stored in the <tt>MVBTree</tt>. */ public Object remove(Version removeVersion, Object data) { setCurrentVersion(removeVersion); Object key = getKey.invoke(data); Stack path = pathToNode(key, currentVersion(), 0); Iterator it = ((MVBTree.Node)node(path)).iterator(); LeafEntry removed = null; while (it.hasNext()) { LeafEntry obj = (LeafEntry)it.next(); if (obj.data().equals(data) && obj.getLifespan().isAlive()) { it.remove(); removed = obj; break; } } treatUnderflow(path); return removed; } /** Inserts a data object into <tt>MVBTree</tt>. * @param insertVersion the <tt>Version</tt> of this Operation. * @param data the data object which is to store. * @param mvSep the <tt>MVSeparator</tt> of the data object. * @param targetLevel the target level on which the insertion has to stop (leaf level=0). */ protected void insert(Version insertVersion, Object data, MVSeparator mvSep, int targetLevel) { if (rootEntry()==null) { // rootDescriptor = createMVRegion(mvSep.insertVersion(), null, mvSep.sepValue(), mvSep.sepValue()); // TODO: set separator value to minimum of key domain //new code test rootDescriptor = createMVRegion(mvSep.insertVersion(), null, this.keyDomainMinValue , mvSep.sepValue()); // rootDescriptor = createMVRegion(mvSep.insertVersion(), null, mvSep.sepValue(), mvSep.sepValue()); grow(data); } else { // super.insert(data, mvSep, targetLevel); Stack path = new Stack(); chooseLeaf(mvSep, targetLevel, path).growAndPost(data, path); ((IndexEntry)rootEntry).separator().union(mvSep); } } /** * Removes a data object form the <tt>MVBTree</tt>. * * @param data the data object which is to remove. * @param equals a <tt>Predicate</tt> which is used to check whether an element of the <tt>MVBTree</tt> equals * the given data object. * @return the removed data object or null if no such element is stored in the <tt>MVBTree</tt>. */ public Object remove(final Object data, final Predicate equals) { Comparable key=key(data); Version inVer=currentVersion(); MVRegion querySep= createMVRegion(inVer, null, key, key); return super.remove(querySep, 0, new AbstractPredicate() { public boolean invoke (Object object) { return equals.invoke(object, data); } } ); } /** Gives the maximal number of entries which a new created <tt>Node</tt> may contain after a split. * @return the maximal number of entries which a new created <tt>Node</tt> may contain after a split. */ protected int strongUpperBound(int level) { float maxRatio= ((Float) getSplitMaxRatio.invoke()).floatValue(); return (int)(maxRatio*(level == 0 ? getLeafNodeB() : getIndexNodeB())); } /** Gives the minimal number of entries which a new created <tt>Node</tt> may contain after a split. * @return the minimal number of entries which a new created <tt>Node</tt> may contain after a split. */ protected int strongLowerBound(int level) { float maxRatio= ((Float) getSplitMinRatio.invoke()).floatValue(); return (int)(maxRatio*(level == 0 ? getLeafNodeB() : getIndexNodeB())); } /** * Searches the data object stored in the given <tt>Version</tt> of the <tt>MVBTree</tt> with the given key. * @param key the key of the element which is to search. * @param version the <tt>Version</tt> in which the element is to search. * @return the found object if the seach is successful and null otherwise. */ public Object exactMatchQuery_old(Comparable key, Version version) { Cursor result= rangePeriodQuery(key,key,version, version); if (result.hasNext()) { Object obj = result.next(); result.close(); return obj; } else return null; } /** * Searches the data object stored in the given <tt>Version</tt> of the <tt>MVBTree</tt> with the given key. * * @param key the key of the element which is to search. * @param version the <tt>Version</tt> in which the element is to search. * @return the found object if the search is successful and null otherwise. */ public Object exactMatchQuery(Comparable key, Version version) { Stack path = pathToNode(key, version, 0); Iterator it = ((MVBTree.Node)node(path)).iterator(); LeafEntry found = null; while (it.hasNext()) { LeafEntry obj = (LeafEntry)it.next(); if (getKey.invoke(obj.data()).equals(key)) { found = obj; break; } } while (!path.isEmpty()) up(path); return found; } /** Searches all elements which are stored in the <tt>MVBTree</tt> with the given key and valid in the version * range <tt>[beginVersion, endVersion]</tt>. * @param key the key of the elements. * @param beginVersion the begin of the version range of the query. * @param endVersion the end of the version range of the query. * @return a lazy <tt>Cursor</tt> pointing to all query responses. */ public Cursor timePeriodQuery(Comparable key, Version beginVersion, Version endVersion) { return rangePeriodQuery(key,key,beginVersion, endVersion); } /** Searches all elements stored in the given <tt>Version</tt> of the <tt>MVBTree</tt> and whose keys lie in the * key range <tt>[min, max]</tt>. * @param min the minimal bound of the key range of the query. * @param max the maximal bound of the key range of the query. * @param version the <tt>Version</tt> in which the query is to execute. * @return a lazy <tt>Cursor</tt> pointing to all query responses. */ public Cursor keyRangeQuery(Comparable min, Comparable max, Version version) { return rangePeriodQuery(min,max,version,version); // return ( version.compareTo(currentVersion())>= 0)? treequery(createMVRegion(version, version, min, max)) : // rangePeriodQuery(min,max,version,version); // return treequery(createMVRegion(version, version, min, max)); } /** Searches all elements stored in the <tt>CurrentVersion</tt> of the <tt>MVBTree</tt> and whose keys lie in the * key range <tt>[min, max]</tt>. * @param min the minimal bound of the key range of the query. * @param max the maximal bound of the key range of the query. * @param version the <tt>Version</tt> in which the query is to execute. * @return a lazy <tt>Cursor</tt> pointing to all query responses. */ public Cursor rangeQuery(Comparable min, Comparable max) { return rangePeriodQuery(min,max,currentVersion(),currentVersion()); } /** * Searches the single data object with the given key stored in the * in the <tt>CurrentVersion</tt> of the <tt>MVBTree</tt>. * * @param key * the key of the data object which has to be searched * @return the single data object with the given key or <tt>null</tt> if * no such object could be found * @throws UnsupportedOperationException * when the tree is in duplicate mode */ public Object exactMatchQuery(Comparable key) { return exactMatchQuery(key,currentVersion()) ; } /** Searches all elements stored in the <tt>MVBTree</tt> which are valid in the version range * <tt>[beginVersion, endVersion]</tt> and whose keys lie in the key range <tt>[min, max]</tt>. * @param min the minimal bound of the key range of the query. * @param max the maximal bound of the key range of the query. * @param beginVersion the begin of the version range of the query. * @param endVersion the end of the version range of the query. * @return a lazy <tt>Cursor</tt> pointing to all query responses. */ public Cursor rangePeriodQuery(Comparable min, Comparable max, Version beginVersion, Version endVersion) { MVRegion queryRegion= createMVRegion(beginVersion, endVersion, min, max); IndexEntry root = null; root = determineRootEntry(endVersion); return query(root, queryRegion); } /** * Test Method * @param min * * @param max * @param beginVersion * @param endVersion * @param keyComparator * @return */ public Cursor rangePriorityQuery(Comparable min, Comparable max, Version beginVersion, Version endVersion, Comparator keyComparator) { MVRegion queryRegion= createMVRegion(beginVersion, endVersion, min, max); return new PriorityQueryCursor(new DynamicHeap(keyComparator), 0, queryRegion); } /** Searches all elements stored in the <tt>MVBTree</tt> that lie in the given <tt>MVRegion</tt>. * @param subRootEntry the root of subtree in which the query is to execute. * @param queryRegion the <tt>MVRegion</tt> which specifies the query. * @return a lazy <tt>Cursor</tt> pointing to all query responses. */ public Cursor query(IndexEntry subRootEntry, final MVRegion queryRegion) { return query(subRootEntry, queryRegion, 0); } /** Searches all elements stored in the <tt>MVBTree</tt> that lie in the given <tt>MVRegion</tt>. * @param subRootEntry the root of subtree in which the query is to execute. * @param queryRegion the <tt>MVRegion</tt> which specifys the query. * @param targetLevel has to be zero (leaf level). * @return a lazy <tt>Cursor</tt> pointing to all query responses. */ public Cursor query(IndexEntry subRootEntry, final BPlusTree.KeyRange queryRegion, final int targetLevel) { if(targetLevel!=0) throw new IllegalArgumentException("Target level must be zero!"); return linkReferenceQuery(subRootEntry, (MVRegion)queryRegion, new Stack()); } public Cursor query(Descriptor queryDescriptor, int targetLevel) { MVRegion region; if(queryDescriptor instanceof MVSeparator) region=toMVRegion((MVSeparator)queryDescriptor); else region=(MVRegion)queryDescriptor; return rangePeriodQuery(region.minBound(),region.maxBound(), region.beginVersion(), region.endVersion()); } /** Uses the so called "Link Reference Method" to execute the query specified by the given <tt>MVRegion</tt>. * @param indexEntry the root of the subtree in which the query is to execute. * @param queryRegion the <tt>MVRegion</tt> which specifys the query. * @param path the path from the root to the <tt>Node</tt> in which the given <tt>IndexEntry</tt> is stored. * @return a lazy <tt>Cursor</tt> pointing to all query responses. */ protected Cursor linkReferenceQuery(IndexEntry indexEntry, MVRegion queryRegion, Stack path) { if (cutoffVersion != null && queryRegion.lifespan().beginVersion().compareTo(cutoffVersion) < 0) throw new IllegalArgumentException("Cannot query versions before cutoff version."); MVRegion nodeRegion=computeRegion(indexEntry, path); return linkReferenceQuery(indexEntry, nodeRegion, queryRegion, path); } /** Uses the so called "Link Reference Method" to execute the query specified by the given <tt>MVRegion</tt>. * @param indexEntry the root of the subtree in which the query is to execute. * @param nodeRegion the <tt>MVRegion</tt> of the <tt>Node</tt> pointed by the given <tt>IndexEntry</tt>. * @param queryRegion the <tt>MVRegion</tt> which specifys the query. * @return a lazy <tt>Cursor</tt> pointing to all query responses. */ protected Cursor linkReferenceQuery(IndexEntry indexEntry, MVRegion nodeRegion, MVRegion queryRegion) { if(!nodeRegion.overlaps(queryRegion)){ return new EmptyCursor(); } return new MVBTreeQueryCursor(indexEntry, nodeRegion, queryRegion); } /** Uses the so called "Link Reference Method" to execute the query specified by the given <tt>MVRegion</tt>. * @param indexEntry the root of the subtree in which the query is to execute. * @param nodeRegion the <tt>MVRegion</tt> of the <tt>Node</tt> pointed by the given <tt>IndexEntry</tt>. * @param queryRegion the <tt>MVRegion</tt> which specifys the query. * @param path the path from the root to the <tt>Node</tt> in which the given <tt>IndexEntry</tt> is stored. * @return a lazy <tt>Cursor</tt> pointing to all query responses. */ protected Cursor linkReferenceQuery(IndexEntry indexEntry, MVRegion nodeRegion, MVRegion queryRegion, Stack path) { if(!nodeRegion.overlaps(queryRegion)) return new EmptyCursor(); return new MVBTreeQueryCursor(indexEntry, nodeRegion, queryRegion, path); } /** Treats the underflow which can accrue after a deletion. * @param path the path from the root to the <tt>Node</tt> from which the element has been removed. */ protected void treatUnderflow(Stack path) { treatOverflow(path); } /** Chooses the suitable <tt>IndexEntry</tt> from the given <tt>Iterator</tt> in which an insertion should be * continued. * @param mvSeparator the <tt>MVSeparator</tt> of the data object which is to insert. * @param entries an <tt>Iterator</tt> pointing to the <tt>IndexEntries</tt> from which a suitable on is to * choose. * @return the suitable <tt>IndexEntry</tt> in which an insertion should be continued. */ protected IndexEntry chooseEntry(MVSeparator mvSeparator, Iterator entries) { List currentEntries = new ArrayList(Math.max(MVBTree.this.B_LeafNode, MVBTree.this.B_IndexNode)); while(entries.hasNext()) { currentEntries.add(entries.next()); } int index= searchMinimumKey(currentEntries); if(index==-1) { return null; } IndexEntry subtree= (IndexEntry)currentEntries.get(index); Iterator iterator = currentEntries.iterator(); while(iterator.hasNext()) { IndexEntry entry= (IndexEntry)iterator.next(); if((entry.separator.compareTo(mvSeparator)<=0)&&(entry.separator.compareTo(subtree.separator)>0)){ subtree=entry; } } return subtree; } /** Creates a copy of the given entry. * @param entry a <tt>IndexEntry</tt> or <tt>LeafEntry</tt> which is to copy. * @return a copy of the given entry. */ protected Object copyEntry(Object entry) { if(entry instanceof LeafEntry) { LeafEntry leafEntry=(LeafEntry)entry; return createLeafEntry((Lifespan)leafEntry.lifespan.clone(), leafEntry.data()); } IndexEntry indexEntry=(IndexEntry)entry; IndexEntry cpy=(IndexEntry)createIndexEntry(indexEntry.parentLevel); cpy.initialize(indexEntry.id(), (Separator)indexEntry.separator.clone()); return cpy; } /**Searchs the entry from the given <tt>List</tt> with the smallest key. * @param entries a <tt>List</tt> containing the entries. * @return the position of the entry with the smallest key in the <tt>List</tt>. */ protected int searchMinimumKey(List entries) { if(entries.isEmpty()) return -1; int index=0; for(int i=1; i<entries.size(); i++) { index=separator(entries.get(i)).compareTo(separator(entries.get(index)))<0 ?i:index; } return index; } /** * * @param c1 * @param c2 * @return */ protected Comparable max(Comparable c1, Comparable c2) { if(c1==null) return c2; if(c2==null) return c1; if(c1.compareTo(c2)>=0) return c1; return c2; } /* * * FIXME remove in productive version: only for test purpose */ public void updateRootDescriptor(Descriptor newRootDescriptor){ this.rootDescriptor = newRootDescriptor; } /* * FIXME remove in productive version: only for test purpose */ public void updateRootEntry(Tree.IndexEntry entry){ this.rootEntry = entry; } /** * * @param c1 * @param c2 * @return */ protected Comparable min(Comparable c1, Comparable c2) { if(c1==null) return c2; if(c2==null) return c1; if(c1.compareTo(c2)<=0) return c1; return c2; } /** * */ private Version minVersion = new Version() { public Object clone() { return this; } public int compareTo(Object obj) { return -1; } }; /** This class represents the entries which can be stored in the leaves of the <tt>MVBTree</tt>. A * <tt>LeafEntry</tt> has a <tt>Lifespan</tt> and capsules a data object. * @author Husain Aljazzar */ public class LeafEntry { /** The <tt>Lifespan</tt> of this <tt>LeafEntry</tt>.*/ protected Lifespan lifespan; /** The data object of the <tt>LeafEntry</tt>.*/ protected Object data; /** Creates a new Leaf Entry. The created Leaf Entry conatains the given Separator and data Object. * @param separator the separator of the new Leaf Entry * @param data the data object of the new Leaf Entry. */ public LeafEntry(Lifespan lifespan, Object data) { this.initialize(lifespan, data); } /** Initializes the Leaf Entry by the given Separator and data Object. * @param separator the new separator of the Leaf Entry * @param data the new data object of the Leaf Entry. * @return the initialized current Leaf Entry itself. */ public LeafEntry initialize(Lifespan lifespan, Object data) { this.lifespan=lifespan; this.data=data; return this; } /** Gives the enclosed data object of this Leaf Entry. * @return the enclosed data object of this Leaf Entry. * @see IndexEntry#data() */ public Object data() { return data; } /** * MVBT TODO Specific * @return */ public Comparable getKey(){ return key(data); } public Lifespan getLifespan() { return lifespan; } /**Computes the <tt>MVSeparator</tt> of this <tt>LeafEntry</tt>. * @return the <tt>MVSeparator</tt> of this <tt>LeafEntry</tt>. */ public MVSeparator getMVSeparator() { return createMVSeparator(lifespan.beginVersion(), lifespan.endVersion(), key(data)); } public boolean equals(Object object) { return data.equals(((LeafEntry)object).data); } /** Converts this Leaf Entry to a String. It is used to show the Leaf Entry on the display. * @return the String display of this Leaf Entry. */ public String toString() { StringBuffer sb=new StringBuffer("("); sb.append(lifespan.toString()); sb.append(", "); sb.append(data()); sb.append(")"); return sb.toString(); } } public int counter= 0; public List<IndexEntry> leafentries = new ArrayList<IndexEntry>(); /**This class represents the <tt>Nodes</tt> of the <tt>MVBTree</tt>. * @author Husain Aljazzar */ public class Node extends BPlusTree.Node { /** A <tt>List</tt> containing the temporal predecessors of this <tt>Node</tt>. Note the every <tt>Node</tt> * has maximal two temporal predecessors. */ protected List predecessors=new ArrayList(2); /**Creates a new <tt>Node</tt>. * @param level the level of the <tt>Node</tt>. * @param createEntryList a Function to create the entry list. */ public Node(int level, Function createEntryList) { super(level, createEntryList); } /**Creates a new <tt>Node</tt>. * @param level the level of the <tt>Node</tt>. */ public Node(int level) { super(level); } /** * This method needs to be called when the node is inserted into the container * and assigned an id. * * @param id ID of this node. */ public void onInsert(Object id) { } /**Indicates whether a new splitted <tt>Node</tt> violates the strong version condetion. * @return true if the <tt>Node</tt> has too many entries and false otherwise. */ protected boolean strongOverflows() { // System.out.println("strong max l:" + level() +" " + strongUpperBound(level()) + " num : " + number() ); return number()> strongUpperBound(level()); } /**Inicates whether a new splitted <tt>Node</tt> violates the strong version condetion. * @return true if the <tt>Node</tt> has too few entries and false otherwise. */ protected boolean strongUnderflows() { //System.out.println("l:" + level() +" " + strongLowerBound(level()) + " num : " + number() ); return number()< strongLowerBound(level()); } /**Gives the <tt>List</tt> of the <tt>Node's</tt> temporal predecessors. * @return the <tt>List</tt> of the <tt>Node's</tt> temporal predecessors. */ public List predecessors() { return predecessors; } /** * hack for bulk loading * @return */ public List getEntries(){ return this.entries; } /** Chooses the subtree which is followed during an insertion. * @param descriptor the <tt>MVSeparator</tt> of data object * @param path the path from the root to the current node * @return the index entry refering to the root of the chosen subtree */ protected Tree.IndexEntry chooseSubtree (Descriptor descriptor, Stack path) { final MVSeparator mvSeparator= (MVSeparator) descriptor; Iterator iterator = getCurrentEntries(); return chooseEntry(mvSeparator, iterator); } // test method for bulk loading public Tree.IndexEntry chooseSubtree(MVSeparator mvSeparator){ Iterator iterator = getCurrentEntries(); return chooseEntry(mvSeparator, iterator); } /** Searches the position of the given version. If the version is found its position is returned, * else (the negation of the insertion position of the version)-1. * Example: * <pre> * Search for 7 in [3, 5, 5, 5, 7, 7, 11] returns 4 * Search for 6 in [3, 5, 5, 5, 7, 7, 11] returns -5 * <pre> * @param version the version to search. * @return If the version is found its position is returned, else * (the negation of its insertion position)-1. */ protected int search(Version version) { return binarySearch(version); } /**Searches a given key in a given range of the <tt>Node</tt>. * @param key the key which is to search. * @param from the begin position of the search range. * @param to the end position of the search range. * @return If the key is found its position is returned, else * (the negation of its insertion position)-1. */ protected int search(Comparable key, int from, int to) { List minima= new MappedList(entries.subList(from,to), new AbstractFunction() { public Object invoke(Object entry) { return separator(entry).sepValue(); } }); int index= Collections.binarySearch(minima, key); index=(index>=0)? index+from : index-from; return index; } /**Searches the minimal key in the <tt>Node</tt>. * @return the position of the entry with the smallest key. */ protected int searchMinimumKey() { return MVBTree.this.searchMinimumKey(this.entries); } /**Sorts the entries of the <tt>Node</tt> in respect of their <tt>Versions</tt>. */ protected void sortEntries() { sortEntries(false); } /**Sorts the entries of the <tt>Node</tt> in respect of their <tt>Versions</tt> or keys. * @param keySort indicates whether the entries have to sorted in respect of their keys. */ protected void sortEntries(boolean keySort) { Comparator comp; if(keySort) comp= new Comparator() { public int compare(Object o1, Object o2) { MVSeparator mvSep1=(MVSeparator)separator(o1); MVSeparator mvSep2=(MVSeparator)separator(o2); int x= mvSep1.compareTo(mvSep2); if(x==0) x=mvSep1.lifespan().compareTo(mvSep2.lifespan()); return x; } }; else comp= new Comparator() { public int compare(Object o1, Object o2) { MVSeparator mvSep1=(MVSeparator)separator(o1); MVSeparator mvSep2=(MVSeparator)separator(o2); int x=mvSep1.lifespan().compareTo(mvSep2.lifespan()); if(x==0) x= mvSep1.compareTo(mvSep2); return x; } }; Collections.sort(entries,comp); } /**Searches the begining of a given <tt>Version</tt>. * @param version * @return If the version is found its position is returned, else * (the negation of its insertion position)-1. */ protected int binarySearch(Version version) { List insertVersionList= new MappedList(entries, new AbstractFunction() { public Object invoke(Object entry) { return ((MVSeparator)separator(entry)).insertVersion(); } }); return Collections.binarySearch(insertVersionList,version); } /** * * @param queryRegion * @param nodeRegion * @return */ private Cursor referenceLeafQuery(final MVRegion queryRegion, final MVRegion nodeRegion) { if(level()>0) throw new UnsupportedOperationException("The node is not a leaf."); Predicate test=new AbstractPredicate() { public boolean invoke(Object entry) {//Umgestellt auf halboffene Intervale MVSeparator entrySeparator = (MVSeparator)separator(entry); Lifespan entryLifeSpan = entrySeparator.lifespan; Comparable entryKey = entrySeparator.sepValue; if (queryRegion.contains(entryKey) && queryRegion.lifespan.overlaps(entryLifeSpan) ){ Comparable referenceKey= max(queryRegion.minBound(), entryKey); Version referenceTime=(Version) min(queryRegion.endVersion(), entryLifeSpan.endVersion()); if (nodeRegion.contains(referenceKey) ){ if (nodeRegion.isAlive()) return true; else if ((nodeRegion.endVersion().compareTo(referenceTime)>0)){ return true; }//sonder fall EntryIntervall endet in rechte Grenze von NodeIntervall else if ((entryLifeSpan.isDead()) && entryLifeSpan.endVersion().compareTo(nodeRegion.endVersion()) == 0){ return true; } } } return false; } }; return new Filter(this.iterator(), test); } /** Searches all entries of this <tt>Node</tt> which are alive at the given <tt>Version</tt>. * @param version the <tt>Version</tt> of the query. * @return a <tt>Iterator</tt> pointing to all responses (i.e. all entries of this <tt>Node</tt> * which are alive at the given <tt>Version</tt>). */ public Iterator query(Version version) { return query(new Lifespan(version,version)); } /** Searches all entries of this <tt>Node</tt> whose <tt>Lifespans</tt> overlap the given <tt>Lifespan</tt>. * @param lifespan the <tt>Lifespan</tt> of the query. * @return a <tt>Iterator</tt> pointing to all responses (i.e. all entries of this <tt>Node</tt> whose * <tt>Lifespans</tt> overlap the given <tt>Lifespan</tt>). */ public Iterator query(final Lifespan lifespan) { return new Filter( iterator(), new AbstractPredicate() { public boolean invoke(Object entry) { return ((MVSeparator)separator(entry)).lifespan().overlaps(lifespan); } }); } /** Gives an <tt>Iterator</tt> pointing to all entries of this <tt>Node</tt>. * @return an <tt>Iterator</tt> pointing to all entries of this <tt>Node</tt>. */ public Iterator iterator() { return new Iterator() { private int index=0; private boolean removeable=false; public boolean hasNext() { return index<MVBTree.Node.this.number(); } public Object next() { if(!hasNext()) throw new NoSuchElementException(); removeable=true; return MVBTree.Node.this.getEntry(index++); } public void remove() { if(removeable) { removeable=false; MVBTree.Node.this.remove(index-1); } else throw new NoSuchElementException(); } }; } /** Gives an <tt>Iterator</tt> pointing to all entries of this <tt>Node</tt> which have not been deleted yet. * @return an <tt>Iterator</tt> pointing to all entries of this <tt>Node</tt> which have not been deleted yet. */ public Iterator getCurrentEntries() { return new Filter( iterator(), new AbstractPredicate() { public boolean invoke(Object entry) { return ((MVSeparator)separator(entry)).isAlive(); } }); } /** Gives all <tt>Node's</tt> entries which have been inserted into the <tt>Node</tt> at or after the given * <tt>Version</tt>. * @return a <tt>List</tt> containting all such entries. */ protected List entriesFromVersion(Version version) { int index= search(version); index=(index>=0)?index:-index-1; return entries.subList(index, number()); } /** Counts all alive entries stored in this <tt>Node</tt>. * @return the number of alive entries stored in this <tt>Node</tt>. */ public int countCurrentEntries() { return Cursors.count(getCurrentEntries()); } /** Gives all <tt>Node's</tt> entries which have been inserted into the <tt>Node</tt> at the given * <tt>Version</tt>. * @return a <tt>List</tt> containting all such entries. */ protected List entriesOf(Version insertVersion) { int[] coord= coordinatesOf(insertVersion); if(coord[0]<0) return entries.subList(0,0); return entries.subList(coord[0], coord[1]); } /** * */ private Comparator separatorComp = new Comparator() { public int compare(Object o1, Object o2) { return separator(o1).compareTo(separator(o2)); } }; /** * Returns an Iterator pointing to entries whose descriptors overlap the queryDescriptor * Required in and called from * {@link MVBTree#keyRangeQuery(Comparable, Comparable, xxl.core.indexStructures.MVBTree.Version)} query. * Does not support Lifespan range. Supports only point Lifespans. * @param queryDescriptor the descriptor describing the query * @return an Iterator pointing to entries whose descriptors overlap the <tt>queryDescriptor</tt> * @throws IllegalArgumentException */ public Iterator query(Descriptor queryDescriptor) { MVRegion region = (MVRegion)queryDescriptor; if (!region.beginVersion().equals(region.endVersion())) throw new IllegalArgumentException("Version interval not supported."); Version version = region.beginVersion(); List list = new ArrayList(); for (int i = 0; i < number(); i++) { Object entry = getEntry(i); MVSeparator sep = (MVSeparator)separator(entry); if (sep.lifespan().contains(version)) list.add(entry); } Collections.sort(list, separatorComp); int minIndex = Collections.binarySearch(list, createMVSeparator(null, null, region.minBound()), separatorComp); int maxIndex = Collections.binarySearch(list, createMVSeparator(null, null, region.maxBound()), separatorComp); minIndex = (minIndex >= 0) ? minIndex : (minIndex == -1 )? 0 : -minIndex - 2; maxIndex = (maxIndex >= 0) ? maxIndex : (maxIndex == -1 )? 0 : -maxIndex - 2; minIndex = Math.max(minIndex, 0); maxIndex = Math.min(maxIndex + 1, list.size()); List response = list.subList(minIndex, maxIndex); return response.iterator(); } /** Inserts an entry into this <tt>Node</tt>. If level>0 <tt>data</tt> must be an * <tt>IndexEntry</tt>. * @param data the entry which has to be inserted into the node * @param path the path from the root to the current node */ protected void grow(Object entry, Stack path) { if (level() != 0) removePointEntries(); int index; if(number()==0) index=-1; else { MVSeparator mvSep=(MVSeparator)separator(entry); int[] coord= coordinatesOf(mvSep.insertVersion()); index= coord[0]; if(index>=0) { index= search(mvSep.sepValue(), coord[0], coord[1]); } } if(index>=0) { throw new RuntimeException("Insertion failed: An entry having the same key was found"); } index=-index-1; entries.add(index, entry); // test code check if the entry separator key the new min key ist ??? // if(!path.isEmpty()){ // get entry pointing to this node BPlusTree.IndexEntry indexEntry = (BPlusTree.IndexEntry)indexEntry(path); // check descriptor if(indexEntry.separator().compareTo(separator(entry)) > 0){ MVSeparator sepIndex = ((MVSeparator)indexEntry.separator()); MVSeparator entrySep = (MVSeparator)(separator(entry)).clone(); sepIndex.updateSepValue(entrySep.sepValue()); // get parent node if exists MapEntry pathEntry = (MapEntry)path.pop(); if(!path.isEmpty()){ update(path); } path.push(pathEntry); } } } public void simpleInsert(Object entry){ int index; if(number()==0) index=-1; else { MVSeparator mvSep=(MVSeparator)separator(entry); int[] coord= coordinatesOf(mvSep.insertVersion()); index= coord[0]; if(index>=0) { index= search(mvSep.sepValue(), coord[0], coord[1]); } } if(index>=0) { throw new RuntimeException("Insertion failed: An entry having the same key was found"); } index=-index-1; entries.add(index, entry); } /** * Removes entries from this node that have an empty lifespan. Such entries * can result from a repeated version split of a node within one version (this * can only happen if multiple updates in the tree within one version are * allowed). */ protected void removePointEntries() { Iterator it = entries.iterator(); while (it.hasNext()) if (((MVSeparator)separator(it.next())).lifespan().isPoint()) it.remove(); } /** * * @param version * @return */ private int[] coordinatesOf(Version version) { int from=search(version); int to=from; if(from>=0) { to=from+1; while(to<number()){ if(((MVSeparator)separator(getEntry(to))).insertVersion().compareTo(version)>0) break; to++; } } return new int[]{from,to}; } protected void removeEntriesFromVersion(Version version) { int index= search(version); index=(index>=0)?index:-index-1; entries.subList(index, number()).clear(); } protected Object remove(int index) { if((index<0)||(index>= entries.size())) return null; Object entry=getEntry(index); Lifespan life; if(level>0) { ((MVSeparator)((IndexEntry)entry).separator).delete(currentVersion()); life=((MVSeparator)((IndexEntry)entry).separator).lifespan(); } else { ((LeafEntry)entry).lifespan.delete(currentVersion()); life=((LeafEntry)entry).lifespan; } if(life.endVersion().compareTo(life.beginVersion())<=0) super.remove(index); return entry; } /** * */ protected Collection redressOverflow (Stack path, List newIndexEntries, boolean up) { if (overflows() || underflows()) { if((path.size()==1)&& underflows() && !overflows()) { // root Node rootNode=(Node)node(path); if((rootNode.level()!=0)&&(rootNode.countCurrentEntries()==1)) { reorg=true; Object oldRootId= rootEntry.id(); int rootParentLevel=rootEntry.parentLevel(); MVRegion oldRootReg= toMVRegion((MVSeparator)((IndexEntry)rootEntry).separator()); Version delVersion=oldRootReg.beginVersion(); Iterator iter= rootNode.entries(); while(iter.hasNext()) { MVSeparator mvSep=(MVSeparator)((IndexEntry)iter.next()).separator; if(mvSep.isDead() && mvSep.deleteVersion().compareTo(delVersion)>0) delVersion=mvSep.deleteVersion(); } Iterator current=rootNode.getCurrentEntries(); if(current.hasNext()) rootEntry=(IndexEntry)current.next(); else throw new IllegalStateException(); //oldRootReg.updateMaxBound(((MVRegion)rootDescriptor).maxBound()); oldRootReg.updateEndVersion(delVersion); Root oldRoot= new Root(oldRootReg, oldRootId, rootParentLevel); // new code // if multiple operations are allowed in one version, a root with an // empty lifespan may be generated. It has to be discarded. if (!oldRootReg.lifespan().isPoint()) { roots.insert(oldRoot); //Daniar: new code KeyRange newRange = roots.createKeyRange(oldRootReg.beginVersion(), currentVersion); roots.rootDescriptor.union(newRange); // ((Lifespan)roots.rootDescriptor).updateMaxBound(currentVersion); // .updateMaxBound(currentVersion); } } } else { reorg=true; Node versionSplitNode=(Node)MVBTree.this.createNode(level()); SplitInfo splitInfo= (SplitInfo)versionSplitNode.split(path); IndexEntry indexEntryOfVersionSplitNode=(IndexEntry) createIndexEntry(level()+1); Object idOfVersionSplitNode= indexEntryOfVersionSplitNode.container().insert(splitInfo.versionSplitNode()); splitInfo.versionSplitNode().onInsert(idOfVersionSplitNode); indexEntryOfVersionSplitNode.initialize(idOfVersionSplitNode, splitInfo.getMVSeparatorOfVersionSplitNode()); IndexEntry indexEntryOfKeySplitNode=null; // Test if(splitInfo.isKeySplit()) { indexEntryOfKeySplitNode=(IndexEntry)createIndexEntry(indexEntryOfVersionSplitNode.parentLevel()); Object idOfKeySplitNode= indexEntryOfKeySplitNode.container().insert(splitInfo.keySplitNode); splitInfo.keySplitNode.onInsert(idOfKeySplitNode); indexEntryOfKeySplitNode.initialize(idOfKeySplitNode, splitInfo.getMVSeparatorOfKeySplitNode()); } //A root overflow... if (splitInfo.isRootSplit()) { int oldRootParentLevel= rootEntry.parentLevel(); Object oldRootId= rootEntry.id(); MVRegion oldRootReg= toMVRegion((MVSeparator)((IndexEntry)rootEntry).separator()); if(splitInfo.isKeySplit()) { Entry newRootTuple = MVBTree.this.grow(indexEntryOfVersionSplitNode); Node newRootNode= (Node)newRootTuple.getValue(); newRootNode.entries.add(indexEntryOfKeySplitNode); MVSeparator rootSeparator= (MVSeparator)indexEntryOfVersionSplitNode.separator; Object newRootNodeId= MVBTree.this.container().insert(newRootNode); newRootNode.onInsert(newRootNodeId); ((IndexEntry)rootEntry).initialize(newRootNodeId,rootSeparator); } else rootEntry = indexEntryOfVersionSplitNode; oldRootReg.updateMaxBound(((MVRegion)rootDescriptor).maxBound()); Root oldRoot= new Root(oldRootReg, oldRootId, oldRootParentLevel); // if multiple operations are allowed in one version, a root with an // empty lifespan may be generated. It has to be discarded. if (!oldRootReg.lifespan().isPoint()) { roots.insert(oldRoot); //Daniar: new code bug fix KeyRange newRange = roots.createKeyRange(oldRootReg.beginVersion(), currentVersion); roots.rootDescriptor.union(newRange); // ((Lifespan)roots.rootDescriptor).updateMaxBound(currentVersion); } } else { MapEntry pathEntry = (MapEntry)path.pop(); Node parentNode = (Node)node(path); // new code first grow parentNode.grow(indexEntryOfVersionSplitNode, path); newIndexEntries.add(newIndexEntries.size(), indexEntryOfVersionSplitNode); if(splitInfo.isKeySplit()) { parentNode.grow(indexEntryOfKeySplitNode, path); newIndexEntries.add(newIndexEntries.size(), indexEntryOfKeySplitNode); } parentNode.sortEntries(); // new code first grow path.push(pathEntry); } } } if (up) { update(path); up(path); } if (level==0){ counter+=newIndexEntries.size(); leafentries.addAll(newIndexEntries); } return newIndexEntries; } protected Tree.Node.SplitInfo split(Stack path) { return split(currentVersion, path); } protected SplitInfo split(Version splitVersion, Stack path) { SplitInfo splitInfo = new SplitInfo(path); splitInfo = this.versionSplit(splitVersion, path, splitInfo); if (strongOverflows()) splitInfo = this.treatStrongOverflow(path, splitInfo); else if (strongUnderflows()) splitInfo = this.treatStrongUnderflow(path, splitInfo); return splitInfo; } /***************************************************************************************** * For bulk loading ******************************************************************************************/ public List getDataEntries(){ return entries; } public void sortKeyDimension(){ sortEntries(true); } public void sortTimeDimension(){ sortEntries(false); } /***************************************************************************************** * !!! ******************************************************************************************/ /** * * @param splitVersion * @param path * @param splitInfo * @return */ protected SplitInfo versionSplit(Version splitVersion, Stack path, SplitInfo splitInfo) { Node node = (Node)node(path); IndexEntry indexEntry = (IndexEntry)indexEntry(path); splitInfo.initVersionSplit(splitVersion, (MVSeparator)indexEntry.separator()); if (splitVersion.compareTo(((MVSeparator)indexEntry.separator).insertVersion()) < 0){ throw new IllegalArgumentException("Split of:"+ indexEntry+" at Version:"+splitVersion); } ((MVSeparator)indexEntry.separator).delete(splitVersion); Iterator currentEntries = node.query(splitVersion); while (currentEntries.hasNext()) { Object entry = currentEntries.next(); Object cpy = copyEntry(entry); entries.add(cpy); } if (level()==0) { this.predecessors.clear(); IndexEntry predEntry = (IndexEntry)createIndexEntry(indexEntry.parentLevel); MVSeparator predSep = (MVSeparator)separator(indexEntry).clone(); predSep.delete(splitVersion); predEntry.initialize(indexEntry.id(), predSep); this.predecessors.add(predEntry); } node.removeEntriesFromVersion(splitVersion); purgeQueue.enqueue(new BlockDelInfo(indexEntry.id(), splitVersion)); return splitInfo; } protected SplitInfo versionSplit(Version splitVersion, Stack path) { SplitInfo splitInfo = new SplitInfo(path); return versionSplit(splitVersion, path, splitInfo); } protected SplitInfo treatStrongOverflow(Stack path, SplitInfo splitInfo) { splitInfo = keySplit(path, splitInfo); return splitInfo; } protected SplitInfo treatStrongUnderflow(Stack path, SplitInfo splitInfo) { if (!splitInfo.isRootSplit()) { splitInfo = strongMerge(path,splitInfo); if (strongOverflows()){ treatStrongOverflow(path,splitInfo); //Fall if (this.level == 0) adjustPredecessors(splitInfo); } } return splitInfo; } /** * * @param splitInfo */ protected void adjustPredecessors(SplitInfo splitInfo){ if(!(splitInfo.isMergePerformed && splitInfo.isKeySplit())) throw new IllegalStateException(); Comparable minSplitBound = splitInfo.separatorOfNewNode().sepValue; IndexEntry predecessor1 = (IndexEntry)predecessors.get(0); IndexEntry predecessor2 = (IndexEntry)predecessors.get(1); int index = (predecessor1.separator.sepValue.compareTo( predecessor2.separator.sepValue ) > 0) ? 0: 1; Comparable mergeSiblingBound = ((IndexEntry)predecessors.get(index)).separator.sepValue; if ( mergeSiblingBound.compareTo(minSplitBound) > 0){ int minIndex = (index == 0)? 1: 0; IndexEntry predecessorMin = (IndexEntry)predecessors.get(minIndex); predecessors.remove(index); splitInfo.keySplitNode.predecessors.add(predecessorMin); }else if (mergeSiblingBound.compareTo(minSplitBound) == 0){ predecessors.remove(index); } } protected SplitInfo keySplit(Stack path, SplitInfo splitInfo) { this.sortEntries(true); List cpyEntries = this.entries.subList((number()+1)/2, number()); Node newNode = (Node)MVBTree.this.createNode(level()); newNode.entries.addAll(cpyEntries); cpyEntries.clear(); this.sortEntries(); MVSeparator newSeparator = (MVSeparator)separator(newNode.getFirst()).clone(); newSeparator.union(separator(newNode.getLast())); newSeparator.setInsertVersion(splitInfo.splitVersion()); newNode.sortEntries(); splitInfo.initKeySplit(newSeparator, newNode); if (level()==0) newNode.predecessors.add(this.predecessors.get(0)); return splitInfo; } protected SplitInfo strongMerge(Stack path, SplitInfo splitInfo) { if (path.size() <= 1) throw new IllegalStateException("There is no parent on the stack."); Node node = splitInfo.versionSplitNode(); // created node IndexEntry mergeSibling = determineStrongMergeSibling(path, splitInfo); //test new code test splitInfo.setSiblingMergeSeparator((MVSeparator)mergeSibling.separator); MapEntry pathEntry = (MapEntry)path.pop(); down(path, mergeSibling); Node tempNode = (Node)MVBTree.this.createNode(level()); tempNode.versionSplit(splitInfo.splitVersion(), path); node.entries.addAll(tempNode.entries); node.sortEntries(); if (node.level() == 0) node.predecessors.add(tempNode.predecessors.get(0)); update(path); up(path); path.push(pathEntry); splitInfo.setMergePerformed(); assert node.countCurrentEntries() >= (level() == 0 ? getLeafNodeD() : getIndexNodeD()); return splitInfo; } protected MVRegion computeLeafMVRegion() { if(level!=0) throw new UnsupportedOperationException(); MVRegion mvRegion= toMVRegion((MVSeparator)separator(getFirst())); for(int i=0; i<number(); i++) { MVSeparator mvSep=(MVSeparator)separator(getEntry(i)); if(mvSep.sepValue().compareTo(mvRegion.minBound())<0) mvRegion.updateMinBound(mvSep.sepValue()); if(mvSep.sepValue().compareTo(mvRegion.maxBound())>0) mvRegion.updateMaxBound(mvSep.sepValue()); if(mvSep.insertVersion().compareTo(mvRegion.beginVersion())<0) mvRegion.updateBeginVersion(mvSep.insertVersion()); if(mvSep.isAlive()|| mvRegion.isDead() && (mvSep.deleteVersion().compareTo(mvRegion.endVersion())>0)) mvRegion.updateEndVersion(mvSep.deleteVersion()); } return mvRegion; } protected IndexEntry determineStrongMergeSibling(Stack path, SplitInfo splitInfo) { IndexEntry indexEntry= (IndexEntry)indexEntry(path); MapEntry pathEntry=(MapEntry) path.pop(); Node parentNode=(Node)node(path); IndexEntry mergeEntry=(IndexEntry)parentNode.chooseSubtree(indexEntry.separator, path); path.push(pathEntry); return mergeEntry; } public class SplitInfo extends BPlusTree.Node.SplitInfo { protected Version splitVersion = null; protected MVSeparator oldSeparator = null; protected MVSeparator mergeSiblingSeparator = null; protected Node keySplitNode = null; protected boolean isRootSplit; protected boolean isMergePerformed; public SplitInfo(Stack path) { super(path); isRootSplit=(path.size()==1); } protected SplitInfo initVersionSplit(Version splitVersion, MVSeparator oldSeparator) { this.splitVersion=(Version)splitVersion.clone(); this.oldSeparator=oldSeparator; return this; } protected SplitInfo initKeySplit(MVSeparator separatorOfNewNode, Node newNode) { keySplitNode=newNode; separatorOfNewNode.setInsertVersion(splitVersion); separatorOfNewNode.setDeleteVersion(null); return (SplitInfo) super.initialize(separatorOfNewNode); } protected Node versionSplitNode() { return (Node) super.newNode(); } boolean isKeySplit() { return this.keySplitNode!=null; } //test protected void setSiblingMergeSeparator(MVSeparator mergeSiblingSeparator){ this.mergeSiblingSeparator = mergeSiblingSeparator; } protected boolean isMergeSiblingBelow(){ if (!isMergePerformed) throw new IllegalStateException("Merge was not performed!" ); return this.oldSeparator.sepValue.compareTo(this.mergeSiblingSeparator.sepValue) > 0 ; } /* * 3 Faelle * 1. Unterlauf+Merge * 2. Unterlauf+Merge+KeySplit * 3. Ueberlauf+KeySplit */ MVSeparator getMVSeparatorOfVersionSplitNode() { MVSeparator mvSeparator = (MVSeparator)oldSeparator.clone(); if (isMergePerformed && isMergeSiblingBelow()){ mvSeparator = (MVSeparator)mergeSiblingSeparator.clone(); } mvSeparator.setInsertVersion(splitVersion); mvSeparator.setDeleteVersion(null); return mvSeparator; } /* * */ MVSeparator getMVSeparatorOfKeySplitNode() { MVSeparator mvSeparator = (MVSeparator)separatorOfNewNode; return mvSeparator; } Version splitVersion() { return splitVersion; } boolean isRootSplit() { return isRootSplit; } boolean isMergePerformed() { return isMergePerformed; } void setMergePerformed() { isMergePerformed = true; } } } /** * * Need to handle root in the bplus tree with max key * */ public static class LifeSpanSeparator extends Separator { /**A factury <tt>Function</tt> to create <tt>Lifespans</tt>.*/ public static final Function FACTORY_FUNCTION= new AbstractFunction<Object,Object>() { public Object invoke(Object version) { return new LifeSpanSeparator((Version)version); } }; public LifeSpanSeparator(Version beginVersion) { super(beginVersion); } public Object clone() { return new LifeSpanSeparator((Version)((Version)this.sepValue).clone()); } } /** The instances of this class are refernces pointing to a root node * of the MVBTree. Also they are leaf entries stored in the BTree "roots". * @author Husain Aljazzar * @version Aug 19, 2003 */ public class Root { protected MVRegion region; protected Object rootId; protected int parentLevel; public Root(MVRegion region, Object rootId, int parentLevel) { this.region=region; this.rootId=rootId; this.parentLevel=parentLevel; } public Lifespan lifespan() { return region.lifespan(); } public MVRegion getRegion() { return region; } public Object rootNodeId() { return rootId; } public int parentLevel() { return parentLevel; } public Node get(boolean unfix) { return (Node) MVBTree.this.container().get(rootId, unfix); } public IndexEntry toIndexEntry() { IndexEntry indexEntry=(IndexEntry)createIndexEntry(parentLevel); indexEntry.initialize(rootId, toMVSeparator(region)); return indexEntry; } public String toString() { StringBuffer sb= new StringBuffer("R("); sb.append(lifespan().toString()); sb.append(" ->"); sb.append(rootNodeId()); sb.append(")"); return sb.toString(); } } public static abstract class MVSeparator extends Separator { protected Lifespan lifespan; public MVSeparator(Version insertVersion, Comparable sepValue) { this(insertVersion, null, sepValue); } protected MVSeparator( Version insertVersion, Version deleteVersion, Comparable sepValue) { super(sepValue); this.lifespan=new Lifespan(insertVersion, deleteVersion); } public void setInsertVersion(Version newInsertVersion) { lifespan.updateMinBound(newInsertVersion); } public void setDeleteVersion(Version newDeleteVersion) { lifespan.updateMaxBound(newDeleteVersion); } public void delete(Version deleteVersion) { lifespan.delete(deleteVersion); } public Lifespan lifespan() { return lifespan; } public Version insertVersion() { return lifespan.beginVersion(); } public Version deleteVersion() { return lifespan.endVersion(); } public boolean isAlive() { return lifespan.isAlive(); } public boolean isDead() { return lifespan.isDead(); } public boolean contains(Version version) { return lifespan.contains(version); } public String toString() { StringBuffer sb= new StringBuffer("["); sb.append(sepValue()+" "); sb.append(lifespan().toString()); sb.append("["); return sb.toString(); } } public static abstract class MVRegion extends BPlusTree.KeyRange { protected Lifespan lifespan; public MVRegion(Version beginVersion, Version endVersion, Comparable beginKey, Comparable endKey) { super(beginKey, endKey); this.lifespan=new Lifespan(beginVersion, endVersion, true); } public Lifespan lifespan() { return lifespan; } public Version beginVersion() { return lifespan.beginVersion(); } public Version endVersion() { return lifespan.endVersion(); } public void updateBeginVersion(Version newBeginVersion) { lifespan.updateMinBound((Version)newBeginVersion.clone()); } public void updateEndVersion(Version newEndVersion) { Version vers=newEndVersion==null? null:(Version)newEndVersion.clone(); lifespan.updateMaxBound(vers); } public boolean isAlive() { return lifespan.isAlive(); } public boolean isDead() { return lifespan.isDead(); } public void union(Descriptor descriptor, boolean time) { super.union(descriptor); if(time) { Lifespan life=(descriptor instanceof MVRegion)? ((MVRegion)descriptor).lifespan() : ((MVSeparator)descriptor).lifespan(); this.lifespan().union((Descriptor)life); } } public void union(Descriptor descriptor) { union(descriptor, true); } public boolean overlaps(Descriptor descriptor) { if(!(descriptor instanceof MVRegion)) return false; MVRegion mvReg=(MVRegion) descriptor; return keyOverlaps(mvReg)&& versionOverlaps(mvReg); } public boolean keyOverlaps(MVRegion mvReg) { return super.overlaps(mvReg); } public boolean versionOverlaps(MVRegion mvReg) { return lifespan.overlaps(mvReg.lifespan); } public boolean contains(Descriptor descriptor) { if (descriptor instanceof MVSeparator) return super.contains(descriptor)&&this.lifespan.contains((Descriptor)((MVSeparator)descriptor).lifespan); if (descriptor instanceof MVRegion) return super.contains(descriptor)&&this.lifespan.contains((Descriptor)((MVRegion)descriptor).lifespan); return false; } public boolean contains(Version version) { return lifespan.contains(version); } public String toString() { StringBuffer sb= new StringBuffer("["); sb.append("[" + minBound() + " " + (maxBound()== null ? "--": maxBound) ); sb.append("]"); sb.append(" "); sb.append(lifespan().toString()); sb.append("["); return sb.toString(); } } /** Objects of this class are version intervals and represent life spans of the entries sotred in the * <tt>MVBTree</tt>. A <tt>Lifespan</tt> consists of a begin and end versions and has one the following forms: * <ul> * <li><tt>[beginVersion, null[</tt> the entry became valid startig from <tt>beginVersion</tt> and it is still * alive.</li> * <li><tt>[beginVersion, endVersion[</tt> the entry became valid startig from <tt>beginVersion</tt> and it was * deleted at <tt>endVersion</tt>.</li> * <li><tt>[beginVersion, endVersion]</tt> this form is used to express query or root regions.</li> * * @author Husain Aljazzar * @version Aug 19, 2003 */ public static class Lifespan extends BPlusTree.KeyRange { /**Indicates whether the <tt>endVersion</tt> is contained in the life interval.*/ protected boolean isRightClose=false; /**A factury <tt>Function</tt> to create <tt>Lifespans</tt>.*/ public static final Function FACTORY_FUNCTION= new AbstractFunction<Object,Object>() { public Object invoke(Object beginVersion) { return new Lifespan((Version)beginVersion); } public Object invoke(Object beginVersion, Object endVersion) { return new Lifespan((Version)beginVersion, (Version)endVersion); } public Object invoke(List<? extends Object> args) { switch(args.size()) { case 1: return invoke(args.get(0)); case 2: return invoke(args.get(0), args.get(1)); case 3: return new Lifespan((Version)args.get(0), (Version)args.get(1), ((Boolean)args.get(2)).booleanValue()); } throw new IllegalArgumentException(); } }; /**Creates a new <tt>Lifespan</tt> from the form <tt>[beginVersion, endVersion[</tt> with the given begin *and end version. *@param beginVersion the begin version of the <tt>Lifespan</tt>. *@param endVersion the end version of the <tt>Lifespan</tt>. */ public Lifespan(Version beginVersion, Version endVersion) { this(beginVersion, endVersion, false); } /**Creates a new <tt>Lifespan</tt> from the form <tt>[beginVersion, null[</tt> with the given begin version. *@param beginVersion the begin version of the <tt>Lifespan</tt>. */ public Lifespan(Version beginVersion) { this(beginVersion, null, false); } /**Creates a new <tt>Lifespan</tt> with the given begin and end version. *@param beginVersion the begin version of the <tt>Lifespan</tt>. *@param endVersion the end version of the <tt>Lifespan</tt>. *@param isRightClose indicates whether the new <tt>Lifespan</tt> is a right close interval. */ public Lifespan(Version beginVersion, Version endVersion, boolean isRightClose) { super(beginVersion == null ? null : (Version)beginVersion.clone(),(endVersion==null)? null: (Version)endVersion.clone()); this.isRightClose=endVersion()!=null && isRightClose; } /** Checks whether the given <tt>Descriptor</tt> overlaps this <tt>Lifespan</tt>. * @param descriptor the <tt>Lifespan</tt> to check. * @return true if the given <tt>Descriptor</tt> an instance of of <tt>Lifespan</tt> and overlaps this * <tt>Lifespan</tt> and false otherwise. * @see xxl.indexStructures.Separator#overlaps(xxl.indexStructures.Tree.Descriptor) */ public boolean overlaps(Descriptor descriptor) { if(descriptor instanceof Lifespan) { Lifespan lifespan=(Lifespan) descriptor; return contains(lifespan.beginVersion())|| lifespan.contains(this.beginVersion()); } return false; } /** Builds the union of two <tt>Lifespans</tt>. The current <tt>Lifespan</tt> will be changed and returned. * @param descriptor the <tt>Lifespan</tt> which is to unite with the current <tt>Lifespan</tt>. * @return the current <tt>Lifespan</tt> is changed then returned. * @see xxl.indexStructures.Separator#union(xxl.indexStructures.Tree.Descriptor) */ public void union(Descriptor descriptor) { if(!(descriptor instanceof Lifespan)) return; Lifespan lifespan=(Lifespan) descriptor; if(beginVersion().compareTo(lifespan.beginVersion())>0) updateMinBound(lifespan.beginVersion()); if(isAlive()||lifespan.isAlive()) updateMaxBound(null); else { int x=endVersion().compareTo(lifespan.endVersion()); if(x==0) isRightClose=this.isRightClose()||lifespan.isRightClose(); if(x<0) { updateMaxBound(lifespan.endVersion()); isRightClose=lifespan.isRightClose(); } } } public int compareTo(Object lifespan) { return beginVersion().compareTo(((Lifespan)lifespan).beginVersion()); } public boolean isRightOf(Comparable version) { return isAlive() || endVersion().compareTo(version)>0; } /** Checks whether the given <tt>Descriptor</tt> is totally contained in this <tt>Lifespan</tt>. * @param descriptor the <tt>Lifespan</tt> to check. * @return true if the given <tt>Descriptor</tt> an instance of <tt>Lifespan</tt> and lies totally in this * <tt>Lifespan</tt> and false otherwise. * @see xxl.indexStructures.Separator#contains(xxl.indexStructures.Tree.Descriptor) */ /* public boolean contains(Descriptor descriptor) { if(!(descriptor instanceof Lifespan)) return false; Lifespan lifespan=(Lifespan) descriptor; return contains(lifespan.beginVersion())&& contains(lifespan.endVersion()); }*/ /**Checks whether this <tt>Lifespan</tt> contains the given <tt>version</tt>. * @param version the <tt>Version</tt> to check. * @return true if this <tt>Lifespan</tt> contains the given <tt>version</tt> and false otherwise. */ // protected public boolean contains(Version version) { if(version==null) return isAlive(); return (beginVersion().compareTo(version)<=0) && (isAlive()|| endVersion().compareTo(version)>0 || isRightClose()&& endVersion().compareTo(version)==0); } /**Creates a physical copy of this <tt>Lifespan</tt>. */ public Object clone() { Version in=isDefinite()? (Version)beginVersion().clone(): null; Version del= isDead()? (Version)endVersion().clone(): null; return new Lifespan(in, del, isRightClose()); } /**Gives the <tt>begin version</tt> of this <tt>Lifespan</tt>. * @return the <tt>begin version</tt> of this <tt>Lifespan</tt>. */ public Version beginVersion() { return (Version)sepValue(); } /**Gives the <tt>end version</tt> of this <tt>Lifespan</tt>. * @return the <tt>end version</tt> of this <tt>Lifespan</tt>. NOTE: The <tt>end version</tt> may be * <tt>null</tt>. */ public Version endVersion() { return (Version)maxBound(); } /**Deletes the <tt>Lifespan</tt> virually by setting the <tt>end version</tt> to the given * <tt>deleteVersion</tt>. * @param deleteVersion the </tt>Version</tt> at which the <tt>Lifespan</tt> is to delete. */ public void delete(Version deleteVersion) { if(isDead()&& (endVersion().compareTo(deleteVersion)!=0)) throw new UnsupportedOperationException("Delete a dead entry: Lifespan="+this+ " \t the deletion version="+deleteVersion); updateMaxBound(deleteVersion); isRightClose=false; } /**Checks whether this <tt>Lifespan</tt> is already dead, i.e. the <tt>end version</tt> is not null. * @return false if the <tt>end version</tt> is null and true otherwise. */ public boolean isDead() { return endVersion()!=null; } /**Checks whether this <tt>Lifespan</tt> is alive, i.e. the <tt>end version</tt> is null. * @return true if the <tt>end version</tt> is null and false otherwise. */ public boolean isAlive() { return endVersion()==null; } /** Checks whether the separation value of this <tt>Separator</tt> is definite (not null). * * @return <tt>true</tt> if the separation value is not <tt>null</tt>, <tt>false</tt> otherwise. */ public boolean isDefinite() { return sepValue!=null; } /**Gives whether this <tt>Lifespan</tt> is a right close interval, i.e. it is already dead and the flag * {@link Lifespan#isRightClose} is true. * @return true if this <tt>Lifespan</tt> is already dead and the flag {@link Lifespan#isRightClose} is true * and false otherwise. */ public boolean isRightClose() { return isDead()&& isRightClose; } @Override public boolean isPoint() { return isDead() && super.isPoint(); } /** Converts this <tt>Lifespan</tt> to a String. It is used to show the <tt>Lifespan</tt> on the * display. * @return the String display of this <tt>Lifespan</tt>. */ public String toString() { String del= isAlive()? "*": endVersion().toString(); return "<"+(beginVersion() == null ? "NULL" : beginVersion().toString())+", "+ del+">"; } public boolean equals(Object obj) { Lifespan lifespan = (Lifespan)obj; return this.beginVersion().equals(lifespan.beginVersion()) && (this.endVersion() == null ? lifespan.endVersion() == null : this.endVersion().equals(lifespan.endVersion())) && this.isRightClose() == lifespan.isRightClose(); } } /** This is an interface for version instances used by the MVBTree. * The <tt>MVBTree</tt> can work with any version objects. */ public static interface Version extends Comparable { public Object clone(); public int hashCode(); } /** * * * */ protected class MVBTreeQueryCursor extends xxl.core.indexStructures.QueryCursor { protected MVRegion nodeRegion; private Cursor nodeQuery; private int index; private int indexPrevious; private boolean routed; protected LeafEntry currentEntry; /** * List with entries which overlap right bound of query time interval, sorted according the key */ private List keySortedEntries; /** * * @param indexEntry * @param nodeRegion * @param queryRegion */ public MVBTreeQueryCursor(IndexEntry indexEntry, MVRegion nodeRegion, MVRegion queryRegion) { super(MVBTree.this, indexEntry, queryRegion, 0); this.indexEntry=indexEntry; this.nodeRegion=nodeRegion; currentNode= indexEntry.get(false); //if regio is alive if (queryRegion.endVersion() == null){ keySortedEntries = keySortCurrentNodeEntries(((Node)currentNode).getCurrentEntries()); }else{ // keySortedEntries = computeQueryCandidates(); keySortedEntries = keySortCurrentNodeEntries( ((Node)currentNode).query(queryRegion.endVersion())); } // System.out.println("Node Region " + nodeRegion); // System.out.println("Cursor Current Node " + currentNode); // // index=0; indexPrevious=0; routed=false; } /** * * @param indexEntry * @param nodeRegion * @param queryRegion * @param path */ public MVBTreeQueryCursor(IndexEntry indexEntry, MVRegion nodeRegion, MVRegion queryRegion, Stack path) { this(indexEntry, nodeRegion, queryRegion); this.path=path; path.push(new MapEntry(indexEntry, currentNode)); } /** * Test * @param entriesIterator * @return */ private List keySortCurrentNodeEntries(Iterator entriesIterator){ List sortedList = new ArrayList(); Comparator comparator = new Comparator() { // key + time sort public int compare(Object o1, Object o2) { MVSeparator mvSep1=(MVSeparator)separator(o1); MVSeparator mvSep2=(MVSeparator)separator(o2); int x= mvSep1.compareTo(mvSep2); if(x==0) x=mvSep1.lifespan().compareTo(mvSep2.lifespan()); return x; } }; while(entriesIterator.hasNext()){ sortedList.add(entriesIterator.next()); } Collections.sort(sortedList, comparator); return sortedList; } /** * * @param entriesIterator * @return */ protected List computeQueryCandidates(){ // entries which are cut the right region bound List sortedList = keySortCurrentNodeEntries( ((Node)currentNode).query(((MVRegion)queryRegion).endVersion())); // all entries which are overlap the query time interval Iterator entriesIterator = ((Node)currentNode).query(((MVRegion)queryRegion).lifespan()); while(entriesIterator.hasNext()){ Object item = entriesIterator.next(); MVSeparator itemSeparator = (MVSeparator)separator(item); Comparable key = itemSeparator.sepValue(); if(((MVRegion)queryRegion).lifespan().overlaps(itemSeparator.lifespan()) ){ // check if the key is already Comparable lastKey = ((MVSeparator)separator(sortedList.get(sortedList.size()-1))).sepValue(); Comparable firstKey = ((MVSeparator)separator(sortedList.get(0))).sepValue(); if (lastKey.compareTo(key) < 0 ){ // append key sortedList.add(item); }else if (firstKey.compareTo(key) > 0 ){ sortedList.add(0, item); } } } return sortedList; } /** * */ public boolean hasNextObject() { if(nodeQuery==null) if(currentNode.level()==0){ nodeQuery= ((Node)currentNode).referenceLeafQuery((MVRegion)queryRegion, nodeRegion); } else { nodeQuery= recursivQuery(); } while(!nodeQuery.hasNext()) { nodeQuery.close(); if(currentNode.level()==0) { if((indexPrevious>=((Node)currentNode).predecessors.size()) || (((MVRegion)queryRegion).beginVersion().compareTo(nodeRegion.beginVersion())>=0)) { return false; } else{ nodeQuery=queryTemporalPred(); } } else { if(index>=keySortedEntries.size()) return false; else { nodeQuery=recursivQuery(); } } } return true; } /** * */ public Object nextObject() { return currentEntry=(LeafEntry) nodeQuery.next(); } /** * */ public void removeObject() { nodeQuery.remove(); if(!routed) { if(!hasPath()) path= pathToNode(nodeRegion, 0); if(!indexEntry.equals(indexEntry(path))) throw new IllegalStateException("Wrong path!"); treatUnderflow(path); } } /** * */ public void updateObject(Object newData) { if(!routed){ nodeQuery.remove(); Lifespan lifespan=new Lifespan(currentVersion()); LeafEntry newEntry= new LeafEntry(lifespan, newData); if(!hasPath()) path= pathToNode(nodeRegion, 0); ((Node)currentNode).grow(newEntry, path); treatOverflow(path); } else nodeQuery.update(newData); } /** * */ public void close() { nodeQuery.close(); if(hasPath()&& !path.isEmpty()) up(path); indexEntry.unfix(); super.close(); } /** * * @return */ private Cursor queryTemporalPred() { IndexEntry predecessor=(IndexEntry)((Node)currentNode).predecessors.get(indexPrevious); indexPrevious++; if (((Node)currentNode).predecessors.size() == 2){ IndexEntry predecessor1 = (IndexEntry)((Node)currentNode).predecessors.get(0); IndexEntry predecessor2 = (IndexEntry)((Node)currentNode).predecessors.get(1); Comparable upperBound = max(((MVSeparator)predecessor1.separator).sepValue(), ((MVSeparator)predecessor2.separator).sepValue()); Comparable minBoundQueryRegion = ((MVRegion)queryRegion).minBound(); if (upperBound.compareTo(((MVSeparator)predecessor.separator).sepValue()) > 0 && minBoundQueryRegion.compareTo(upperBound) > 0){ return new EmptyCursor(); } } Comparable referenceKey= max(((MVSeparator)predecessor.separator).sepValue(), ((MVRegion)queryRegion).minBound()); Comparable predecessorMinBound = ((MVSeparator)predecessor.separator).sepValue(); if(referenceKey.compareTo(nodeRegion.minBound())>=0){ if (referenceKey.compareTo(nodeRegion.minBound()) == 0 //new code && predecessorMinBound.compareTo(nodeRegion.minBound()) < 0 ){ return new EmptyCursor(); } if (((MVRegion)queryRegion).maxBound().compareTo(predecessorMinBound) < 0 ){ return new EmptyCursor(); } routed=true; abolishPath(); MVRegion nodeReg = toMVRegion((MVSeparator)predecessor.separator); nodeReg.updateMaxBound(null); return linkReferenceQuery(predecessor, nodeReg, (MVRegion)queryRegion); } return new EmptyCursor(); } /** * new code */ private Cursor recursivQuery() { IndexEntry currentEntry=(IndexEntry)keySortedEntries.get(index); MVSeparator currentEntrySeparator =((MVSeparator)currentEntry.separator); Comparable maxBound = (nodeRegion).maxBound; if (((MVRegion)queryRegion).maxBound.compareTo(currentEntrySeparator.sepValue) < 0 ){ index = keySortedEntries.size(); } else{ MVRegion currentEntrySubRegion = toMVRegion(currentEntrySeparator); if (index < keySortedEntries.size()-1){ IndexEntry nextEntry=(IndexEntry)keySortedEntries.get(index + 1); maxBound = nextEntry.separator.sepValue(); } index++; currentEntrySubRegion.updateMaxBound(maxBound); if ( currentEntrySubRegion.keyOverlaps((MVRegion)queryRegion) ){ Stack newPath=(Stack)path.clone(); routed=true; // // System.out.println("chosed + " + currentEntry); return linkReferenceQuery(currentEntry, currentEntrySubRegion, (MVRegion)queryRegion, newPath); } } return new EmptyCursor(); } } // end of class MVBTreeQueryCursor /** * Test Cursor * */ public class PriorityQueryCursor extends Query{ /**Query Region */ protected MVRegion queryRegion; /**stores visited nodes ids*/ protected HashSet nodesIDs; /**Default key comparator needed for sorting the objects in the nodes*/ Comparator keyComparator = new Comparator() { public int compare(Object o1, Object o2) { MVSeparator mvSep1=(MVSeparator)separator(o1); MVSeparator mvSep2=(MVSeparator)separator(o2); int x= mvSep1.compareTo(mvSep2); if(x==0) x=mvSep1.lifespan().compareTo(mvSep2.lifespan()); return x; } }; /** * * @param queue * @param targetLevel * @param queryRegion */ public PriorityQueryCursor(Queue queue, int targetLevel, MVRegion queryRegion) { super(queue, targetLevel); this.queryRegion = queryRegion; nodesIDs = new HashSet(); if (!this.queue.isEmpty()) queue.dequeue(); // deleteRootEntry of Live version // put initQueue(); } /** Checks whether there exists a next result * @return <tt>true</tt> if the Query contains more objects */ public boolean hasNextObject () { for (; !queue.isEmpty() && ((Candidate)queue.peek()).parentLevel!=targetLevel;) { Candidate candidate = (Candidate)queue.dequeue(); Node node = (Node)((IndexEntry)candidate.entry()).get(true); if (node.level>=targetLevel) Queues.enqueueAll(queue, expand(candidate, node)); } return !queue.isEmpty(); } /** * Initializes queue. Runs range query on BPlusTree storing the VersionRoots, * and puts the result root entries in the queue. * */ protected void initQueue(){ Version minVersion = queryRegion.beginVersion(); Version maxVersion = queryRegion.endVersion(); Iterator rootsIterator = roots.rangeQuery(minVersion, maxVersion); while(rootsIterator.hasNext()){ Root rootEntry = (Root)rootsIterator.next(); IndexEntry indexEntry = rootEntry.toIndexEntry();//TODO queue.enqueue(createCandidate(null, indexEntry, indexEntry.separator(), indexEntry.parentLevel())); } Version lastRootSplitVersion= ((MVSeparator)((IndexEntry)rootEntry).separator()).insertVersion(); if((maxVersion==null)||(maxVersion.compareTo(lastRootSplitVersion)>=0)){ queue.enqueue(createCandidate(null, rootEntry, ((IndexEntry)rootEntry).separator(), height())); } } /** Expands Candidates stored in a node. * * @param parent the parent candidate * @param node the node to expand * @return an iterator containing the new candidates */ protected Iterator expand (final Candidate parent, final Node node){ Iterator entries = null; if (node.level > 0 ){ entries = expandIndexNode(queryRegion, node); } else entries = expandLeafNode(queryRegion, node, (MVSeparator)parent.descriptor()) ; return new Mapper( new AbstractFunction() { public Object invoke (Object entry) { return createCandidate(parent, entry, descriptor(entry), node.level); } } , entries); } /** * Expands the index node. * @param queryRegion * @param node * @return */ protected Iterator expandIndexNode(final MVRegion queryRegion, final Node node){ if (node.level == 0) throw new IllegalArgumentException(); List listEntries = new ArrayList(); Iterator it = node.entries(); // Filter time overlap entries while(it.hasNext()){ Object entry = it.next(); MVSeparator separator = (MVSeparator)descriptor(entry); MVRegion entryRegion = toMVRegion(separator); entryRegion.lifespan.isRightClose = false; if (entryRegion.lifespan().overlaps(queryRegion.lifespan())) listEntries.add(entry); } // sort according the key Collections.sort(listEntries, keyComparator); //Search maxBoundIndex if (separator(listEntries.get(0)).sepValue.compareTo(queryRegion.maxBound) > 0 ) return new EmptyCursor(); int maxBoundIndex = listEntries.size(); while( separator(listEntries.get(--maxBoundIndex)).sepValue.compareTo(queryRegion.maxBound) > 0 ){} List candidates = listEntries.subList(0, maxBoundIndex+1); //Search candidates and tries to define the maximal key value of the underlying //entry candidate List result = new ArrayList(); firstFor: for (int i = 0; i < candidates.size(); i++ ){ Object entry = candidates.get(i); MVSeparator separator = (MVSeparator)descriptor(entry); MVRegion entryRegion = toMVRegion(separator); entryRegion.lifespan.isRightClose = false; for (int j = i+1; j < candidates.size(); j++ ){ Object nextEntry = candidates.get(j); MVSeparator nextSeparator = (MVSeparator)descriptor(nextEntry); MVRegion nextEntryRegion = toMVRegion(nextSeparator); nextEntryRegion.lifespan.isRightClose = false; if (nextEntryRegion.lifespan().overlaps(entryRegion.lifespan())){ Comparable regionMaxBound = nextSeparator.sepValue(); entryRegion.updateMaxBound(regionMaxBound); if (entryRegion.overlaps(queryRegion)){ IndexEntry indexEntry = (IndexEntry)entry; Object id = indexEntry.id(); if (!nodesIDs.contains(id)){ nodesIDs.add(id); result.add(entry); } } continue firstFor; } } IndexEntry indexEntry = (IndexEntry)entry; Object id = indexEntry.id(); if (!nodesIDs.contains(id)){ nodesIDs.add(id); result.add(entry); } } return (result.isEmpty()) ? new EmptyCursor(): result.iterator(); } /** * Expands LeafNode. Works similar to * @see MVBTree.Node#referenceLeafQuery(xxl.core.indexStructures.MVBTree.MVRegion, xxl.core.indexStructures.MVBTree.MVRegion) * @param queryRegion * @param node * @param nodeSeparator * @return */ protected Iterator expandLeafNode(final MVRegion queryRegion, final Node node, MVSeparator nodeSeparator){ node.sortEntries(true); Comparable maxBound = separator(node.getLast()).sepValue(); final MVRegion nodeRegion = toMVRegion(nodeSeparator); nodeRegion.updateMaxBound(maxBound); Predicate test=new AbstractPredicate() { //reference Point left public boolean invoke(Object entry) { MVSeparator entrySeparator = (MVSeparator)separator(entry); Lifespan entryLifeSpan = entrySeparator.lifespan; Comparable entryKey = entrySeparator.sepValue; if (queryRegion.contains(entryKey) && queryRegion.lifespan.overlaps(entryLifeSpan) ){ Comparable referenceKey= max(queryRegion.minBound(), entryKey); Version referenceTime=(Version) max(queryRegion.beginVersion(), entryLifeSpan.beginVersion()); return nodeRegion.contains(referenceKey) && nodeRegion.lifespan().contains(referenceTime); } return false; } }; return new Filter(node.iterator(), test); } // End Priority Class } /** This Converter is concerned with serializing of nodes to write or read them into * or from the external storage. */ public class NodeConverter extends BPlusTree.NodeConverter { public Object read (DataInput dataInput, Object object) throws IOException { int level=dataInput.readInt(); int number=dataInput.readInt(); Node node = (Node)createNode(level); readPredecessors(dataInput, node); for (int i=0; i<number; i++) { Object entry; if(node.level()==0) entry= readLeafEntry(dataInput); else entry=readIndexEntry(dataInput,node.level); node.entries.add(entry); } return node; } public void write (DataOutput dataOutput, Object object) throws IOException { Node node = (Node)object; IntegerConverter.DEFAULT_INSTANCE.writeInt(dataOutput, node.level); IntegerConverter.DEFAULT_INSTANCE.writeInt(dataOutput, node.number()); writePredecessors(dataOutput, node); for(int i=0; i<node.number();i++) { Object entry=node.getEntry(i); if(node.level==0) writeLeafEntry(dataOutput,(LeafEntry)entry); else writeIndexEntry(dataOutput,(IndexEntry)entry); } } public int headerSize() { return 2*IntegerConverter.SIZE + predecessorsSize(); } protected void writePredecessors(DataOutput dataOutput, Node node) throws IOException { IntegerConverter.DEFAULT_INSTANCE.writeInt(dataOutput, node.predecessors.size()); for(int i=0; i<node.predecessors.size(); i++) { IndexEntry predecessor=(IndexEntry)node.predecessors.get(i); writeIndexEntry(dataOutput, predecessor); } } protected void readPredecessors(DataInput dataInput, Node node) throws IOException { int count=IntegerConverter.DEFAULT_INSTANCE.readInt(dataInput); for(int i=0; i<count; i++) { IndexEntry predecessor= readIndexEntry(dataInput, 1); node.predecessors.add(predecessor); } } protected void writeMVRegion(DataOutput output, MVRegion region) throws IOException { keyConverter.write(output, region.minBound()); keyConverter.write(output, region.maxBound()); versionConverter.write(output, region.beginVersion()); BooleanConverter.DEFAULT_INSTANCE.write(output, new Boolean(region.isDead())); if(region.isDead()) versionConverter.write(output, region.endVersion()); } protected MVRegion readMVRegion(DataInput input) throws IOException { Comparable min=(Comparable)keyConverter.read(input, null); Comparable max=(Comparable)keyConverter.read(input, null); Version begin=(Version)versionConverter.read(input, null); Version end=null; boolean isDead=BooleanConverter.DEFAULT_INSTANCE.readBoolean(input); if(isDead) end=(Version)versionConverter.read(input, null); return createMVRegion(begin, end, min, max); } protected int mvRegionSize() { return 2*keyConverter.getMaxObjectSize()+2*versionConverter.getMaxObjectSize()+BooleanConverter.SIZE; } protected int predecessorsSize() { return IntegerConverter.SIZE+2*(IntegerConverter.SIZE+mvRegionSize()+container().getIdSize()); } protected IndexEntry readIndexEntry(DataInput input, int parentLevel) throws IOException { IndexEntry indexEntry=(IndexEntry)createIndexEntry(parentLevel); Object id= container().objectIdConverter().read(input, null); Comparable sepValue=(Comparable)keyConverter.read(input, null); Lifespan life= (Lifespan)lifespanConverter.read(input, null); MVSeparator mvSeparator= createMVSeparator(life.beginVersion(), life.endVersion(), sepValue); indexEntry.initialize(id, mvSeparator); return indexEntry; } protected void writeIndexEntry(DataOutput output, IndexEntry entry) throws IOException { container().objectIdConverter().write(output, entry.id()); keyConverter.write(output, entry.separator.sepValue()); lifespanConverter.write(output, ((MVSeparator)entry.separator()).lifespan()); } @Override protected int indexEntrySize() { return container().getIdSize()+keyConverter.getMaxObjectSize()+lifespanConverter.getMaxObjectSize(); } public int maxIndexEntrySize(){ return indexEntrySize(); } public int maxLeafEntrySize(){ return leafEntrySize() ; } protected LeafEntry readLeafEntry(DataInput input) throws IOException { Lifespan life= (Lifespan)lifespanConverter.read(input, null); Object data= dataConverter.read(input, null); return new LeafEntry(life, data); } protected void writeLeafEntry(DataOutput output, LeafEntry entry) throws IOException { lifespanConverter.write(output, entry.lifespan); dataConverter.write(output, entry.data()); } @Override protected int leafEntrySize() { return dataConverter.getMaxObjectSize()+lifespanConverter.getMaxObjectSize(); } /** * Computes the maximal size (in bytes) of a record (IndexEntry or data * object). It calls the method getIdSize() of the tree container. If * the tree container has not been initialized a NullPointerException is * thrown. * * @return the maximal size of a record (IndexEntry or data object) */ public int getMaxRecordSize() { return Math.max(dataConverter.getMaxObjectSize() + lifespanConverter.getMaxObjectSize(), indexEntrySize()); } } }