/* 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.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Stack; import xxl.core.collections.MapEntry; import xxl.core.collections.MappedList; import xxl.core.collections.containers.Container; import xxl.core.cursors.Cursor; 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.Converter; import xxl.core.io.converters.IntegerConverter; import xxl.core.io.converters.MeasuredConverter; import xxl.core.predicates.AbstractPredicate; import xxl.core.predicates.Predicate; /** * This class implements a B+Tree. * For a detailed discussion see Douglas Comer: "The Ubiquitous B-Tree", ACM * Comput. Surv. 11(2), 121-137, 1979. */ public class BPlusTree extends Tree { /** * This declares the block size of the underlying external storage. This * field is initialized finally in the constructor by the value given from * the user as a parameter. It is used to compute the maximal capacity of * the tree's nodes. * * @see #B_IndexNode * @see #B_LeafNode */ public final int BLOCK_SIZE; /** * This is the maximal capacity of the tree's index nodes (except the root), * i.e. the maximal number of entries which a node can contain. Its value is * the quotient of the given block size and the maximal entry size. * * @see #BLOCK_SIZE */ protected int B_IndexNode; /** * This is the maximal capacity of the tree's leaf nodes, i.e. * the maximal number of entries which a node can contain. Its value is the * quotient of the given block size and the maximal entry size. * * @see #BLOCK_SIZE */ protected int B_LeafNode; /** * This is the minimal capacity of the tree's index nodes, i.e. the minimal number * of entries which a node can contain. Its value is: * <code> {@link #B_IndexNode}*{@link #minCapacityRatio}</code>. * * @see #B_IndexNode * @see #minCapacityRatio */ protected int D_IndexNode; /** * This is the minimal capacity of the tree's leaf nodes, i.e. the minimal number * of entries which a node can contain. Its value is: * <code> {@link #B_LeafNode}*{@link #minCapacityRatio}</code>. * * @see #B_LeafNode * @see #minCapacityRatio */ protected int D_LeafNode; /** * This defines the minimal capacity ratio of the tree's nodes. */ protected final double minCapacityRatio; /** * This is a Converter for data objects stored in the tree. It is used to * read or write these data objects from or into the external storage. */ protected MeasuredConverter dataConverter; /** * This is a <tt>Converter</tt> for keys used by the tree. It is used to * read or write these keys from or into the external storage. */ protected MeasuredConverter keyConverter; /** * This <tt>Converter</tt> is used to read or write nodes (i.e. blocks) * from or into the external storage. */ protected NodeConverter nodeConverter; /** * This <tt>Function</tt> is used to get the key of a data object. * Example: <code><pre> * * Comparable key = (Comparable) getKey.invoke(data); * </pre></code> */ protected Function getKey; /** * This <tt>Function</tt> is used by the method * {BPlusTree#createSeparator(Comparable)} to create <tt>Separators</tt>. * The user has to initialize this field with a suitable <tt>Function</tt>. * * @see BPlusTree#createSeparator(Comparable) */ protected Function createSeparator; /** * This <tt>Function</tt> is used by the method * {BPlusTree#createKeyRange(Comparable, Comparable)} to create a * <tt>KeyRange</tt>. The user has to initialize this field with a * suitable <tt>Function</tt>. * * @see BPlusTree#createKeyRange(Comparable, Comparable) */ protected Function createKeyRange; /** * Flag indicating if a reorganization took place. */ protected boolean reorg = false; /** * Contains last used Descriptor for a query. */ protected Descriptor lastQueryDisscriptor; /** * Indicates whether a B+Tree can contain Duplicates or not. */ protected boolean duplicate; /** * Creates a new <tt>BPlusTree</tt>. With a default setting of duplicates = false * * @param blockSize * the block size of the underlying storage * @param minCapacityRatio * the minimal capacity ratio of the tree's nodes */ public BPlusTree(int blockSize, double minCapacityRatio) { this(blockSize, minCapacityRatio, false ); } /** * Creates a new <tt>BPlusTree</tt>. The minimal capacity ratio is set to * the default value 0.5 (i.e. 50%). and a default setting of duplicates = false * * @param blockSize * the block size of the underlying storage. */ public BPlusTree(int blockSize) { this(blockSize, 0.5f); } /** * Creates a new <tt>BPlusTree</tt>. The minimal capacity ratio is set to * the default value 0.5 (i.e. 50%) with the option to set duplicates * @param blockSize * @param allowDuplicate */ public BPlusTree(int blockSize, boolean allowDuplicate){ this(blockSize, 0.5f, allowDuplicate); } /** * Creates a new <tt>BPlusTree</tt>. * with the option to set duplicates * @param blockSize * the block size of the underlying storage * @param minCapacityRatio * the minimal capacity ratio of the tree's nodes */ public BPlusTree(int blockSize, double minCapacityRatio, boolean allowDuplicate) { super(); if ((minCapacityRatio >= 1) || (minCapacityRatio <= 0)) throw new IllegalArgumentException("Illegal min. capacity: " + minCapacityRatio); this.minCapacityRatio = minCapacityRatio; BLOCK_SIZE = blockSize; nodeConverter = createNodeConverter(); duplicate = allowDuplicate; } /** * Initializes the <tt>BPlusTree</tt>. * * @param getKey * the <tt>Function</tt> to get the key of a data object * @param keyConverter * the <tt>Converter</tt> for the keys used by the tree * @param dataConverter * the <tt>Converter</tt> for data objects stored in the tree * @param createSeparator * a factory <tt>Function</tt> to create <tt>Separators</tt> * @param createKeyRange * a factory <tt>Function</tt> to create <tt>KeyRanges</tt> * @param getSplitMinRatio * a <tt>Function</tt> to determine the minimal relative number * of entries which the node may contain after a split * @param getSplitMaxRatio * a <tt>Function</tt> to determine the maximal relative number * of entries which the node may contain after a split * @return the initialized <tt>BPlusTree</tt> itself */ protected BPlusTree initialize(Function getKey, MeasuredConverter keyConverter, MeasuredConverter dataConverter, Function createSeparator, Function createKeyRange, Function getSplitMinRatio, Function getSplitMaxRatio) { this.getKey = getKey; this.keyConverter = keyConverter; this.dataConverter = dataConverter; this.nodeConverter = createNodeConverter(); this.createSeparator = createSeparator; this.createKeyRange = createKeyRange; int space = this.BLOCK_SIZE - nodeConverter.headerSize(); this.B_IndexNode = space / nodeConverter.indexEntrySize(); this.B_LeafNode = space / nodeConverter.leafEntrySize(); this.D_IndexNode = (int) (minCapacityRatio * this.B_IndexNode); this.D_LeafNode = (int) (minCapacityRatio * this.B_LeafNode); Function getDescriptor = new AbstractFunction() { public Object invoke(Object o) { if (o instanceof Separator) return o; if (o instanceof IndexEntry) return ((IndexEntry) o).separator; return createSeparator(key(o)); } }; Predicate overflows = new AbstractPredicate() { public boolean invoke(Object o) { Node node = (Node) o; return node.number() > (node.level() == 0 ? B_LeafNode : B_IndexNode); } }; Predicate underflows = new AbstractPredicate() { public boolean invoke(Object o) { Node node = (Node) o; return node.number() < (node.level() == 0 ? D_LeafNode : D_IndexNode); } }; return initialize(getDescriptor, underflows, overflows, getSplitMinRatio, getSplitMaxRatio); } /** * Initializes the <tt>BPlusTree</tt>. * * @param getKey * the <tt>Function</tt> to get the key of a data object * @param getContainer * returns the Container that is used to store the nodes of the * tree * @param determineContainer * a Function to determine the Container that is used to store a * new node created by a split operation * @param keyConverter * the <tt>Converter</tt> for the keys used by the tree * @param dataConverter * the <tt>Converter</tt> for data objects stored in the tree * @param createSeparator * a factory <tt>Function</tt> to create <tt>Separators</tt> * @param createKeyRange * a factory <tt>Function</tt> to create <tt>KeyRanges</tt> * @param getSplitMinRatio * a <tt>Function</tt> to determine the minimal relative number * of entries which the node may contain after a split * @param getSplitMaxRatio * a <tt>Function</tt> to determine the maximal relative number * of entries which the node may contain after a split * @return the initialized <tt>BPlusTree</tt> itself */ public BPlusTree initialize(Function getKey, Function getContainer, Function determineContainer, MeasuredConverter keyConverter, MeasuredConverter dataConverter, Function createSeparator, Function createKeyRange, Function getSplitMinRatio, Function getSplitMaxRatio) { this.getContainer = getContainer; this.determineContainer = determineContainer; return initialize(getKey, keyConverter, dataConverter, createSeparator, createKeyRange, getSplitMinRatio, getSplitMaxRatio); } /** * Initializes the <tt>BPlusTree</tt>. * * NOTE: This method is used in the case that the tree has only one * container. For multidisk storage the user has to use the method: * {@link #initialize (Function getKey, Container container, * MeasuredConverter keyConverter, MeasuredConverter dataConverter, Function * createSeparator, Function createKeyRange, Function getSplitMinRatio, * Function getSplitMaxRatio)}. * * @param getKey * the <tt>Function</tt> to get the key of a data object * @param container * the Container that is used to store the nodes of the tree * @param keyConverter * the <tt>Converter</tt> for the keys used by the tree * @param dataConverter * the <tt>Converter</tt> for data objects stored in the tree * @param createSeparator * a factory <tt>Function</tt> to create <tt>Separators</tt> * @param createKeyRange * a factory <tt>Function</tt> to create <tt>KeyRanges</tt> * @param getSplitMinRatio * a <tt>Function</tt> to determine the minimal relative number * of entries which the node maycontain after a split * @param getSplitMaxRatio * a <tt>Function</tt> to determine the maximal relative number * of entries which the node may contain after a split * @return the initialized <tt>BPlusTree</tt> itself * * @see #initialize(Function, Container, MeasuredConverter, * MeasuredConverter, Function, Function, Function, Function) */ public BPlusTree initialize(Function getKey, final Container container, MeasuredConverter keyConverter, MeasuredConverter dataConverter, Function createSeparator, Function createKeyRange, Function getSplitMinRatio, Function getSplitMaxRatio) { Function getContainer = new Constant(container); Function determineContainer = new Constant(container); return initialize(getKey, getContainer, determineContainer, keyConverter, dataConverter, createSeparator, createKeyRange, getSplitMinRatio, getSplitMaxRatio); } /** * Initializes the <tt>BPlusTree</tt>. It initializes * {@link xxl.core.indexStructures.Tree#getSplitMinRatio}and * {@link xxl.core.indexStructures.Tree#getSplitMaxRatio} in the following * manner: <code><pre> * Function getSplitMinRatio = new Constant(minCapacityRatio); * Function getSplitMaxRatio = new Constant(1.0); * return initialize(getKey, container, keyConverter, dataConverter, * createSeparator, createKeyRange, getSplitMinRatio, getSplitMaxRatio); * </pre></code> NOTE: This method is used in the case that the tree has only one * container. For multidisk storage the user has to use the method: * {@link #initialize (Function, Function, Function, MeasuredConverter, MeasuredConverter, Function, Function)}. * * @param getKey * the <tt>Function</tt> to get the key of a data object * @param container * the Container that is used to store the nodes of the tree * @param keyConverter * the <tt>Converter</tt> for the keys used by the tree * @param dataConverter * the <tt>Converter</tt> for data objects stored in the tree * @param createSeparator * a factory <tt>Function</tt> to create <tt>Separators</tt> * @param createKeyRange * a factory <tt>Function</tt> to create <tt>KeyRanges</tt> * @return the initialized <tt>BPlusTree</tt> itself * * @see #initialize (Function, Function, Function, * MeasuredConverter, MeasuredConverter, Function, Function) * @see #initialize (Function, Container, MeasuredConverter, * MeasuredConverter, Function, Function, Function, Function) */ public BPlusTree initialize(final Function getKey, final Container container, MeasuredConverter keyConverter, MeasuredConverter dataConverter, final Function createSeparator, final Function createKeyRange) { Function getSplitMinRatio = new Constant(this.minCapacityRatio); Function getSplitMaxRatio = new Constant(1.0); return initialize(getKey, container, keyConverter, dataConverter, createSeparator, createKeyRange, getSplitMinRatio, getSplitMaxRatio); } /** * Initializes the <tt>BPlusTree</tt>. It initializes * {@link xxl.core.indexStructures.Tree#getSplitMinRatio}and * {@link xxl.core.indexStructures.Tree#getSplitMaxRatio} in the following * manner: <code><pre> * Function getSplitMinRatio = new Constant(minCapacityRatio); * Function getSplitMaxRatio = new Constant(1.0); * return initialize(getKey, getContainer, determineContainer, keyConverter, * dataConverter, createSeparator, createKeyRange, getSplitMinRatio, * getSplitMaxRatio); * </pre></code> * * @param getKey * the <tt>Function</tt> to get the key of a data object * @param getContainer * returns the Container that is used to store the nodes of the * tree * @param determineContainer * a Function to determine the Container that is used to store a * new node created by a split operation * @param keyConverter * the <tt>Converter</tt> for the keys used by the tree * @param dataConverter * the <tt>Converter</tt> for data objects stored in the tree * @param createSeparator * a factory <tt>Function</tt> to create <tt>Separators</tt> * @param createKeyRange * a factory <tt>Function</tt> to create <tt>KeyRanges</tt> * @return the initialized <tt>BPlusTree</tt> itself * * @see #initialize (Function, Function, Function, * MeasuredConverter, MeasuredConverter, Function, Function, Function, * Function) * @see #initialize (Function, Container, MeasuredConverter, * MeasuredConverter, Function, Function) */ public BPlusTree initialize(Function getKey, Function getContainer, Function determineContainer, MeasuredConverter keyConverter, MeasuredConverter dataConverter, final Function createSeparator, final Function createKeyRange) { Function getSplitMinRatio = new Constant(this.minCapacityRatio); Function getSplitMaxRatio = new Constant(1.0); return initialize(getKey, getContainer, determineContainer, keyConverter, dataConverter, createSeparator, createKeyRange, getSplitMinRatio, getSplitMaxRatio); } /** * Initializes the <tt>BPlusTree</tt> and sets the member fields * <tt>rootEntry</tt> and <tt>rootDescriptor</tt> to null. * * @param getDescriptor * the new {@link xxl.core.indexStructures.Tree#getDescriptor} * @param underflows * a <tt>Predicate</tt> that tests whether a node underflows * @param overflows * a <tt>Predicate</tt> that tests whether a node overflows * @param getSplitMinRatio * a <tt>Function</tt> to determine the minimal relative number * of entries which the node may contain after a split * @param getSplitMaxRatio * a <tt>Function</tt> to determine the maximal relative number * of entries which the node may contain after a split * @return the initialized <tt>BPlusTree</tt> itself */ protected BPlusTree initialize(Function getDescriptor, Predicate underflows, Predicate overflows, Function getSplitMinRatio, Function getSplitMaxRatio) { return initialize(getDescriptor, this.getContainer, this.determineContainer, underflows, overflows, getSplitMinRatio, getSplitMaxRatio); } /** * Initializes the <tt>BPlusTree</tt> and sets the member fields * <tt>rootEntry</tt> and <tt>rootDescriptor</tt> to null. * * @param getDescriptor * the new {@link xxl.core.indexStructures.Tree#getDescriptor} * @param getContainer * returns the Container that is used to store the nodes of the * tree * @param determineContainer * a Function to determine the Container that is used to store a * new node created by a split operation * @param underflows * a <tt>Predicate</tt> that tests whether a node underflows * @param overflows * a <tt>Predicate</tt> that tests whether a node overflows * @param getSplitMinRatio * a <tt>Function</tt> to determine the minimal relative number * of entries which the node may contain after a split * @param getSplitMaxRatio * a <tt>Function</tt> to determine the maximal relative number * of entries which the node may contain after a split * @return the initialized <tt>BPlusTree</tt> itself */ protected BPlusTree initialize(Function getDescriptor, Function getContainer, Function determineContainer, Predicate underflows, Predicate overflows, Function getSplitMinRatio, Function getSplitMaxRatio) { return (BPlusTree) super.initialize((IndexEntry) null, (Descriptor) null, getDescriptor, getContainer, determineContainer, underflows, overflows, getSplitMinRatio, getSplitMaxRatio); } /** * Initializes the <tt>BPlusTree</tt>.It initializes * {@link xxl.core.indexStructures.Tree#getSplitMinRatio}and * {@link xxl.core.indexStructures.Tree#getSplitMaxRatio} in the following * manner: <code><pre> * Function getSplitMinRatio = new Constant(minCapacityRatio); * Function getSplitMaxRatio = new Constant(1.0); * return initialize(rootEntry, rootDescriptor, getKey, container, * keyConverter, dataConverter, createSeparator, createKeyRange, * getSplitMinRatio, getSplitMaxRatio); * </pre></code> * @param rootEntry * the new {@link Tree#rootEntry} * @param rootDescriptor * the new {@link Tree#rootDescriptor} * @param getKey * the <tt>Function</tt> to get the key of a data object * @param container * the Container that is used to store the nodes of the tree * @param keyConverter * the <tt>Converter</tt> for the keys used by the tree * @param dataConverter * the <tt>Converter</tt> for data objects stored in the tree * @param createSeparator * a factory <tt>Function</tt> to create <tt>Separators</tt> * @param createKeyRange * a factory <tt>Function</tt> to create <tt>KeyRanges</tt> * @return the initialized <tt>BPlusTree</tt> itself */ public BPlusTree initialize(IndexEntry rootEntry, Descriptor rootDescriptor, final Function getKey, final Container container, MeasuredConverter keyConverter, MeasuredConverter dataConverter, final Function createSeparator, final Function createKeyRange) { Function getSplitMinRatio = new Constant(this.minCapacityRatio); Function getSplitMaxRatio = new Constant(1.0); return initialize(rootEntry, rootDescriptor, getKey, container, keyConverter, dataConverter, createSeparator, createKeyRange, getSplitMinRatio, getSplitMaxRatio); } /** * Initializes the <tt>BPlusTree</tt>. It initializes * {@link xxl.core.indexStructures.Tree#getContainer}and * {@link xxl.core.indexStructures.Tree#determineContainer} in the following * manner: <code><pre> * Function getContainer = new Constant(container); * Function determineContainer = new Constant(container); * return initialize(rootEntry, rootDescriptor, getKey, getContainer, * determineContainer, keyConverter, dataConverter, * createSeparator, createKeyRange, getSplitMinRatio, * getSplitMaxRatio); * </pre></code> * @param rootEntry * the new {@link Tree#rootEntry} * @param rootDescriptor * the new {@link Tree#rootDescriptor} * @param getKey * the <tt>Function</tt> to get the key of a data object * @param container * the Container that is used to store the nodes of the tree * @param keyConverter * the <tt>Converter</tt> for the keys used by the tree * @param dataConverter * the <tt>Converter</tt> for data objects stored in the tree * @param createSeparator * a factory <tt>Function</tt> to create <tt>Separators</tt> * @param createKeyRange * a factory <tt>Function</tt> to create <tt>KeyRanges</tt> * @param getSplitMinRatio * a <tt>Function</tt> to determine the minimal relative number * of entries which the node may contain after a split * @param getSplitMaxRatio * a <tt>Function</tt> to determine the maximal relative number * of entries which the node may contain after a split * @return the initialized <tt>BPlusTree</tt> itself */ public BPlusTree initialize(IndexEntry rootEntry, Descriptor rootDescriptor, Function getKey, final Container container, MeasuredConverter keyConverter, MeasuredConverter dataConverter, Function createSeparator, Function createKeyRange, Function getSplitMinRatio, Function getSplitMaxRatio) { Function getContainer = new Constant(container); Function determineContainer = new Constant(container); return initialize(rootEntry, rootDescriptor, getKey, getContainer, determineContainer, keyConverter, dataConverter, createSeparator, createKeyRange, getSplitMinRatio, getSplitMaxRatio); } /** * Initializes the <tt>BPlusTree</tt>. It initializes * {@link xxl.core.indexStructures.Tree#getContainer}and * {@link xxl.core.indexStructures.Tree#determineContainer} in the following * manner: <code><pre> * this.getContainer = getContainer; * this.determineContainer = determineContainer; * return initialize(rootEntry, rootDescriptor, getKey, keyConverter, * dataConverter, createSeparator, createKeyRange, * getSplitMinRatio, getSplitMaxRatio); * </pre></code> * @param rootEntry * the new {@link Tree#rootEntry} * @param rootDescriptor * the new {@link Tree#rootDescriptor} * @param getKey * the <tt>Function</tt> to get the key of a data object * @param getContainer * returns the Container that is used to store the nodes of the * tree * @param determineContainer * a Function to determine the Container that is used to store a * new node created by a split operation * @param keyConverter * the <tt>Converter</tt> for the keys used by the tree * @param dataConverter * the <tt>Converter</tt> for data objects stored in the tree * @param createSeparator * a factory <tt>Function</tt> to create <tt>Separators</tt> * @param createKeyRange * a factory <tt>Function</tt> to create <tt>KeyRanges</tt> * @param getSplitMinRatio * a <tt>Function</tt> to determine the minimal relative number * of entries which the node may contain after a split * @param getSplitMaxRatio * a <tt>Function</tt> to determine the maximal relative number * of entries which the node may contain after a split * @return the initialized <tt>BPlusTree</tt> itself */ public BPlusTree initialize(IndexEntry rootEntry, Descriptor rootDescriptor, Function getKey, Function getContainer, Function determineContainer, MeasuredConverter keyConverter, MeasuredConverter dataConverter, Function createSeparator, Function createKeyRange, Function getSplitMinRatio, Function getSplitMaxRatio) { this.getContainer = getContainer; this.determineContainer = determineContainer; initialize(getKey, keyConverter, dataConverter, createSeparator, createKeyRange, getSplitMinRatio, getSplitMaxRatio); this.rootEntry = rootEntry; this.rootDescriptor = rootDescriptor; return this; } /** * Checks whether the duplicates mode is enabled * @return */ public boolean isDuplicatesEnabled(){ return this.duplicate; } /** * Creates a new node on a given level. * @param level the level of the new Node * @see xxl.core.indexStructures.Tree#createNode(int) */ public Tree.Node createNode(int level) { Node node = new Node(level); return node; } /** * Creates a new Index Entry. * @param parentLevel * the level of the node in which the new indexEntry is stored * @return a new indexEntr * @see xxl.core.indexStructures.Tree#createIndexEntry(int) * @see xxl.core.indexStructures.BPlusTree.IndexEntry */ public Tree.IndexEntry createIndexEntry(int parentLevel) { return new IndexEntry(parentLevel); } /** * This method invokes the Function {BPlusTree#createSeparator} with the * given argument and casts the result to <tt>Separator</tt>. * * @param sepValue * the separation value of the new <tt>Separator</tt> * @return the new created <tt>Separator</tt> */ protected Separator createSeparator(Comparable sepValue) { return (Separator) this.createSeparator.invoke(sepValue); } /** * This method invokes the Function {#createKeyRange} with the * given arguments and casts the result to <tt>KeyRange</tt>. * * @param min * the minimal boundary of the <tt>KeyRange</tt> * @param max * the maximal boundary of the <tt>KeyRange</tt> * @return the new created <tt>KeyRange</tt>. */ protected KeyRange createKeyRange(Comparable min, Comparable max) { return (KeyRange) createKeyRange.invoke(min, max); } /** * This method invokes the Function {BPlusTree#getKey} with the given * argument and casts the result to <tt>Comparable</tt>. It is used to * get the key value of a data object. * * @param data * the data object whose key is required * @return the key of the given data object */ protected Comparable key(Object data) { return (Comparable) getKey.invoke(data); } /** * It is used to determine the <tt>Separator</tt> of a given entry (e.g. * <tt>IndexEntry</tt>, data entry, etc.). It is equivalent to: * * <pre><code> * return (Separator) descriptor(entry); * </code></pre> * * @param entry * the entry whose <tt>Separator</tt> is required * @return the <tt>Separator</tt> of the given entry */ protected Separator separator(Object entry) { return (Separator)getDescriptor.invoke(entry); } /** * Returns the maximal size of a <tt>Node</tt>, i.e. {@link #B_LeafNode}. * * @return {@link #B_LeafNode} */ public int getLeafNodeB() { return B_LeafNode; } /** * Returns the maximal size of a <tt>Node</tt>, i.e. {@link #B_IndexNode}. * * @return {@link #B_IndexNode} */ public int getIndexNodeB() { return B_IndexNode; } /** * Returns the minimal size of a <tt>Node</tt>, i.e. {@link #D_LeafNode}. * * @return {@link #D_LeafNode} */ public int getLeafNodeD() { return D_LeafNode; } /** * Returns the minimal size of a <tt>Node</tt>, i.e. {@link #D_IndexNode}. * * @return {@link #D_IndexNode} */ public int getIndexNodeD() { return D_IndexNode; } /** * Creates a new <tt>NodeConverter</tt>. * * @return a new <tt>NodeConverter</tt> */ protected NodeConverter createNodeConverter() { return new NodeConverter(); } /** * Gives the <tt>NodeConverter</tt> used by the <tt>BPlusTree</tt>. * * @return the <tt>NodeConverter</tt> <tt>BPlusTree</tt> * * @see #nodeConverter */ public NodeConverter nodeConverter() { return nodeConverter; } /** * Gives the <tt>Container</tt> used by the <tt>BPlusTree</tt> to store * the nodes. It is equivalent to: <code><pre> * return (Container) this.getContainer.invoke(this); * </pre></code> NOTE: This only makes sense if the whole Tree is stored in a * single <tt>Container</tt>. This method is used by the default * <tt>NodeConverter</tt>. Therefore the user has to overwrite the * <tt>NodeConverter</tt> if the tree works in multidisk mode. * * @return the <tt>Container</tt> of the <tt>BPlusTree</tt> */ public Container container() { return (Container) this.getContainer.invoke(this); } /** * Checks whether the <tt>BPlusTree</tt> is empty. The <tt>BPlusTree</tt> * is empty if the <tt>rootEntry</tt> is <tt>null</tt> or the root node * is empty. * * @return <tt>true</tt> if the <tt>BPlusTree</tt> is empty and * <tt>false</tt> otherwise */ public boolean isEmpty() { return (rootEntry == null) || (rootEntry.get().number() == 0); } /** * Gives last used Descriptor for a query. * * @return last used Descriptor for a query. */ public Descriptor getLastDescriptor() { return lastQueryDisscriptor; } /** * * This method is used to remove a data object from the tree. It uses the * method {@link BPlusTree#separator(Object)}to get the <tt>Separator</tt> * of the given object. Thereafter it searches in the tree for an Object * with the same <tt>Separator</tt>. The method * {Separator#equals{Object)} is used to check whether two Separators are * the same. * NOTE: In duplicate mode the objects are compared with o1.equals(o2) * * @param data * the object which has to be removed * @return the removed object if the search was successful, and * <tt>null</tt> otherwise */ public Object remove(final Object data) { return remove(data, new AbstractPredicate() { public boolean invoke(Object o1, Object o2) { return (duplicate) ? o1.equals(o2) : key(o1).compareTo(key(o2)) == 0; } }); } /** Calls {@link #remove(Descriptor, int, Predicate)} whereas * the target level is set to zero. The used <tt>Descriptor</tt> is the result of * <pre><code> getDescriptor.invoke(data); </code></pre> * @param data object to remove * @param equals predicate determining if an object in the tree equals <tt>data</tt> * and should therefore be removed * @return the removed object or <tt>null</tt> if no object was removed * @see Tree#remove(Descriptor, int, Predicate) */ public Object remove (final Object data, final Predicate equals) { return remove((Descriptor)getDescriptor.invoke(data), 0, new AbstractPredicate () { public boolean invoke (Object object) { return equals.invoke(object, data); } } ); } /** * Removes the object which is specified by the given descriptor. * First query is called: * <pre> <code> Iterator objects = query(descriptor, targetLevel); </pre></code> * Then the first object which meets the <tt>test</tt>(Predicate) is removed from the tree and returned. * @param descriptor specifies the query * @param targetLevel the level on which the query has to stop * @param test a predicate the object to remove has to fulfill * @return the removed object or <tt>null</tt> if there was no such object */ public Object remove (Descriptor descriptor, int targetLevel, Predicate test) { Cursor objects = query(descriptor, targetLevel); Object retValue=null; while (objects.hasNext()) { Object object = objects.next(); if (test.invoke(object)) { objects.remove(); retValue= object; break; } } objects.close(); return retValue; } /** * Inserts an object into a given level of the tree. If level > 0 * <tt>data</tt> has to be an <tt>IndexEntry</tt> (this only makes sense * for trees whose index nodes support Node.grow()). * * @param data * the data to insert * @param descriptor * is used to find the node into which data must be inserted * @param targetLevel * is the tree-level into which <tt>data</tt> has to be * inserted (<tt>targetLevel==0</tt> means insert into the * suitable leaf node) */ protected void insert(Object data, Descriptor descriptor, int targetLevel) { if (rootEntry() == null) { Comparable key = ((Separator) descriptor).sepValue(); rootDescriptor = createKeyRange(key, key); grow(data); } else{ super.insert(data, descriptor, targetLevel); // // test code Separator sep = ((IndexEntry)rootEntry()).separator(); Separator entrySep = (Separator)descriptor; if(sep.sepValue().compareTo(entrySep.sepValue()) < 0){ ((IndexEntry)rootEntry()).separator().updateSepValue(entrySep.sepValue()); } } } /** * This method computes the path from the root to the leaf node referred by * the given <tt>indexEntry</tt>. The path nodes are fixed in the * underlying buffer. If they are no longer required the caller has to unfix them. * If the given node is not a leaf node an * {@link java.lang.UnsupportedOperationException}is thrown. The method * calls * {@link xxl.core.indexStructures.BPlusTree#pathToLeaf(xxl.core.indexStructures.BPlusTree.Node)} * where <tt>node</tt> is the leaf referred by <tt>indexEntry</tt>. * * @param indexEntry * the Index Entry which refers the leaf node * @return the path to the leaf node referred by the given index entry * @throws UnsupportedOperationException * if invoked an a none leaf node * * @see #pathToLeaf(xxl.core.indexStructures.BPlusTree.Node) */ protected Stack pathToLeaf(IndexEntry indexEntry) throws UnsupportedOperationException { if (indexEntry.level() != 0) throw new UnsupportedOperationException( "The node is not a leaf node."); Node node = (Node) indexEntry.get(false); return pathToLeaf(node); } /** * This method computes the path from the root to a leaf node. If the given * node is not a leaf node an * {@link java.lang.UnsupportedOperationException}is thrown. The method * computes the key range of the leaf and then calls the method * {@link BPlusTree#pathToNode(BPlusTree.KeyRange, int)}(level=0). The path * nodes are fixed in the underlying buffer. * If they are no longer required the caller has to unfix them. * * @param leaf * node whose path is needed * @return the path to the given leaf * @throws UnsupportedOperationException * if invoked an a none leaf node * * @see #pathToNode(BPlusTree.KeyRange, int) */ protected Stack pathToLeaf(Node leaf) throws UnsupportedOperationException { if (leaf.level() != 0) throw new UnsupportedOperationException( "The node is not a leaf node."); Comparable min = key(leaf.getFirst()); Comparable max = key(leaf.getLast()); return pathToNode(createKeyRange(min, max), 0); } /** * This method determines the path from the root to a <tt>Node</tt>. The * <tt>Node</tt> is specified by its key range. The level parameter is required to * determine the target level. The path nodes are fixed in the * underlying buffer. If they are no longer required the caller has to unfix them. * * @param nodeRegion * the <tt>KeyRange</tt> of the <tt>Node</tt> * @param level * the level on which the <tt>Node</tt> is * @return the path from the root to the <tt>Node</tt> with the given * <tt>KeyRange</tt> on the given level */ protected Stack pathToNode(KeyRange nodeRegion, int level) { QueryCursor cursor = (QueryCursor) query(nodeRegion, level); cursor.hasNext(); return cursor.path(); } /** * Searches for data objects with the given key stored in the * <tt>BPlusTree</tt>. It calls <code> <pre> * KeyRange range = createKeyRange(key, key); * return query(range, 0); * </pre></code>. Returns the set of results as a cursor. * If the tree is not in duplicate mode * {@link java.lang.UnsupportedOperationException} is thrown. * * @param key * @return cursor * @throws UnsupportedOperationException * when the tree is not in duplicate mode */ public Cursor aloneKeyQuery(Comparable key){ if(!this.duplicate ){ throw new UnsupportedOperationException("B+Tree does not support duplicates!!! Please run " + "exactMatchQuery(Comparable key) method "); } KeyRange range = createKeyRange(key, key); return query(range, 0); } /** * Searches the single data object with the given key stored in the * <tt>BPlusTree</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) { if (this.duplicate){ throw new UnsupportedOperationException("B+Tree supports Duplicates!!! Please run aloneKeyQuery() method"); } Predicate p = new AbstractPredicate() { public boolean invoke() { return true; } public boolean invoke(Object argument) { return invoke(); } }; return exactMatchQuery(key, p); } /** * Searches for the single entry with the given key stored in the * <tt>BPlusTree</tt> which fulfills the given Predicate. The method * {@link #query(Descriptor queryDescriptor, int targetLevel)} * takes over the search process. * * @param key * the key of the data object which has to be searched * @param test * a Predicate which the found entry has to fulfill * @return the single data object with the given key or <tt>null</tt> if * no such object could be found */ protected Object exactMatchQuery(Comparable key, Predicate test) { KeyRange range = createKeyRange(key, key); Cursor results = query(range, 0); Object result = null; while (results.hasNext()) { Object o = results.next(); if (test.invoke(o)) { result = o; break; } } results.close(); return result; } /** * Searches all data objects stored in the tree whose key lie in the given * key range [minKey, maxKey]. The method * {@link #query(Descriptor queryDescriptor, int targetLevel)} * takes over the search process. * * @param minKey * the minimal boundary of the query's key range * @param maxKey * the maximal boundary of the query's key range * @return a <tt>Cursor</tt> pointing all qualified entries */ public Cursor rangeQuery(Comparable minKey, Comparable maxKey) { KeyRange range = createKeyRange(minKey, maxKey); return query(range, 0); } /** * This method calls {@link #query(IndexEntry subRootEntry, KeyRange queryInterval,int targetLevel)} * which uses an implementation of an efficient querying algorithm. The * result is a lazy <tt>Cursor</tt> pointing to all entries whose keys are * contained in <tt>queryDescriptor</tt>. * Initially query creates the correct parameters for the call. * * @param queryDescriptor * describes the query in terms of a <tt>Descriptor</tt>. In * this case it has to an instance of <tt>Separator</tt> oder * <tt>KeyRange</tt> * @param targetLevel * the tree-level to provide the answer-objects * @return a lazy <tt>Cursor</tt> pointing to all response objects */ public Cursor query(Descriptor queryDescriptor, int targetLevel) { KeyRange queryRange; if(queryDescriptor == null) return new EmptyCursor(); if (!(queryDescriptor instanceof KeyRange)) { Comparable key = ((Separator) queryDescriptor).sepValue(); queryRange = createKeyRange(key, key); } else queryRange = (KeyRange) queryDescriptor; if ((targetLevel >= height()) || !(rootDescriptor).overlaps(queryRange)) return new EmptyCursor(); return query((IndexEntry) rootEntry, queryRange, targetLevel); } // Hack for working around a bug in the MVBTree. // This is called from the MVBTree only. protected Cursor treequery(Descriptor queryDescriptor) { return super.query(queryDescriptor, 0); } /** * This method uses an implementation of an efficient querying algorithm. The * result is a lazy <tt>Cursor</tt> pointing to all entries whose keys are * contained in <tt>queryDescriptor</tt>. Method creates object of type * {@link xxl.core.indexStructures.BPlusTree.QueryCursor} * * @param subRootEntry * the root of the subtree in which the query is to execute * @param queryInterval * describes the query in terms of a <tt>Descriptor</tt>. In * this case it has to be an instance of <tt>KeyRange</tt> * @param targetLevel * the tree-level to provide the answer-objects * @return a lazy <tt>Cursor</tt> pointing to all response objects */ protected Cursor query(IndexEntry subRootEntry, KeyRange queryInterval, int targetLevel) { return new QueryCursor(subRootEntry, queryInterval, targetLevel); } /** * This method is called after an entry was removed in order to repair the * index structure. * * @param path * the path from the root to the <tt>Node</tt> from which the * entry was removed */ protected void treatUnderflow(Stack path) { while (((Node) node(path)).redressUnderflow(path)) ; } /** * This method is called after a new entry was inserted in order to repair * the index structure. * * @param path * the path from the root to the <tt>Node</tt> into which the * new entry was inserted */ protected void treatOverflow(Stack path) { post(path); } /** * Return the flag {@link #reorg}and resets it to <tt>false</tt>. * * @return value of #reorg. */ public boolean wasReorg() { boolean ret = reorg; reorg = false; return ret; } /** * This class describes the index entries of the <tt>BPlusTree</tt> (i.e. * the entries of the non-leaf nodes). Each <tt>IndexEntry</tt> refers to * a {@link BPlusTree.Node Node}which is the root node of the subtree. Each * <tt>IndexEntry</tt> has a <tt>Separator</tt> which describes the key * range of this subtree. * * @see xxl.core.indexStructures.Tree.IndexEntry */ public class IndexEntry extends Tree.IndexEntry { /** * The <tt>Separator</tt> of the <tt>IndexEntry</tt>. It describes * the key range of the subtree refered by this <tt>IndexEntry</tt>. */ public Separator separator; /** * Creates a new <tt>IndexEntry</tt> with a given * {@link xxl.core.indexStructures.Tree.IndexEntry#parentLevel}. * * @param parentLevel * the parent level of the new <tt>IndexEntry</tt> */ public IndexEntry(int parentLevel) { super(parentLevel); } /** * Initializes the (new created) <tt>IndexEntry</tt> using split * information. The <tt>SplitInfo</tt> is used to determine the new * <tt>Separator</tt> of the new <tt>IndexEntry</tt>. * * @param splitInfo * contains information about the split which led to * this <tt>IndexEntry</tt> being created * @return the <tt>IndexEntry</tt> itself * * @see BPlusTree.Node.SplitInfo * @see BPlusTree.Node.SplitInfo#separatorOfNewNode() */ public Tree.IndexEntry initialize(Tree.Node.SplitInfo splitInfo) { super.initialize(splitInfo); Separator sep = ((Node.SplitInfo) splitInfo).separatorOfNewNode(); if (sep == null) return this; return initialize(sep); } /** * Initializes the <tt>IndexEntry</tt> by the given <tt>Separator</tt>. * * @param separator * the new <tt>Separator</tt> of the current * <tt>IndexEntry</tt>. * @return the <tt>IndexEntry</tt> itself. */ public IndexEntry initialize(Separator separator) { this.separator = (Separator) separator.clone(); return this; } /** * Initializes the <tt>IndexEntry</tt> with the given ID and * <tt>Separator</tt>. * * @param id * the new ID of the current <tt>IndexEntry</tt> * @param separator * the new <tt>Separator</tt> of the current * <tt>IndexEntry</tt> * @return the <tt>IndexEntry</tt> itself */ public IndexEntry initialize(Object id, Separator separator) { return ((IndexEntry) this.initialize(id)).initialize(separator); } /** * Gives the <tt>Separator</tt> of this <tt>IndexEntry</tt>. * * @return the <tt>Separator</tt> of this <tt>IndexEntry</tt> */ public Separator separator() { return separator; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ public String toString() { return "(" + separator+ ", ->" + id +")"; } } /** * This class is used to represent leaf- and non-leaf nodes of a * <tt>BPlusTree</tt>. A <tt>Node</tt> contains a List of entries (data * objects or <tt>IndexEntries</tt>). All <tt>Nodes</tt> are stored in * <tt>Containers</tt>. * NOTE: if the tree is running in non-duplicate mode, the indexEntry descriptor * holds the value of the left most element. In duplicate mode it is the right most */ public class Node extends Tree.Node { /** * A <tt>List</tt> to hold the entries of the <tt>Node</tt>. */ protected List entries; /** * This is a reference to the <tt>Node</tt> containing the smallest * key which is larger than all keys stored in the subtree of this * <tt>Node</tt>. */ public IndexEntry nextNeighbor; /** * A <tt>Function</tt> to create the entries list. That makes the * <tt>Node</tt> more flexible, because it can use any implementation * of the abstract interface {@link java.util.List}. */ protected Function createEntryList; /** * Creates a new <tt>Node</tt> on a given level. * * @param level * the level of the new <tt>Node</tt> * @param createEntryList * a factory <tt>Function</tt> to create the entries list * * @see BPlusTree.Node#createEntryList */ public Node(int level, Function createEntryList) { super(); initialize(level, createEntryList); } /** * Creates a new <tt>Node</tt> on a given level. An * {@link java.util.ArrayList ArrayList}is used to store the entries. * * @param level * the level of the new <tt>Node</tt> */ public Node(final int level) { super(); initialize(level, new AbstractFunction() { public Object invoke() { return new ArrayList(level == 0 ? (BPlusTree.this.B_LeafNode + 1) : (BPlusTree.this.B_IndexNode + 1)); } }); } /** * Initializes the (new) <tt>Node</tt> with a level and a * <tt>Function</tt> for creating the <tt>Entries List</tt>. * * @param level * the level of the <tt>Node</tt> * @param createEntryList * a factory <tt>Function</tt> to create the entries list * @return the initialized <tt>Node</tt> */ private Node initialize(int level, Function createEntryList) { this.level = level; this.createEntryList = createEntryList; entries = (List) this.createEntryList.invoke(); return this; } /** * Initializes the <tt>Node</tt> and inserts a new entry into it. * * @param entry * the entry which has to be inserted * @return <tt>SplitInfo</tt> which contains information about a * possible split */ protected Tree.Node.SplitInfo initialize(Object entry) { Stack path = new Stack(); // Daniar: changed old code // if (level() > 0) { // IndexEntry indexEntry = (IndexEntry) entry; // if (indexEntry == rootEntry) // indexEntry.separator = createSeparator(((KeyRange) rootDescriptor).maxBound); // // } grow(entry, path); return new SplitInfo(path).initialize((Separator)separator(this.getLast()).clone()); } /** * This method can be used for bulk loading or insertion * @param level * @param entries * @return */ public Node initialize (final int level, List entries) { initialize(level, new AbstractFunction() { public Object invoke() { return new ArrayList(level == 0 ? (BPlusTree.this.B_LeafNode + 1) : (BPlusTree.this.B_IndexNode + 1)); } }); this.entries = entries; return this; } /** * Gives the next neighbor of this <tt>Node</tt>. * * @return the next neighbor of this <tt>Node</tt> * * @see BPlusTree.Node#nextNeighbor */ public IndexEntry nextNeighbor() { return nextNeighbor; } /** * Gives the entry stored on the given position in the <tt>Node</tt>. * * @param index * the position of the required entry * @return the entry stored on the given position in the <tt>Node</tt> * or <tt>null</tt> if the <tt>Node</tt> is empty */ public Object getEntry(int index) { if (entries.isEmpty()) return null; return entries.get(index); } /** * Gives the last entry of the <tt>Node</tt>. * * @return the last entry of the <tt>Node</tt> or <tt>null</tt> if * the <tt>Node</tt> is empty */ public Object getLast() { if (entries.isEmpty()) return null; return entries.get(entries.size() - 1); } /** * Gives the first entry of the <tt>Node</tt>. * * @return the first entry of the <tt>Node</tt> or <tt>null</tt> if * the <tt>Node</tt> is empty */ public Object getFirst() { if (entries.isEmpty()) return null; return entries.get(0); } /** * Gives the number of entries which are currently stored in this * <tt>Node</tt>. * * @return the number of entries which are currently stored in this * <tt>Node</tt> */ public int number() { return entries.size(); } /** * Gives an <tt>Iterator</tt> pointing to all entries stored in this * <tt>Node</tt> * * @return an <tt>Iterator</tt> pointing to all entries stored in this * <tt>Node</tt> */ public Iterator entries() { return iterator(); } /** * Gives an <tt>Iterator</tt> pointing to all entries stored in this * <tt>Node</tt> * * @return an <tt>Iterator</tt> pointing to all entries stored in this * <tt>Node</tt> */ protected Iterator iterator() { return entries.iterator(); } /** * Gives an <tt>Iterator</tt> pointing to the <tt>Descriptors</tt> * of each entry in this <tt>Node</tt>. * * @param nodeDescriptor * the descriptor of this <tt>Node</tt> * @return an <tt>Iterator</tt> pointing to the <tt>Descriptors</tt> * of each entry in this <tt>Node</tt> */ public Iterator descriptors(Descriptor nodeDescriptor) { return new Mapper( new AbstractFunction() { public Object invoke(Object entry) { return separator(entry); } },iterator()); } /** * Returns an <tt>Iterator</tt> of entries whose <tt>Separators</tt> * overlap with the <tt>queryDescriptor</tt>. * Initialization of minIndex and maxIndex similar to * {@link BPlusTree.Node#chooseSubtree(Descriptor descriptor, Stack path)} * @param queryDescriptor * the <tt>KeyRange</tt> describing the query * @return an <tt>Iterator</tt> of entries whose <tt>Separators</tt> * overlap with the <tt>queryDescriptor</tt> */ public Iterator query(Descriptor queryDescriptor) { KeyRange qInterval = (KeyRange) queryDescriptor; int minIndex = search(qInterval.minBound()); int maxIndex = rightMostSearch(qInterval.maxBound()); List response = null; minIndex = (minIndex >= 0) ? minIndex : (-minIndex-1 == this.number() )? -minIndex - 2: -minIndex - 1; maxIndex = (maxIndex >= 0) ? maxIndex : (-maxIndex-1 == this.number() )? -maxIndex - 2: -maxIndex - 1; maxIndex = Math.min(maxIndex + 1, number()); response = entries.subList(minIndex, maxIndex); return response.iterator(); } /** * Searches the given key in this <tt>Node</tt> using the binary * search algorithm on a sorted list. * NOTE: if running in duplicate mode left most duplicate value will be * returned * * @param key * the key which is to search in this <tt>Node</tt> * @return the position of the key if it was found or its insertion * position in the list otherwise */ protected int binarySearch(Comparable key) { if (entries.size() == 0) return -1; List sepValues = new MappedList(entries, new AbstractFunction() { public Object invoke(Object entry) { return separator(entry).sepValue(); } }); int minIndex = Collections.binarySearch(sepValues, key); return (duplicate) ? leftMostSearch(sepValues, minIndex, key ): minIndex ; } /** * required for duplicate mode. Left most search of duplicate value. * @param entryList * @param key * @return the position of the left most duplicate value */ protected int leftMostSearch(List entryList, int index, Comparable key ){ if (index >= 0){ while(!(index == 0) && entryList.get(index-1).equals(key)){ index--; } } return index; } /** * required for duplicate mode. Right most search of duplicate value. * @return */ protected int rightMostSearch(Comparable key){ List sepValues = new MappedList(entries, new AbstractFunction() { public Object invoke(Object entry) { return separator(entry).sepValue(); } }); int index = Collections.binarySearch(sepValues, key); Comparable duplicateKey = key; if (index <0 ) { // take a key of the sepvalue index = (-index - 1 == this.number()) ? -index - 2 : -index - 1; duplicateKey = (Comparable) sepValues.get(index); } while (index != (sepValues.size() - 1) && sepValues.get(index + 1).equals(duplicateKey)) index++; return index; } /** * Searches the given key in this <tt>Node</tt>. The default * implementation simply calls the method * {@link BPlusTree.Node#binarySearch(Comparable)}. * * @param key * the key which is to search in this <tt>Node</tt> * @return the position of the key if it was found or its insertion * position in the list otherwise */ protected int search(Comparable key) { return binarySearch(key); } /** * Searches the given <tt>Separator</tt> in this <tt>Node</tt>. The * default implementation takes the separation value of the given * <tt>Separator</tt> and searches for it using the method * {@link BPlusTree.Node#search(Comparable)}. * * @param separator * the <tt>Separator</tt> which is to search in this * <tt>Node</tt> * @return the position of the <tt>Separator</tt> if it was found or * its insertion position in the list otherwise */ protected int search(Separator separator ) { if (entries.size() == 0) return -1; if (!separator.isDefinite()) return 0; return search(separator.sepValue()); } /** * Searches the given <tt>IndexEntry</tt> in this <tt>Node</tt>. * The default implementation takes the <tt>Separator</tt> of the * given <tt>IndexEntry</tt> and searches for it using the method * {@link BPlusTree.Node#search(Separator)}. * * @param indexEntry * the <tt>IndexEntry</tt> which is to search in this * <tt>Node</tt> * @return the position of the <tt>IndexEntry</tt> if it was found or * its insertion position in the list otherwise */ protected int search(IndexEntry indexEntry) { return search(indexEntry.separator); } /** * Chooses the subtree which is followed during an insertion. * * In duplicate mode the minIndex is evaluated to positve when with a successful search * or negative when the value separator is not found, this negative value points to the * position in the list where the new item should be inserted. when this position lies * at the end of the list a special case is used to correct the value. * In normal mode for negative values of minIndex the item must be inserted before the * item at the position pointed at by the returned value. * {@link BPlusTree.Node#binarySearch(Comparable)} * @param descriptor * the <tt>Separator</tt> of the entry which is to insert * @param path * the path from the root to this <tt>Node</tt>. The * default implementation does not use this path * @return the <tt>IndexEntry</tt> pointing to the subtree which is * followed during an insertion */ protected Tree.IndexEntry chooseSubtree(Descriptor descriptor, Stack path) { Separator separator = (Separator) descriptor; int minIndex = search(separator); minIndex = (minIndex >= 0) ? minIndex : (-minIndex-1 == this.number() )? -minIndex - 2: -minIndex - 1; IndexEntry subTreeEntry = (IndexEntry) entries.get(minIndex); if ( minIndex == this.number()-1 && subTreeEntry.separator().compareTo(descriptor) < 0){ subTreeEntry.separator.sepValue = ((Separator)((descriptor).clone())).sepValue; BPlusTree.this.update(path); } return subTreeEntry; } /** * Posts the <tt>SplitInfo</tt> from the child <tt>Nodes</tt> to the * current <tt>Node</tt>. The method inserts the new * <tt>IndexEntry</tt> into the <tt>Node</tt> using the method * {@link BPlusTree.Node#grow(Object, Stack)}. It gets the path from * the given <tt>SplitInfo</tt>. * * @param splitInfo * contains the information about the split which led to * create the new <tt>IndexEntry</tt> * @param newIndexEntry * the new <tt>IndexEntry</tt> created by the split * * @see BPlusTree.Node#grow(Object, Stack) */ protected void post(Tree.Node.SplitInfo splitInfo, Tree.IndexEntry newIndexEntry) { if(!duplicate){ Separator boundary = (Separator)separator(((Node)node(splitInfo.path)).getLast()).clone(); ((IndexEntry)indexEntry(splitInfo.path)).separator.updateSepValue( boundary.sepValue); } grow(newIndexEntry, splitInfo.path); if (duplicate){ Separator boundary = (Separator)separator(((Node)node(splitInfo.path)).getLast()).clone(); ((IndexEntry)indexEntry(splitInfo.path)).separator.updateSepValue( boundary.sepValue); } } /** * Inserts an entry into the current <tt>Node</tt>. * * @param entry * the entry which has to be inserted into the node * @param path * the path from the root to the current node. The default * Implementation does not use this path * * @see BPlusTree.Node#grow(Object) */ protected void grow(Object entry, Stack path) { grow(entry); } /** * Inserts a new entry into this <tt>Node</tt> at the suitable * position. The position is found using a binary search. * {@link BPlusTree.Node#binarySearch(Comparable)} * * @param entry * the new entry which has to be inserted * @exception IllegalArgumentException in normal mode if the key already exists in the node. * */ protected void grow(Object entry) { int index; if (entries.isEmpty()) index = 0; else { index = search((separator(entry))); if (duplicate){ if (index >= 0 ) index = (level != 0 ) ? index+1 : index; else index = -index-1; } else{ if (index >= 0){ throw new IllegalArgumentException( "Insertion failed: An entry having the same key " + "was found");} index = -index - 1; } } entries.add(index, entry); } /** * Treats overflows which occur during an insertion. * It splits the overflowing node in new nodes. It creates new index-entries * for the new nodes. These index-entries will be be added to the given <tt>List</tt>. If the Leaf Node * is found to be full, {@link BPlusTree.Node#redistributeNode(Stack)} * is attempted to avoid a split. * The method {@link BPlusTree.Node#post(Tree.Node.SplitInfo, Tree.IndexEntry)} will later be used to post the * created index-entries to the parent node. * * @param path the path from the root to the overflowing node * @param newIndexEntries a <tt>List</tt> to carry the new index-entries created by the split * @param up signals whether the top node on stack will be written into the * storage by calling {@link Tree#update(Stack path)} and unfixed from the underlying buffer * by calling {@link Tree#up(Stack path)}. * @return a Collection which accrues from the given collection and the created * index-entries during the split. */ protected Collection redressOverflow (Stack path, List newIndexEntries, boolean up) { boolean redistributed = redistributeNode(path); if (!redistributed){ return super.redressOverflow(path, newIndexEntries, up); } if (up) { update(path); up(path); } return newIndexEntries; } /** * Treats overflows which occur during an insertion. The method * {@link xxl.core.indexStructures.Tree.Node#redressOverflow(Stack, xxl.core.indexStructures.Tree.Node, List)} * of the super class is used to split the overflowing <tt>Node</tt> * in two <tt>Nodes</tt>. It creates a new <tt>IndexEntry</tt> for * the new <tt>Node</tt>. This <tt>IndexEntry</tt> will be added to * the given List. * Finally the new <tt>Node</tt> is linked * from the split <tt>Node</tt> as {@link BPlusTree.Node#nextNeighbor}. * * @param path * the path from the root to the overflowing <tt>Node</tt> * @param parentNode * the parent <tt>Node</tt> of the overflowing * <tt>Node</tt> * @param newIndexEntries * a List to carry the new <tt>IndexEntries</tt> created by * the split * @return a <tt>SplitInfo</tt> containing required information about * the split */ protected Tree.Node.SplitInfo redressOverflow(Stack path, Tree.Node parentNode, List newIndexEntries) { Node node = (Node) node(path); SplitInfo splitInfo = (SplitInfo) super.redressOverflow(path, parentNode, newIndexEntries); IndexEntry newIndexEntry = (IndexEntry) newIndexEntries .get(newIndexEntries.size() - 1); node.nextNeighbor = newIndexEntry; return splitInfo; } /** * This method searches the right sibling node for a possible insertion point, * will be successful when right sibling is not full. * The method is reserved for improved BPlusTree. * In this implementation this method returns always false * * @param path the path from the root to this node * @return true when redistribution succeed */ protected boolean redistributeNode(Stack path){ return false; } /** * Splits the overflowed node. In non duplicate mode both the leaf and index nodes * are splited in the middle. In duplicate mode the leaf nodes are splited * according following strategy: * with leaf nodes the last element is selected and checked against * the the element at 75% position, when both are equal search for further duplicates until 25% reached * or no more duplicates exists, then split at found index position, otherwise split in the middle. * * @param path * @return a <tt>SplitInfo</tt> containing all needed information about * the split */ protected Tree.Node.SplitInfo split(Stack path) { reorg = true; Node node = (Node) node(path); List newEntries = null; int number = node.number(); int index = (number+1) / 2; if (this.level() == 0 && duplicate){ int dupIndex = node.number()-1; Comparable pivotEntry = separator(node.entries.get(dupIndex)).sepValue(); if ( pivotEntry.compareTo(separator(node.entries.get((number / 4)*3)).sepValue()) == 0 ){ // 75 % dupIndex = (number / 4)*3; while( dupIndex > (number/4) && pivotEntry.compareTo(separator(node.entries.get(dupIndex-1)).sepValue()) == 0 ){ dupIndex--; } index = dupIndex; } } newEntries = node.entries.subList(index, node.number()); Separator sepNewNode = (Separator) separator(newEntries.get(newEntries.size()-1)).clone(); entries.addAll(newEntries); newEntries.clear(); nextNeighbor = node.nextNeighbor; return (new SplitInfo(path)).initialize(sepNewNode); //TODO SplitStrategie } /** * Searches the given <tt>IndexEntry</tt> and removes it from this * <tt>Node</tt>. * * @param indexEntry * the <tt>IndexEntry</tt> which is to remove * @return the removed <tt>IndexEntry</tt> if it was found or * <tt>null</tt> otherwise */ protected IndexEntry remove(IndexEntry indexEntry) { if (level == 0) return null; int index = 0; if (duplicate){ // while(!getEntry(index).equals(indexEntry) && index < this.number()){ index++; } } else{ index = search(indexEntry); } return (IndexEntry) remove(index); } /** * Removes the entry stored in the <tt>Node</tt> on the given * position. * * @param index * the position of the entry which is to be removed * @return the removed entry if the given position is valid or * <tt>null</tt> otherwise */ protected Object remove(int index) { if ((index < 0) || (index >= entries.size())) return null; Object entry = entries.get(index); entries.remove(index); return entry; } /** * Treats underflows which occur during a remove operation. If the given * path is empty an <tt>IllegalStateException</tt> is thrown, otherwise the * method {@link BPlusTree.Node#redressUnderflow(Stack, boolean)}is * called with the parameter path and <tt>true</tt> to repair the * underflow. * * @param path * the path from the root to the underflowing <tt>Node</tt> * @return a boolean which indicates whether the parent <tt>Node</tt> * was updated during the operation */ protected boolean redressUnderflow(Stack path) { if (path.isEmpty()) throw new IllegalStateException(); return redressUnderflow(path, true); } /** * Treats underflows which occur during a remove operation. In case of * underflow the method * {@link BPlusTree.Node#redressUnderflow (BPlusTree.IndexEntry, BPlusTree.Node, Stack, boolean)} * is called to repair the underflow. * When the root node has only one entry the root entry pointer is deleted and replaced with the * child node. * @param path * the path from the root to the underflowing <tt>Node</tt> * @param up * signals whether the treated <tt>Nodes</tt> have to be * updated in the underlying <tt>Container</tt> * @return a boolean which indicates whether the parent <tt>Node</tt> * was been updated during the operation */ protected boolean redressUnderflow(Stack path, boolean up) { MapEntry pathEntry = (MapEntry) path.pop(); IndexEntry indexEntry = (IndexEntry) pathEntry.getKey(); Node node = (Node) pathEntry.getValue(); boolean parentUpdated = false; if (path.isEmpty() && (node.number() == 1) && (node.level > 0)) { //root node... IndexEntry newRootEntry = (IndexEntry) node.getLast(); rootEntry.remove(); rootEntry = newRootEntry; ((IndexEntry) rootEntry).separator = null; } else { if (!path.isEmpty() && node.underflows()){ parentUpdated = redressUnderflow(indexEntry, node, path, up); } else if (up) { parentUpdated = adjustSeparatorValue(node, indexEntry, path); indexEntry.update(node, true); indexEntry.unfix(); } } return parentUpdated; } /** * Ensures the separator value is updated, when last element is deleted * * @param node Node <tt>Node</tt> * @param indexEntry * the <tt>IndexEntry</tt> referring to the <tt>Node</tt> * @param path * the path from the root to parent of the * <tt>Node</tt> * @return true when an update was completed, otherwise false */ protected boolean adjustSeparatorValue(Node node, IndexEntry indexEntry, Stack path){ if (node.getLast()!=null && indexEntry.separator() != null ){ // node.getLast()!=null && Comparable sepLastValue = ((Separator)separator(node.getLast()).clone()).sepValue(); if (sepLastValue.compareTo(indexEntry.separator().sepValue()) < 0){ indexEntry.separator.updateSepValue(sepLastValue); // update return !path.isEmpty(); } else return false; } else{ return false; } } /** * Treats underflows which occur during a remove operation. It calls the * method {@link BPlusTree.Node#merge(BPlusTree.IndexEntry, Stack)}to * redistribute the elements of the underflowing <tt>Node</tt> an a * suitable sibling or to merge them so that the underflow is repaired. * * @param indexEntry * the <tt>IndexEntry</tt> referring to the underflowing * <tt>Node</tt> * @param node * the underflowing <tt>Node</tt> * @param path * the path from the root to parent of the underflowing * <tt>Node</tt> * @param up * signals whether the treated <tt>Nodes</tt> have to be * updated in the underlying <tt>Container</tt> * @return a boolean which indicate whether the parent <tt>Node</tt> * was been updated during the operation */ protected boolean redressUnderflow(IndexEntry indexEntry, Node node, Stack path, boolean up) { Node parentNode = (Node) node(path); // The merge... MergeInfo mergeInfo = node.merge(indexEntry, path); // Post the changes to the parent Node... if (mergeInfo.isMerge()) { if (mergeInfo.isRightSide()) { ((IndexEntry) parentNode.getEntry(mergeInfo.index())).separator = mergeInfo .newSeparator(); mergeInfo.indexEntry().update(mergeInfo.node(), true); ((IndexEntry) parentNode.remove(mergeInfo.index() + 1)) .remove(); } else { ((IndexEntry) parentNode.getEntry(mergeInfo.index() - 1)).separator = mergeInfo .siblingNewSeparator(); mergeInfo.siblingIndexEntry().update( mergeInfo.siblingNode(), true); ((IndexEntry) parentNode.remove(mergeInfo.index())) .remove(); } } else { ((IndexEntry) parentNode.getEntry(mergeInfo.index())).separator = mergeInfo .newSeparator(); int sibIndex = mergeInfo.isRightSide() ? mergeInfo.index() + 1 : mergeInfo.index() - 1; ((IndexEntry) parentNode.getEntry(sibIndex)).separator = mergeInfo .siblingNewSeparator(); if (up) { mergeInfo.indexEntry().update(mergeInfo.node(), true); mergeInfo.siblingIndexEntry().update( mergeInfo.siblingNode(), true); } } return true; } /** * Used to redistribute the elements of the underflowing <tt>Node</tt> * from a suitable sibling or to merge them so that the underflow is * repaired. * * @param indexEntry * the <tt>IndexEntry</tt> referring to the underflowing * <tt>Node</tt> * @param path * the path from the root to parent of the underflowing * <tt>Node</tt> * @return a <tt>MergeInfo</tt> containing some information about the * merge (or redistribution) */ protected MergeInfo merge(IndexEntry indexEntry, Stack path) { reorg = true; // Get the neighbor node with the most entries... Node parentNode = (Node) node(path); int index = parentNode.search(indexEntry); index = Math.max(0, index); IndexEntry rightSibling = null; Node rightNode = null; IndexEntry leftSibling = null; Node leftNode = null; if (index > 0) { leftSibling = (IndexEntry) parentNode.getEntry(index - 1); leftNode = (Node) leftSibling.get(false); } else { rightSibling = (IndexEntry) parentNode.getEntry(index + 1); rightNode = (Node) rightSibling.get(false); } MergeInfo mergeInfo = new MergeInfo(indexEntry, this, index, path); // merge left if (leftNode != null) { int D = leftNode.level() == 0 ? D_LeafNode : D_IndexNode; // After the merge operation, both nodes must have at least D entries if (leftNode.number() > D && (this.number()+(leftNode.number()-D)) > D ) { List newEntries = leftNode.entries.subList(D + (leftNode.number() - D) / 2, leftNode.number()); this.entries.addAll(0, newEntries); newEntries.clear(); mergeInfo.initialize(leftSibling, leftNode, false); } else { leftNode.entries.addAll(leftNode.number(), this.entries); leftNode.nextNeighbor = this.nextNeighbor(); // mergeInfo.initialize(leftSibling, leftNode, true); } } else {// merge right int D = rightNode.level() == 0 ? D_LeafNode : D_IndexNode; if (rightNode.number() > D && (this.number()+(rightNode.number()-D)) > D) { List newEntries = rightNode.entries.subList(0, (rightNode .number() - D + 1) / 2); this.entries.addAll(this.number(), newEntries); newEntries.clear(); mergeInfo.initialize(rightSibling, rightNode, false); } else { this.entries.addAll(this.number(), rightNode.entries); this.nextNeighbor = rightNode.nextNeighbor(); mergeInfo.initialize(rightSibling, rightNode, true); } } return mergeInfo; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ public String toString() { String nextNeighborString = (nextNeighbor == null)? "none" : nextNeighbor.toString(); return "{" + number() + ": " + entries + "}" + " NN: " + nextNeighborString; } /** * A <tt>SplitInfo</tt> contains information about a split. The * enclosing object of this SplitInfo-Object (i.e. <tt>Node.this</tt>) * is the new node which was created by the split. */ public class SplitInfo extends Tree.Node.SplitInfo { /** * The <tt>Separator</tt> of the <tt>IndexEntry</tt> of the new * <tt>Node</tt>. */ protected Separator separatorOfNewNode; /** * Creates a new <tt>SplitInfo</tt>. * * @param path * the current path */ public SplitInfo(Stack path) { super(path); } /** * Initializes the <tt>SplitInfo</tt> with the <tt>Separator</tt> * of the <tt>IndexEntry</tt> of the new <tt>Node</tt>. * * @param newSeparator * the <tt>Separator</tt> of the <tt>IndexEntry</tt> * of the new <tt>Node</tt> * @return the initialized <tt>SplitInfo</tt> itself */ public SplitInfo initialize(Separator newSeparator) { this.separatorOfNewNode = newSeparator; return this; } /** * Gives <tt>Separator</tt> of the <tt>IndexEntry</tt> of the * new <tt>Node</tt>. * * @return <tt>Separator</tt> of the <tt>IndexEntry</tt> of the * new <tt>Node</tt> */ public Separator separatorOfNewNode() { return separatorOfNewNode; } } /** * A <tt>MergeInfo</tt> contains information about a merge (or * redistribution). */ public class MergeInfo { /** * The path including the underflowing node. */ protected Stack path; /** * The position of the <tt>IndexEntry</tt> of the underflowing * <tt>Node</tt> in the parent <tt>Node</tt>. */ protected int index; /** * The <tt>IndexEntry</tt> of the underflowing <tt>Node</tt>. */ protected IndexEntry indexEntry; /** * The underflowing <tt>Node</tt>. */ protected Node node; /** * The <tt>IndexEntry</tt> of the sibling <tt>Node</tt>. */ protected IndexEntry siblingIndexEntry; /** * The sibling <tt>Node</tt>. */ protected Node siblingNode; /** * Indicates whether this MergeInfo describes a real merge or an * element redistribution. */ protected boolean isMerge = false; /** * Creates a new <tt>MergeInfo</tt>. * * @param indexEntry * the <tt>IndexEntry</tt> of the underflowing * <tt>Node</tt> * @param node * the underflowing <tt>Node</tt> * @param index * the position of the <tt>IndexEntry</tt> of the * underflowing <tt>Node</tt> in the parent * <tt>Node</tt> * @param path * the path including the underflowing node */ public MergeInfo(IndexEntry indexEntry, Node node, int index, Stack path) { this.indexEntry = indexEntry; this.node = node; this.index = index; this.path = path; } /** * Initializes the <tt>MergeInfo</tt>. * * @param siblingIndexEntry * the <tt>IndexEntry</tt> of the sibling <tt>Node</tt> * @param sibilingNode * the sibling <tt>Node</tt> * @param isMerge * indicates whether a real merge or a element * redistribution is described * @return the initialized <tt>MergeInfo</tt> itself */ protected MergeInfo initialize(IndexEntry siblingIndexEntry, Node sibilingNode, boolean isMerge) { this.siblingIndexEntry = siblingIndexEntry; this.siblingNode = sibilingNode; this.isMerge = isMerge; return this; } /** * Computes the new <tt>Separator</tt> of the <tt>IndexEntry</tt> * after the merge. The result is as follows: * <ul> * <li>Merge with the left sibling: * <ul> * <li>Real merge --> <tt>IllegalStateException</tt></li> * <li>Non real merge --> a copy of the <tt>Separator</tt> of the * <tt>Node's</tt>last entry</li> * @return the new <tt>Separator</tt> of the <tt>IndexEntry</tt> */ public Separator newSeparator() { if (!isRightSide() && isMerge()) throw new IllegalStateException(); return (Separator) separator(node.getLast()).clone(); } /** * Computes the new <tt>Separator</tt> of the sibling * <tt>IndexEntry</tt> after the merge. The result is as follows: * <ul> * <li>It is the right sibling of the merged node --> a copy of the <tt>Separator</tt> of the * first entry of the sibling <tt>Node</tt></li> * <li>It is the right sibling * <ul> * <li>Real merge --> <tt>IllegalStateException</tt></li> * <li>Non real merge --> a copy of the <tt>Separator</tt> of the * first entry of the sibling <tt>Node</tt></li> * </ul> * </li> * </ul> * @return the new <tt>Separator</tt> of the sibling * <tt>IndexEntry</tt> */ public Separator siblingNewSeparator() { if (isRightSide()) { if (isMerge()) throw new IllegalStateException(); return siblingIndexEntry.separator; // return (Separator) separator(siblingNode.getLast()).clone(); } else return (Separator) separator(siblingNode.getLast()).clone(); } /** * Indicates whether this MergeInfo describes a real merge or an * element redistribution. * * @return <tt>true</tt> if it is a real merge or <tt>false</tt> * otherwise */ public boolean isMerge() { return isMerge; } /** * Indicates whether the sibling <tt>Node</tt> lies on the right * side of the underflowing <tt>Node</tt>. * * @return <tt>true</tt> if the sibling <tt>Node</tt> lies on * the right side of the underflowing <tt>Node</tt> and * <tt>false</tt> otherwise */ public boolean isRightSide() { return index == 0; } /** * Gives the position of the <tt>IndexEntry</tt> of the * underflowing <tt>Node</tt> in the parent <tt>Node</tt>. * * @return the position of the <tt>IndexEntry</tt> of the * underflowing <tt>Node</tt> in the parent <tt>Node</tt> */ public int index() { return index; } /** * Gives the path including the underflowing node. * * @return the path including the underflowing node */ public Stack path() { return path; } /** * Gives the <tt>IndexEntry</tt> of the sibling <tt>Node</tt>. * * @return the <tt>IndexEntry</tt> of the sibling <tt>Node</tt> */ public IndexEntry siblingIndexEntry() { return siblingIndexEntry; } /** * Gives the parent <tt>Node</tt> of the underflowing * <tt>Node</tt>. * * @return the parent <tt>Node</tt> of the underflowing * <tt>Node</tt> */ public Node parentNode() { return (Node) BPlusTree.this.node(path); } /** * Gives the underflowing <tt>Node</tt>. * * @return the underflowing <tt>Node</tt> */ public Node node() { return node; } /** * Gives the sibling <tt>Node</tt>. * * @return the sibling <tt>Node</tt> */ public Node siblingNode() { return siblingNode; } /** * Gives the <tt>IndexEntry</tt> of the underflowing <tt>Node</tt>. * * @return the <tt>IndexEntry</tt> of the underflowing * <tt>Node</tt> */ public IndexEntry indexEntry() { return indexEntry; } } } /** * This class represents key ranges (i.e. intervals of keys). It is used to * specify (range) queries on the <tt>BPlusTree</tt> and to hold the key * range of the data objects stored in the tree in the member field * <tt>rootDescriptor</tt>. */ public static abstract class KeyRange extends Separator { /** * The maximal bound of the interval. */ protected Comparable maxBound; /** * Creates a new <tt>QueryRange</tt>. * * @param min * the minimal bound of the <tt>KeyRange</tt> * @param max * the maximal bound of the <tt>KeyRange</tt> */ public KeyRange(Comparable min, Comparable max) { super(min); this.maxBound = max; } /** * Gives the minimal bound of the <tt>KeyRange</tt>. * * @return the minimal bound of the <tt>KeyRange</tt> */ public Comparable minBound() { return sepValue; } /** * Gets the maximal bound of the <tt>KeyRange</tt>. * * @return the maximal bound of the <tt>KeyRange</tt> */ public Comparable maxBound() { return maxBound; } /** * Updates the minimal bound of the <tt>KeyRange</tt>. * * @param newMinBound * the new minimal bound */ public void updateMinBound(Comparable newMinBound) { updateSepValue(newMinBound); } /** * Updates the maximal bound of the <tt>KeyRange</tt>. * * @param newMaxBound * the new maximal bound */ public void updateMaxBound(Comparable newMaxBound) { this.maxBound = newMaxBound; } /** * Checks whether the given <tt>Descriptor</tt> overlaps the current * <tt>KeyRange</tt>. * * @param descriptor * the <tt>KeyRange</tt> to check * @return <tt>true</tt> if the given <tt>Descriptor</tt> an * instance of of <tt>KeyRange</tt> and overlaps this * <tt>KeyRange</tt> and <tt>false</tt> otherwise. * * @see xxl.core.indexStructures.Descriptor#overlaps(xxl.core.indexStructures.Descriptor) */ public boolean overlaps(Descriptor descriptor) { if (!(descriptor instanceof KeyRange)) return false; KeyRange qInterval = (KeyRange) descriptor; return contains(qInterval.minBound()) || qInterval.contains(this.minBound()); } /** * Checks whether the given <tt>Descriptor</tt> is totally contained * in this <tt>KeyRange</tt>. * * @param descriptor * the <tt>KeyRange</tt> to check * @return <tt>true</tt> if the given <tt>Descriptor</tt> an * instance of <tt>KeyRange</tt> and lies totally in this * <tt>KeyRange</tt> and <tt>false</tt> otherwise * * @see xxl.core.indexStructures.Descriptor#contains(xxl.core.indexStructures.Descriptor) */ public boolean contains(Descriptor descriptor) { if (!(descriptor instanceof KeyRange)) return false; KeyRange qInterval = (KeyRange) descriptor; return contains(qInterval.minBound()) && contains(qInterval.maxBound()); } /** * Checks whether the current <tt>KeyRangel</tt> contains the given * key. * * @param key * the key to check * @return <tt>true</tt> if key lies in this <tt>KeyRangel</tt> and * <tt>false</tt> otherwise */ protected boolean contains(Comparable key) { return minBound().compareTo(key) <= 0 && (maxBound==null ||maxBound().compareTo(key) >= 0); } /** * Tests whether this <tt>KeyRange</tt> equals the given * <tt>KeyRange</tt>. * * @param object * the second <tt>KeyRange</tt> * @return <tt>true</tt> if the given object an instance of * <tt>KeyRange</tt> and equals the current <tt>KeyRange</tt> * and <tt>false</tt> otherwise */ public boolean equals(Object object) { if (!(object instanceof KeyRange)) return false; KeyRange qInterval = (KeyRange) object; return minBound().compareTo(qInterval.minBound()) == 0 && maxBound().compareTo(qInterval.maxBound()) == 0; } /** * Builds the union of two <tt>KeyRanges</tt>. The current * <tt>KeyRange</tt> will be changed and returned. * * @param descriptor * the <tt>KeyRange</tt> which is to unite with the current * <tt>KeyRange</tt> * * @see xxl.core.indexStructures.Descriptor#union(xxl.core.indexStructures.Descriptor) */ public void union(Descriptor descriptor) { if (!(descriptor instanceof Separator)) return; if (descriptor instanceof KeyRange) { KeyRange qInterval = (KeyRange) descriptor; if (minBound().compareTo(qInterval.minBound()) > 0) updateMinBound(qInterval.minBound()); if (maxBound().compareTo(qInterval.maxBound()) < 0) updateMaxBound(qInterval.maxBound()); } else union(((Separator) descriptor).sepValue()); } /** * Builds the union of this <tt>KeyRanges</tt> and a key. The union is * the minimal extenstion of the current <tt>KeyRange</tt> which * contains the given key. The current <tt>KeyRange</tt> will be * changed and returned. * * @param key * the key which is to unite with the current * <tt>KeyRange</tt> */ public void union(Comparable key) { if (minBound().compareTo(key) > 0) updateMinBound(key); if (maxBound().compareTo(key) < 0) updateMaxBound(key); } /** * Checks whether this <tt>KeyRangel</tt> is a point (i.e. minimal and * maximal bounds are equal). * * @return <tt>true</tt> if this <tt>KeyRangel</tt> is a point and * <tt>false</tt> otherwise */ public boolean isPoint() { return minBound().compareTo(maxBound()) == 0; } /** * Overwrites the method * {@link xxl.core.indexStructures.Separator#isDefinite()}so that is * always returns <tt>true</tt>. * * @return <tt>true</tt> */ public boolean isDefinite() { return true; } /** * Unsupported operation. * * @param separator * unused * @return never returns * @throws UnsupportedOperationException */ public int compareTo(Object separator) { throw new UnsupportedOperationException(); } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ public String toString() { StringBuffer sb = new StringBuffer("[" + minBound()); if (!isPoint()) { sb.append(", "); sb.append(maxBound()); } sb.append("]"); return sb.toString(); } } public int leafs = 0; /** * This class uses an efficient algorithm to perform queries on the * <tt>BPlusTree</tt>. It does not traverse the <tt>BPlusTree</tt> in * TOP-DOWN fashion to find the response set, but it uses the known efficient * querying algorithm of B+-Tree. It only traverses the path to the first * <tt>Node</tt>, which can contain answers, and then traverses the * <tt>Nodes</tt> on the same level cross the <tt>BPlusTree</tt> from * left to right, using the <tt>nextNeighbor</tt> references of * <tt>BPlusTree.Node</tt>. */ protected class QueryCursor extends xxl.core.indexStructures.QueryCursor { /** * The position of the current entry in the current <tt>Node</tt>. */ protected int index; /** * The position of the last entry in the last <tt>Node</tt>. */ protected int lastIndex; /** * The <tt>IndexEntry</tt> of the last <tt>Node</tt>. */ protected IndexEntry lastIndexEntry; /** * The last <tt>Node</tt>, i.e. the <tt>Node</tt> which contains * the last entry returned by the <tt>QueryCursor</tt>. It is used as * a history information to support remove an update. */ protected Node lastNode; /** * A counter which counts how many <tt>Nodes</tt> on the target level * was already visited. The number of the visited <tt>Nodes</tt> * equals <tt>nodeChangeover+1</tt>. */ protected int nodeChangeover; /** * The previous Separator of the lastIndexEntry. Needed to support the * remove-operation. */ protected Separator prevSeparator; /** * subRootEntry is the <tt>IndexEntry</tt> which is the root of the * subtree in which the query has to be executed. */ private IndexEntry subRootEntry; /** * counts occurrence of the elements with the same separator key in the cursor */ protected int counterRightShiftDup; /** * Creates a new <tt>QueryCursor</tt>. * * @param subRootEntry * the <tt>IndexEntry</tt> which is the root of the subtree * in which the query has to be executed * @param qInterval * a <tt>Descriptor</tt> which specifies the query * @param targetLevel * the target level of the query */ public QueryCursor(IndexEntry subRootEntry, KeyRange qInterval, int targetLevel) { super(BPlusTree.this, subRootEntry, qInterval, targetLevel); this.subRootEntry = subRootEntry; lastQueryDisscriptor = qInterval; path = new Stack(); index = -1; lastIndex = index; lastNode = null; lastIndexEntry = null; nodeChangeover = 0; counterRightShiftDup = 1; } /** * Checks whether a next element exists. * First builds the path down to the target level, using only the left element from the Iterator returned by * {@link BPlusTree.Node#query(Descriptor)} * Method searches through target level from left to right within the query range, looking through each node in turn. * At the end of each node it switches to the next node using {@link BPlusTree.QueryCursor#nodeChangeover}, * It searches until it has found an item right of min bound then tests this item if it is left from max bound and returns this boolean. * * @see xxl.core.cursors.AbstractCursor#hasNextObject(); * @return <tt>true</tt> if there is a next element and <tt>false</tt> * otherwise */ public boolean hasNextObject() { if (index == -1) {//if is empty down(path, indexEntry);//look at root of the tree, place in stack. while (level(path) > targetLevel) {//search down the tree until past target level Iterator entries = node(path).query(queryRegion);//finds a region but only uses the first(left) element if (entries.hasNext())//if the first element exists , place it in the path. otherwise hasNextObject is false down(path, (IndexEntry) entries.next()); else return false; } indexEntry = indexEntry(path); currentNode = node(path); lastIndexEntry = (IndexEntry) indexEntry; lastNode = (Node) currentNode; if(currentNode.level() == 0){ leafs++; } for (int i = 0; i < currentNode.number(); i++) { lastIndex = index; index = i; if (separator(((Node) currentNode).getEntry(i)).isRightOf( ((KeyRange) queryRegion).minBound())) { break; } if (index == currentNode.number() - 1 && ((Node) currentNode).nextNeighbor != null) { nodeChangeOver(); i = - 1; } if (index == currentNode.number() - 1 && ((Node) currentNode).nextNeighbor == null) { return false; } } } if (index == -1) return false; if (index >= currentNode.number()) { if (((Node) currentNode).nextNeighbor == null){ // right flank return false; } nodeChangeOver(); } return separator(((Node) currentNode).getEntry(index)).sepValue() .compareTo(((KeyRange) queryRegion).maxBound()) <= 0; } /** * Computes the next element of the <tt>QueryCursor</tt>. * * @return the next element */ public Object nextObject() { if (!indexEntry.equals(lastIndexEntry)) { abolishPath(); lastIndexEntry.unfix(); } Separator currentSeapartor = separator((lastNode).getEntry(index)); if ( prevSeparator != null && prevSeparator.sepValue().compareTo(currentSeapartor.sepValue())== 0){ counterRightShiftDup++; }else{ counterRightShiftDup = 1; } prevSeparator = currentSeapartor; lastIndex = index; lastIndexEntry = (IndexEntry) indexEntry; lastNode = (Node) currentNode; return ((Node) currentNode).getEntry(index++); } /** * Removes the last element returned by the method next() and calls the * method {@link BPlusTree#treatUnderflow(Stack)}to repair underflows * which can occur. It is only supported on the level 0. * If the path is not correct (due to nodeChangover) then it will be calculated. * The root descriptor is updated if the min or max object is deleted. */ protected void removeObject() { if (!hasPath()){ path = new Stack(); // compute new path when the cursor moved to the right Separator range = separator((lastNode).getEntry(index - 1)); backToFirstEntry(path, range, false); prevSeparator = range; } Object oldData = lastNode.remove(lastIndex); if (duplicate){ // swap deleted element with duplicate value in computed left most duplicate node IndexEntry startEntry = (IndexEntry)indexEntry(path); Node startNode = (Node)node(path); if (!startEntry.equals(lastIndexEntry)){ Object changeData = startNode.remove(startNode.number()-1); // lastNode.entries.add(0, changeData); } } lastIndexEntry.update(lastNode, false); KeyRange root = (KeyRange) rootDescriptor(); // update root descriptor Separator old = separator(oldData); Container rootContainer = null; Object rootID = null; if (old.sepValue().compareTo(root.minBound()) == 0) { if(lastNode.number() != 0){ Comparable newMinbound = separator(lastNode.getFirst()).sepValue(); root.updateMinBound(newMinbound); }else{ rootContainer = rootEntry.container(); rootID = rootEntry.id(); rootDescriptor = null; rootEntry = null; } }else{ if (old.sepValue().compareTo(root.maxBound()) == 0 && lastNode.nextNeighbor() == null) { if(lastNode.number() != 0){ Comparable newMaxbound = separator(lastNode.getLast()).sepValue(); root.updateMaxBound(newMaxbound); }else{// last Element was removed rootContainer = rootEntry.container(); rootID = rootEntry.id(); rootDescriptor = null; rootEntry = null; } } } treatUnderflow(path); // UNDERFLOW if(rootDescriptor != null && rootEntry != null){ backToFirstEntry(path, prevSeparator, true); if (counterRightShiftDup > 0) counterRightShiftDup--; // count down returned duplicate Objects } else { rootContainer.remove(rootID); } } /** * Searches for the left most element that has the * same key as the deleted item, constructs the path in queryCursor. * @param path * @param prevSep */ protected void backToFirstEntry(Stack path, Separator prevSep, boolean computeIndex) { KeyRange prev = createKeyRange(prevSep.sepValue(), prevSep .sepValue()); if (path == null) { path = new Stack(); } if (path.isEmpty()) { down(path, rootEntry); } while (level(path) > targetLevel) { Iterator entries = node(path).query(prev); if (entries.hasNext()) { down(path, (IndexEntry) entries.next()); } else return; } this.path = path; if (computeIndex){ indexEntry = indexEntry(path); currentNode = node(path); lastIndexEntry = (IndexEntry) indexEntry; lastNode = (Node) currentNode; for (int i = 0, j = 1; i < currentNode.number(); i++) { index = i; if (separator(((Node) currentNode).getEntry(i)).isRightOf( (prev).minBound())) { j++; if (j > counterRightShiftDup) break; } if (index == currentNode.number() - 1 && ((Node) currentNode).nextNeighbor != null) { nodeChangeOver(); i = - 1; } if (index == currentNode.number() - 1 && ((Node) currentNode).nextNeighbor == null) { index = currentNode.number(); break; } } } } /** * Replaces the last element returned by the method next() by a new data * object. It is only supported on the level 0. * * @param newData * the new data object */ protected void updateObject(Object newData) { if (targetLevel != 0) throw new UnsupportedOperationException( "update(Object) is to use only on leaf level."); Object oldDat = lastNode.getEntry(lastIndex); if (!key(oldDat).equals(key(newData))) throw new IllegalArgumentException( "The updated new data have to be the different key like the old one."); Object oldData = lastNode.remove(lastIndex); lastNode.grow(newData); lastIndexEntry.container().update(lastIndexEntry.id(), lastNode, true); } /** * It unfixes all <tt>Nodes</tt> loaded in the underlying buffer then * calls super.close(). */ public void close() { if (isClosed) return; abolishPath(); try { if (!lastIndexEntry.equals(indexEntry)) lastIndexEntry.unfix(); } catch (NoSuchElementException e) { } try { indexEntry.unfix(); } catch (NoSuchElementException e) { } super.close(); } /** * Moves the <tt>QueryCursor</tt> to next right neighbor. */ protected void nodeChangeOver() { indexEntry = ((Node) currentNode).nextNeighbor; currentNode = indexEntry.get(false); index = 0; abolishPath(); lastIndexEntry.unfix(); lastIndexEntry = (IndexEntry) indexEntry; lastNode = (Node) currentNode; nodeChangeover++; if(currentNode.level() == 0){ leafs++; } } /** * @see xxl.core.cursors.Cursor#supportsReset() */ public boolean supportsReset() { return true; } /** * @see xxl.core.cursors.Cursor#reset() */ public void reset() throws UnsupportedOperationException { super.reset(); this.indexEntry = subRootEntry; path = null; currentNode = null; path = new Stack(); index = -1; lastIndex = index; lastNode = null; lastIndexEntry = null; nodeChangeover = 0; } } /** * A <tt>NodeConverter</tt> is used by the <tt>BPlusTree</tt> to convert * the <tt>Nodes</tt> for I/O-purposes. */ public class NodeConverter extends Converter { /** * Reads a <tt>Node</tt> from the given <tt>DataInput</tt>. * * @param dataInput * the <tt>DataInput</tt> from which the <tt>Node</tt> * has to be read * @param object * is not used * @return the read <tt>Node</tt> * @throws IOException */ public Object read(DataInput dataInput, Object object) throws IOException { int level = dataInput.readInt(); Node node = (Node) createNode(level); int number = dataInput.readInt(); boolean readNext = dataInput.readBoolean(); if (readNext) { node.nextNeighbor = (IndexEntry) createIndexEntry(level + 1); node.nextNeighbor.initialize(readID(dataInput)); } else node.nextNeighbor = null; readEntries(dataInput, node, number); //init if (node.level != 0) { for (int i = 0; i < node.number(); i++) { Comparable sepValue = (Comparable) keyConverter.read( dataInput, null); ((IndexEntry) node.getEntry(i)) .initialize(createSeparator(sepValue)); } } return node; } /** * Writes a given <tt>Node</tt> into a given <tt>DataOutput</tt>. * * @param dataOutput * the <tt>DataOutput</tt> which the <tt>Node</tt> has to * be written to * @param object * the <tt>Node</tt> which has to be written * @throws IOException */ public void write(DataOutput dataOutput, Object object) throws IOException { Node node = (Node) object; //2x Integer dataOutput.writeInt(node.level); dataOutput.writeInt(node.number()); //Boolean dataOutput.writeBoolean(node.nextNeighbor != null); //ID if (node.nextNeighbor != null) writeID(dataOutput, node.nextNeighbor.id()); //Entries writeEntries(dataOutput, node); //Separators // edit if (node.level != 0) for (int i = 0; i < node.number(); i++) keyConverter.write(dataOutput, separator( node.getEntry(i)).sepValue()); } /** * Read the entries of the given <tt>Node</tt> from the * <tt>DataInput</tt>. If the <tt>Node</tt> is a leaf the * <tt>dataConverter</tt> is used to read its data objects. Otherwise * the method {@link #readIndexEntry(DataInput, int)}is used to read * its <tt>IndexEntries</tt>. * * @param input * the <tt>DataInput</tt> * @param node * the <tt>Node</tt> * @param number * the number of the entries which have to be read * @throws IOException */ protected void readEntries(DataInput input, Node node, int number) throws IOException { for (int i = 0; i < number; i++) { Object entry; if (node.level == 0) entry = dataConverter.read(input, null); else entry = readIndexEntry(input, node.level); node.entries.add(i, entry); } } /** * Writes the entries of the given <tt>Node</tt> into the * <tt>DataOutput</tt>. If the <tt>Node</tt> is a leaf the * <tt>dataConverter</tt> is used to write its data objects. Otherwise * the method {@link #writeIndexEntry(DataOutput, BPlusTree.IndexEntry)} * is used to write its <tt>IndexEntries</tt>. * * @param output * the <tt>DataOutput</tt> * @param node * the <tt>Node</tt> * @throws IOException */ protected void writeEntries(DataOutput output, Node node) throws IOException { Iterator entries = node.entries(); while (entries.hasNext()) { Object entry = entries.next(); if (node.level == 0) dataConverter.write(output, entry); else writeIndexEntry(output, (IndexEntry) entry); } } /** * Computes the maximal size (in bytes) of an <tt>IndexEntry</tt>. 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 an <tt>IndexEntry</tt> */ protected int indexEntrySize() { return BPlusTree.this.container().getIdSize() + keyConverter.getMaxObjectSize(); } protected int leafEntrySize() { return dataConverter.getMaxObjectSize(); } /** * Computes the size (in bytes) of the <tt>Node's</tt> header which * contains some information such as level and entries number. * * @return the maximal size of an <tt>IndexEntry</tt> */ protected int headerSize() { return 2 * IntegerConverter.SIZE + BooleanConverter.SIZE + BPlusTree.this.container().getIdSize(); } /** * Reads an <tt>IndexEntry</tt> from the given <tt>DataInput</tt>. * In the default implementation only the ID of the <tt>IndexEntry</tt> * is read. * * @param input * the <tt>DataInput</tt> * @param parentLevel * the parent level of the <tt>IndexEntry</tt> * @return the read <tt>IndexEntry</tt> * @throws IOException */ protected IndexEntry readIndexEntry(DataInput input, int parentLevel) throws IOException { IndexEntry indexEntry = new IndexEntry(parentLevel); Object id = readID(input); indexEntry.initialize(id); return indexEntry; } /** * Writes an <tt>IndexEntry</tt> into the given <tt>DataOutput</tt>. * In the default implementation only the ID of the <tt>IndexEntry</tt> * is written. * * @param output * the <tt>DataOutput</tt> * @param entry * the <tt>IndexEntry</tt> which has to be written * @throws IOException */ protected void writeIndexEntry(DataOutput output, IndexEntry entry) throws IOException { writeID(output, entry.id); } /** * Reads an ID from the given <tt>DataInput</tt>. * * @param input * the <tt>DataInput</tt> * @return the read ID * @throws IOException */ private Object readID(DataInput input) throws IOException { Converter idConverter = BPlusTree.this.container() .objectIdConverter(); return idConverter.read(input, null); } /** * Writes an ID into the given <tt>DataOutput</tt>. * * @param output * the <tt>DataOutput</tt> * @param id * the ID which has to be written * @throws IOException */ private void writeID(DataOutput output, Object id) throws IOException { Converter idConverter = BPlusTree.this.container() .objectIdConverter(); idConverter.write(output, id); } } }