/* * 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.forestmap; import com.github.geophile.erdo.AbstractKey; import com.github.geophile.erdo.forest.ForestSnapshot; import com.github.geophile.erdo.map.LazyRecord; import com.github.geophile.erdo.map.MapCursor; import com.github.geophile.erdo.map.SealedMap; import com.github.geophile.erdo.map.emptymap.EmptyMapCursor; import com.github.geophile.erdo.map.mergescan.MergeCursor; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; class ForestMapRangeCursor extends ForestMapCursor { // MapCursor interface @Override public LazyRecord next() throws IOException, InterruptedException { return neighbor(true); } @Override public LazyRecord previous() throws IOException, InterruptedException { return neighbor(false); } @Override public void close() { if (state != State.DONE) { if (cursor != null) { cursor.close(); } else { assert state == State.NEVER_USED; } super.close(); for (MapCursor smallMapScan : smallMapScans.values()) { smallMapScan.close(); } } } // ForestMapRangeCursor interface ForestMapRangeCursor(ForestSnapshot forestSnapshot, AbstractKey startKey) throws IOException, InterruptedException { super(forestSnapshot, startKey, false); } // For use by this class private LazyRecord updateRecord(AbstractKey key) throws IOException, InterruptedException { SealedMap map = forestSnapshot.mapContainingTransaction(key.transactionTimestamp()); if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Getting record of {0} from {1}", new Object[]{key, map}); } assert map != null : key; MapCursor cursor = keyFinder(map, key); LazyRecord updateRecord = cursor.next(); assert updateRecord != null : key; return updateRecord; } private MapCursor keyFinder(SealedMap map, AbstractKey key) throws IOException, InterruptedException { MapCursor smallMapScan = smallMapScans.get(map.mapId()); if (smallMapScan == null) { smallMapScan = map.cursor(key, true); smallMapScans.put(map.mapId(), smallMapScan); } smallMapScan.goTo(key); return smallMapScan; } private static MapCursor merge(List<SealedMap> maps, AbstractKey startKey, boolean forward) throws IOException, InterruptedException { MapCursor cursor; int mapSize = maps.size(); if (mapSize == 0) { cursor = new EmptyMapCursor(); } else if (mapSize == 1) { cursor = maps.get(0).cursor(startKey, false); } else { MergeCursor mergeScan = new MergeCursor(startKey, forward); for (SealedMap map : maps) { mergeScan.addInput(map.keyScan(startKey, false)); } mergeScan.start(); cursor = mergeScan; } return cursor; } private LazyRecord neighbor(boolean forward) throws IOException, InterruptedException { LazyRecord neighbor = null; if (state != State.DONE) { if (state == State.NEVER_USED) { MergeCursor combinedScan = new MergeCursor(startKey, forward); combinedScan.addInput(new KeyToUpdatedRecordCursor(merge(forestSnapshot.smallTrees(), startKey, forward))); combinedScan.addInput(merge(forestSnapshot.bigTrees(), startKey, forward)); combinedScan.start(); cursor = combinedScan; state = State.IN_USE; } neighbor = forward ? cursor.next() : cursor.previous(); if (neighbor == null || !isOpen(neighbor.key())) { close(); } } return neighbor; } // Object state private MapCursor cursor; private final Map<Long, MapCursor> smallMapScans = new HashMap<>(); // mapId -> MapCursor // Inner classes private class KeyToUpdatedRecordCursor extends MapCursor { // MapCursor interface @Override public LazyRecord next() throws IOException, InterruptedException { return neighbor(true); } @Override public LazyRecord previous() throws IOException, InterruptedException { return neighbor(false); } @Override public void goToFirst() throws IOException, InterruptedException { cursor.goToFirst(); } @Override public void goToLast() throws IOException, InterruptedException { cursor.goToLast(); } @Override public void goTo(AbstractKey key) throws IOException, InterruptedException { cursor.goTo(key); } @Override public void close() { cursor.close(); } // KeyToUpdatedRecordCursor interface public KeyToUpdatedRecordCursor(MapCursor cursor) { super(null, false); this.cursor = cursor; } // For use by this class private LazyRecord neighbor(boolean forward) throws IOException, InterruptedException { LazyRecord neighbor = null; LazyRecord record = forward ? cursor.next() : cursor.previous(); if (record == null) { close(); } else { AbstractKey key = record.key(); record.destroyRecordReference(); neighbor = updateRecord(key); } return neighbor; } // Object state private MapCursor cursor; } }