/* * 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.apiimpl; import com.github.geophile.erdo.AbstractRecord; import com.github.geophile.erdo.Cursor; import com.github.geophile.erdo.UsageError; import com.github.geophile.erdo.map.LazyRecord; import com.github.geophile.erdo.map.MapCursor; import com.github.geophile.erdo.transaction.Transaction; import java.io.IOException; public class CursorImpl extends Cursor { // Cursor interface @Override public AbstractRecord next() throws IOException, InterruptedException { try { CURRENT_CURSOR.set(this); database.checkDatabaseOpen(); return neighbor(true); } finally { CURRENT_CURSOR.set(null); } } @Override public AbstractRecord previous() throws IOException, InterruptedException { try { CURRENT_CURSOR.set(this); database.checkDatabaseOpen(); return neighbor(false); } finally { CURRENT_CURSOR.set(null); } } @Override public void close() { try { CURRENT_CURSOR.set(this); if (mapCursor != null) { // Don't call checkTransaction. If a transaction commits and rolls back other transactions, then // cursors can be closed from the committing transaction's thread. transaction.unregisterCursor(this); mapCursor.close(); mapCursor = null; TreePositionTracker.destroyRemainingTreePositions(threadContext()); } } finally { CURRENT_CURSOR.set(null); } } // CursorImpl interface // The Cursor currently being operated on by this thread. Returns null if there is no Cursor method on the // stack. CursorImpl is an API implementation object, not internal like a MapCursor. It is therefore not possible // to next CursorImpl method invocations, so the current cursor need not be tracked using a stack. public static CursorImpl threadContext() { return CURRENT_CURSOR.get(); } CursorImpl(DatabaseImpl database, MapCursor.Expression mapCursorExpression) throws IOException, InterruptedException { this.database = database; this.transaction = database.transactionManager().currentTransaction(); this.transaction.registerCursor(this); try { CURRENT_CURSOR.set(this); this.mapCursor = mapCursorExpression.evaluate(); } finally { CURRENT_CURSOR.set(null); } } // For use by this class private AbstractRecord neighbor(boolean forward) throws IOException, InterruptedException { AbstractRecord record = null; if (mapCursor != null) { LazyRecord neighbor; boolean deleted = false; checkTransaction(); do { neighbor = forward ? mapCursor.next() : mapCursor.previous(); if (neighbor != null) { deleted = neighbor.key().deleted(); if (deleted) { neighbor.destroyRecordReference(); database.factory().testObserver().readDeletedKey(); } } } while (neighbor != null && deleted); if (neighbor != null) { record = neighbor.materializeRecord(); if (!neighbor.prefersSerialized()) { // LazyRecord stores an actual record that is part of the database. Copy it so that any // changes by the application don't modify database state. record = record.copy(); } neighbor.destroyRecordReference(); // Give application a records without a timestamp set, which will allow it to update // the record, setting the transaction. record.key().clearTransactionState(); } else { record = null; close(); } } return record; } private void checkTransaction() { if (database.transactionManager().currentTransaction() != transaction) { throw new UsageError("Cursor cannot be used across transaction boundaries"); } } // Class state private static final ThreadLocal<CursorImpl> CURRENT_CURSOR = new ThreadLocal<>(); // Object state private final DatabaseImpl database; private final Transaction transaction; private MapCursor mapCursor; }