/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.github.geophile.erdo.map.mergescan; import com.github.geophile.erdo.AbstractKey; import com.github.geophile.erdo.map.LazyRecord; import com.github.geophile.erdo.map.MapCursor; import com.github.geophile.erdo.map.forestmap.TimestampMerger; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class MergeCursor extends MapCursor { // Cursor interface @Override public LazyRecord next() throws IOException, InterruptedException { LazyRecord next = null; if (state != State.DONE) { if (!forward) { restartAtStartKey(true); } next = neighbor(); } return next; } @Override public LazyRecord previous() throws IOException, InterruptedException { LazyRecord previous = null; if (state != State.DONE) { if (forward) { restartAtStartKey(false); } previous = neighbor(); } return previous; } @Override public void goToFirst() throws IOException, InterruptedException { super.goToFirst(); for (MapCursor input : inputs) { input.goToFirst(); } start(); } @Override public void goToLast() throws IOException, InterruptedException { super.goToLast(); for (MapCursor input : inputs) { input.goToLast(); } start(); } @Override public void goTo(AbstractKey key) throws IOException, InterruptedException { super.goTo(key); for (MapCursor input : inputs) { input.goTo(key); } start(); } @Override protected boolean isOpen(AbstractKey key) { throw new UnsupportedOperationException(); } @Override public void close() { if (state != State.DONE) { super.close(); if (inputs != null) { for (MapCursor input : inputs) { input.close(); } inputs = null; root = null; } } } // MergeCursor interface public void addInput(MapCursor input) { inputs.add(input); } public void start() throws IOException, InterruptedException { // Number of nodes at leaf level is the smallest power of 2 >= inputs.size(). int nLeaves = 1; while (nLeaves < inputs.size()) { nLeaves *= 2; } int nNodes = 2 * nLeaves - 1; firstLeaf = nNodes / 2; // Create tree root = createNode(0); // Move records up the tree root.prime(); } public MergeCursor(AbstractKey startKey, boolean forward) { this(TimestampMerger.only(), startKey, forward); } // For use by this package Node mergeNode(int position, Node left, Node right, boolean forward) { return new MergeNode(this, position, left, right, forward); } Node inputNode(int position, MapCursor input, boolean forward) { return new InputNode(position, input, forward); } Node fillerNode(int position) { return new FillerNode(position); } MergeCursor(Merger merger, AbstractKey startKey, boolean forward) { super(startKey, false); this.merger = merger; this.forward = forward; } // For use by this class private Node createNode(int position) { return position < firstLeaf ? mergeNode(position, createNode(2 * position + 1), createNode(2 * position + 2), forward) : position < firstLeaf + inputs.size() ? inputNode(position, inputs.get(position - firstLeaf), forward) : fillerNode(position); } private void restartAtStartKey(boolean forward) throws IOException, InterruptedException { this.forward = forward; if (startKey == null) { if (unboundStartAtFirstKey) { goToFirst(); } else { goToLast(); } } else { goTo(startKey); // Get past the startKey if it has already been visited. LazyRecord current = root.record; if (current != null && current.key().equals(startKey)) { neighbor(); } } } private LazyRecord neighbor() throws IOException, InterruptedException { LazyRecord neighbor = null; switch (state) { case NEVER_USED: state = State.IN_USE; break; case IN_USE: root.promote(); break; case DONE: break; } if (root != null) { neighbor = root.record; } if (neighbor == null) { close(); } else { startKey = neighbor.key(); } return neighbor; } // Object state final Merger merger; private boolean forward; private List<MapCursor> inputs = new ArrayList<>(); private int firstLeaf; private Node root; }