package xxl.core.indexStructures.rtrees; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import xxl.core.collections.MapEntry; import xxl.core.collections.containers.Container; import xxl.core.collections.queues.Queue; import xxl.core.cursors.filters.Filter; import xxl.core.cursors.sources.EmptyCursor; import xxl.core.cursors.sources.SingleObjectCursor; import xxl.core.functions.Constant; import xxl.core.functions.Functional.NullaryFunction; import xxl.core.functions.Functional.UnaryFunction; import xxl.core.indexStructures.Descriptor; import xxl.core.indexStructures.ORTree; import xxl.core.indexStructures.RTree; import xxl.core.io.converters.Converter; import xxl.core.predicates.AbstractPredicate; import xxl.core.spatial.rectangles.DoublePointRectangle; import xxl.core.util.Pair; import xxl.core.util.Triple; /** * * Implementation of R-tree with buffer technique designed by * * Lars Arge, Klaus Hinrichs, Jan Vahrenhold, Jeffrey Scott Vitter: Efficient Bulk Operations on Dynamic R-Trees. Algorithmica 33(1): 104-128 (2002) * * Splits are performed in bottom-up manner. Routing algorithm and node splits are from R*tree. * * * Bulk-loading is conducted by calling the {@link #bulkLoad(Iterator, NullaryFunction, int)} function. * * For bulk-loading we provide a factory function for creating a buffers (queues). These are attached to the buffer nodes. * In this implementation the linkage of buffer nodes to their buffers (queues) is managed by a map. This map is memory resident. * * here is an example for initializing a BufferedRTree * * int memorySizeForBuffers= 1024*1024*10; // assume we provide a 1 MB memory for loading and input objects are DoublePointRectangles in two-dimensional space * int dataSize = DIMENSION * 2 * 8; // number of bytes needed to store DoublePointRectangle * // NOTE: actual size of a memory is larger, since we have a constant amount of an additional memory per java object. * int descriptorSize = dataSize; // in our example they are equal * double minMaxFactor = 0.33; // is used to define a minimal number of elements per node * int memoryEntries = memorySizeForBuffers / dataSize; * int bufferPages = memorySizeForBuffers / BLOCK_SIZE; * RtreeBuffer<DoublePointRectangle> rtree = new RtreeBuffer<>(BLOCK_SIZE, dataSize, DIMENSION); * //1. create container * // since we initialize container for the first time, we need two parameter path and blocksize * // otherwise we provide only path parameter, block size is then obtained from the meta information stored in blockfile container * Container fileContainer = new BlockFileContainer(RTREE_PATH + "bufferRtree", BLOCK_SIZE); * //2.now we need to provide converterContainer that serializes (maps rtree nodes to a blocks) * // before we can initialize converterContainer, we need initialize node converter of the rtree * // default descriptor typ of the rtree is DoublePointRectangle. Therefore, we need to provide converter for input objects * //Since, they are also of type DoublePointRectangle we do the following * Converter<DoublePointRectangle> objectConverter = new ConvertableConverter<>(Rectangles.factoryFunctionDoublePointRectangle(DIMENSION)); * // we wrap file container with counter * CounterContainer ioCounter = new CounterContainer(fileContainer); * Container converterContainer = new ConverterContainer(ioCounter, rtree.nodeConverter(objectConverter, DIMENSION)); * //3.converterContainer is now responsible for serializing rtree nodes. * //4. we use buffer this implements available memory and holds node buffers * LRUBuffer<?, ?, ?> lruBuffer = new LRUBuffer<>(bufferPages); * CounterContainer treeContainer = new CounterContainer( new BufferedContainer(converterContainer, lruBuffer)); * // now we initialize container that manages buffers * final Container bufferedContainer = new BufferedContainer( * new ConverterContainer(new BlockFileContainer(RTREE_PATH + "buffers", BLOCK_SIZE), * QueueBuffer.getPageConverter(Rectangles.getDoublePointRectangleConverter(DIMENSION))), lruBuffer); * NullaryFunction<Queue<DoublePointRectangle>> queueFunction = new NullaryFunction<Queue<DoublePointRectangle>>() { * @Override * public Queue<DoublePointRectangle> invoke() { * return new xxl.core.collections.queues.io.QueueBuffer<>(bufferedContainer,dataSize, BLOCK_SIZE); * } * }; * //5. now we can initialize tree * // the first argument is null * // if we want to reuse an Rtree we can provide root entry, but in our case we do it for the first time. * rtree.initialize(null, new Identity<DoublePointRectangle>(), treeContainer, BLOCK_SIZE, dataSize, descriptorSize, minMaxFactor); * Iterator<DoublePointRectangle> unsortedInput = new FileInputCursor<DoublePointRectangle>(objectConverter, new File(DATA_PATH)); * rtree.bulkLoad(unsortedInput, queueFunction, memoryEntries); * * * * @author achakeye * * @param <E> */ public class BufferedRtree<E> extends RTree{ /** * debug */ public static final boolean DEBUG = false; /** * This is an internal stack that holds buffer node information. * * * @author achakeye * * @param <E> */ protected static class WorkStack<E,M> extends Stack<E>{ /** * */ Set<M> lookupSet; /** * */ UnaryFunction<E,M> getKey; /** * * @param getKey */ public WorkStack(UnaryFunction<E,M> getKey) { this.getKey = getKey; this.lookupSet = new HashSet<>(); } /** * */ public E push(E item) { //Do nothing if element conatains M mark = getKey.invoke(item); if(!lookupSet.contains(mark)){ super.push(item); lookupSet.add(mark); // if (DEBUG){ //// System.out.println("Buffer of entry is full -> " + item); // } } return item; }; /** * */ @Override public synchronized E pop() { E pop = super.pop(); lookupSet.remove(getKey.invoke(pop)); return pop; } } /** * marks leaf level */ public static final int LEAF_LEVEL = 0; /** * converter for data objects */ protected Converter<E> dataConverter; /** * first buffer level */ protected int firstBufferLevel = 1; /** * stores index entry buffer information */ protected Map<Long,Queue<E>> bufferMap; /** * factory function for queue creation */ protected NullaryFunction<Queue<E>> factoryBufferFunction; /** * root queue */ protected Queue<E> rootQueue; /** * M/4 entries */ protected int reducedMemory; /** * block size */ protected int blockSize; /** * serialized size of a data object */ protected int dataSize; /** * number of entries per leaf node */ protected int B_LEAF; /** * number of entries per index node */ protected int B_INDEX; /** * extracts id from an index entry */ UnaryFunction<IndexEntry, Long> getId = new UnaryFunction<ORTree.IndexEntry, Long>() { @Override public Long invoke(IndexEntry arg) { return (Long)arg.id(); } }; /** * default constructor * * * @param blockSize * @param dataSize serialized size of input data * @param dimension number of dimensions */ public BufferedRtree(int blockSize, int dataSize, int dimension){ super(); this.blockSize = blockSize; int payLoad = blockSize - 6; B_LEAF = payLoad / dataSize; B_INDEX = payLoad / (dimension*2*8+8); } /** * internal initialization function for bulk-loading * @param memory * @param factoryBufferFunction * @return */ protected BufferedRtree initForLoading(NullaryFunction<Queue<E>> factoryBufferFunction, int memory){ this.factoryBufferFunction = factoryBufferFunction; bufferMap = new HashMap<Long, Queue<E>>(); this.reducedMemory = memory/4; double logLevel = Math.floor(Math.log(reducedMemory/B_INDEX) / Math.log(B_INDEX)); firstBufferLevel = (int) Math.max(logLevel, 1.0); rootQueue = factoryBufferFunction.invoke(); return this; } /** * Bulk-loading method. * * * @param dataToInsert Iterator with input objects * @param factoryBufferFunction factory function that creates new queues. These queues are attached to the buffer nodes. * @param memory */ public void bulkLoad(Iterator<E> dataToInsert, NullaryFunction<Queue<E>> factoryBufferFunction, int memory){ initForLoading(factoryBufferFunction, memory); // create first node for(E entry = null; dataToInsert.hasNext();){ entry = dataToInsert.next(); DoublePointRectangle rectangle = (DoublePointRectangle) this.descriptor(entry); rootQueue.enqueue(entry); if(rootQueue.size() >= reducedMemory){ if(rootEntry.level() > firstBufferLevel ){ E entryFromQueue = null; Stack<IndexEntry> workStack = new WorkStack<IndexEntry, Long>(getId); for(int i = 0; i < reducedMemory; i++){ entryFromQueue = rootQueue.dequeue(); ((IndexEntry)rootEntry).descriptor().union(rectangle); Stack<PathEntry> path = pushDownEntry(entryFromQueue, (IndexEntry)rootEntry, workStack); updatePath(path); } processWorkStack(workStack, false); // process }else{ clearLowestBuffer((ORTree.IndexEntry)rootEntry, rootQueue); } } if(rootDescriptor == null && rootEntry == null){ rootDescriptor = new DoublePointRectangle(rectangle); rootEntry = (IndexEntry) createIndexEntry(LEAF_LEVEL+1); Node firstNode = (Node) createNode(LEAF_LEVEL); Object id = getContainer().reserve(new Constant<Node>(firstNode)); ((ORTree.IndexEntry)(rootEntry).initialize( getContainer(), id)).initialize(new DoublePointRectangle(rectangle)); rootEntry.update(firstNode); } // update root descriptor rootDescriptor.union(rectangle); } Stack<IndexEntry> workStack = new WorkStack<IndexEntry, Long>(getId); for(E entry = null; !rootQueue.isEmpty();){ entry = rootQueue.dequeue(); Stack<PathEntry> path = pushDownEntry(entry, (IndexEntry)rootEntry, workStack); updatePath(path); } processWorkStack(workStack, false); // process clearAllBuffers((IndexEntry)rootEntry); // clear all } /* * (non-Javadoc) * @see xxl.core.indexStructures.RTree#createNode(int) */ @SuppressWarnings("rawtypes") @Override public xxl.core.indexStructures.Tree.Node createNode(int level) { return new Node().initialize(level, new LinkedList()); } /** * empties all buffers. This method is called, for example, after the input iterator is consumed. * * @param rootEntry */ protected void clearAllBuffers(IndexEntry rootEntry){ java.util.Queue<IndexEntry> levelsOfBUfferEntries = computeAllDownLevelQueues((IndexEntry)rootEntry); for(IndexEntry downEntry = null; !levelsOfBUfferEntries.isEmpty(); ){ downEntry = levelsOfBUfferEntries.poll(); Queue<E> currentRootBuffer = getBuffer(downEntry); if(!currentRootBuffer.isEmpty()){ List<IndexEntry> indexBufferList = clearBuffer(downEntry, currentRootBuffer, true); for(IndexEntry topEntry: indexBufferList){ currentRootBuffer = getBuffer(topEntry); if(currentRootBuffer != null && !currentRootBuffer.isEmpty()){ levelsOfBUfferEntries.offer(topEntry); } } } } } /** * computes all buffer nodes for emptying * * @param rEntry * @return */ private java.util.Queue<IndexEntry> computeAllDownLevelQueues(IndexEntry rEntry){ java.util.Queue<IndexEntry> queue = new LinkedList<>(); queue.offer(rEntry); int currentLevel = -1; java.util.Queue<IndexEntry> bufferEntryList = new LinkedList<>(); for(IndexEntry rootEntry = null ; !queue.isEmpty() ;){ rootEntry = queue.poll(); if(rootEntry.level() >= firstBufferLevel){ Node node = (Node) rootEntry.get(true);// for(Iterator currentEntries = node.entries(); currentEntries.hasNext(); ){ IndexEntry entry = (IndexEntry)currentEntries.next(); queue.offer(entry); if(isBufferEntry(entry) && bufferMap.containsKey((Long)entry.id())){ // put to level list// level changes bufferEntryList.offer(entry); } } } } return bufferEntryList; } /** * processes buffer nodes pushed to the woring stack * * * @param workStack */ protected List<IndexEntry> processWorkStack(Stack<IndexEntry> workStack, boolean clearQueueFully){ List<IndexEntry> reorganizedBufferEntry = new ArrayList<>(); for(IndexEntry entry = null; !workStack.isEmpty() ;){ entry = workStack.pop(); Queue<E> currentRootBuffer = getBuffer(entry); reorganizedBufferEntry.addAll(clearBuffer(entry, currentRootBuffer, clearQueueFully)); } return reorganizedBufferEntry; } /** * empties a single buffer of a buffer node * * @param currentRoot * @param all if true buffer is emptied completely otherwise only a portion */ protected List<IndexEntry> clearBuffer(IndexEntry currentRoot, Queue<E> currentRootBuffer, boolean all){ if(isLowestBufferEntry(currentRoot)){ return clearLowestBuffer(currentRoot, currentRootBuffer); } List<IndexEntry> reorganizedBufferEntry = new LinkedList<>(); Stack<IndexEntry> workStack = new WorkStack<IndexEntry, Long>(getId); int minPushSize = Math.min(reducedMemory, currentRootBuffer.size()); for(int i = 0; i < minPushSize; i++){ E record = currentRootBuffer.dequeue(); Stack<PathEntry> path = pushDownEntry(record, currentRoot, workStack); updatePath(path); } reorganizedBufferEntry.addAll(processWorkStack(workStack, false)); if(all){ for( E record = null; !currentRootBuffer.isEmpty(); ){ record = currentRootBuffer.dequeue(); Stack<PathEntry> path = pushDownEntry(record, currentRoot, workStack); updatePath(path); } reorganizedBufferEntry.addAll(processWorkStack(workStack, false)); } return reorganizedBufferEntry; } /** * writes dirty nodes to a container * * @param path */ protected void updatePath(Stack<PathEntry> path){ while(!path.isEmpty()){ // process PathEntry pathEntry = path.pop(); if(pathEntry.getElement3()){ pathEntry.getElement1().update(pathEntry.getElement2(), true); } } } /** * empties lowest buffer. * * this can used e.g. to devise strategies for leaf node layouts * * * @param currentRoot */ @SuppressWarnings("unchecked") protected List<IndexEntry> clearLowestBuffer(IndexEntry cRoot, Queue<E> currentRootBuffer){ Stack<IndexEntry> workStack = new WorkStack<IndexEntry, Long>(getId); IndexEntry currentRoot = cRoot; // union descriptor List<IndexEntry> reorganizedBufferEntries = new ArrayList<>(); for( ;!currentRootBuffer.isEmpty(); ){ E record = currentRootBuffer.dequeue(); DoublePointRectangle rectangle = (DoublePointRectangle) this.descriptor(record); currentRoot.descriptor().union(rectangle); Stack<PathEntry> path = pushDownEntry(record, currentRoot, workStack); // if (DEBUG){ // if (!workStack.isEmpty()) // throw new RuntimeException("workStack must be empty in low buffer level"); // } // process buffer split nodes List<PathEntry> newEntries = processBottomUpBuffer(path, reorganizedBufferEntries); // process overflow if applicable if(!newEntries.isEmpty()){ Stack<PathEntry> pathToLowestNode = computePath(currentRoot); // compute path from root to the lowest buffer node if(!pathToLowestNode.isEmpty()){ PathEntry newCreateSubRoot = newEntries.get(0); // insert only the first one, since the pathToLowestNode.peek().getElement2().grow(newCreateSubRoot.getElement1()); // insert new create node List<PathEntry> entries = processBottomUpBuffer(pathToLowestNode, reorganizedBufferEntries); if(!entries.isEmpty()){ // create new root Container container = this.getContainer(); int rootNodeLevel = rootEntry.level()+1; Node rootNode = (Node) createNode(rootNodeLevel); DoublePointRectangle universe = null; for(PathEntry entry: entries){ rootNode.grow(entry.getElement1()); if(universe == null) universe = new DoublePointRectangle((DoublePointRectangle)entry.getElement1().descriptor()); else universe.union((DoublePointRectangle)entry.getElement1().descriptor() ); } IndexEntry newRootEntry = (IndexEntry) createIndexEntry(rootNodeLevel+1); ((IndexEntry)newRootEntry.initialize(container, container.insert(rootNode))).initialize(universe); rootEntry = newRootEntry; } }else{// new root TODO Container container = this.getContainer(); int rootNodeLevel = rootEntry.level()+1; Node rootNode = (Node) createNode(rootNodeLevel); DoublePointRectangle universe = null; for(PathEntry entry: newEntries){ rootNode.grow(entry.getElement1()); if(universe == null) universe = new DoublePointRectangle((DoublePointRectangle)entry.getElement1().descriptor()); else universe.union((DoublePointRectangle)entry.getElement1().descriptor() ); } IndexEntry newRootEntry = (IndexEntry) createIndexEntry(rootNodeLevel+1); ((IndexEntry)newRootEntry.initialize(container, container.insert(rootNode))).initialize(universe); rootEntry = newRootEntry; currentRoot = (IndexEntry) rootEntry; } } } return reorganizedBufferEntries; } /** * goes bottom-up the insertion path and performs structure reorganization, if applicable. * * @param path * @param newEntries * @return */ protected List<PathEntry> processBottomUpBuffer(Stack<PathEntry> path, List<IndexEntry> reorganizedBufferEntries){ List<PathEntry> newEntries = new ArrayList<>(); // go up the stack for(PathEntry pathEntry = null ; !path.isEmpty(); ){ pathEntry = path.pop(); // check if split is needed if(pathEntry.getElement2().overflows()){ PathEntry newPathEntry = reorganize(pathEntry); // split if(isBufferEntry(pathEntry.getElement1())){ reorganizedBufferEntries.add(pathEntry.getElement1()); reorganizedBufferEntries.add(newPathEntry.getElement1()); } if(!path.isEmpty()){ // insert new node, get parent node and post new entries PathEntry parentEntry = path.peek(); parentEntry.getElement2().grow(newPathEntry.getElement1()); }else{ newEntries.add(newPathEntry); // new entry newEntries.add(pathEntry); // old entry } } if (pathEntry.getElement3()) { pathEntry.getElement1().update(pathEntry.getElement2(), true);// update only if descriptors changed } } return newEntries; } /** * * Computes path root to buffer level * * * @param currentRoot * @return path to the currentRoot */ @SuppressWarnings("unchecked") protected Stack<PathEntry> computePath(IndexEntry currentRoot){ // use descriptor to find the parent node int parentLevel = currentRoot.parentLevel(); Stack<PathEntry> path = new Stack<>(); if(parentLevel > firstBufferLevel && currentRoot != rootEntry){ Stack<Iterator<IndexEntry>> stack = new Stack<>(); stack.push(new SingleObjectCursor<IndexEntry>((IndexEntry)rootEntry)); final DoublePointRectangle entryDescriptor = (DoublePointRectangle)currentRoot.descriptor(); for( ;!stack.isEmpty(); ){ Iterator<IndexEntry> iterator = stack.peek(); if(iterator.hasNext()){ IndexEntry indexEntry = iterator.next(); if(indexEntry.parentLevel() == parentLevel && indexEntry.id().equals(currentRoot.id())){ // update indexEntry.initialize(currentRoot.descriptor()); break;//stop }else if (!isLeafEntry(indexEntry) && indexEntry.parentLevel() > parentLevel ){ Node node = (Node)indexEntry.get(false); path.push(new PathEntry(indexEntry, node, true)); Iterator<IndexEntry> levelIterator = new Filter(node.entries(), new AbstractPredicate() { @Override public boolean invoke(Object obj) { IndexEntry idx = (IndexEntry)obj; DoublePointRectangle dpr = (DoublePointRectangle)idx.descriptor(); boolean contains = entryDescriptor.contains(dpr) || dpr.contains(entryDescriptor); return contains; } }); stack.push(levelIterator); }else{ stack.push(new EmptyCursor<IndexEntry>()); path.push(new PathEntry(null, null, null));// dummy } }else{ stack.pop(); path.pop(); } } } return path; } /** * conducts structure reorganization * * @param oldEntry * @return */ protected PathEntry reorganize(PathEntry oldEntry){ PathEntry newPathEntry = oldEntry.getElement2().split(oldEntry.getElement1()); // split Container container = this.getContainer(); newPathEntry.getElement1().initialize(container, container.insert(newPathEntry.getElement2())); // insert into container if(isBufferEntry(oldEntry.getElement1())){ redistributeBuffer(oldEntry, newPathEntry); } return newPathEntry; } /** * redistribute buffers * */ @SuppressWarnings("unchecked") protected void redistributeBuffer(PathEntry entryOld, PathEntry entryNew){ // take the buffer from the lod node Queue<E> oldBuffer = getBuffer(entryOld.getElement1()); if(oldBuffer != null){ // the buffer can be empty in last phase in case of all buffers are emptied in top down level wise manner //create mocked node Node node = (Node) createNode(entryOld.getElement2().level()+1); node.grow(entryOld.getElement1()); node.grow(entryNew.getElement1()); Queue<E> newBufferOld = factoryBufferFunction.invoke(); Queue<E> newBufferNew = factoryBufferFunction.invoke(); for(E entry= null; !oldBuffer.isEmpty(); ){ entry = oldBuffer.dequeue(); DoublePointRectangle descriptor = (DoublePointRectangle) descriptor(entry); Pair<IndexEntry, Boolean> pair = node.chooseSubtree(descriptor); if(pair.getElement1().id().equals(entryOld.getElement1().id())){ // old one newBufferOld.enqueue(entry); }else{ // new one newBufferNew.enqueue(entry); } } bufferMap.put((Long)entryOld.getElement1().id(), newBufferOld); bufferMap.put((Long)entryNew.getElement1().id(), newBufferNew); }else{ // do nothing! } } /** * transports from buffer to buffer level or from the * lowest buffer level to leaf nodes * * @param record * @param currentRoot * @param parentNodes * @param workStack * */ @SuppressWarnings("unchecked") protected Stack<PathEntry> pushDownEntry(E record, IndexEntry currentRoot, Stack<IndexEntry> workStack){ IndexEntry currentEntry = currentRoot; Node currentNode = (Node) currentEntry.get(true); // parenmt node may be also buffer node DoublePointRectangle dpr = (DoublePointRectangle) descriptor(record); // XXX descriptors are double point rectangles Stack<PathEntry> pathBufferToBuffer = new Stack<>(); pathBufferToBuffer.push(new PathEntry(currentEntry, currentNode, Boolean.valueOf(false))); if (isLeafEntry(currentEntry)){ currentNode = (Node) currentEntry.get(true); pathBufferToBuffer.peek().setElement3( Boolean.valueOf(true)); currentNode.grow(record); // insert into leaf node }else{ Pair<ORTree.IndexEntry, Boolean> currentEntryPair = null; do{ currentEntryPair = currentNode.chooseSubtree(dpr); // updates also MBR if applicable pathBufferToBuffer.peek().setElement3(currentEntryPair.getElement2()); // FIXME check thi method currentEntry = currentEntryPair.getElement1(); currentNode = (Node) currentEntry.get(true); pathBufferToBuffer.push(new PathEntry(currentEntry, currentNode, Boolean.valueOf(false))); } while(!isBufferEntry(currentEntry) && !isLeafEntry(currentEntry)); // { // // } if (isLeafEntry(currentEntry)){ currentNode = (Node) currentEntry.get(true); pathBufferToBuffer.peek().setElement3( Boolean.valueOf(true)); currentNode.grow(record); // insert into leaf node }else { // append to buffer if(currentRoot != rootEntry && isBufferEntry(currentEntry) appendToBuffer(record, currentEntry); if (isBufferFull(currentEntry)){ // search instack workStack.push(currentEntry); } } } return pathBufferToBuffer; } /** * * @return */ @SuppressWarnings({ "unchecked", "deprecation" }) protected Container getContainer(){ return (Container) this.determineContainer.invoke(this); } /** * Appends to queue if queue not exists allocates new queue * @param rootEntry */ protected void appendToBuffer(E record, IndexEntry rootEntry) { Queue<E> buffer = bufferMap.get((Long)rootEntry.id()); if(buffer == null){ buffer = factoryBufferFunction.invoke(); bufferMap.put((Long)rootEntry.id(), buffer); // if(DEBUG){ // System.out.println("Buffer assigned to entry -> " + rootEntry.toString()); // } } buffer.enqueue(record); } /** * returns buffer of a buffer node * * @param entry * @return */ protected Queue<E> getBuffer(IndexEntry entry){ return this.bufferMap.get(entry.id()); } /** * attaches buffer to index entry */ protected void assignBuffer(IndexEntry entry){ Long id = (Long)entry.id(); Queue<E> buffer = this.factoryBufferFunction.invoke(); bufferMap.put(id, buffer); } /** * remove buffer node from map * * @param entry */ protected void removeBuffer(IndexEntry entry){ bufferMap.remove((Long)entry.id()); } /** * checks if the entry is on buffer level * note that root of the tree is specially treated * @param indexEntry * @return */ protected boolean isBufferEntry(IndexEntry indexEntry) { boolean hasBuffer = (indexEntry.parentLevel()-1 == 0) ? false : (indexEntry.parentLevel()-1) % firstBufferLevel == 0; return hasBuffer && indexEntry != rootEntry; } /** * checks if the entry is on buffer level * note that root of the tree is specially treated * @param indexEntry * @return */ protected boolean isLowestBufferEntry(IndexEntry indexEntry) { boolean hasBuffer = (indexEntry.parentLevel()-1 == 0) ? false : (indexEntry.parentLevel()-1) == firstBufferLevel; return hasBuffer && indexEntry != rootEntry; } /** * Indicates whether currenEntry is point to leaf * * @param currentEntry * @return */ protected boolean isLeafEntry(IndexEntry currentEntry) { return currentEntry.level() == 0; } /** * returns true if index entry point to a buffer node * * @param currentEntry * @return */ protected boolean isBufferFull(IndexEntry currentEntry) { if(bufferMap.containsKey((Long)currentEntry.id())){ Queue<E> buffer = bufferMap.get((Long)currentEntry.id()); return buffer.size() > reducedMemory; } return false; } /** * Typedef for a information object stored in a reorganization path * @author achakeye * */ @SuppressWarnings("serial") public class PathEntry extends Triple<IndexEntry, Node, Boolean>{ public PathEntry(IndexEntry indexEntry, Node node, Boolean update){ super(indexEntry, node, update); } } /** * This class extends R*tree node. It inherits split and routing algorithm. * */ public class Node extends RTree.Node{ /** * * @param descriptor * @return */ protected Pair<IndexEntry, Boolean> chooseSubtree (Descriptor descriptor){ IndexEntry indexEntry = (IndexEntry)super.chooseSubtree(descriptor, this.entries()); boolean updateNode = false; if (!indexEntry.descriptor().contains(descriptor)) { indexEntry.descriptor().union(descriptor); updateNode = true; } return new Pair<>(indexEntry, Boolean.valueOf(updateNode)); } /* * we additionally add update of descriptor * * (non-Javadoc) * @see xxl.core.indexStructures.RTree.Node#chooseSubtree(xxl.core.indexStructures.Descriptor, java.util.Iterator) */ @SuppressWarnings("rawtypes") @Override protected ORTree.IndexEntry chooseSubtree (Descriptor descriptor, Iterator minima){ IndexEntry indexEntry = super.chooseSubtree(descriptor, minima); if (!indexEntry.descriptor().contains(descriptor)) { indexEntry.descriptor().union(descriptor); } return indexEntry; } /* * */ protected void grow (Object data) { super.grow(data, null); } /* * (non-Javadoc) * @see xxl.core.indexStructures.Tree.Node#overflows() */ @Override protected boolean overflows() { return super.overflows(); } /** * * @return */ @SuppressWarnings({ "unchecked", "rawtypes" }) protected PathEntry split(IndexEntry indexEntry) { Node newNode = (Node) createNode(this.level); Stack path = new Stack(); path.push(new MapEntry(indexEntry, this)); SplitInfo info = (SplitInfo) newNode.split(path); newNode = (Node) info.newNode(); IndexEntry newIndexEntry = (IndexEntry) createIndexEntry(this.level()+1).initialize(info); return new PathEntry(newIndexEntry, newNode, true); } @Override public String toString() { return "Node [entries=" + entries + ", level=" + level + "]"; } } }