/* 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.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Stack; import xxl.core.collections.MapEntry; import xxl.core.collections.containers.Container; import xxl.core.cursors.AbstractCursor; import xxl.core.cursors.Cursor; import xxl.core.cursors.filters.Filter; import xxl.core.cursors.mappers.Mapper; import xxl.core.cursors.sources.EmptyCursor; import xxl.core.cursors.sources.SingleObjectCursor; import xxl.core.cursors.unions.Sequentializer; import xxl.core.functions.AbstractFunction; import xxl.core.functions.Function; import xxl.core.io.converters.Converter; import xxl.core.io.converters.IntegerConverter; import xxl.core.io.converters.ShortConverter; import xxl.core.predicates.AbstractPredicate; import xxl.core.predicates.Predicate; /** The class <tt>ORTree</tt> (Overlapping Region Tree) is a generic and highly flexible * super-class that implements features used by grow- and post-trees which stores * objects with descriptors which may overlap. * * @see Tree */ public abstract class ORTree extends Tree { /** This is the basic method to create a new tree. It is not sufficient to call the constructor. * Before the tree is usable, it must be initialized. * * @param rootEntry the new {@link Tree#rootEntry} * @param getDescriptor the new {@link Tree#getDescriptor} * @param getContainer the new {@link Tree#getContainer} * @param determineContainer the new {@link Tree#determineContainer} * @param underflows the new {@link Tree#underflows} * @param overflows the new {@link Tree#overflows} * @param getSplitMinRatio the new {@link Tree#getSplitMinRatio} * @param getSplitMaxRatio the new {@link Tree#getSplitMaxRatio} * @return the initialized tree (i.e. return this) * @see Tree#initialize (Tree.IndexEntry, Descriptor, Function, Function, Function, Predicate, Predicate, Function, Function) */ public ORTree initialize (IndexEntry rootEntry, Function getDescriptor, Function getContainer, Function determineContainer, Predicate underflows, Predicate overflows, Function getSplitMinRatio, Function getSplitMaxRatio) { return (ORTree)super.initialize(rootEntry, rootEntry==null? null: rootEntry.descriptor(), getDescriptor, getContainer, determineContainer, underflows, overflows, getSplitMinRatio, getSplitMaxRatio); } /** Initializes the tree. Both Functions <tt>getContainer</tt> and <tt>determineContainer</tt> are * initialized with constant functions: --> <tt>Container</tt> which always return the given container. * The predicate {@link Tree#underflows} is initialized with: * <pre><code> new AbstractPredicate() { public boolean invoke (Object node) { return ((Tree.Node)node).number() < minCapacity; //or > in case of overflow } } </code></pre> * {@link Tree#getSplitMinRatio} is defined as: * <pre><code> new AbstractFunction() { public Object invoke (Object node) { return new Double(minCapacity/(double)((Tree.Node)node).number()); } } </code></pre> * {@link Tree#getSplitMaxRatio} is defined as: * <pre><code> new AbstractFunction() { public Object invoke (Object node) { return new Double(1.0-minCapacity/(double)((Tree.Node)node).number()); } } </code></pre> * @param rootEntry the new {@link Tree#rootEntry} * @param getDescriptor the new {@link Tree#getDescriptor} * @param container used as constant for {@link Tree#determineContainer} * @param minCapacity is used to define {@link Tree#underflows}, {@link Tree#getSplitMinRatio} * and {@link Tree#getSplitMaxRatio} * @param maxCapacity is used to define {@link Tree#overflows} * @return the initialized tree */ public ORTree initialize (IndexEntry rootEntry, Function getDescriptor, Container container, final int minCapacity, final int maxCapacity) { return (ORTree)super.initialize(rootEntry, rootEntry==null? null: rootEntry.descriptor(), getDescriptor, container, minCapacity, maxCapacity); } /** * Calls {@link Tree#initialize(Tree.IndexEntry, Descriptor, Function, Container, int, int, int, int)} with * the root descriptor. * * @param rootEntry the new {@link Tree#rootEntry} * @param getDescriptor the new {@link Tree#getDescriptor} * @param container used as constant for {@link Tree#determineContainer} * @param innerNodeMinCapacity is used to define {@link Tree#underflows}, {@link Tree#getSplitMinRatio} * and {@link Tree#getSplitMaxRatio} for inner nodes * @param innerNodeMaxCapacity is used to define {@link Tree#overflows} for inner nodes * @param leafMinCapacity is used to define {@link Tree#underflows}, {@link Tree#getSplitMinRatio} * and {@link Tree#getSplitMaxRatio} for leaf nodes * @param leafMaxCapacity is used to define {@link Tree#overflows} for leaf nodes * @return the initialized tree * * @since 1.1 */ public ORTree initialize (IndexEntry rootEntry, Function getDescriptor, Container container, final int innerNodeMinCapacity, final int innerNodeMaxCapacity, final int leafMinCapacity, final int leafMaxCapacity) { return (ORTree)super.initialize(rootEntry, rootEntry==null? null: rootEntry.descriptor(), getDescriptor, container, innerNodeMinCapacity, innerNodeMaxCapacity, leafMinCapacity, leafMaxCapacity); } /** * Before the tree is usable, it must be initialized. This version of initialize * takes basic informations about the size of the blocks and entries and calculates * the capacity of inner and leaf nodes. * * @param getDescriptor the new {@link Tree#getDescriptor} * @param container the new {@link Tree#determineContainer} * @param blockSize the size of a block of the <code>container</code> in bytes * @param dataSize the size of an data object in bytes * @param descriptorSize the size of a Descriptor returned by <code>getDescriptor</code> in bytes * @param minMaxFactor the quotient between minimum and maximum number of entries in an node, e.g. 0.5 * @return the initialized tree * * @since 1.1 */ public ORTree initialize (Function getDescriptor, Container container, int blockSize, int dataSize, int descriptorSize, double minMaxFactor) { return (ORTree)super.initialize(getDescriptor, blockSize, container, dataSize, descriptorSize, minMaxFactor); } public ORTree initialize (IndexEntry rootEntry, Function getDescriptor, Container container, int blockSize, int dataSize, int descriptorSize, double minMaxFactor) { return (ORTree)super.initialize(rootEntry, rootEntry==null? null: rootEntry.descriptor(), getDescriptor, blockSize, container, dataSize, descriptorSize, minMaxFactor); } /** Removes the object from the target level whose descriptor equals the given descriptor. * * @param descriptor to specify the object * @param targetLevel the level on which the search has to stop * @return the removed object */ public Object remove (final Descriptor descriptor, int targetLevel) { return remove(descriptor, targetLevel, new AbstractPredicate() { public boolean invoke (Object object) { return descriptor.equals(descriptor(object)); } } ); } /** Returns the descriptor of a given entry. If this entry is an index entry its descriptor * is returned, else the method {@link Tree#descriptor(Object)} is used to determine the * descriptor of the entry. * @param entry the entry whose descriptor is to be determined * @return the descriptor of the entry * @see Tree#descriptor(Object) */ public Descriptor descriptor (Object entry) { return (entry instanceof IndexEntry)? ((IndexEntry)entry).descriptor(): super.descriptor(entry); } /** Computes the union of the descriptors of the collection's entries. * The method {@link ORTree#descriptor(Object)} is used to determine the * descriptors of these entries. * * @param collection a collection of objects * @return the union of the descriptors of the collection's entries * @see ORTree#descriptor(Object) */ public Descriptor computeDescriptor (Collection collection) { Descriptor descriptor = null; Iterator entries = collection.iterator(); if (entries.hasNext()) { descriptor = (Descriptor)descriptor(entries.next()).clone(); while (entries.hasNext()) descriptor.union(descriptor(entries.next())); } return descriptor; } /** Creates a new IndexEntry. * @param parentLevel the level of the node in which the new indexEntry is stored * @return a new indexEntry * @see ORTree.IndexEntry */ public Tree.IndexEntry createIndexEntry (int parentLevel) { return new IndexEntry(parentLevel); } /** Creates a new <tt>Node</tt> which does not have any information except its level. * @param level the level of this node (i.e. the maximum distance * to the leaves of the subtree) * @return a new <tt>Node</tt> * @see ORTree.Node */ public abstract Tree.Node createNode (int level); /** This class describes the index entries (i.e. the entries of the non-leaf nodes) * of a tree. Each index entry refers to a {@link Tree.Node node} which is the root * of the subtree. We call this node the subnode of the index entry. * @see Tree.IndexEntry */ public class IndexEntry extends Tree.IndexEntry { /** The descriptor of the subtree of this index entry. */ protected Descriptor descriptor; /** Creates a new <tt>IndexEntry</tt> with a given {@link Tree.IndexEntry#parentLevel}. * * @param parentLevel the parent level of the new <tt>IndexEntry</tt> */ public IndexEntry (int parentLevel) { super(parentLevel); } /** Initializes the (new) IndexEntry using some split information. * The default implementation calls * {@link Tree.IndexEntry#initialize(Tree.Node.SplitInfo) super.initialize(splitInfo)} * and initializes the descriptor of the index entry by * {ORTree.Node.SplitInfo#newDescriptor() the new descriptor of the splitInfo}. * * @param splitInfo contains information about the split * which led to create this index entry * @return the initialized index entry itself * @see Tree.IndexEntry#initialize(Tree.Node.SplitInfo) * @see ORTree.Node.SplitInfo#newDescriptor() */ public Tree.IndexEntry initialize (Tree.Node.SplitInfo splitInfo) { super.initialize(splitInfo); return initialize(((Node.SplitInfo)splitInfo).newDescriptor()); } /** Initializes the descriptor of the index entry by the given descriptor. * * @param descriptor the new descriptor of the index entry * @return the initialized index entry itself */ public Tree.IndexEntry initialize (Descriptor descriptor) { this.descriptor = descriptor; return this; } /** Gets the descriptor of the index entry. * * @return the descriptor of the index entry */ public Descriptor descriptor () { return descriptor; } } /** <tt>Node</tt> is the class used to represent leaf- and non-leaf nodes of <tt>ORTree</tt>. * Nodes are stored in containers. * * @see Tree#determineContainer * @see Tree#getContainer * @see Tree.Node */ public abstract class Node extends Tree.Node { /** SplitInfo contains information about a split. The enclosing * Object of this SplitInfo-Object (i.e. Node.this) is the new node * that was created by the split. */ public class SplitInfo extends Tree.Node.SplitInfo { /** * The descriptor of the new node created during the split. */ protected Descriptor newDescriptor = rootDescriptor(); /** Creates a new <tt>SplitInfo</tt> with a given path. * @param path the path from the root to the splitted node */ public SplitInfo (Stack path) { super(path); } /** Initializes the SplitInfo by setting the descriptor of * the new node. * * @param newDescriptor the descriptor of the new node * @return the initialized <tt>SplitInfo</tt> */ public SplitInfo initialize (Descriptor newDescriptor) { this.newDescriptor = newDescriptor; return this; } /** Gets the descriptor of the new node * * @return the descriptor of the new node */ public Descriptor newDescriptor () { return newDescriptor; } } /** The entries collection of this node. */ protected Collection entries; /** Initializes the node with a level and new entries. * * @param level the node's level * @param entries the node's entries * @return the initialized node */ public Node initialize (int level, Collection entries) { super.initialize(level); this.entries = entries; return this; } /** Initializes the node by inserting a new entry. * * @param entry the entry wto inserted * @return SplitInfo contains information about a possible split */ public Tree.Node.SplitInfo initialize (Object entry) { Stack path = new Stack(); if (height()>0) { IndexEntry indexEntry = (IndexEntry)entry; indexEntry.descriptor = (Descriptor)indexEntry.descriptor.clone(); } grow(entry, path); return createSplitInfo(path); } /** Creates a new SplitInfo with a given path. * * @param path the path from the root to the splitted node * @return a new instance of <tt>SplitInfo</tt> */ protected SplitInfo createSplitInfo (Stack path) { return new SplitInfo(path); } /** The number of entries that are currently stored * in this node. * @return the number of entries that are currently stored * in this node */ public int number () { return entries.size(); } /** Gets an iterator pointing to all entries that are currently stored * in this node. * @return an Iterator pointing to all the entries of this node */ public Iterator entries () { return entries.iterator(); } /** Gets an iterator pointing to the descriptors of each entry in this node. * * @param nodeDescriptor the descriptor of this node. The default implementation * ignores this parameter * @return an iterator pointing to the descriptors of each entry in this node */ public Iterator descriptors (Descriptor nodeDescriptor) { return new Mapper( level==0 ? getDescriptor : new AbstractFunction() { public Object invoke (Object entry) { return ((IndexEntry)entry).descriptor; } },entries() ); } /** Returns an iterator pointing to entries whose descriptors overlap <tt>queryDescriptor</tt>. * @param queryDescriptor the descriptor describing the query * @return an iterator pointing to entries whose descriptors overlap with <tt>queryDescriptor</tt> */ public Iterator query (final Descriptor queryDescriptor) { return new Filter(entries(), new AbstractPredicate() { public boolean invoke (Object object) { return descriptor(object).overlaps(queryDescriptor); } } ); } /** Chooses the subtree which is followed during an insertion. * @param descriptor the descriptor of the data object to insert * @param path the path from the root to the current node modified by this function * @return the index entry refering to the root of the chosen subtree */ protected Tree.IndexEntry chooseSubtree (Descriptor descriptor, Stack path) { IndexEntry indexEntry = chooseSubtree(descriptor, entries()); if (!indexEntry.descriptor().contains(descriptor)) { indexEntry.descriptor().union(descriptor); update(path); } return indexEntry; } /** Chooses the subtree which is followed during an insertion. * @param descriptor the descriptor of the data object to insert * @param path the path from the root to this node * @param isValidEntry Function (IndexEntry->Boolean) that determines whether an indexEntry may be chosen * @return the index entry refering to the root of the chosen subtree */ protected Tree.IndexEntry chooseSubtree (Descriptor descriptor, Stack path, Function isValidEntry) { IndexEntry indexEntry = chooseSubtree(descriptor, new Filter(entries(), isValidEntry)); if (!indexEntry.descriptor.contains(descriptor)) { indexEntry.descriptor.union(descriptor); update(path); } return indexEntry; } /** Chooses the subtree which is followed during an insertion. * @param object the object to be inserted. Its descriptor is used to choose the subtree * @param entries an iterator of the IndexEntrys which may be chosen * @return the index entry refering to the root of the chosen subtree */ protected Tree.IndexEntry chooseSubtree (Object object, Iterator entries) { return chooseSubtree(descriptor(object), entries); } /** Chooses the subtree which is followed during an insertion. * * @param descriptor the Descriptor of data object * @param entries an iterator of the IndexEntrys which may be chosen * @return the index entry refering to the root of the chosen subtree */ protected abstract IndexEntry chooseSubtree (Descriptor descriptor, Iterator entries); /** Inserts data into the current node. If level>0 data must be an * index entry. * @param data the entry to be inserted into the node * @param path the path from the root to the current node */ protected void grow (Object data, Stack path) { entries.add(data); } /** Updates the current node with the split information, * e.g. inserts the new index entry of the new node * @param splitInfo the information created by split * @param newIndexEntry the new index entry refering to the * new node created during the split */ protected void post (Tree.Node.SplitInfo splitInfo, Tree.IndexEntry newIndexEntry) { grow(newIndexEntry, splitInfo.path); } /** Splits the current node. A new node is created, but no handle is * reserved for this node in its container (i.e. * container.insert() or container.reserve() are not * called at this stage). This means that no index * entry for this node is passed to its parent-node. * @param path the nodes already visited during this insert * @return a <tt>SplitInfo</tt> containig all information needed about the split */ protected abstract Tree.Node.SplitInfo split (Stack path); } /** Gets a suitable converter to serialize the index entries. * * @param descriptorConverter the converter to serialze the descriptors * @return an <tt>IndexEntryConverter</tt> * @see Converter * @see ORTree.IndexEntryConverter */ public Converter indexEntryConverter (Converter descriptorConverter) { return new IndexEntryConverter(descriptorConverter); } /** The instances of this class are converters to write index entries to the * external storage (or any other {@link DataOutput}) or read * them from it (or any other {@link DataInput}). * @see Converter */ public class IndexEntryConverter extends Converter { /** Converter for descriptors used in the index entry. */ protected Converter descriptorConverter; /** Creates a new index entry converter. * * @param descriptorConverter converter used to convert the descriptors */ public IndexEntryConverter (Converter descriptorConverter) { this.descriptorConverter = descriptorConverter; } /** Reads an index entry from the input. * @param dataInput the data input stream * @param object an index entry which is updated by the read data * @return the read index entry * @see Converter#read(java.io.DataInput, java.lang.Object) */ public Object read (DataInput dataInput, Object object) throws IOException { IndexEntry indexEntry = (IndexEntry)object; Container container = indexEntry.container(); indexEntry.id = container.objectIdConverter().read(dataInput, null); indexEntry.descriptor = (Descriptor)descriptorConverter.read(dataInput, null); return indexEntry; } /** Writes an index entry into the output. * @param dataOutput the data output stream * @param object an index entry to be written * @see Converter#write(java.io.DataOutput, java.lang.Object) */ public void write (DataOutput dataOutput, Object object) throws IOException { IndexEntry indexEntry = (IndexEntry)object; Container container = indexEntry.container(); container.objectIdConverter().write(dataOutput, indexEntry.id()); descriptorConverter.write(dataOutput, indexEntry.descriptor()); } } /** Gets a suitable Converter to serialize the tree's nodes. * * @param objectConverter a converter to convert the data objects stored in the tree * @param indexEntryConverter a converter to convert the index entries * @return a NodeConverter * @see Converter * @see ORTree.NodeConverter */ public Converter nodeConverter (Converter objectConverter, Converter indexEntryConverter) { return new NodeConverter(objectConverter, indexEntryConverter); } /** The instances of this class are converters to write nodes to the * external storage (or any other {@link DataOutput}) or read * them from it (or any other {@link DataInput}). * * @see Converter */ public class NodeConverter extends Converter { /** * A converter for index entries. */ protected Converter indexEntryConverter; /** * A converter for objects. */ protected Converter objectConverter; /** Creates a new NodeConverter. * * @param objectConverter a converter to convert the data objects stored in the tree * @param indexEntryConverter a converter to convert the index entries */ public NodeConverter (Converter objectConverter, Converter indexEntryConverter) { this.indexEntryConverter = indexEntryConverter; this.objectConverter = objectConverter; } /** Reads a node from the data input. * * @param dataInput the data input stream * @param object is ignored * @return the read node * * @see Converter#read(java.io.DataInput, java.lang.Object) */ public Object read (DataInput dataInput, Object object) throws IOException { Node node = (Node)createNode(dataInput.readShort()); for (int i=dataInput.readInt(); --i>=0;) node.entries.add(node.level==0? objectConverter.read(dataInput, null): indexEntryConverter.read(dataInput, createIndexEntry(node.level)) ); return node; } /** Writes a node into the data output. * @param dataOutput the data output stream * @param object the node to write * @see xxl.core.io.converters.Converter#write(java.io.DataOutput, java.lang.Object) */ public void write (DataOutput dataOutput, Object object) throws IOException { Node node = (Node)object; Converter converter = node.level==0? objectConverter: indexEntryConverter; ShortConverter.DEFAULT_INSTANCE.write(dataOutput, new Short((short)node.level)); IntegerConverter.DEFAULT_INSTANCE.write(dataOutput, new Integer(node.number())); for (Iterator entries = node.entries(); entries.hasNext();) converter.write(dataOutput, entries.next()); } } public int leafsTouched = 0; /** This method is an implemtation of an efficient querying algorithm. * The result is a lazy Cursor pointing to all objects whose descriptors * overlap with the given queryDescriptor. * @param queryDescriptor describes the query in terms of a descriptor * @param targetLevel the tree-level to provide the answer-objects * @return a lazy cursor pointing to all response objects */ public Cursor query (final Descriptor queryDescriptor, final int targetLevel) { final Iterator [] iterators = new Iterator[height()+1]; Arrays.fill(iterators, EmptyCursor.DEFAULT_INSTANCE); if (height()>0 && queryDescriptor.overlaps(rootDescriptor())) iterators[height()] = new SingleObjectCursor(rootEntry()); leafsTouched=0; return new AbstractCursor () { int queryAllLevel = 0; Object toRemove = null; Stack path = new Stack(); public boolean hasNextObject() { for (int parentLevel = targetLevel;;) if (iterators[parentLevel].hasNext()) if (parentLevel==targetLevel) return true; else { IndexEntry indexEntry = (IndexEntry)iterators[parentLevel].next(); if (indexEntry.level()>=targetLevel) { Tree.Node node = indexEntry.get(true); Iterator queryIterator; if(node.level() == 0){ // leaf node leafsTouched++; } if (parentLevel<=queryAllLevel || queryDescriptor.contains(indexEntry.descriptor())) { queryIterator = node.entries(); if (parentLevel>queryAllLevel && !iterators[node.level].hasNext()) queryAllLevel = node.level; } else{ queryIterator = node.query(queryDescriptor); } iterators[parentLevel = node.level] = iterators[parentLevel].hasNext()? new Sequentializer(queryIterator, iterators[parentLevel]): queryIterator; path.push(new MapEntry(indexEntry, node)); } } else if (parentLevel==height()) return false; else { if (parentLevel==queryAllLevel) queryAllLevel = 0; if (level(path)==parentLevel) path.pop(); iterators[parentLevel++] = EmptyCursor.DEFAULT_INSTANCE; } } public Object nextObject() { return toRemove = iterators[targetLevel].next(); } public void update (Object object) throws UnsupportedOperationException, IllegalStateException, IllegalArgumentException { super.update(object); if (targetLevel > 0) throw new IllegalStateException(); else if (targetLevel!=0 || !descriptor(object).equals(descriptor(toRemove))) throw new IllegalArgumentException(); else { IndexEntry indexEntry = (IndexEntry)indexEntry(path); Node node = (Node)node(path); iterators[0].remove(); node.grow(object, path); indexEntry.update(node, true); } } public boolean supportsUpdate() { return true; } public void remove () throws UnsupportedOperationException, IllegalStateException { super.remove(); if (targetLevel<height()) { IndexEntry indexEntry = (IndexEntry)indexEntry(path); Node node = (Node)node(path); iterators[node.level].remove(); for (;;) { if (indexEntry==rootEntry() && node.level>0 && node.number()==1) { rootEntry = (IndexEntry)node.entries().next(); rootDescriptor = ((IndexEntry)rootEntry()).descriptor(); indexEntry.remove(); break; } if (node.number()==0) { up(path); indexEntry.remove(); if (height()==1) { rootEntry = null; rootDescriptor = null; break; } else { indexEntry = (IndexEntry)indexEntry(path); node = (Node)node(path); iterators[node.level].remove(); } } else if (indexEntry!=rootEntry() && node.underflows()) { Iterator entries = node.entries(); indexEntry.descriptor = computeDescriptor(node.entries); up(path); indexEntry.remove(); iterators[level(path)].remove(); indexEntry = (IndexEntry)node(path).chooseSubtree(indexEntry.descriptor(), path); ORTree.this.update(path); node = (Node)down(path, indexEntry); while (entries.hasNext()) node.grow(entries.next(), path); if (node.overflows()) node.redressOverflow(path); else { ORTree.this.update(path); up(path); } indexEntry = (IndexEntry)indexEntry(path); node = (Node)node(path); } else { ORTree.this.update(path); while (up(path)!=rootEntry()) { if (!indexEntry.descriptor().equals(indexEntry.descriptor = computeDescriptor(node.entries))) ORTree.this.update(path); indexEntry = (IndexEntry)indexEntry(path); node = (Node)node(path); } ((IndexEntry)rootEntry).descriptor = rootDescriptor = computeDescriptor(node.entries); break; } } } else { rootEntry = null; rootDescriptor = null; } if (targetLevel>0) { IndexEntry indexEntry = (IndexEntry)toRemove; indexEntry.removeAll(); } } public boolean supportsRemove() { return true; } }; } /*********************************************************************/ /* DEBUG FUNCTIONALITY */ /*********************************************************************/ /** Checks all descriptors in the tree. * * @return <tt>true</tt> if the check does not find errors, <tt>false</tt> otherwise */ public boolean checkDescriptors () { if (height()>0) return checkDescriptors((ORTree.IndexEntry)rootEntry()); else return true; } /** Checks whether all descriptors of a subtree are correct. * * @param indexEntry reference to the root node of the subtree * @return true iff the descrptors are correct */ public boolean checkDescriptors (IndexEntry indexEntry) { return checkDescriptors(indexEntry, new LinkedList()); } /** Checks whether all descriptors of a subtree are correct. * * @param indexEntry reference to the root node of the subtree * @param descriptorList a list containing all descriptors on the * path from the start index entry to the current index entry * @return true iff the descrptors are correct */ public boolean checkDescriptors (IndexEntry indexEntry, List descriptorList) { boolean returnValue = true; Node node = (Node)indexEntry.get(true); // index node: node.level > 0 if (node.level > 0) for (Iterator entries = node.entries(); entries.hasNext();) { IndexEntry next = (IndexEntry)entries.next(); descriptorList.add(next.descriptor); if (!checkDescriptors(next, descriptorList)) returnValue = false; descriptorList.remove(next.descriptor); } // leaf: node.level == 0 else { Iterator entries = node.entries(); while (entries.hasNext()) { Iterator descriptors = descriptorList.listIterator(); Descriptor nextLeafEntryDescriptor = descriptor(entries.next()); while (descriptors.hasNext()) { Descriptor nextIndexEntryDescriptor = (Descriptor)descriptors.next(); if (!nextIndexEntryDescriptor.contains(nextLeafEntryDescriptor)) { System.out.println("Error concerning OR-property occurred: \n" +"Descriptor of leaf entry "+nextLeafEntryDescriptor+"\n" +"is not contained in the index entry "+nextIndexEntryDescriptor+"\n" +"lying on the path bottom up to root."); returnValue = false; } } } } return returnValue; } /** Checks for each node in the tree whether the number of its entries is correct. * This means that no overflows or underflows are detected. * @return the number of nodes which have over- respectively underflows. */ public int checkNumberOfEntries () { if (height() > 0) { Stack path = new Stack(); Node next = (Node)down(path, rootEntry()); return checkNumberOfEntries(next, path); } else return 0; } /** Checks for each node in the subtree whose root is the given node, * whether the number of its entries is correct. * This means that overflows or underflows are not detected. * @param node the node by which the checking must start * @param path the path from the tree's root to the given node * @return the number of nodes which have over- respectively underflows. */ public int checkNumberOfEntries (Node node, Stack path) { int number=0; Iterator it = node.entries(); while (it.hasNext()) { Object o = it.next(); if (o instanceof IndexEntry) { Node next = (Node)down(path, (IndexEntry)o); if (next.underflows() || next.overflows()) { System.out.println("Number of actual node entries is not correct. Overflow or underflow detected."); System.out.println("Number of entries in this node: "+next.number()); number++; } number += checkNumberOfEntries (next, path); ((IndexEntry)o).update(next); } } return number; } }